Skip to content

JavaScript: fix non-FQ type on enum declarations and harden parseProject#7425

Merged
knutwannheden merged 1 commit intomainfrom
wiry-yak
Apr 19, 2026
Merged

JavaScript: fix non-FQ type on enum declarations and harden parseProject#7425
knutwannheden merged 1 commit intomainfrom
wiry-yak

Conversation

@knutwannheden
Copy link
Copy Markdown
Contributor

Motivation

Building certain TypeScript projects — originally reported for nashtech-garage/yas — fails with:

java.lang.IllegalArgumentException: A class can only be type attributed with a fully qualified type name
  at org.openrewrite.java.tree.J$ClassDeclaration.withType(J.java:1362)
  at org.openrewrite.java.internal.rpc.JavaReceiver.visitClassDeclaration(JavaReceiver.java:150)
  ...
  at org.openrewrite.rpc.RewriteRpc.getObject(RewriteRpc.java:553)
  at org.openrewrite.javascript.rpc.JavaScriptRewriteRpc$1.tryAdvance(JavaScriptRewriteRpc.java:182)

The trigger turned out to be as small as:

export enum ContentType {
  APPLICATION_JSON = 'application/json',
}

Two separate problems combined to make this a project-killer rather than a per-file error:

  1. The actual bug. visitEnumDeclaration (and the other J.ClassDeclaration producers: visitClassDeclaration, visitClassExpression, visitInterfaceDeclaration) set the node's type via mapType(node), which calls checker.getTypeAtLocation(node). TypeScript resolves this to the type of the declared value — for a string enum, that collapses to the union of string-literal members and on the JS side becomes Type.Primitive.String. The Java side's J.ClassDeclaration.withType validates that the type is FullyQualified and throws.

  2. No per-file resilience. The failure surfaced deep in RPC tree reconstruction (getObject), which runs outside the try/catch in parseProject's tryAdvance. A single bad file therefore aborts the entire stream instead of degrading to a ParseError for that file.

Summary

  • Introduce JavaScriptTypeMapping.declarationType(node), used by visitClassDeclaration, visitClassExpression, visitInterfaceDeclaration, and visitEnumDeclaration. It resolves the declared type through the declaration's symbol:
    • For classes and interfaces, getDeclaredTypeOfSymbol yields a class-shaped ts.Type that feeds through the existing pipeline (members, methods, supertypes, type parameters preserved).
    • For enums — which have no class-shaped representation in TypeScript — we build a minimal Type.Class with classKind = Enum and the declaration's own FQN. This preserves the FullyQualified invariant.
  • Extract a getFullyQualifiedNameFromSymbol(symbol) helper so the FQN logic works directly from a symbol.
  • Wrap the per-file getObject(...) call in JavaScriptRewriteRpc.parseProject with a try/catch. On failure we produce a ParseError that points at the offending file and continue iterating.
  • Add an optional sourcePath field to ParseProjectResponse.Item / ParseProjectResponseItem so the Java side can report which file failed when getObject throws.
  • Add a regression test parseStringEnumDeclaration that parses a string enum over RPC and asserts the resulting J.ClassDeclaration carries a JavaType.Class with Kind.Enum and the right FQN.

Test plan

  • New test JavaScriptRewriteRpcTest.parseStringEnumDeclaration passes
  • Existing JavaScriptRewriteRpcTest suite passes
  • TS-side npm run testhelper -- test/javascript/parser/ — 627 passed / 6 skipped
  • License format check passes
  • End-to-end: parsing the full nashtech-garage/yas/backoffice tree (206 source files) now completes with parseErrors=0, where previously it aborted mid-stream.

@github-project-automation github-project-automation bot moved this to In Progress in OpenRewrite Apr 19, 2026
@knutwannheden knutwannheden changed the title JS RPC: fix non-FQ type on enum declarations and harden parseProject JavaScript: fix non-FQ type on enum declarations and harden parseProject Apr 19, 2026
…parseProject`

A string or numeric enum was parsed with `mapType(enumDecl)`, which calls
`getTypeAtLocation`. TypeScript resolves that to the union of enum literals —
for a string enum this ends up as `Type.Primitive.String`. The Java-side RPC
receiver then failed with "A class can only be type attributed with a fully
qualified type name" in `J.ClassDeclaration.withType`, aborting the entire
stream of parsed source files.

Resolve class/interface/enum/class-expression declaration types via the
declaration's symbol (`getDeclaredTypeOfSymbol` for classes/interfaces; a
minimal class shell with the declaration's FQN and `classKind` for enums,
since TypeScript has no class-shaped enum type). The result is always
`FullyQualified`.

Also harden `parseProject`: wrap the per-file `getObject` in a try/catch so a
single failing file becomes a `ParseError` instead of killing the stream, and
add `sourcePath` to `ParseProjectResponse.Item` so those errors can point at
the offending file.

Fixes moderneinc/customer-requests#2234
@knutwannheden knutwannheden merged commit a6a8b25 into main Apr 19, 2026
1 check passed
@knutwannheden knutwannheden deleted the wiry-yak branch April 19, 2026 14:04
@github-project-automation github-project-automation bot moved this from In Progress to Done in OpenRewrite Apr 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

JS RPC: ClassDeclaration sent with non-fully-qualified type name causes build failure

1 participant