@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.
- package/README.md +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/java/formatting-utils.d.ts +2 -1
- package/dist/java/formatting-utils.d.ts.map +1 -1
- package/dist/java/formatting-utils.js +4 -3
- package/dist/java/formatting-utils.js.map +1 -1
- package/dist/java/rpc.d.ts.map +1 -1
- package/dist/java/rpc.js +2 -0
- package/dist/java/rpc.js.map +1 -1
- package/dist/java/tree.d.ts +8 -0
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +11 -1
- package/dist/java/tree.js.map +1 -1
- package/dist/java/type.d.ts +7 -1
- package/dist/java/type.d.ts.map +1 -1
- package/dist/java/type.js +16 -1
- package/dist/java/type.js.map +1 -1
- package/dist/javascript/add-import.d.ts +27 -21
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +303 -143
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts.map +1 -1
- package/dist/javascript/amd.js +25 -10
- package/dist/javascript/amd.js.map +1 -1
- package/dist/javascript/autodetect.js +36 -45
- package/dist/javascript/autodetect.js.map +1 -1
- package/dist/javascript/binding.d.ts +6 -4
- package/dist/javascript/binding.d.ts.map +1 -1
- package/dist/javascript/binding.js +54 -18
- package/dist/javascript/binding.js.map +1 -1
- package/dist/javascript/comparator.d.ts +22 -5
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +116 -38
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts +2 -0
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +11 -1
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/index.d.ts +4 -2
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +10 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/markers.d.ts.map +1 -1
- package/dist/javascript/markers.js +64 -0
- package/dist/javascript/markers.js.map +1 -1
- package/dist/javascript/method-matcher.d.ts +13 -2
- package/dist/javascript/method-matcher.d.ts.map +1 -1
- package/dist/javascript/method-matcher.js +32 -19
- package/dist/javascript/method-matcher.js.map +1 -1
- package/dist/javascript/package-name.d.ts +6 -0
- package/dist/javascript/package-name.d.ts.map +1 -0
- package/dist/javascript/package-name.js +47 -0
- package/dist/javascript/package-name.js.map +1 -0
- package/dist/javascript/preconditions.d.ts +4 -1
- package/dist/javascript/preconditions.d.ts.map +1 -1
- package/dist/javascript/preconditions.js +7 -4
- package/dist/javascript/preconditions.js.map +1 -1
- package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
- package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
- package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
- package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
- package/dist/javascript/recipes/index.d.ts +1 -0
- package/dist/javascript/recipes/index.d.ts.map +1 -1
- package/dist/javascript/recipes/index.js +1 -0
- package/dist/javascript/recipes/index.js.map +1 -1
- package/dist/javascript/scope.d.ts +60 -16
- package/dist/javascript/scope.d.ts.map +1 -1
- package/dist/javascript/scope.js +596 -131
- package/dist/javascript/scope.js.map +1 -1
- package/dist/javascript/search/uses-method.d.ts +1 -1
- package/dist/javascript/search/uses-method.d.ts.map +1 -1
- package/dist/javascript/search/uses-method.js +2 -2
- package/dist/javascript/search/uses-method.js.map +1 -1
- package/dist/javascript/search/uses-type.d.ts +14 -1
- package/dist/javascript/search/uses-type.d.ts.map +1 -1
- package/dist/javascript/search/uses-type.js +24 -9
- package/dist/javascript/search/uses-type.js.map +1 -1
- package/dist/javascript/templating/bindings.d.ts +1 -1
- package/dist/javascript/templating/bindings.d.ts.map +1 -1
- package/dist/javascript/templating/bindings.js +10 -50
- package/dist/javascript/templating/bindings.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +8 -11
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +12 -110
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +34 -24
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +200 -112
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +11 -5
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +86 -20
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +126 -60
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/precedence.d.ts +3 -1
- package/dist/javascript/templating/precedence.d.ts.map +1 -1
- package/dist/javascript/templating/precedence.js +28 -8
- package/dist/javascript/templating/precedence.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +7 -6
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +31 -28
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +6 -3
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +10 -1
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +22 -3
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/tree-debug.d.ts +2 -0
- package/dist/javascript/tree-debug.d.ts.map +1 -1
- package/dist/javascript/tree-debug.js +6 -0
- package/dist/javascript/tree-debug.js.map +1 -1
- package/dist/javascript/tsconfig.d.ts.map +1 -1
- package/dist/javascript/tsconfig.js +7 -17
- package/dist/javascript/tsconfig.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +34 -16
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +253 -249
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/javascript/type-report-cli.d.ts +7 -0
- package/dist/javascript/type-report-cli.d.ts.map +1 -0
- package/dist/javascript/type-report-cli.js +129 -0
- package/dist/javascript/type-report-cli.js.map +1 -0
- package/dist/javascript/type-report.d.ts +61 -0
- package/dist/javascript/type-report.d.ts.map +1 -0
- package/dist/javascript/type-report.js +488 -0
- package/dist/javascript/type-report.js.map +1 -0
- package/dist/markers.d.ts +4 -0
- package/dist/markers.d.ts.map +1 -1
- package/dist/markers.js +8 -0
- package/dist/markers.js.map +1 -1
- package/dist/recipe.d.ts +5 -0
- package/dist/recipe.d.ts.map +1 -1
- package/dist/recipe.js +5 -0
- package/dist/recipe.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/queue.d.ts +16 -0
- package/dist/rpc/queue.d.ts.map +1 -1
- package/dist/rpc/queue.js +27 -9
- package/dist/rpc/queue.js.map +1 -1
- package/dist/rpc/request/prepare-recipe.d.ts +3 -1
- package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
- package/dist/rpc/request/prepare-recipe.js +13 -6
- package/dist/rpc/request/prepare-recipe.js.map +1 -1
- package/dist/search/is-source-file.d.ts +1 -0
- package/dist/search/is-source-file.d.ts.map +1 -1
- package/dist/search/is-source-file.js +7 -2
- package/dist/search/is-source-file.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +5 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +14 -3
- package/dist/test/rewrite-test.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +3 -1
- package/src/java/formatting-utils.ts +5 -4
- package/src/java/rpc.ts +2 -0
- package/src/java/tree.ts +15 -0
- package/src/java/type.ts +19 -2
- package/src/javascript/add-import.ts +338 -152
- package/src/javascript/amd.ts +28 -12
- package/src/javascript/autodetect.ts +32 -41
- package/src/javascript/binding.ts +64 -22
- package/src/javascript/comparator.ts +119 -43
- package/src/javascript/format/format.ts +9 -0
- package/src/javascript/index.ts +6 -3
- package/src/javascript/markers.ts +43 -1
- package/src/javascript/method-matcher.ts +34 -22
- package/src/javascript/package-name.ts +45 -0
- package/src/javascript/preconditions.ts +7 -4
- package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
- package/src/javascript/recipes/index.ts +1 -0
- package/src/javascript/scope.ts +705 -169
- package/src/javascript/search/uses-method.ts +2 -2
- package/src/javascript/search/uses-type.ts +30 -9
- package/src/javascript/templating/bindings.ts +13 -38
- package/src/javascript/templating/comparator.ts +16 -127
- package/src/javascript/templating/engine.ts +229 -127
- package/src/javascript/templating/pattern.ts +107 -28
- package/src/javascript/templating/placeholder-replacement.ts +154 -62
- package/src/javascript/templating/precedence.ts +33 -11
- package/src/javascript/templating/template.ts +35 -30
- package/src/javascript/templating/types.ts +6 -3
- package/src/javascript/templating/utils.ts +24 -2
- package/src/javascript/tree-debug.ts +9 -0
- package/src/javascript/tsconfig.ts +7 -17
- package/src/javascript/type-mapping.ts +258 -252
- package/src/javascript/type-report-cli.ts +96 -0
- package/src/javascript/type-report.ts +575 -0
- package/src/markers.ts +8 -0
- package/src/recipe.ts +6 -0
- package/src/rpc/queue.ts +44 -5
- package/src/rpc/request/prepare-recipe.ts +16 -6
- package/src/search/is-source-file.ts +8 -3
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
608
|
-
if (
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
746
|
-
|
|
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 (
|
|
846
|
+
if (afterNodeModules.split('/')[0] === '.pnpm') {
|
|
826
847
|
return undefined;
|
|
827
848
|
}
|
|
828
849
|
|
|
829
|
-
return
|
|
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
|
-
|
|
1050
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
-
//
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1374
|
-
|
|
1375
|
-
if (
|
|
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
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
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
|
|
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
|
-
//
|
|
1572
|
-
|
|
1573
|
-
if (classType.classKind === Type.Class.Kind.Class
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
if (
|
|
1577
|
-
|
|
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.
|