@openrewrite/rewrite 8.92.17 → 8.92.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +48 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/java/formatting-utils.d.ts +2 -1
  6. package/dist/java/formatting-utils.d.ts.map +1 -1
  7. package/dist/java/formatting-utils.js +4 -3
  8. package/dist/java/formatting-utils.js.map +1 -1
  9. package/dist/java/rpc.d.ts.map +1 -1
  10. package/dist/java/rpc.js +2 -0
  11. package/dist/java/rpc.js.map +1 -1
  12. package/dist/java/tree.d.ts +8 -0
  13. package/dist/java/tree.d.ts.map +1 -1
  14. package/dist/java/tree.js +11 -1
  15. package/dist/java/tree.js.map +1 -1
  16. package/dist/java/type.d.ts +7 -1
  17. package/dist/java/type.d.ts.map +1 -1
  18. package/dist/java/type.js +16 -1
  19. package/dist/java/type.js.map +1 -1
  20. package/dist/javascript/add-import.d.ts +27 -21
  21. package/dist/javascript/add-import.d.ts.map +1 -1
  22. package/dist/javascript/add-import.js +303 -143
  23. package/dist/javascript/add-import.js.map +1 -1
  24. package/dist/javascript/amd.d.ts.map +1 -1
  25. package/dist/javascript/amd.js +25 -10
  26. package/dist/javascript/amd.js.map +1 -1
  27. package/dist/javascript/autodetect.js +36 -45
  28. package/dist/javascript/autodetect.js.map +1 -1
  29. package/dist/javascript/binding.d.ts +6 -4
  30. package/dist/javascript/binding.d.ts.map +1 -1
  31. package/dist/javascript/binding.js +54 -18
  32. package/dist/javascript/binding.js.map +1 -1
  33. package/dist/javascript/comparator.d.ts +22 -5
  34. package/dist/javascript/comparator.d.ts.map +1 -1
  35. package/dist/javascript/comparator.js +116 -38
  36. package/dist/javascript/comparator.js.map +1 -1
  37. package/dist/javascript/format/format.d.ts +2 -0
  38. package/dist/javascript/format/format.d.ts.map +1 -1
  39. package/dist/javascript/format/format.js +11 -1
  40. package/dist/javascript/format/format.js.map +1 -1
  41. package/dist/javascript/index.d.ts +4 -2
  42. package/dist/javascript/index.d.ts.map +1 -1
  43. package/dist/javascript/index.js +10 -1
  44. package/dist/javascript/index.js.map +1 -1
  45. package/dist/javascript/markers.d.ts.map +1 -1
  46. package/dist/javascript/markers.js +64 -0
  47. package/dist/javascript/markers.js.map +1 -1
  48. package/dist/javascript/method-matcher.d.ts +13 -2
  49. package/dist/javascript/method-matcher.d.ts.map +1 -1
  50. package/dist/javascript/method-matcher.js +32 -19
  51. package/dist/javascript/method-matcher.js.map +1 -1
  52. package/dist/javascript/package-name.d.ts +6 -0
  53. package/dist/javascript/package-name.d.ts.map +1 -0
  54. package/dist/javascript/package-name.js +47 -0
  55. package/dist/javascript/package-name.js.map +1 -0
  56. package/dist/javascript/preconditions.d.ts +4 -1
  57. package/dist/javascript/preconditions.d.ts.map +1 -1
  58. package/dist/javascript/preconditions.js +7 -4
  59. package/dist/javascript/preconditions.js.map +1 -1
  60. package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
  61. package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
  62. package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
  63. package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
  64. package/dist/javascript/recipes/index.d.ts +1 -0
  65. package/dist/javascript/recipes/index.d.ts.map +1 -1
  66. package/dist/javascript/recipes/index.js +1 -0
  67. package/dist/javascript/recipes/index.js.map +1 -1
  68. package/dist/javascript/scope.d.ts +60 -16
  69. package/dist/javascript/scope.d.ts.map +1 -1
  70. package/dist/javascript/scope.js +596 -131
  71. package/dist/javascript/scope.js.map +1 -1
  72. package/dist/javascript/search/uses-method.d.ts +1 -1
  73. package/dist/javascript/search/uses-method.d.ts.map +1 -1
  74. package/dist/javascript/search/uses-method.js +2 -2
  75. package/dist/javascript/search/uses-method.js.map +1 -1
  76. package/dist/javascript/search/uses-type.d.ts +14 -1
  77. package/dist/javascript/search/uses-type.d.ts.map +1 -1
  78. package/dist/javascript/search/uses-type.js +24 -9
  79. package/dist/javascript/search/uses-type.js.map +1 -1
  80. package/dist/javascript/templating/bindings.d.ts +1 -1
  81. package/dist/javascript/templating/bindings.d.ts.map +1 -1
  82. package/dist/javascript/templating/bindings.js +10 -50
  83. package/dist/javascript/templating/bindings.js.map +1 -1
  84. package/dist/javascript/templating/comparator.d.ts +8 -11
  85. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  86. package/dist/javascript/templating/comparator.js +12 -110
  87. package/dist/javascript/templating/comparator.js.map +1 -1
  88. package/dist/javascript/templating/engine.d.ts +34 -24
  89. package/dist/javascript/templating/engine.d.ts.map +1 -1
  90. package/dist/javascript/templating/engine.js +200 -112
  91. package/dist/javascript/templating/engine.js.map +1 -1
  92. package/dist/javascript/templating/pattern.d.ts +11 -5
  93. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  94. package/dist/javascript/templating/pattern.js +86 -20
  95. package/dist/javascript/templating/pattern.js.map +1 -1
  96. package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
  97. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  98. package/dist/javascript/templating/placeholder-replacement.js +126 -60
  99. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  100. package/dist/javascript/templating/precedence.d.ts +3 -1
  101. package/dist/javascript/templating/precedence.d.ts.map +1 -1
  102. package/dist/javascript/templating/precedence.js +28 -8
  103. package/dist/javascript/templating/precedence.js.map +1 -1
  104. package/dist/javascript/templating/template.d.ts +7 -6
  105. package/dist/javascript/templating/template.d.ts.map +1 -1
  106. package/dist/javascript/templating/template.js +31 -28
  107. package/dist/javascript/templating/template.js.map +1 -1
  108. package/dist/javascript/templating/types.d.ts +6 -3
  109. package/dist/javascript/templating/types.d.ts.map +1 -1
  110. package/dist/javascript/templating/utils.d.ts +10 -1
  111. package/dist/javascript/templating/utils.d.ts.map +1 -1
  112. package/dist/javascript/templating/utils.js +22 -3
  113. package/dist/javascript/templating/utils.js.map +1 -1
  114. package/dist/javascript/tree-debug.d.ts +2 -0
  115. package/dist/javascript/tree-debug.d.ts.map +1 -1
  116. package/dist/javascript/tree-debug.js +6 -0
  117. package/dist/javascript/tree-debug.js.map +1 -1
  118. package/dist/javascript/tsconfig.d.ts.map +1 -1
  119. package/dist/javascript/tsconfig.js +7 -17
  120. package/dist/javascript/tsconfig.js.map +1 -1
  121. package/dist/javascript/type-mapping.d.ts +34 -16
  122. package/dist/javascript/type-mapping.d.ts.map +1 -1
  123. package/dist/javascript/type-mapping.js +253 -249
  124. package/dist/javascript/type-mapping.js.map +1 -1
  125. package/dist/javascript/type-report-cli.d.ts +7 -0
  126. package/dist/javascript/type-report-cli.d.ts.map +1 -0
  127. package/dist/javascript/type-report-cli.js +129 -0
  128. package/dist/javascript/type-report-cli.js.map +1 -0
  129. package/dist/javascript/type-report.d.ts +61 -0
  130. package/dist/javascript/type-report.d.ts.map +1 -0
  131. package/dist/javascript/type-report.js +488 -0
  132. package/dist/javascript/type-report.js.map +1 -0
  133. package/dist/markers.d.ts +4 -0
  134. package/dist/markers.d.ts.map +1 -1
  135. package/dist/markers.js +8 -0
  136. package/dist/markers.js.map +1 -1
  137. package/dist/recipe.d.ts +5 -0
  138. package/dist/recipe.d.ts.map +1 -1
  139. package/dist/recipe.js +5 -0
  140. package/dist/recipe.js.map +1 -1
  141. package/dist/rewrite-javascript-version.txt +1 -1
  142. package/dist/rpc/queue.d.ts +16 -0
  143. package/dist/rpc/queue.d.ts.map +1 -1
  144. package/dist/rpc/queue.js +27 -9
  145. package/dist/rpc/queue.js.map +1 -1
  146. package/dist/rpc/request/prepare-recipe.d.ts +3 -1
  147. package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
  148. package/dist/rpc/request/prepare-recipe.js +13 -6
  149. package/dist/rpc/request/prepare-recipe.js.map +1 -1
  150. package/dist/search/is-source-file.d.ts +1 -0
  151. package/dist/search/is-source-file.d.ts.map +1 -1
  152. package/dist/search/is-source-file.js +7 -2
  153. package/dist/search/is-source-file.js.map +1 -1
  154. package/dist/test/rewrite-test.d.ts +5 -0
  155. package/dist/test/rewrite-test.d.ts.map +1 -1
  156. package/dist/test/rewrite-test.js +14 -3
  157. package/dist/test/rewrite-test.js.map +1 -1
  158. package/package.json +4 -3
  159. package/src/index.ts +3 -1
  160. package/src/java/formatting-utils.ts +5 -4
  161. package/src/java/rpc.ts +2 -0
  162. package/src/java/tree.ts +15 -0
  163. package/src/java/type.ts +19 -2
  164. package/src/javascript/add-import.ts +338 -152
  165. package/src/javascript/amd.ts +28 -12
  166. package/src/javascript/autodetect.ts +32 -41
  167. package/src/javascript/binding.ts +64 -22
  168. package/src/javascript/comparator.ts +119 -43
  169. package/src/javascript/format/format.ts +9 -0
  170. package/src/javascript/index.ts +6 -3
  171. package/src/javascript/markers.ts +43 -1
  172. package/src/javascript/method-matcher.ts +34 -22
  173. package/src/javascript/package-name.ts +45 -0
  174. package/src/javascript/preconditions.ts +7 -4
  175. package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
  176. package/src/javascript/recipes/index.ts +1 -0
  177. package/src/javascript/scope.ts +705 -169
  178. package/src/javascript/search/uses-method.ts +2 -2
  179. package/src/javascript/search/uses-type.ts +30 -9
  180. package/src/javascript/templating/bindings.ts +13 -38
  181. package/src/javascript/templating/comparator.ts +16 -127
  182. package/src/javascript/templating/engine.ts +229 -127
  183. package/src/javascript/templating/pattern.ts +107 -28
  184. package/src/javascript/templating/placeholder-replacement.ts +154 -62
  185. package/src/javascript/templating/precedence.ts +33 -11
  186. package/src/javascript/templating/template.ts +35 -30
  187. package/src/javascript/templating/types.ts +6 -3
  188. package/src/javascript/templating/utils.ts +24 -2
  189. package/src/javascript/tree-debug.ts +9 -0
  190. package/src/javascript/tsconfig.ts +7 -17
  191. package/src/javascript/type-mapping.ts +258 -252
  192. package/src/javascript/type-report-cli.ts +96 -0
  193. package/src/javascript/type-report.ts +575 -0
  194. package/src/markers.ts +8 -0
  195. package/src/recipe.ts +6 -0
  196. package/src/rpc/queue.ts +44 -5
  197. package/src/rpc/request/prepare-recipe.ts +16 -6
  198. package/src/search/is-source-file.ts +8 -3
  199. package/src/test/rewrite-test.ts +14 -3
@@ -15,7 +15,9 @@
15
15
  */
16
16
  import ts from "typescript";
17
17
  import * as path from "path";
18
+ import {isBuiltin} from "module";
18
19
  import {Type} from "../java";
20
+ import {packageNameOf} from "./package-name";
19
21
  import FUNCTION_TYPE_NAME = Type.FUNCTION_TYPE_NAME;
20
22
  import OBJECT_TYPE_NAME = Type.OBJECT_TYPE_NAME;
21
23
 
@@ -37,6 +39,20 @@ function typeSignatureToJSON(this: Type): string {
37
39
  return Type.signature(this);
38
40
  }
39
41
 
42
+ /**
43
+ * A module specifier with a redundant `node:` scheme removed.
44
+ * `node:url` and `url` import the same built-in, which is named without the scheme.
45
+ * A built-in such as `node:test` has no bare form, and `test` is another package, so it keeps it.
46
+ */
47
+ function moduleName(specifier: string): string {
48
+ const bare = specifier.substring('node:'.length);
49
+ return specifier.startsWith('node:') && isBuiltin(bare) ? bare : specifier;
50
+ }
51
+
52
+ function isGlobalAugmentation(declaration: ts.Declaration): boolean {
53
+ return ts.isModuleDeclaration(declaration) && (declaration.flags & ts.NodeFlags.GlobalAugmentation) !== 0;
54
+ }
55
+
40
56
  export class JavaScriptTypeMapping {
41
57
  // Primary cache: Use type signatures (preferring type.id) as cache keys
42
58
  // TypeScript assigns stable IDs to all types, so we don't need secondary caches
@@ -47,6 +63,12 @@ export class JavaScriptTypeMapping {
47
63
  private readonly stringWrapperType: ts.Type | undefined;
48
64
  private readonly numberWrapperType: ts.Type | undefined;
49
65
  private readonly booleanWrapperType: ts.Type | undefined;
66
+ private readonly symbolWrapperType: ts.Type | undefined;
67
+
68
+ // The declarations of function types being populated. An instantiation of one of these is
69
+ // unknown, since `type Rec<T> = (x: T) => Rec<T[]>` instantiates without end and each
70
+ // instantiation has a type id of its own that the type cache cannot match.
71
+ private readonly functionTypesInProgress = new Set<ts.Symbol>();
50
72
 
51
73
 
52
74
  constructor(
@@ -64,6 +86,7 @@ export class JavaScriptTypeMapping {
64
86
  const stringSymbol = checker.resolveName("String", undefined, ts.SymbolFlags.Type, false);
65
87
  const numberSymbol = checker.resolveName("Number", undefined, ts.SymbolFlags.Type, false);
66
88
  const booleanSymbol = checker.resolveName("Boolean", undefined, ts.SymbolFlags.Type, false);
89
+ const symbolSymbol = checker.resolveName("Symbol", undefined, ts.SymbolFlags.Type, false);
67
90
 
68
91
  // Store the TypeScript types; conversion to Type happens on-demand
69
92
  if (stringSymbol) {
@@ -77,6 +100,10 @@ export class JavaScriptTypeMapping {
77
100
  if (booleanSymbol) {
78
101
  this.booleanWrapperType = checker.getDeclaredTypeOfSymbol(booleanSymbol);
79
102
  }
103
+
104
+ if (symbolSymbol) {
105
+ this.symbolWrapperType = checker.getDeclaredTypeOfSymbol(symbolSymbol);
106
+ }
80
107
  }
81
108
 
82
109
  type(node: ts.Node): Type | undefined {
@@ -290,10 +317,13 @@ export class JavaScriptTypeMapping {
290
317
  }
291
318
  // Skip instantiated types ONLY if they're not type references
292
319
  // Type references like Array<string>, Promise<T> are instantiated but should be mapped
320
+ // Function types, such as a generic method's callback parameter, are mapped too
293
321
  // Other instantiated types (like object literals) should return unknown
294
322
  if (objectFlags & ts.ObjectFlags.Instantiated) {
295
323
  const isTypeReference = objectFlags & ts.ObjectFlags.Reference;
296
- if (!isTypeReference) {
324
+ const isFunctionType = type.getCallSignatures().length > 0 &&
325
+ !this.functionTypesInProgress.has(type.symbol);
326
+ if (!isTypeReference && !isFunctionType) {
297
327
  return Type.unknownType;
298
328
  }
299
329
  }
@@ -393,11 +423,7 @@ export class JavaScriptTypeMapping {
393
423
  if (symbol.flags & ts.SymbolFlags.Function) {
394
424
  const callSignatures = type.getCallSignatures();
395
425
  if (callSignatures.length > 0) {
396
- // Shell-cache: Create stub, cache it, then populate (prevents cycles)
397
- const functionType = this.createEmptyFunctionType();
398
- this.typeCache.set(signature, functionType);
399
- this.populateFunctionType(functionType, callSignatures[0]);
400
- return functionType;
426
+ return this.createFunctionType(type, callSignatures[0], signature);
401
427
  }
402
428
  }
403
429
 
@@ -405,11 +431,7 @@ export class JavaScriptTypeMapping {
405
431
  if (symbol.flags & (ts.SymbolFlags.FunctionScopedVariable | ts.SymbolFlags.BlockScopedVariable)) {
406
432
  const callSignatures = type.getCallSignatures();
407
433
  if (callSignatures.length > 0) {
408
- // Shell-cache: Create stub, cache it, then populate (prevents cycles)
409
- const functionType = this.createEmptyFunctionType();
410
- this.typeCache.set(signature, functionType);
411
- this.populateFunctionType(functionType, callSignatures[0]);
412
- return functionType;
434
+ return this.createFunctionType(type, callSignatures[0], signature);
413
435
  }
414
436
  }
415
437
 
@@ -465,11 +487,7 @@ export class JavaScriptTypeMapping {
465
487
  // Check for function types without symbols (anonymous functions, function types)
466
488
  const callSignatures = type.getCallSignatures();
467
489
  if (callSignatures && callSignatures.length > 0) {
468
- // Shell-cache: Create stub, cache it, then populate (prevents cycles)
469
- const functionType = this.createEmptyFunctionType();
470
- this.typeCache.set(signature, functionType);
471
- this.populateFunctionType(functionType, callSignatures[0]);
472
- return functionType;
490
+ return this.createFunctionType(type, callSignatures[0], signature);
473
491
  }
474
492
 
475
493
  // A structural type has no nominal name, so every object literal `{a: 1}` and type
@@ -604,34 +622,20 @@ export class JavaScriptTypeMapping {
604
622
  // Check if the variable is imported
605
623
  if (symbol.flags & ts.SymbolFlags.Alias) {
606
624
  // For imported variables, find the module specifier
607
- const declarations = symbol.declarations;
608
- if (declarations && declarations.length > 0) {
609
- let importNode: ts.Node | undefined = declarations[0];
610
-
611
- // Traverse up to find the ImportDeclaration
612
- while (importNode && !ts.isImportDeclaration(importNode)) {
613
- importNode = importNode.parent;
614
- }
615
-
616
- if (importNode && ts.isImportDeclaration(importNode)) {
617
- const importDecl = importNode as ts.ImportDeclaration;
618
- if (ts.isStringLiteral(importDecl.moduleSpecifier)) {
619
- const moduleSpecifier = importDecl.moduleSpecifier.text;
620
- // Create a Type.Class representing the module
621
- ownerType = {
622
- kind: Type.Kind.Class,
623
- flags: 0,
624
- classKind: Type.Class.Kind.Interface,
625
- fullyQualifiedName: moduleSpecifier,
626
- typeParameters: [],
627
- annotations: [],
628
- interfaces: [],
629
- members: [],
630
- methods: [],
631
- toJSON: typeSignatureToJSON
632
- } as Type.Class;
633
- }
634
- }
625
+ const moduleSpecifier = this.importedModule(symbol.declarations?.[0]);
626
+ if (moduleSpecifier) {
627
+ ownerType = {
628
+ kind: Type.Kind.Class,
629
+ flags: 0,
630
+ classKind: Type.Class.Kind.Interface,
631
+ fullyQualifiedName: moduleSpecifier,
632
+ typeParameters: [],
633
+ annotations: [],
634
+ interfaces: [],
635
+ members: [],
636
+ methods: [],
637
+ toJSON: typeSignatureToJSON
638
+ } as Type.Class;
635
639
  }
636
640
  } else {
637
641
  // For non-imported variables, check if they belong to a class/interface/namespace
@@ -683,6 +687,7 @@ export class JavaScriptTypeMapping {
683
687
  // Create the Type.Variable
684
688
  const variable = {
685
689
  kind: Type.Kind.Variable,
690
+ flags: 0,
686
691
  name: actualSymbol.getName(),
687
692
  owner: ownerType,
688
693
  type: mappedType,
@@ -705,7 +710,7 @@ export class JavaScriptTypeMapping {
705
710
  return undefined;
706
711
  }
707
712
  const moduleArg = node.arguments[0];
708
- return ts.isStringLiteral(moduleArg) ? moduleArg.text : undefined;
713
+ return ts.isStringLiteral(moduleArg) ? moduleName(moduleArg.text) : undefined;
709
714
  }
710
715
 
711
716
  /**
@@ -742,14 +747,10 @@ export class JavaScriptTypeMapping {
742
747
  if (!declaration) {
743
748
  return undefined;
744
749
  }
745
- let importDecl: ts.Node | undefined = declaration;
746
- while (importDecl && !ts.isImportDeclaration(importDecl)) {
747
- importDecl = importDecl.parent;
748
- }
749
- if (!importDecl || !ts.isStringLiteral((importDecl as ts.ImportDeclaration).moduleSpecifier)) {
750
+ const module = this.importedModule(declaration);
751
+ if (!module) {
750
752
  return undefined;
751
753
  }
752
- const module = ((importDecl as ts.ImportDeclaration).moduleSpecifier as ts.StringLiteral).text;
753
754
  if (ts.isNamespaceImport(declaration)) {
754
755
  return {module, namespace: true};
755
756
  }
@@ -775,6 +776,41 @@ export class JavaScriptTypeMapping {
775
776
  return binding && binding.exportName === undefined ? binding.module : undefined;
776
777
  }
777
778
 
779
+ /**
780
+ * The module an import declaration containing `node` imports, or undefined when `node` is not
781
+ * part of one or its specifier is not a string literal.
782
+ */
783
+ private importedModule(node: ts.Node | undefined): string | undefined {
784
+ while (node && !ts.isImportDeclaration(node)) {
785
+ node = node.parent;
786
+ }
787
+ return node && ts.isStringLiteral(node.moduleSpecifier) ? moduleName(node.moduleSpecifier.text) : undefined;
788
+ }
789
+
790
+ /**
791
+ * A parsed file's module, named after its path relative to the source root without its extension,
792
+ * as TypeScript names the module of an export. A function declared in the file is declared on it.
793
+ */
794
+ private sourceModuleType(sourceFile: ts.SourceFile): Type.FullyQualified {
795
+ return this.moduleType(this.sourceModuleName(sourceFile));
796
+ }
797
+
798
+ private sourceModuleName(sourceFile: ts.SourceFile): string {
799
+ const fileName = sourceFile.fileName;
800
+ const relative = this.sourceRoot && path.isAbsolute(fileName) ? path.relative(this.sourceRoot, fileName) : fileName;
801
+ return relative.replace(/\.[^/.]+$/, '');
802
+ }
803
+
804
+ /**
805
+ * The declaration of a function in a parsed source file. A `.d.ts` file is not parsed, so a
806
+ * function such as `parseInt` has none.
807
+ */
808
+ private parsedFunctionDeclaration(symbol: ts.Symbol): ts.FunctionDeclaration | undefined {
809
+ const declaration = symbol.valueDeclaration;
810
+ return declaration && ts.isFunctionDeclaration(declaration) && !declaration.getSourceFile().isDeclarationFile ?
811
+ declaration : undefined;
812
+ }
813
+
778
814
  private moduleType(module: string): Type.FullyQualified {
779
815
  return {
780
816
  kind: Type.Kind.Class,
@@ -804,59 +840,16 @@ export class JavaScriptTypeMapping {
804
840
  const lastNodeModulesIndex = fileName.lastIndexOf('node_modules/');
805
841
  const afterNodeModules = fileName.substring(lastNodeModulesIndex + 'node_modules/'.length);
806
842
 
807
- // Split by '/' to get path segments
808
- const segments = afterNodeModules.split('/');
809
- if (segments.length === 0) {
810
- return undefined;
811
- }
812
-
813
- let moduleName: string;
814
-
815
- // Handle scoped packages (@scope/package)
816
- if (segments[0].startsWith('@') && segments.length > 1) {
817
- moduleName = `${segments[0]}/${segments[1]}`;
818
- } else {
819
- moduleName = segments[0];
820
- }
821
-
822
843
  // Skip pnpm's .pnpm directory - it contains versioned package paths
823
844
  // In pnpm, the actual package is in: .pnpm/pkg@version/node_modules/pkg
824
845
  // So we already handled this by using lastIndexOf above
825
- if (moduleName === '.pnpm') {
846
+ if (afterNodeModules.split('/')[0] === '.pnpm') {
826
847
  return undefined;
827
848
  }
828
849
 
829
- return this.normalizePackageName(moduleName);
850
+ return packageNameOf(afterNodeModules);
830
851
  }
831
852
 
832
- /**
833
- * Normalize a node_modules package name to the specifier consumers actually import.
834
- *
835
- * DefinitelyTyped packages (`@types/<pkg>`) are never importable under that name — the
836
- * importable specifier is `<pkg>`, with DefinitelyTyped's `__` scoped-package encoding
837
- * decoded back to a `@scope/name` form. Using the importable specifier keeps attributed
838
- * fully qualified names consistent regardless of whether a type is reached through a direct
839
- * import (which already resolves via the module specifier) or transitively (e.g. a call's
840
- * return type), which falls back to the declaration file's `node_modules` path.
841
- *
842
- * Examples:
843
- * - `@types/express-serve-static-core` -> `express-serve-static-core`
844
- * - `@types/node` -> `node`
845
- * - `@types/testing-library__react` -> `@testing-library/react`
846
- */
847
- private normalizePackageName(packageName: string): string {
848
- if (packageName.startsWith('@types/')) {
849
- packageName = packageName.substring('@types/'.length);
850
- // Decode __ encoding for scoped packages: testing-library__react -> @testing-library/react
851
- if (packageName.includes('__')) {
852
- const parts = packageName.split('__');
853
- if (parts.length === 2) {
854
- packageName = `@${parts[0]}/${parts[1]}`;
855
- }
856
- }
857
- }
858
- return packageName;
859
- }
860
853
 
861
854
  /**
862
855
  * Helper to create a Type.Method object from common parameters
@@ -926,6 +919,27 @@ export class JavaScriptTypeMapping {
926
919
  }
927
920
  }
928
921
 
922
+ /**
923
+ * The class or interface that declares a member, which is a call's declaring type, as in Java.
924
+ * A union's member has one only when every member type inherits it from the same class.
925
+ */
926
+ private declaringClassOf(member: ts.Symbol): Type.FullyQualified | undefined {
927
+ let owner: ts.Type | undefined;
928
+ for (const declaration of member.declarations ?? []) {
929
+ const parent = declaration.parent;
930
+ if (!ts.isClassLike(parent) && !ts.isInterfaceDeclaration(parent)) {
931
+ return undefined;
932
+ }
933
+ const type = this.checker.getTypeAtLocation(parent);
934
+ if (owner && owner.symbol !== type.symbol) {
935
+ return undefined;
936
+ }
937
+ owner = type;
938
+ }
939
+ const declaringType = owner && this.declaringType(owner);
940
+ return declaringType === Type.unknownType ? undefined : declaringType;
941
+ }
942
+
929
943
  private wrapperType(declaringType: (Type.FullyQualified & Type.Primitive) | Type.FullyQualified) {
930
944
  if (declaringType === Type.Primitive.String && this.stringWrapperType) {
931
945
  return this.getType(this.stringWrapperType) as Type.FullyQualified;
@@ -1046,8 +1060,10 @@ export class JavaScriptTypeMapping {
1046
1060
  const anonymous = !mappedType || mappedType.kind !== Type.Kind.Class ||
1047
1061
  (mappedType as Type.Class).fullyQualifiedName.startsWith('{');
1048
1062
 
1049
- // Handle different types
1050
- if (receiverBinding?.namespace && anonymous) {
1063
+ const declaringClass = this.declaringClassOf(symbol);
1064
+ if (declaringClass) {
1065
+ declaringType = declaringClass;
1066
+ } else if (receiverBinding?.namespace && anonymous) {
1051
1067
  declaringType = this.moduleType(receiverBinding.module);
1052
1068
  if (methodName === 'default') {
1053
1069
  methodName = '<default>';
@@ -1120,56 +1136,34 @@ export class JavaScriptTypeMapping {
1120
1136
  // For namespace imports, use the namespace symbol's `name` as the module specifier (e.g. `React` instead of `react`)
1121
1137
  moduleSpecifier = aliasedParentSymbol.name;
1122
1138
  } else {
1123
- // Now find the import declaration to get the module specifier
1124
- if (exprSymbol.declarations && exprSymbol.declarations.length > 0) {
1125
- let importNode: ts.Node = exprSymbol.declarations[0];
1126
-
1127
- // Traverse up to find the ImportDeclaration
1128
- while (importNode && !ts.isImportDeclaration(importNode)) {
1129
- importNode = importNode.parent;
1130
- }
1131
-
1132
- if (importNode && ts.isImportDeclaration(importNode)) {
1133
- const importDeclNode = importNode as ts.ImportDeclaration;
1134
- if (ts.isStringLiteral(importDeclNode.moduleSpecifier)) {
1135
- moduleSpecifier = importDeclNode.moduleSpecifier.text;
1136
- }
1137
- }
1138
- }
1139
+ moduleSpecifier = this.importedModule(exprSymbol.declarations?.[0]);
1139
1140
  }
1140
1141
  }
1141
1142
  }
1142
1143
 
1143
- if (moduleSpecifier) {
1144
+ const declared = this.parsedFunctionDeclaration(aliasedSymbol ?? symbol);
1145
+ if (declared) {
1146
+ declaringType = this.sourceModuleType(declared.getSourceFile());
1147
+ methodName = declared.name ? declared.name.text : "<anonymous>";
1148
+ } else if (moduleSpecifier) {
1144
1149
  // This is an imported function - use the module specifier as declaring type
1145
- if (moduleSpecifier.startsWith('node:')) {
1146
- // Node.js built-in module
1147
- declaringType = {
1148
- kind: Type.Kind.Class,
1149
- flags: 0, // TODO - determine flags
1150
- fullyQualifiedName: 'node'
1151
- } as Type.FullyQualified;
1152
- methodName = moduleSpecifier.substring(5); // Remove 'node:' prefix
1150
+ declaringType = {
1151
+ kind: Type.Kind.Class,
1152
+ flags: 0, // TODO - determine flags
1153
+ fullyQualifiedName: moduleSpecifier
1154
+ } as Type.FullyQualified;
1155
+ // A default import binds an `ImportClause`; its aliased symbol carries the internal
1156
+ // name of the default export (e.g. `e` for express), so represent it as `<default>`.
1157
+ // Named imports (`ImportSpecifier`) keep the original exported name.
1158
+ const isDefaultImport = exprSymbol?.declarations?.some(ts.isImportClause) ?? false;
1159
+ if (isDefaultImport) {
1160
+ methodName = '<default>';
1161
+ } else if (aliasedSymbol?.declarations?.length) {
1162
+ methodName = aliasedSymbol.name;
1153
1163
  } else {
1154
- // Regular module import
1155
- declaringType = {
1156
- kind: Type.Kind.Class,
1157
- flags: 0, // TODO - determine flags
1158
- fullyQualifiedName: moduleSpecifier
1159
- } as Type.FullyQualified;
1160
- // A default import binds an `ImportClause`; its aliased symbol carries the internal
1161
- // name of the default export (e.g. `e` for express), so represent it as `<default>`.
1162
- // Named imports (`ImportSpecifier`) keep the original exported name.
1163
- const isDefaultImport = exprSymbol?.declarations?.some(ts.isImportClause) ?? false;
1164
- if (isDefaultImport) {
1165
- methodName = '<default>';
1166
- } else if (aliasedSymbol?.declarations?.length) {
1167
- methodName = aliasedSymbol.name;
1168
- } else {
1169
- // A package without type declarations resolves the import to the checker's
1170
- // `unknown` symbol, so the exported name comes from the import itself.
1171
- methodName = this.importBinding(node.expression)?.exportName ?? aliasedSymbol?.name ?? methodName;
1172
- }
1164
+ // A package without type declarations resolves the import to the checker's
1165
+ // `unknown` symbol, so the exported name comes from the import itself.
1166
+ methodName = this.importBinding(node.expression)?.exportName ?? aliasedSymbol?.name ?? methodName;
1173
1167
  }
1174
1168
  } else if (this.requiredModuleOfExpression(node.expression)) {
1175
1169
  // `const m = require('m'); m()` calls the module's default export.
@@ -1272,23 +1266,7 @@ export class JavaScriptTypeMapping {
1272
1266
 
1273
1267
  methodName = node.name ? node.name.getText() : "<anonymous>";
1274
1268
 
1275
- // Derive declaring type from source file module path (like Go's type_mapper.go).
1276
- // Use the same relativization as getFullyQualifiedName() so that declarations
1277
- // and invocations produce matching FQNs.
1278
- let moduleFqn: string;
1279
- const fileName = node.getSourceFile().fileName;
1280
- if (this.sourceRoot && path.isAbsolute(fileName)) {
1281
- moduleFqn = path.relative(this.sourceRoot, fileName);
1282
- } else {
1283
- moduleFqn = fileName;
1284
- }
1285
- // Strip file extension to get the module name
1286
- moduleFqn = moduleFqn.replace(/\.[^/.]+$/, '');
1287
- declaringType = {
1288
- kind: Type.Kind.Class,
1289
- flags: 0,
1290
- fullyQualifiedName: moduleFqn
1291
- } as Type.FullyQualified;
1269
+ declaringType = this.sourceModuleType(node.getSourceFile());
1292
1270
 
1293
1271
  // Get type parameters from node
1294
1272
  if (node.typeParameters) {
@@ -1326,35 +1304,7 @@ export class JavaScriptTypeMapping {
1326
1304
  if (symbol.flags & ts.SymbolFlags.Alias) {
1327
1305
  const aliasedSymbol = this.checker.getAliasedSymbol(symbol);
1328
1306
  if (aliasedSymbol && aliasedSymbol !== symbol && symbol.declarations && symbol.declarations.length > 0) {
1329
- // Try to find the import declaration to get the module specifier
1330
- let importNode: ts.Node | undefined = symbol.declarations[0];
1331
-
1332
- // Traverse up to find the ImportDeclaration or ImportSpecifier
1333
- while (importNode && importNode.parent && !ts.isImportDeclaration(importNode) && !ts.isImportSpecifier(importNode)) {
1334
- importNode = importNode.parent;
1335
- }
1336
-
1337
- let moduleSpecifier: string | undefined;
1338
-
1339
- if (importNode && ts.isImportSpecifier(importNode)) {
1340
- // Named import like: import { ClipLoader } from 'react-spinners'
1341
- // ImportSpecifier -> NamedImports -> ImportClause -> ImportDeclaration
1342
- const namedImports = importNode.parent; // NamedImports
1343
- if (namedImports && ts.isNamedImports(namedImports)) {
1344
- const importClause = namedImports.parent; // ImportClause
1345
- if (importClause && ts.isImportClause(importClause)) {
1346
- const importDecl = importClause.parent; // ImportDeclaration
1347
- if (importDecl && ts.isImportDeclaration(importDecl) && ts.isStringLiteral(importDecl.moduleSpecifier)) {
1348
- moduleSpecifier = importDecl.moduleSpecifier.text;
1349
- }
1350
- }
1351
- }
1352
- } else if (importNode && ts.isImportDeclaration(importNode)) {
1353
- // Default or namespace import
1354
- if (ts.isStringLiteral(importNode.moduleSpecifier)) {
1355
- moduleSpecifier = importNode.moduleSpecifier.text;
1356
- }
1357
- }
1307
+ const moduleSpecifier = this.importedModule(symbol.declarations[0]);
1358
1308
 
1359
1309
  if (moduleSpecifier) {
1360
1310
  // Build the fully qualified name from module specifier + symbol name
@@ -1368,47 +1318,35 @@ export class JavaScriptTypeMapping {
1368
1318
  // This returns names with quotes that we need to clean up
1369
1319
  // e.g., '"React"."Component"' -> 'React.Component'
1370
1320
  const tsQualifiedName = this.checker.getFullyQualifiedName(symbol);
1371
- let cleanedName = tsQualifiedName.replace(/"/g, '');
1372
-
1373
- // Check if this is a file path from node_modules (happens with some packages)
1374
- // TypeScript sometimes returns full paths instead of module names
1375
- if (cleanedName.includes('node_modules/')) {
1321
+ let cleanedName = moduleName(tsQualifiedName.replace(/"/g, ''));
1322
+ const outermost = this.outermostDeclaration(symbol);
1323
+ const sourceModule = outermost && this.sourceModuleOf(outermost);
1324
+
1325
+ if (sourceModule) {
1326
+ cleanedName = `${sourceModule}.${cleanedName}`;
1327
+ } else if (cleanedName.includes('node_modules/')) {
1328
+ // Check if this is a file path from node_modules (happens with some packages)
1329
+ // TypeScript sometimes returns full paths instead of module names
1376
1330
  // Extract the module name from the path
1377
1331
  // Example: /private/var/.../node_modules/react-spinners/src/index.ClipLoader
1378
1332
  // Should become: react-spinners.ClipLoader
1379
1333
  const nodeModulesIndex = cleanedName.indexOf('node_modules/');
1380
1334
  const afterNodeModules = cleanedName.substring(nodeModulesIndex + 'node_modules/'.length);
1381
1335
 
1382
- // Split by '/' to get parts of the path
1383
- const pathParts = afterNodeModules.split('/');
1384
-
1385
- if (pathParts.length > 0) {
1386
- // First part is the package name (might be scoped like @types)
1387
- let packageName = pathParts[0];
1388
-
1389
- // Handle scoped packages
1390
- if (packageName.startsWith('@') && pathParts.length > 1) {
1391
- packageName = `${packageName}/${pathParts[1]}`;
1392
- }
1393
-
1394
- // Normalize `@types/<pkg>` to the importable specifier `<pkg>` so that types
1395
- // reached transitively (e.g. a call's return type, resolved via the declaration
1396
- // file's node_modules path) match the names used for directly imported types.
1397
- packageName = this.normalizePackageName(packageName);
1398
-
1399
- // The symbol name is appended to the declaration file's path, so it is whatever
1400
- // follows the last dot of the final path segment. Directory names carry dots of
1401
- // their own — `.pnpm`, `.yarn`, a hidden parent such as `.claude`, a versioned
1402
- // pnpm directory — so a search over the whole path would split there instead and
1403
- // fold most of the path into the name.
1404
- const lastSegmentIndex = cleanedName.lastIndexOf('/') + 1;
1405
- const lastDotIndex = cleanedName.lastIndexOf('.');
1406
- if (lastDotIndex > lastSegmentIndex) {
1407
- const symbolName = cleanedName.substring(lastDotIndex + 1);
1408
- cleanedName = `${packageName}.${symbolName}`;
1409
- } else {
1410
- cleanedName = packageName;
1411
- }
1336
+ const packageName = packageNameOf(afterNodeModules);
1337
+
1338
+ // The symbol name is appended to the declaration file's path, so it is whatever
1339
+ // follows the last dot of the final path segment. Directory names carry dots of
1340
+ // their own — `.pnpm`, `.yarn`, a hidden parent such as `.claude`, a versioned
1341
+ // pnpm directory — so a search over the whole path would split there instead and
1342
+ // fold most of the path into the name.
1343
+ const lastSegmentIndex = cleanedName.lastIndexOf('/') + 1;
1344
+ const lastDotIndex = cleanedName.lastIndexOf('.');
1345
+ if (lastDotIndex > lastSegmentIndex) {
1346
+ const symbolName = cleanedName.substring(lastDotIndex + 1);
1347
+ cleanedName = `${packageName}.${symbolName}`;
1348
+ } else {
1349
+ cleanedName = packageName;
1412
1350
  }
1413
1351
  } else if (path.isAbsolute(cleanedName)) {
1414
1352
  // TypeScript returns absolute file paths as module names for project source files
@@ -1445,7 +1383,7 @@ export class JavaScriptTypeMapping {
1445
1383
  // Preserved as-is:
1446
1384
  // - UMD globals (`export as namespace X`, e.g. React, lodash `_`, jQuery `$`): the
1447
1385
  // namespace name is the conventional public identifier;
1448
- // - `global.*` (TypeScript's marker for ambient globals);
1386
+ // - `global.*`, a `declare global` augmentation, whose prefix is dropped below;
1449
1387
  // - namespaces that already match the package name, and any non-node_modules type.
1450
1388
  const namespaceName = cleanedName.substring(0, cleanedName.indexOf('.'));
1451
1389
  if (namespaceName !== 'global') {
@@ -1460,11 +1398,40 @@ export class JavaScriptTypeMapping {
1460
1398
  }
1461
1399
  }
1462
1400
 
1401
+ // A `declare global` augmentation is in the same global scope as the built-ins, which have no prefix.
1402
+ if (outermost && isGlobalAugmentation(outermost) && cleanedName.startsWith('global.')) {
1403
+ cleanedName = cleanedName.substring('global.'.length);
1404
+ }
1405
+
1463
1406
  return cleanedName.endsWith('Constructor') ?
1464
1407
  cleanedName.substring(0, cleanedName.length - 'Constructor'.length) :
1465
1408
  cleanedName;
1466
1409
  }
1467
1410
 
1411
+ /**
1412
+ * The project module whose top level holds a declaration local to it, such as a class it does not
1413
+ * export. TypeScript names that as bare as a global, so a module's own `class Array` needs the module.
1414
+ */
1415
+ private sourceModuleOf(declaration: ts.Declaration): string | undefined {
1416
+ if (!declaration.parent || !ts.isSourceFile(declaration.parent) || isGlobalAugmentation(declaration) ||
1417
+ ts.isModuleDeclaration(declaration) && ts.isStringLiteral(declaration.name)) {
1418
+ return undefined;
1419
+ }
1420
+ const sourceFile = declaration.parent;
1421
+ const isModule = ts.isExternalModule(sourceFile) || (sourceFile as any).commonJsModuleIndicator !== undefined;
1422
+ return isModule && !sourceFile.isDeclarationFile && !sourceFile.fileName.includes('node_modules/') ?
1423
+ this.sourceModuleName(sourceFile) : undefined;
1424
+ }
1425
+
1426
+ /** The declaration of the symbol a qualified name starts from, such as a class's namespace. */
1427
+ private outermostDeclaration(symbol: ts.Symbol): ts.Declaration | undefined {
1428
+ let outermost = symbol;
1429
+ while ((outermost as any).parent) {
1430
+ outermost = (outermost as any).parent;
1431
+ }
1432
+ return outermost.declarations?.[0];
1433
+ }
1434
+
1468
1435
  /**
1469
1436
  * Whether {@code namespaceName} is exposed as a UMD global from the given declaration file
1470
1437
  * (e.g. `export as namespace React`). The namespace name of a UMD global is the conventional
@@ -1517,12 +1484,42 @@ export class JavaScriptTypeMapping {
1517
1484
  } as Type.Class;
1518
1485
  }
1519
1486
 
1487
+ private baseClass(classType: Type.Class, baseType: ts.Type): Type.Class | undefined {
1488
+ const mapped = this.getType(baseType);
1489
+ const base = Type.isParameterized(mapped) ? mapped.type : mapped;
1490
+ // TypeScript accepts heritage cycles (`class A implements A`). Whichever edge closes one,
1491
+ // in mapping order, is dropped.
1492
+ return Type.isClass(base) && !this.inheritsFrom(base, classType, new Set()) ? base : undefined;
1493
+ }
1494
+
1495
+ private inheritsFrom(type: Type.Class, ancestor: Type.Class, seen: Set<Type.Class>): boolean {
1496
+ if (type === ancestor) {
1497
+ return true;
1498
+ }
1499
+ if (seen.has(type)) {
1500
+ return false;
1501
+ }
1502
+ seen.add(type);
1503
+ return (type.supertype !== undefined && this.inheritsFrom(type.supertype, ancestor, seen)) ||
1504
+ type.interfaces.some(i => this.inheritsFrom(i, ancestor, seen));
1505
+ }
1506
+
1507
+ private addInterface(classType: Type.Class, baseType: ts.Type): void {
1508
+ const iface = this.baseClass(classType, baseType);
1509
+ if (iface && !classType.interfaces.includes(iface)) {
1510
+ classType.interfaces.push(iface);
1511
+ }
1512
+ }
1513
+
1520
1514
  /**
1521
1515
  * Populates the class type with members, methods, heritage, and type parameters
1522
1516
  * Since the shell is already in the cache, any recursive references will find it
1523
1517
  */
1524
1518
  private populateClassType(classType: Type.Class, type: ts.Type): void {
1525
1519
  const symbol = type.getSymbol?.();
1520
+ const classSymbol = symbol && symbol.flags & ts.SymbolFlags.Alias ?
1521
+ this.checker.getAliasedSymbol(symbol) : symbol;
1522
+ const classDeclaration = classSymbol?.declarations?.find(ts.isClassLike);
1526
1523
 
1527
1524
  // Try to get base types using TypeScript's getBaseTypes API
1528
1525
  // This works for both local and external types (from node_modules)
@@ -1540,9 +1537,6 @@ export class JavaScriptTypeMapping {
1540
1537
  } else if (symbol) {
1541
1538
  // For constructor functions or type references, we need to get the actual class type
1542
1539
  // Try to get the type of the class itself (not the constructor or instance)
1543
- const classSymbol = symbol.flags & ts.SymbolFlags.Alias ?
1544
- this.checker.getAliasedSymbol(symbol) : symbol;
1545
-
1546
1540
  if (classSymbol && classSymbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface)) {
1547
1541
  // Get the type of the class declaration itself
1548
1542
  const declaredType = this.checker.getDeclaredTypeOfSymbol(classSymbol);
@@ -1568,32 +1562,27 @@ export class JavaScriptTypeMapping {
1568
1562
  }
1569
1563
 
1570
1564
  if (baseTypes && baseTypes.length > 0) {
1571
- // For classes, the first base type is usually the superclass
1572
- // Additional base types are interfaces
1573
- if (classType.classKind === Type.Class.Kind.Class) {
1574
- const firstBase = this.getType(baseTypes[0]);
1575
- // Handle both Class and Parameterized (e.g., Component<Props>)
1576
- if (Type.isClass(firstBase)) {
1577
- (classType as any).supertype = firstBase;
1578
- } else if (Type.isParameterized(firstBase)) {
1579
- // For parameterized types, use the base class as the supertype
1580
- (classType as any).supertype = (firstBase as Type.Parameterized).type;
1581
- }
1582
- // Rest are interfaces
1583
- for (let i = 1; i < baseTypes.length; i++) {
1584
- const interfaceType = this.getType(baseTypes[i]);
1585
- if (Type.isClass(interfaceType)) {
1586
- classType.interfaces.push(interfaceType);
1587
- }
1588
- }
1589
- } else {
1590
- // For interfaces, all base types are extended interfaces
1591
- for (const baseType of baseTypes) {
1592
- const interfaceType = this.getType(baseType);
1593
- if (Type.isClass(interfaceType)) {
1594
- classType.interfaces.push(interfaceType);
1595
- }
1565
+ // A merged interface's bases follow the class's, so the first is a superclass only beside `extends`
1566
+ let interfaces = baseTypes;
1567
+ if (classType.classKind === Type.Class.Kind.Class && (!classDeclaration ||
1568
+ classDeclaration.heritageClauses?.some(c => c.token === ts.SyntaxKind.ExtendsKeyword))) {
1569
+ const supertype = this.baseClass(classType, baseTypes[0]);
1570
+ if (supertype) {
1571
+ classType.supertype = supertype;
1596
1572
  }
1573
+ interfaces = baseTypes.slice(1);
1574
+ }
1575
+ for (const baseType of interfaces) {
1576
+ this.addInterface(classType, baseType);
1577
+ }
1578
+ }
1579
+ }
1580
+
1581
+ // `getBaseTypes` omits a class's `implements` clause
1582
+ for (const clause of classDeclaration?.heritageClauses ?? []) {
1583
+ if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
1584
+ for (const implemented of clause.types) {
1585
+ this.addInterface(classType, this.checker.getTypeAtLocation(implemented));
1597
1586
  }
1598
1587
  }
1599
1588
  }
@@ -1632,6 +1621,7 @@ export class JavaScriptTypeMapping {
1632
1621
  const propType = this.checker.getTypeOfSymbolAtLocation(prop, declaration);
1633
1622
  const variable: Type.Variable = {
1634
1623
  kind: Type.Kind.Variable,
1624
+ flags: 0,
1635
1625
  name: prop.getName(),
1636
1626
  owner: classType, // Cyclic reference to the containing class (already in cache)
1637
1627
  type: this.getType(propType), // This will find classType in cache if it's recursive
@@ -1677,6 +1667,9 @@ export class JavaScriptTypeMapping {
1677
1667
  return Type.Primitive.String;
1678
1668
  } else if (type.flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral | ts.TypeFlags.BooleanLike)) {
1679
1669
  return Type.Primitive.Boolean;
1670
+ } else if (type.flags & ts.TypeFlags.ESSymbolLike && this.symbolWrapperType) {
1671
+ // Java has no primitive for a symbol, so it is typed as the class it boxes to.
1672
+ return this.getType(this.symbolWrapperType);
1680
1673
  }
1681
1674
 
1682
1675
  // Check for type aliases that may resolve to primitives
@@ -1798,6 +1791,19 @@ export class JavaScriptTypeMapping {
1798
1791
  } as Type.Class;
1799
1792
  }
1800
1793
 
1794
+ private createFunctionType(type: ts.Type, signature: ts.Signature, cacheKey: string | number): Type.Class {
1795
+ // Shell-cache: Create stub, cache it, then populate (prevents cycles)
1796
+ const functionType = this.createEmptyFunctionType();
1797
+ this.typeCache.set(cacheKey, functionType);
1798
+ this.functionTypesInProgress.add(type.symbol);
1799
+ try {
1800
+ this.populateFunctionType(functionType, signature);
1801
+ } finally {
1802
+ this.functionTypesInProgress.delete(type.symbol);
1803
+ }
1804
+ return functionType;
1805
+ }
1806
+
1801
1807
  /**
1802
1808
  * Create an empty function type shell with FQN 𝑓.
1803
1809
  * The shell will be populated later to handle circular references.