@openrewrite/rewrite 8.92.16 → 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 (254) 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 +120 -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/format/tabs-and-indents-visitor.d.ts.map +1 -1
  42. package/dist/javascript/format/tabs-and-indents-visitor.js +31 -4
  43. package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
  44. package/dist/javascript/index.d.ts +4 -2
  45. package/dist/javascript/index.d.ts.map +1 -1
  46. package/dist/javascript/index.js +10 -1
  47. package/dist/javascript/index.js.map +1 -1
  48. package/dist/javascript/markers.d.ts +12 -0
  49. package/dist/javascript/markers.d.ts.map +1 -1
  50. package/dist/javascript/markers.js +81 -2
  51. package/dist/javascript/markers.js.map +1 -1
  52. package/dist/javascript/method-matcher.d.ts +13 -2
  53. package/dist/javascript/method-matcher.d.ts.map +1 -1
  54. package/dist/javascript/method-matcher.js +32 -19
  55. package/dist/javascript/method-matcher.js.map +1 -1
  56. package/dist/javascript/node-resolution-result.js +3 -2
  57. package/dist/javascript/node-resolution-result.js.map +1 -1
  58. package/dist/javascript/package-name.d.ts +6 -0
  59. package/dist/javascript/package-name.d.ts.map +1 -0
  60. package/dist/javascript/package-name.js +47 -0
  61. package/dist/javascript/package-name.js.map +1 -0
  62. package/dist/javascript/parser.d.ts +9 -0
  63. package/dist/javascript/parser.d.ts.map +1 -1
  64. package/dist/javascript/parser.js +185 -88
  65. package/dist/javascript/parser.js.map +1 -1
  66. package/dist/javascript/preconditions.d.ts +4 -1
  67. package/dist/javascript/preconditions.d.ts.map +1 -1
  68. package/dist/javascript/preconditions.js +7 -4
  69. package/dist/javascript/preconditions.js.map +1 -1
  70. package/dist/javascript/print.d.ts +10 -1
  71. package/dist/javascript/print.d.ts.map +1 -1
  72. package/dist/javascript/print.js +120 -77
  73. package/dist/javascript/print.js.map +1 -1
  74. package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
  75. package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
  76. package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
  77. package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
  78. package/dist/javascript/recipes/index.d.ts +1 -0
  79. package/dist/javascript/recipes/index.d.ts.map +1 -1
  80. package/dist/javascript/recipes/index.js +1 -0
  81. package/dist/javascript/recipes/index.js.map +1 -1
  82. package/dist/javascript/scope.d.ts +60 -16
  83. package/dist/javascript/scope.d.ts.map +1 -1
  84. package/dist/javascript/scope.js +596 -131
  85. package/dist/javascript/scope.js.map +1 -1
  86. package/dist/javascript/search/uses-method.d.ts +1 -1
  87. package/dist/javascript/search/uses-method.d.ts.map +1 -1
  88. package/dist/javascript/search/uses-method.js +2 -2
  89. package/dist/javascript/search/uses-method.js.map +1 -1
  90. package/dist/javascript/search/uses-type.d.ts +14 -1
  91. package/dist/javascript/search/uses-type.d.ts.map +1 -1
  92. package/dist/javascript/search/uses-type.js +24 -9
  93. package/dist/javascript/search/uses-type.js.map +1 -1
  94. package/dist/javascript/templating/bindings.d.ts +1 -1
  95. package/dist/javascript/templating/bindings.d.ts.map +1 -1
  96. package/dist/javascript/templating/bindings.js +10 -50
  97. package/dist/javascript/templating/bindings.js.map +1 -1
  98. package/dist/javascript/templating/comparator.d.ts +8 -11
  99. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  100. package/dist/javascript/templating/comparator.js +12 -110
  101. package/dist/javascript/templating/comparator.js.map +1 -1
  102. package/dist/javascript/templating/engine.d.ts +34 -24
  103. package/dist/javascript/templating/engine.d.ts.map +1 -1
  104. package/dist/javascript/templating/engine.js +208 -115
  105. package/dist/javascript/templating/engine.js.map +1 -1
  106. package/dist/javascript/templating/pattern.d.ts +11 -5
  107. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  108. package/dist/javascript/templating/pattern.js +86 -20
  109. package/dist/javascript/templating/pattern.js.map +1 -1
  110. package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
  111. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  112. package/dist/javascript/templating/placeholder-replacement.js +126 -60
  113. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  114. package/dist/javascript/templating/precedence.d.ts +3 -1
  115. package/dist/javascript/templating/precedence.d.ts.map +1 -1
  116. package/dist/javascript/templating/precedence.js +28 -8
  117. package/dist/javascript/templating/precedence.js.map +1 -1
  118. package/dist/javascript/templating/template.d.ts +7 -6
  119. package/dist/javascript/templating/template.d.ts.map +1 -1
  120. package/dist/javascript/templating/template.js +31 -28
  121. package/dist/javascript/templating/template.js.map +1 -1
  122. package/dist/javascript/templating/types.d.ts +6 -3
  123. package/dist/javascript/templating/types.d.ts.map +1 -1
  124. package/dist/javascript/templating/utils.d.ts +10 -1
  125. package/dist/javascript/templating/utils.d.ts.map +1 -1
  126. package/dist/javascript/templating/utils.js +22 -3
  127. package/dist/javascript/templating/utils.js.map +1 -1
  128. package/dist/javascript/tree-debug.d.ts +2 -0
  129. package/dist/javascript/tree-debug.d.ts.map +1 -1
  130. package/dist/javascript/tree-debug.js +6 -0
  131. package/dist/javascript/tree-debug.js.map +1 -1
  132. package/dist/javascript/tsconfig.d.ts.map +1 -1
  133. package/dist/javascript/tsconfig.js +7 -17
  134. package/dist/javascript/tsconfig.js.map +1 -1
  135. package/dist/javascript/type-mapping.d.ts +35 -16
  136. package/dist/javascript/type-mapping.d.ts.map +1 -1
  137. package/dist/javascript/type-mapping.js +271 -252
  138. package/dist/javascript/type-mapping.js.map +1 -1
  139. package/dist/javascript/type-report-cli.d.ts +7 -0
  140. package/dist/javascript/type-report-cli.d.ts.map +1 -0
  141. package/dist/javascript/type-report-cli.js +129 -0
  142. package/dist/javascript/type-report-cli.js.map +1 -0
  143. package/dist/javascript/type-report.d.ts +61 -0
  144. package/dist/javascript/type-report.d.ts.map +1 -0
  145. package/dist/javascript/type-report.js +488 -0
  146. package/dist/javascript/type-report.js.map +1 -0
  147. package/dist/markers.d.ts +4 -0
  148. package/dist/markers.d.ts.map +1 -1
  149. package/dist/markers.js +8 -0
  150. package/dist/markers.js.map +1 -1
  151. package/dist/print.d.ts +6 -1
  152. package/dist/print.d.ts.map +1 -1
  153. package/dist/print.js +21 -7
  154. package/dist/print.js.map +1 -1
  155. package/dist/recipe.d.ts +5 -0
  156. package/dist/recipe.d.ts.map +1 -1
  157. package/dist/recipe.js +5 -0
  158. package/dist/recipe.js.map +1 -1
  159. package/dist/rewrite-javascript-version.txt +1 -1
  160. package/dist/rpc/java-rpc-client.d.ts +2 -0
  161. package/dist/rpc/java-rpc-client.d.ts.map +1 -1
  162. package/dist/rpc/java-rpc-client.js +1 -0
  163. package/dist/rpc/java-rpc-client.js.map +1 -1
  164. package/dist/rpc/queue.d.ts +24 -0
  165. package/dist/rpc/queue.d.ts.map +1 -1
  166. package/dist/rpc/queue.js +71 -11
  167. package/dist/rpc/queue.js.map +1 -1
  168. package/dist/rpc/request/get-object.d.ts +4 -0
  169. package/dist/rpc/request/get-object.d.ts.map +1 -1
  170. package/dist/rpc/request/get-object.js +23 -1
  171. package/dist/rpc/request/get-object.js.map +1 -1
  172. package/dist/rpc/request/parse.d.ts.map +1 -1
  173. package/dist/rpc/request/parse.js +3 -1
  174. package/dist/rpc/request/parse.js.map +1 -1
  175. package/dist/rpc/request/prepare-recipe.d.ts +3 -1
  176. package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
  177. package/dist/rpc/request/prepare-recipe.js +13 -6
  178. package/dist/rpc/request/prepare-recipe.js.map +1 -1
  179. package/dist/rpc/request/print.d.ts +8 -2
  180. package/dist/rpc/request/print.d.ts.map +1 -1
  181. package/dist/rpc/request/print.js +12 -9
  182. package/dist/rpc/request/print.js.map +1 -1
  183. package/dist/rpc/rewrite-rpc.d.ts +3 -2
  184. package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
  185. package/dist/rpc/rewrite-rpc.js +9 -3
  186. package/dist/rpc/rewrite-rpc.js.map +1 -1
  187. package/dist/rpc/server.d.ts.map +1 -1
  188. package/dist/rpc/server.js +19 -5
  189. package/dist/rpc/server.js.map +1 -1
  190. package/dist/search/is-source-file.d.ts +1 -0
  191. package/dist/search/is-source-file.d.ts.map +1 -1
  192. package/dist/search/is-source-file.js +7 -2
  193. package/dist/search/is-source-file.js.map +1 -1
  194. package/dist/test/java-rpc.d.ts.map +1 -1
  195. package/dist/test/java-rpc.js +3 -0
  196. package/dist/test/java-rpc.js.map +1 -1
  197. package/dist/test/rewrite-test.d.ts +5 -0
  198. package/dist/test/rewrite-test.d.ts.map +1 -1
  199. package/dist/test/rewrite-test.js +14 -3
  200. package/dist/test/rewrite-test.js.map +1 -1
  201. package/package.json +4 -3
  202. package/src/index.ts +3 -1
  203. package/src/java/formatting-utils.ts +5 -4
  204. package/src/java/rpc.ts +2 -0
  205. package/src/java/tree.ts +15 -0
  206. package/src/java/type.ts +19 -2
  207. package/src/javascript/add-import.ts +338 -152
  208. package/src/javascript/amd.ts +28 -12
  209. package/src/javascript/autodetect.ts +32 -41
  210. package/src/javascript/binding.ts +64 -22
  211. package/src/javascript/comparator.ts +129 -43
  212. package/src/javascript/format/format.ts +9 -0
  213. package/src/javascript/format/tabs-and-indents-visitor.ts +30 -3
  214. package/src/javascript/index.ts +6 -3
  215. package/src/javascript/markers.ts +77 -5
  216. package/src/javascript/method-matcher.ts +34 -22
  217. package/src/javascript/node-resolution-result.ts +3 -2
  218. package/src/javascript/package-name.ts +45 -0
  219. package/src/javascript/parser.ts +200 -91
  220. package/src/javascript/preconditions.ts +7 -4
  221. package/src/javascript/print.ts +123 -76
  222. package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
  223. package/src/javascript/recipes/index.ts +1 -0
  224. package/src/javascript/scope.ts +705 -169
  225. package/src/javascript/search/uses-method.ts +2 -2
  226. package/src/javascript/search/uses-type.ts +30 -9
  227. package/src/javascript/templating/bindings.ts +13 -38
  228. package/src/javascript/templating/comparator.ts +16 -127
  229. package/src/javascript/templating/engine.ts +236 -129
  230. package/src/javascript/templating/pattern.ts +107 -28
  231. package/src/javascript/templating/placeholder-replacement.ts +154 -62
  232. package/src/javascript/templating/precedence.ts +33 -11
  233. package/src/javascript/templating/template.ts +35 -30
  234. package/src/javascript/templating/types.ts +6 -3
  235. package/src/javascript/templating/utils.ts +24 -2
  236. package/src/javascript/tree-debug.ts +9 -0
  237. package/src/javascript/tsconfig.ts +7 -17
  238. package/src/javascript/type-mapping.ts +277 -255
  239. package/src/javascript/type-report-cli.ts +96 -0
  240. package/src/javascript/type-report.ts +575 -0
  241. package/src/markers.ts +8 -0
  242. package/src/print.ts +23 -8
  243. package/src/recipe.ts +6 -0
  244. package/src/rpc/java-rpc-client.ts +3 -0
  245. package/src/rpc/queue.ts +80 -7
  246. package/src/rpc/request/get-object.ts +24 -0
  247. package/src/rpc/request/parse.ts +4 -1
  248. package/src/rpc/request/prepare-recipe.ts +16 -6
  249. package/src/rpc/request/print.ts +12 -8
  250. package/src/rpc/rewrite-rpc.ts +16 -5
  251. package/src/rpc/server.ts +18 -6
  252. package/src/search/is-source-file.ts +8 -3
  253. package/src/test/java-rpc.ts +3 -0
  254. 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
@@ -910,6 +903,43 @@ export class JavaScriptTypeMapping {
910
903
  return method;
911
904
  }
912
905
 
906
+ // A method is declared by one class, which an object literal that spreads a union, for one, is not.
907
+ private declaringType(type: ts.Type): Type.FullyQualified {
908
+ const mapped = this.getType(type);
909
+ switch (mapped.kind) {
910
+ case Type.Kind.Class:
911
+ case Type.Kind.Annotation:
912
+ case Type.Kind.ShallowClass:
913
+ case Type.Kind.Parameterized:
914
+ return mapped as Type.FullyQualified;
915
+ case Type.Kind.Primitive:
916
+ return this.wrapperType(mapped as Type.Primitive);
917
+ default:
918
+ return Type.unknownType as Type.FullyQualified;
919
+ }
920
+ }
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
+
913
943
  private wrapperType(declaringType: (Type.FullyQualified & Type.Primitive) | Type.FullyQualified) {
914
944
  if (declaringType === Type.Primitive.String && this.stringWrapperType) {
915
945
  return this.getType(this.stringWrapperType) as Type.FullyQualified;
@@ -1030,8 +1060,10 @@ export class JavaScriptTypeMapping {
1030
1060
  const anonymous = !mappedType || mappedType.kind !== Type.Kind.Class ||
1031
1061
  (mappedType as Type.Class).fullyQualifiedName.startsWith('{');
1032
1062
 
1033
- // Handle different types
1034
- if (receiverBinding?.namespace && anonymous) {
1063
+ const declaringClass = this.declaringClassOf(symbol);
1064
+ if (declaringClass) {
1065
+ declaringType = declaringClass;
1066
+ } else if (receiverBinding?.namespace && anonymous) {
1035
1067
  declaringType = this.moduleType(receiverBinding.module);
1036
1068
  if (methodName === 'default') {
1037
1069
  methodName = '<default>';
@@ -1104,56 +1136,34 @@ export class JavaScriptTypeMapping {
1104
1136
  // For namespace imports, use the namespace symbol's `name` as the module specifier (e.g. `React` instead of `react`)
1105
1137
  moduleSpecifier = aliasedParentSymbol.name;
1106
1138
  } else {
1107
- // Now find the import declaration to get the module specifier
1108
- if (exprSymbol.declarations && exprSymbol.declarations.length > 0) {
1109
- let importNode: ts.Node = exprSymbol.declarations[0];
1110
-
1111
- // Traverse up to find the ImportDeclaration
1112
- while (importNode && !ts.isImportDeclaration(importNode)) {
1113
- importNode = importNode.parent;
1114
- }
1115
-
1116
- if (importNode && ts.isImportDeclaration(importNode)) {
1117
- const importDeclNode = importNode as ts.ImportDeclaration;
1118
- if (ts.isStringLiteral(importDeclNode.moduleSpecifier)) {
1119
- moduleSpecifier = importDeclNode.moduleSpecifier.text;
1120
- }
1121
- }
1122
- }
1139
+ moduleSpecifier = this.importedModule(exprSymbol.declarations?.[0]);
1123
1140
  }
1124
1141
  }
1125
1142
  }
1126
1143
 
1127
- 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) {
1128
1149
  // This is an imported function - use the module specifier as declaring type
1129
- if (moduleSpecifier.startsWith('node:')) {
1130
- // Node.js built-in module
1131
- declaringType = {
1132
- kind: Type.Kind.Class,
1133
- flags: 0, // TODO - determine flags
1134
- fullyQualifiedName: 'node'
1135
- } as Type.FullyQualified;
1136
- 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;
1137
1163
  } else {
1138
- // Regular module import
1139
- declaringType = {
1140
- kind: Type.Kind.Class,
1141
- flags: 0, // TODO - determine flags
1142
- fullyQualifiedName: moduleSpecifier
1143
- } as Type.FullyQualified;
1144
- // A default import binds an `ImportClause`; its aliased symbol carries the internal
1145
- // name of the default export (e.g. `e` for express), so represent it as `<default>`.
1146
- // Named imports (`ImportSpecifier`) keep the original exported name.
1147
- const isDefaultImport = exprSymbol?.declarations?.some(ts.isImportClause) ?? false;
1148
- if (isDefaultImport) {
1149
- methodName = '<default>';
1150
- } else if (aliasedSymbol?.declarations?.length) {
1151
- methodName = aliasedSymbol.name;
1152
- } else {
1153
- // A package without type declarations resolves the import to the checker's
1154
- // `unknown` symbol, so the exported name comes from the import itself.
1155
- methodName = this.importBinding(node.expression)?.exportName ?? aliasedSymbol?.name ?? methodName;
1156
- }
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;
1157
1167
  }
1158
1168
  } else if (this.requiredModuleOfExpression(node.expression)) {
1159
1169
  // `const m = require('m'); m()` calls the module's default export.
@@ -1184,7 +1194,7 @@ export class JavaScriptTypeMapping {
1184
1194
  const parent = (symbol as any).parent;
1185
1195
  if (parent) {
1186
1196
  const parentType = this.checker.getDeclaredTypeOfSymbol(parent);
1187
- declaringType = this.getType(parentType) as Type.FullyQualified;
1197
+ declaringType = this.declaringType(parentType);
1188
1198
  } else {
1189
1199
  declaringType = Type.unknownType as Type.FullyQualified;
1190
1200
  }
@@ -1220,7 +1230,7 @@ export class JavaScriptTypeMapping {
1220
1230
  const parent = node.parent;
1221
1231
  if (ts.isClassDeclaration(parent) || ts.isInterfaceDeclaration(parent) || ts.isObjectLiteralExpression(parent)) {
1222
1232
  const parentType = this.checker.getTypeAtLocation(parent);
1223
- declaringType = this.getType(parentType) as Type.FullyQualified;
1233
+ declaringType = this.declaringType(parentType);
1224
1234
  } else {
1225
1235
  declaringType = Type.unknownType as Type.FullyQualified;
1226
1236
  }
@@ -1243,7 +1253,7 @@ export class JavaScriptTypeMapping {
1243
1253
  const parent = node.parent;
1244
1254
  if (ts.isClassDeclaration(parent) || ts.isClassExpression(parent)) {
1245
1255
  const parentType = this.checker.getTypeAtLocation(parent);
1246
- declaringType = this.getType(parentType) as Type.FullyQualified;
1256
+ declaringType = this.declaringType(parentType);
1247
1257
  } else {
1248
1258
  declaringType = Type.unknownType as Type.FullyQualified;
1249
1259
  }
@@ -1256,23 +1266,7 @@ export class JavaScriptTypeMapping {
1256
1266
 
1257
1267
  methodName = node.name ? node.name.getText() : "<anonymous>";
1258
1268
 
1259
- // Derive declaring type from source file module path (like Go's type_mapper.go).
1260
- // Use the same relativization as getFullyQualifiedName() so that declarations
1261
- // and invocations produce matching FQNs.
1262
- let moduleFqn: string;
1263
- const fileName = node.getSourceFile().fileName;
1264
- if (this.sourceRoot && path.isAbsolute(fileName)) {
1265
- moduleFqn = path.relative(this.sourceRoot, fileName);
1266
- } else {
1267
- moduleFqn = fileName;
1268
- }
1269
- // Strip file extension to get the module name
1270
- moduleFqn = moduleFqn.replace(/\.[^/.]+$/, '');
1271
- declaringType = {
1272
- kind: Type.Kind.Class,
1273
- flags: 0,
1274
- fullyQualifiedName: moduleFqn
1275
- } as Type.FullyQualified;
1269
+ declaringType = this.sourceModuleType(node.getSourceFile());
1276
1270
 
1277
1271
  // Get type parameters from node
1278
1272
  if (node.typeParameters) {
@@ -1310,35 +1304,7 @@ export class JavaScriptTypeMapping {
1310
1304
  if (symbol.flags & ts.SymbolFlags.Alias) {
1311
1305
  const aliasedSymbol = this.checker.getAliasedSymbol(symbol);
1312
1306
  if (aliasedSymbol && aliasedSymbol !== symbol && symbol.declarations && symbol.declarations.length > 0) {
1313
- // Try to find the import declaration to get the module specifier
1314
- let importNode: ts.Node | undefined = symbol.declarations[0];
1315
-
1316
- // Traverse up to find the ImportDeclaration or ImportSpecifier
1317
- while (importNode && importNode.parent && !ts.isImportDeclaration(importNode) && !ts.isImportSpecifier(importNode)) {
1318
- importNode = importNode.parent;
1319
- }
1320
-
1321
- let moduleSpecifier: string | undefined;
1322
-
1323
- if (importNode && ts.isImportSpecifier(importNode)) {
1324
- // Named import like: import { ClipLoader } from 'react-spinners'
1325
- // ImportSpecifier -> NamedImports -> ImportClause -> ImportDeclaration
1326
- const namedImports = importNode.parent; // NamedImports
1327
- if (namedImports && ts.isNamedImports(namedImports)) {
1328
- const importClause = namedImports.parent; // ImportClause
1329
- if (importClause && ts.isImportClause(importClause)) {
1330
- const importDecl = importClause.parent; // ImportDeclaration
1331
- if (importDecl && ts.isImportDeclaration(importDecl) && ts.isStringLiteral(importDecl.moduleSpecifier)) {
1332
- moduleSpecifier = importDecl.moduleSpecifier.text;
1333
- }
1334
- }
1335
- }
1336
- } else if (importNode && ts.isImportDeclaration(importNode)) {
1337
- // Default or namespace import
1338
- if (ts.isStringLiteral(importNode.moduleSpecifier)) {
1339
- moduleSpecifier = importNode.moduleSpecifier.text;
1340
- }
1341
- }
1307
+ const moduleSpecifier = this.importedModule(symbol.declarations[0]);
1342
1308
 
1343
1309
  if (moduleSpecifier) {
1344
1310
  // Build the fully qualified name from module specifier + symbol name
@@ -1352,47 +1318,35 @@ export class JavaScriptTypeMapping {
1352
1318
  // This returns names with quotes that we need to clean up
1353
1319
  // e.g., '"React"."Component"' -> 'React.Component'
1354
1320
  const tsQualifiedName = this.checker.getFullyQualifiedName(symbol);
1355
- let cleanedName = tsQualifiedName.replace(/"/g, '');
1356
-
1357
- // Check if this is a file path from node_modules (happens with some packages)
1358
- // TypeScript sometimes returns full paths instead of module names
1359
- 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
1360
1330
  // Extract the module name from the path
1361
1331
  // Example: /private/var/.../node_modules/react-spinners/src/index.ClipLoader
1362
1332
  // Should become: react-spinners.ClipLoader
1363
1333
  const nodeModulesIndex = cleanedName.indexOf('node_modules/');
1364
1334
  const afterNodeModules = cleanedName.substring(nodeModulesIndex + 'node_modules/'.length);
1365
1335
 
1366
- // Split by '/' to get parts of the path
1367
- const pathParts = afterNodeModules.split('/');
1368
-
1369
- if (pathParts.length > 0) {
1370
- // First part is the package name (might be scoped like @types)
1371
- let packageName = pathParts[0];
1372
-
1373
- // Handle scoped packages
1374
- if (packageName.startsWith('@') && pathParts.length > 1) {
1375
- packageName = `${packageName}/${pathParts[1]}`;
1376
- }
1377
-
1378
- // Normalize `@types/<pkg>` to the importable specifier `<pkg>` so that types
1379
- // reached transitively (e.g. a call's return type, resolved via the declaration
1380
- // file's node_modules path) match the names used for directly imported types.
1381
- packageName = this.normalizePackageName(packageName);
1382
-
1383
- // The symbol name is appended to the declaration file's path, so it is whatever
1384
- // follows the last dot of the final path segment. Directory names carry dots of
1385
- // their own — `.pnpm`, `.yarn`, a hidden parent such as `.claude`, a versioned
1386
- // pnpm directory — so a search over the whole path would split there instead and
1387
- // fold most of the path into the name.
1388
- const lastSegmentIndex = cleanedName.lastIndexOf('/') + 1;
1389
- const lastDotIndex = cleanedName.lastIndexOf('.');
1390
- if (lastDotIndex > lastSegmentIndex) {
1391
- const symbolName = cleanedName.substring(lastDotIndex + 1);
1392
- cleanedName = `${packageName}.${symbolName}`;
1393
- } else {
1394
- cleanedName = packageName;
1395
- }
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;
1396
1350
  }
1397
1351
  } else if (path.isAbsolute(cleanedName)) {
1398
1352
  // TypeScript returns absolute file paths as module names for project source files
@@ -1429,7 +1383,7 @@ export class JavaScriptTypeMapping {
1429
1383
  // Preserved as-is:
1430
1384
  // - UMD globals (`export as namespace X`, e.g. React, lodash `_`, jQuery `$`): the
1431
1385
  // namespace name is the conventional public identifier;
1432
- // - `global.*` (TypeScript's marker for ambient globals);
1386
+ // - `global.*`, a `declare global` augmentation, whose prefix is dropped below;
1433
1387
  // - namespaces that already match the package name, and any non-node_modules type.
1434
1388
  const namespaceName = cleanedName.substring(0, cleanedName.indexOf('.'));
1435
1389
  if (namespaceName !== 'global') {
@@ -1444,11 +1398,40 @@ export class JavaScriptTypeMapping {
1444
1398
  }
1445
1399
  }
1446
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
+
1447
1406
  return cleanedName.endsWith('Constructor') ?
1448
1407
  cleanedName.substring(0, cleanedName.length - 'Constructor'.length) :
1449
1408
  cleanedName;
1450
1409
  }
1451
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
+
1452
1435
  /**
1453
1436
  * Whether {@code namespaceName} is exposed as a UMD global from the given declaration file
1454
1437
  * (e.g. `export as namespace React`). The namespace name of a UMD global is the conventional
@@ -1501,12 +1484,42 @@ export class JavaScriptTypeMapping {
1501
1484
  } as Type.Class;
1502
1485
  }
1503
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
+
1504
1514
  /**
1505
1515
  * Populates the class type with members, methods, heritage, and type parameters
1506
1516
  * Since the shell is already in the cache, any recursive references will find it
1507
1517
  */
1508
1518
  private populateClassType(classType: Type.Class, type: ts.Type): void {
1509
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);
1510
1523
 
1511
1524
  // Try to get base types using TypeScript's getBaseTypes API
1512
1525
  // This works for both local and external types (from node_modules)
@@ -1524,9 +1537,6 @@ export class JavaScriptTypeMapping {
1524
1537
  } else if (symbol) {
1525
1538
  // For constructor functions or type references, we need to get the actual class type
1526
1539
  // Try to get the type of the class itself (not the constructor or instance)
1527
- const classSymbol = symbol.flags & ts.SymbolFlags.Alias ?
1528
- this.checker.getAliasedSymbol(symbol) : symbol;
1529
-
1530
1540
  if (classSymbol && classSymbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface)) {
1531
1541
  // Get the type of the class declaration itself
1532
1542
  const declaredType = this.checker.getDeclaredTypeOfSymbol(classSymbol);
@@ -1552,32 +1562,27 @@ export class JavaScriptTypeMapping {
1552
1562
  }
1553
1563
 
1554
1564
  if (baseTypes && baseTypes.length > 0) {
1555
- // For classes, the first base type is usually the superclass
1556
- // Additional base types are interfaces
1557
- if (classType.classKind === Type.Class.Kind.Class) {
1558
- const firstBase = this.getType(baseTypes[0]);
1559
- // Handle both Class and Parameterized (e.g., Component<Props>)
1560
- if (Type.isClass(firstBase)) {
1561
- (classType as any).supertype = firstBase;
1562
- } else if (Type.isParameterized(firstBase)) {
1563
- // For parameterized types, use the base class as the supertype
1564
- (classType as any).supertype = (firstBase as Type.Parameterized).type;
1565
- }
1566
- // Rest are interfaces
1567
- for (let i = 1; i < baseTypes.length; i++) {
1568
- const interfaceType = this.getType(baseTypes[i]);
1569
- if (Type.isClass(interfaceType)) {
1570
- classType.interfaces.push(interfaceType);
1571
- }
1572
- }
1573
- } else {
1574
- // For interfaces, all base types are extended interfaces
1575
- for (const baseType of baseTypes) {
1576
- const interfaceType = this.getType(baseType);
1577
- if (Type.isClass(interfaceType)) {
1578
- classType.interfaces.push(interfaceType);
1579
- }
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;
1580
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));
1581
1586
  }
1582
1587
  }
1583
1588
  }
@@ -1616,6 +1621,7 @@ export class JavaScriptTypeMapping {
1616
1621
  const propType = this.checker.getTypeOfSymbolAtLocation(prop, declaration);
1617
1622
  const variable: Type.Variable = {
1618
1623
  kind: Type.Kind.Variable,
1624
+ flags: 0,
1619
1625
  name: prop.getName(),
1620
1626
  owner: classType, // Cyclic reference to the containing class (already in cache)
1621
1627
  type: this.getType(propType), // This will find classType in cache if it's recursive
@@ -1661,6 +1667,9 @@ export class JavaScriptTypeMapping {
1661
1667
  return Type.Primitive.String;
1662
1668
  } else if (type.flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral | ts.TypeFlags.BooleanLike)) {
1663
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);
1664
1673
  }
1665
1674
 
1666
1675
  // Check for type aliases that may resolve to primitives
@@ -1782,6 +1791,19 @@ export class JavaScriptTypeMapping {
1782
1791
  } as Type.Class;
1783
1792
  }
1784
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
+
1785
1807
  /**
1786
1808
  * Create an empty function type shell with FQN 𝑓.
1787
1809
  * The shell will be populated later to handle circular references.