@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.
- 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 +120 -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/format/tabs-and-indents-visitor.d.ts.map +1 -1
- package/dist/javascript/format/tabs-and-indents-visitor.js +31 -4
- package/dist/javascript/format/tabs-and-indents-visitor.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 +12 -0
- package/dist/javascript/markers.d.ts.map +1 -1
- package/dist/javascript/markers.js +81 -2
- 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/node-resolution-result.js +3 -2
- package/dist/javascript/node-resolution-result.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/parser.d.ts +9 -0
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +185 -88
- package/dist/javascript/parser.js.map +1 -1
- 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/print.d.ts +10 -1
- package/dist/javascript/print.d.ts.map +1 -1
- package/dist/javascript/print.js +120 -77
- package/dist/javascript/print.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 +208 -115
- 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 +35 -16
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +271 -252
- 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/print.d.ts +6 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +21 -7
- package/dist/print.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/java-rpc-client.d.ts +2 -0
- package/dist/rpc/java-rpc-client.d.ts.map +1 -1
- package/dist/rpc/java-rpc-client.js +1 -0
- package/dist/rpc/java-rpc-client.js.map +1 -1
- package/dist/rpc/queue.d.ts +24 -0
- package/dist/rpc/queue.d.ts.map +1 -1
- package/dist/rpc/queue.js +71 -11
- package/dist/rpc/queue.js.map +1 -1
- package/dist/rpc/request/get-object.d.ts +4 -0
- package/dist/rpc/request/get-object.d.ts.map +1 -1
- package/dist/rpc/request/get-object.js +23 -1
- package/dist/rpc/request/get-object.js.map +1 -1
- package/dist/rpc/request/parse.d.ts.map +1 -1
- package/dist/rpc/request/parse.js +3 -1
- package/dist/rpc/request/parse.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/rpc/request/print.d.ts +8 -2
- package/dist/rpc/request/print.d.ts.map +1 -1
- package/dist/rpc/request/print.js +12 -9
- package/dist/rpc/request/print.js.map +1 -1
- package/dist/rpc/rewrite-rpc.d.ts +3 -2
- package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
- package/dist/rpc/rewrite-rpc.js +9 -3
- package/dist/rpc/rewrite-rpc.js.map +1 -1
- package/dist/rpc/server.d.ts.map +1 -1
- package/dist/rpc/server.js +19 -5
- package/dist/rpc/server.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/java-rpc.d.ts.map +1 -1
- package/dist/test/java-rpc.js +3 -0
- package/dist/test/java-rpc.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 +129 -43
- package/src/javascript/format/format.ts +9 -0
- package/src/javascript/format/tabs-and-indents-visitor.ts +30 -3
- package/src/javascript/index.ts +6 -3
- package/src/javascript/markers.ts +77 -5
- package/src/javascript/method-matcher.ts +34 -22
- package/src/javascript/node-resolution-result.ts +3 -2
- package/src/javascript/package-name.ts +45 -0
- package/src/javascript/parser.ts +200 -91
- package/src/javascript/preconditions.ts +7 -4
- package/src/javascript/print.ts +123 -76
- 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 +236 -129
- 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 +277 -255
- 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/print.ts +23 -8
- package/src/recipe.ts +6 -0
- package/src/rpc/java-rpc-client.ts +3 -0
- package/src/rpc/queue.ts +80 -7
- package/src/rpc/request/get-object.ts +24 -0
- package/src/rpc/request/parse.ts +4 -1
- package/src/rpc/request/prepare-recipe.ts +16 -6
- package/src/rpc/request/print.ts +12 -8
- package/src/rpc/rewrite-rpc.ts +16 -5
- package/src/rpc/server.ts +18 -6
- package/src/search/is-source-file.ts +8 -3
- package/src/test/java-rpc.ts +3 -0
- package/src/test/rewrite-test.ts +14 -3
package/src/javascript/scope.ts
CHANGED
|
@@ -13,14 +13,63 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {Cursor, isTree} from "../tree";
|
|
16
|
+
import {Cursor, isTree, Tree} from "../tree";
|
|
17
17
|
import {J} from "../java";
|
|
18
|
+
import {UUID} from "../uuid";
|
|
18
19
|
import {JS} from "./tree";
|
|
19
20
|
// scope.ts sits below the visitor, so this stays type-only.
|
|
20
21
|
import type {JavaScriptVisitor} from "./visitor";
|
|
21
22
|
|
|
22
23
|
const noNames: ReadonlySet<string> = new Set();
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* What a name means where it is bound, a value, a type or a namespace, which is what TypeScript
|
|
27
|
+
* resolves a name against. A `const` binds a value, an interface a type, a class both, and the
|
|
28
|
+
* qualifier before a dot in a type reads a namespace, which an interface does not declare. A read
|
|
29
|
+
* with one meaning is hidden only by a binding with that meaning.
|
|
30
|
+
*/
|
|
31
|
+
export type Meaning = 'value' | 'type' | 'namespace';
|
|
32
|
+
|
|
33
|
+
/** One name a scope binds, with every meaning it has there and the declarations giving it each. */
|
|
34
|
+
interface Binding {
|
|
35
|
+
meanings: ReadonlySet<Meaning>;
|
|
36
|
+
declarations: readonly Bound[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type Bindings = ReadonlyMap<string, Binding>;
|
|
40
|
+
|
|
41
|
+
const noBindings: Bindings = new Map();
|
|
42
|
+
|
|
43
|
+
/** Whether `bindings` holds `name` with `meaning`, or with any meaning where none is asked for. */
|
|
44
|
+
function binds(bindings: Bindings, name: string, meaning?: Meaning): boolean {
|
|
45
|
+
const binding = bindings.get(name);
|
|
46
|
+
return binding !== undefined && (meaning === undefined || binding.meanings.has(meaning));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** One name a declaration binds, as the identifier spelling it, and the meanings it has there. */
|
|
50
|
+
interface Bound {
|
|
51
|
+
identifier: J.Identifier;
|
|
52
|
+
meanings: readonly Meaning[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function bound(identifiers: J.Identifier[], ...meanings: Meaning[]): Bound[] {
|
|
56
|
+
return identifiers.map(identifier => ({identifier, meanings}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function toBindings(bound: Bound[]): Bindings {
|
|
60
|
+
const bindings = new Map<string, { meanings: Set<Meaning>; declarations: Bound[] }>();
|
|
61
|
+
for (const declaration of bound) {
|
|
62
|
+
const name = declaration.identifier.simpleName;
|
|
63
|
+
let held = bindings.get(name);
|
|
64
|
+
if (!held) {
|
|
65
|
+
bindings.set(name, held = {meanings: new Set(), declarations: []});
|
|
66
|
+
}
|
|
67
|
+
declaration.meanings.forEach(meaning => held.meanings.add(meaning));
|
|
68
|
+
held.declarations.push(declaration);
|
|
69
|
+
}
|
|
70
|
+
return bindings;
|
|
71
|
+
}
|
|
72
|
+
|
|
24
73
|
/** One scope: the names it binds itself, the scopes around it, and what they answer together. */
|
|
25
74
|
export interface Scope {
|
|
26
75
|
/** The names this scope binds itself, which is not what it reaches: for that, ask `declares`. */
|
|
@@ -33,16 +82,17 @@ export interface Scope {
|
|
|
33
82
|
*/
|
|
34
83
|
walk(visit: (scope: Scope) => boolean): void;
|
|
35
84
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Whether this scope or one enclosing it binds `name`. Any meaning counts unless one is asked
|
|
87
|
+
* for, since a new binding collides with a type of its name as much as with a value.
|
|
88
|
+
*/
|
|
89
|
+
declares(name: string, meaning?: Meaning): boolean;
|
|
38
90
|
|
|
39
91
|
/**
|
|
40
|
-
* The node owning the innermost scope that binds `name
|
|
41
|
-
*
|
|
42
|
-
* nothing in scope does. A caller holding a declaration asks whether this is the node it came
|
|
43
|
-
* from: anything nearer shadows it.
|
|
92
|
+
* The node owning the innermost scope that binds `name`, or undefined. A caller holding a
|
|
93
|
+
* declaration compares this to the node it came from, since anything nearer shadows it.
|
|
44
94
|
*/
|
|
45
|
-
declaringScope(name: string): J | undefined;
|
|
95
|
+
declaringScope(name: string, meaning?: Meaning): J | undefined;
|
|
46
96
|
}
|
|
47
97
|
|
|
48
98
|
/**
|
|
@@ -56,7 +106,7 @@ export function scopeOf(cursor: Cursor): Scope {
|
|
|
56
106
|
|
|
57
107
|
function scopeAt(cursor: Cursor): Scope {
|
|
58
108
|
return {
|
|
59
|
-
names: () =>
|
|
109
|
+
names: () => new Set(bindingsAt(cursor).keys()),
|
|
60
110
|
walk: visit => {
|
|
61
111
|
for (let c: Cursor | undefined = cursor; c; c = enclosingScopeCursor(c.parent)) {
|
|
62
112
|
if (!visit(scopeAt(c))) {
|
|
@@ -64,8 +114,8 @@ function scopeAt(cursor: Cursor): Scope {
|
|
|
64
114
|
}
|
|
65
115
|
}
|
|
66
116
|
},
|
|
67
|
-
declares: name => declaringScopeOf(cursor, name) !== undefined,
|
|
68
|
-
declaringScope: name => declaringScopeOf(cursor, name)
|
|
117
|
+
declares: (name, meaning) => declaringScopeOf(cursor, name, meaning) !== undefined,
|
|
118
|
+
declaringScope: (name, meaning) => declaringScopeOf(cursor, name, meaning)
|
|
69
119
|
};
|
|
70
120
|
}
|
|
71
121
|
|
|
@@ -80,8 +130,10 @@ function enclosingScopeCursor(from: Cursor | undefined): Cursor | undefined {
|
|
|
80
130
|
|
|
81
131
|
/** The nodes {@link readBindings} answers for; everything else binds nothing and is not a scope. */
|
|
82
132
|
const scopeKinds = new Set<string>([
|
|
83
|
-
JS.Kind.CompilationUnit, J.Kind.Block, J.Kind.MethodDeclaration,
|
|
84
|
-
J.Kind.
|
|
133
|
+
JS.Kind.CompilationUnit, J.Kind.Block, J.Kind.MethodDeclaration, JS.Kind.ComputedPropertyMethodDeclaration,
|
|
134
|
+
J.Kind.Lambda, JS.Kind.ArrowFunction, J.Kind.ClassDeclaration, J.Kind.TryCatch, J.Kind.ForLoop,
|
|
135
|
+
J.Kind.ForEachLoop, JS.Kind.ForInLoop, JS.Kind.NamespaceDeclaration, JS.Kind.TypeDeclaration,
|
|
136
|
+
JS.Kind.FunctionType, JS.Kind.MappedType, JS.Kind.ConditionalType
|
|
85
137
|
]);
|
|
86
138
|
|
|
87
139
|
/**
|
|
@@ -132,15 +184,20 @@ export function namesDeclaredWithin(node: unknown, cacheKey: object = node as ob
|
|
|
132
184
|
}
|
|
133
185
|
|
|
134
186
|
const names = new Set<string>();
|
|
135
|
-
const collect = (node:
|
|
136
|
-
declarationNames(node).forEach(
|
|
137
|
-
|
|
187
|
+
const collect = (node: Tree): boolean => {
|
|
188
|
+
declarationNames(node).forEach(({identifier}) => names.add(identifier.simpleName));
|
|
189
|
+
const members = membersOf(node);
|
|
190
|
+
if (!members) {
|
|
138
191
|
return true;
|
|
139
192
|
}
|
|
140
|
-
// The walk ends at this branch, so
|
|
141
|
-
|
|
193
|
+
// The walk ends at this branch, so what else the node holds is read here.
|
|
194
|
+
if (node.kind === J.Kind.ClassDeclaration) {
|
|
195
|
+
typeParameterNames(node).forEach(({identifier}) => names.add(identifier.simpleName));
|
|
196
|
+
} else if (node.kind === J.Kind.NewClass) {
|
|
197
|
+
walk((node as J.NewClass).arguments, collect);
|
|
198
|
+
}
|
|
142
199
|
// A member's name is not one the file binds, though the code inside one still declares names.
|
|
143
|
-
for (const member of
|
|
200
|
+
for (const member of members.statements) {
|
|
144
201
|
const element = unwrap(member);
|
|
145
202
|
walk(element, node => node === element || collect(node));
|
|
146
203
|
}
|
|
@@ -155,7 +212,8 @@ export function namesDeclaredWithin(node: unknown, cacheKey: object = node as ob
|
|
|
155
212
|
/**
|
|
156
213
|
* The names `node` reads and nothing within it binds, so each one reaches a binding further out.
|
|
157
214
|
* A name its parent introduces rather than reads is not one, nor is a name an inner scope rebinds:
|
|
158
|
-
* that reference reads the rebinding.
|
|
215
|
+
* that reference reads the rebinding. A value position is hidden only by a value binding, a type
|
|
216
|
+
* position only by a type.
|
|
159
217
|
*/
|
|
160
218
|
export function namesReferencedWithin(node: unknown, cacheKey: object = node as object): ReadonlySet<string> {
|
|
161
219
|
if (cacheKey === null || typeof cacheKey !== 'object') {
|
|
@@ -166,84 +224,280 @@ export function namesReferencedWithin(node: unknown, cacheKey: object = node as
|
|
|
166
224
|
return cached;
|
|
167
225
|
}
|
|
168
226
|
const names = new Set<string>();
|
|
169
|
-
|
|
227
|
+
treesIn(node).forEach(tree => readReferences(tree, undefined, undefined, 'value', (identifier, reading, scopes) => {
|
|
228
|
+
if (reading !== undefined && !bindingFrame(scopes, identifier.simpleName, reading)) {
|
|
229
|
+
names.add(identifier.simpleName);
|
|
230
|
+
}
|
|
231
|
+
}));
|
|
170
232
|
referenced.set(cacheKey, names);
|
|
171
233
|
return names;
|
|
172
234
|
}
|
|
173
235
|
|
|
174
236
|
/** The scopes enclosing a node, innermost first, each paired with what {@link frameBindings} reads. */
|
|
175
237
|
interface Frames {
|
|
176
|
-
node:
|
|
177
|
-
parent:
|
|
238
|
+
node: Tree;
|
|
239
|
+
parent: Tree | undefined;
|
|
178
240
|
outer?: Frames;
|
|
179
241
|
}
|
|
180
242
|
|
|
181
|
-
/**
|
|
182
|
-
function
|
|
243
|
+
/** The innermost of `scopes` binding `name` as `reading` asks, or undefined where none does. */
|
|
244
|
+
function bindingFrame(
|
|
245
|
+
scopes: Frames | undefined, name: string, reading: Exclude<Reading, undefined>
|
|
246
|
+
): Frames | undefined {
|
|
183
247
|
for (let scope = scopes; scope; scope = scope.outer) {
|
|
184
|
-
if (frameBindings(scope.node, scope.parent)
|
|
185
|
-
return
|
|
248
|
+
if (binds(frameBindings(scope.node, scope.parent), name, meaningRead(reading))) {
|
|
249
|
+
return scope;
|
|
186
250
|
}
|
|
187
251
|
}
|
|
188
|
-
return
|
|
252
|
+
return undefined;
|
|
189
253
|
}
|
|
190
254
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
255
|
+
/**
|
|
256
|
+
* What an identifier reads with. That is one meaning, or `'any'`, which is how an export clause or
|
|
257
|
+
* an `import a = NS.b` reads, or `'write'`, a value it assigns to. Undefined is a slot that names
|
|
258
|
+
* rather than reads.
|
|
259
|
+
*/
|
|
260
|
+
type Reading = Meaning | 'any' | 'write' | undefined;
|
|
261
|
+
|
|
262
|
+
/** The meaning a reading resolves against, or undefined for any. */
|
|
263
|
+
function meaningRead(reading: Exclude<Reading, undefined>): Meaning | undefined {
|
|
264
|
+
return reading === 'any' ? undefined : reading === 'write' ? 'value' : reading;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Told of each identifier a walk finds, with what it reads with and the scopes around it. `trail`
|
|
269
|
+
* holds the nodes from the walk's root down to it, and changes as the walk goes on.
|
|
270
|
+
*/
|
|
271
|
+
type OnIdentifier =
|
|
272
|
+
(identifier: J.Identifier, reading: Reading, scopes: Frames | undefined, trail: readonly Tree[]) => void;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Walks `node` top-down, telling `onIdentifier` of every identifier. `reading` is what `node`
|
|
276
|
+
* itself reads with, or undefined under a slot that names rather than reads.
|
|
277
|
+
*/
|
|
278
|
+
function readReferences(
|
|
279
|
+
node: Tree, parent: Tree | undefined, scopes: Frames | undefined, reading: Reading, onIdentifier: OnIdentifier,
|
|
280
|
+
trail: Tree[] = []
|
|
281
|
+
): void {
|
|
282
|
+
trail.push(node);
|
|
283
|
+
if (node.kind === J.Kind.Identifier) {
|
|
284
|
+
onIdentifier(node as J.Identifier, reading, scopes, trail);
|
|
195
285
|
}
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
286
|
+
const within = framesAt(node, parent, scopes);
|
|
287
|
+
for (const [key, value] of Object.entries(node)) {
|
|
288
|
+
if (key !== 'markers') {
|
|
289
|
+
const [below, reads] = descend(node, parent, key, within, reading);
|
|
290
|
+
// A naming position reads against the tree parent, so the padding between is stepped over.
|
|
291
|
+
treesIn(value).forEach(child => readReferences(child, node, below, reads, onIdentifier, trail));
|
|
292
|
+
}
|
|
201
293
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
294
|
+
trail.pop();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** The scopes a node sits in, with its own in front where it is one. */
|
|
298
|
+
function framesAt(node: Tree, parent: Tree | undefined, scopes: Frames | undefined): Frames | undefined {
|
|
299
|
+
return scopeKinds.has(node.kind) ? {node, parent, outer: scopes} : scopes;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** The scopes the slot `key` of `node` resolves against, and what a read in it reads with. */
|
|
303
|
+
function descend(
|
|
304
|
+
node: Tree, parent: Tree | undefined, key: string, within: Frames | undefined, reading: Reading
|
|
305
|
+
): [Frames | undefined, Reading] {
|
|
306
|
+
return [reachOf(node, parent, key, within), meaningOf(node, key, reading)];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The scopes the slot `key` of `node` resolves against, `within` being those `node` sits in. A
|
|
311
|
+
* conditional type's `infer` names reach its extends clause and true branch alone, so its check
|
|
312
|
+
* type and false branch read past them.
|
|
313
|
+
*/
|
|
314
|
+
function reachOf(node: Tree, parent: Tree | undefined, key: string, within: Frames | undefined): Frames | undefined {
|
|
315
|
+
const pastInferred =
|
|
316
|
+
node.kind === JS.Kind.ConditionalType && key === 'checkType' ||
|
|
317
|
+
node.kind === J.Kind.Ternary && parent?.kind === JS.Kind.ConditionalType && key === 'falsePart';
|
|
318
|
+
return pastInferred ? within?.outer : within;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* What a read in slot `key` of `node` reads with, given what `node` itself reads with. Undefined
|
|
323
|
+
* is a slot that names rather than reads, and so is everything under it. A whole import declares
|
|
324
|
+
* what it spells, and a re-export's clause names another module's members.
|
|
325
|
+
*/
|
|
326
|
+
function meaningOf(node: Tree, key: string, reading: Reading): Reading {
|
|
327
|
+
// A computed key and a binding's default value read from within a naming slot.
|
|
328
|
+
if (node.kind === JS.Kind.ComputedPropertyName || node.kind === JS.Kind.BindingElement && key === 'initializer') {
|
|
329
|
+
return 'value';
|
|
205
330
|
}
|
|
206
|
-
if (
|
|
207
|
-
return;
|
|
331
|
+
if (reading === undefined) {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
if (reading === 'write') {
|
|
335
|
+
return carriesWrite(node, key) ? 'write' : meaningOf(node, key, 'value');
|
|
336
|
+
}
|
|
337
|
+
if (key === 'typeParameters' || key === 'typeArguments') {
|
|
338
|
+
return 'type';
|
|
339
|
+
}
|
|
340
|
+
if (key === 'label' || key === namingSlots[node.kind] || key === 'name' && !readsItsName(node)) {
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
if (writes(node, key)) {
|
|
344
|
+
return 'write';
|
|
345
|
+
}
|
|
346
|
+
switch (node.kind) {
|
|
347
|
+
case JS.Kind.Import:
|
|
348
|
+
// `import a = NS.b` reads `NS` as whatever it is. The rest of an import declares.
|
|
349
|
+
return key === 'initializer' ? 'any' : undefined;
|
|
350
|
+
case JS.Kind.ExportDeclaration:
|
|
351
|
+
if (key !== 'exportClause') {
|
|
352
|
+
return reading;
|
|
353
|
+
}
|
|
354
|
+
return (node as JS.ExportDeclaration).moduleSpecifier === undefined ? 'any' : undefined;
|
|
355
|
+
case J.Kind.FieldAccess:
|
|
356
|
+
// In a type, `NS.Foo` reads the namespace `NS`, as `A.B.C` reads `A`.
|
|
357
|
+
return key === 'target' && reading === 'type' ? 'namespace' : reading;
|
|
358
|
+
case JS.Kind.TypeInfo:
|
|
359
|
+
case JS.Kind.TypeDeclaration:
|
|
360
|
+
case J.Kind.TypeParameter:
|
|
361
|
+
return 'type';
|
|
362
|
+
case JS.Kind.TypeQuery:
|
|
363
|
+
// `typeof x` reads the value `x` from within a type.
|
|
364
|
+
return key === 'typeExpression' ? 'value' : reading;
|
|
365
|
+
case JS.Kind.TypePredicate:
|
|
366
|
+
// `x is Foo` names the parameter `x`, a value, from within a return type.
|
|
367
|
+
return key === 'parameterName' ? 'value' : reading;
|
|
368
|
+
case JS.Kind.IndexSignatureDeclaration:
|
|
369
|
+
return key === 'typeExpression' ? 'type' : reading;
|
|
370
|
+
case JS.Kind.As:
|
|
371
|
+
return key === 'right' ? 'type' : reading;
|
|
372
|
+
case JS.Kind.SatisfiesExpression:
|
|
373
|
+
return key === 'satisfiesType' ? 'type' : reading;
|
|
374
|
+
case J.Kind.TypeCast:
|
|
375
|
+
return key === 'class' ? 'type' : reading;
|
|
376
|
+
case J.Kind.ClassDeclaration:
|
|
377
|
+
if ((node as J.ClassDeclaration).classKind.type === J.ClassDeclaration.Kind.Type.Interface) {
|
|
378
|
+
return 'type';
|
|
379
|
+
}
|
|
380
|
+
return key === 'implements' ? 'type' : reading;
|
|
381
|
+
default:
|
|
382
|
+
return reading;
|
|
208
383
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Whether slot `key` of `node` is one {@link isWrite} counts as assigned to. A declaring loop head names instead. */
|
|
387
|
+
function writes(node: Tree, key: string): boolean {
|
|
388
|
+
switch (node.kind) {
|
|
389
|
+
case J.Kind.Assignment:
|
|
390
|
+
case J.Kind.AssignmentOperation:
|
|
391
|
+
case JS.Kind.AssignmentOperation:
|
|
392
|
+
return key === 'variable';
|
|
393
|
+
case J.Kind.Unary:
|
|
394
|
+
return key === 'expression' && incrementing.has((node as J.Unary).operator.element);
|
|
395
|
+
case J.Kind.ForEachLoopControl:
|
|
396
|
+
return key === 'variable';
|
|
397
|
+
default:
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const incrementing = new Set<J.Unary.Type>([
|
|
403
|
+
J.Unary.Type.PreIncrement, J.Unary.Type.PreDecrement, J.Unary.Type.PostIncrement, J.Unary.Type.PostDecrement
|
|
404
|
+
]);
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Whether slot `key` of `node` passes a write on to what it holds, which parentheses, a type
|
|
408
|
+
* assertion and the shapes of a destructuring target do. Anything else under a target reads, as
|
|
409
|
+
* `x` does in `x.y = 1`.
|
|
410
|
+
*/
|
|
411
|
+
function carriesWrite(node: Tree, key: string): boolean {
|
|
412
|
+
switch (node.kind) {
|
|
413
|
+
case J.Kind.Parentheses:
|
|
414
|
+
return key === 'tree';
|
|
415
|
+
case JS.Kind.As:
|
|
416
|
+
return key === 'left';
|
|
417
|
+
case JS.Kind.ExpressionStatement:
|
|
418
|
+
case JS.Kind.Spread:
|
|
419
|
+
case JS.Kind.SatisfiesExpression:
|
|
420
|
+
case J.Kind.TypeCast:
|
|
421
|
+
return key === 'expression';
|
|
422
|
+
case J.Kind.NewArray:
|
|
423
|
+
return key === 'initializer';
|
|
424
|
+
case J.Kind.NewClass:
|
|
425
|
+
return key === 'body';
|
|
426
|
+
case J.Kind.Block:
|
|
427
|
+
return key === 'statements';
|
|
428
|
+
case JS.Kind.PropertyAssignment:
|
|
429
|
+
return key === (isShorthand(node) ? 'name' : 'initializer');
|
|
430
|
+
case JS.Kind.ArrayBindingPattern:
|
|
431
|
+
return key === 'elements';
|
|
432
|
+
case JS.Kind.ObjectBindingPattern:
|
|
433
|
+
return key === 'bindings';
|
|
434
|
+
case JS.Kind.BindingElement:
|
|
435
|
+
return key === 'name';
|
|
436
|
+
default:
|
|
437
|
+
return false;
|
|
212
438
|
}
|
|
213
|
-
const within = scopeKinds.has(kind!) ? {node, parent, outer: scopes} : scopes;
|
|
214
|
-
Object.entries(node as object).forEach(([key, value]) =>
|
|
215
|
-
key !== 'markers' && collectReferences(value, node, within, names));
|
|
216
439
|
}
|
|
217
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Whether a node's `name` slot reads rather than names. A call names a member of whatever it
|
|
443
|
+
* selects from, so with nothing selected its name is the function called, and a shorthand `{x}`
|
|
444
|
+
* reads the `x` it also names.
|
|
445
|
+
*/
|
|
446
|
+
function readsItsName(node: Tree): boolean {
|
|
447
|
+
return node.kind === J.Kind.MethodInvocation && !(node as J.MethodInvocation).select || isShorthand(node);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** A shorthand property `{x}` or `{x = 1}`, which reads the `x` it names. */
|
|
451
|
+
function isShorthand(node: Tree | undefined): boolean {
|
|
452
|
+
return node?.kind === JS.Kind.PropertyAssignment &&
|
|
453
|
+
(node as JS.PropertyAssignment).assigmentToken === JS.PropertyAssignment.Token.Equals;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** The slot, beside `name` and `label`, in which a node names rather than reads. */
|
|
457
|
+
const namingSlots: Record<string, string> = {
|
|
458
|
+
[JS.Kind.Alias]: 'alias',
|
|
459
|
+
[JS.Kind.BindingElement]: 'propertyName',
|
|
460
|
+
[JS.Kind.ImportType]: 'qualifier',
|
|
461
|
+
[JS.Kind.JsxAttribute]: 'key'
|
|
462
|
+
};
|
|
463
|
+
|
|
218
464
|
/**
|
|
219
465
|
* Every name a binding pattern introduces. `member` is the property a name takes its value from,
|
|
220
466
|
* which only a name an object pattern binds directly has: anything deeper reads a property of a
|
|
221
467
|
* property, an array element is chosen by position, and a rest name gathers what nothing claimed.
|
|
222
468
|
*/
|
|
223
469
|
export function bindingNames(pattern: J | undefined): { name: string; member?: string }[] {
|
|
470
|
+
return patternBindings(pattern).map(({identifier, member}) =>
|
|
471
|
+
member === undefined ? {name: identifier.simpleName} : {name: identifier.simpleName, member});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** As {@link bindingNames}, with the identifier each name is read from. */
|
|
475
|
+
function patternBindings(pattern: J | undefined): { identifier: J.Identifier; member?: string }[] {
|
|
224
476
|
switch (pattern?.kind) {
|
|
225
477
|
case J.Kind.Identifier: {
|
|
226
|
-
const
|
|
227
|
-
return simpleName ? [{
|
|
478
|
+
const identifier = pattern as J.Identifier;
|
|
479
|
+
return identifier.simpleName ? [{identifier}] : [];
|
|
228
480
|
}
|
|
229
481
|
case JS.Kind.Spread:
|
|
230
|
-
return
|
|
482
|
+
return patternBindings((pattern as JS.Spread).expression);
|
|
231
483
|
case JS.Kind.ArrayBindingPattern:
|
|
232
484
|
return unnamedMembers((pattern as JS.ArrayBindingPattern).elements.elements
|
|
233
|
-
.flatMap(element =>
|
|
485
|
+
.flatMap(element => patternBindings(unwrap(element))));
|
|
234
486
|
case JS.Kind.ObjectBindingPattern:
|
|
235
487
|
return (pattern as JS.ObjectBindingPattern).bindings.elements
|
|
236
|
-
.flatMap(element =>
|
|
488
|
+
.flatMap(element => patternBindings(unwrap(element)));
|
|
237
489
|
case JS.Kind.BindingElement: {
|
|
238
490
|
const element = pattern as JS.BindingElement;
|
|
239
491
|
if (element.name?.kind !== J.Kind.Identifier) {
|
|
240
|
-
return unnamedMembers(
|
|
492
|
+
return unnamedMembers(patternBindings(element.name as J));
|
|
241
493
|
}
|
|
242
|
-
const
|
|
494
|
+
const identifier = element.name as J.Identifier;
|
|
243
495
|
const propertyName = unwrap(element.propertyName);
|
|
244
496
|
return [{
|
|
245
|
-
|
|
246
|
-
member: propertyName?.kind === J.Kind.Identifier
|
|
497
|
+
identifier,
|
|
498
|
+
member: propertyName?.kind === J.Kind.Identifier
|
|
499
|
+
? (propertyName as J.Identifier).simpleName
|
|
500
|
+
: identifier.simpleName
|
|
247
501
|
}];
|
|
248
502
|
}
|
|
249
503
|
default:
|
|
@@ -251,79 +505,132 @@ export function bindingNames(pattern: J | undefined): { name: string; member?: s
|
|
|
251
505
|
}
|
|
252
506
|
}
|
|
253
507
|
|
|
254
|
-
function
|
|
255
|
-
return
|
|
508
|
+
function boundIdentifiers(pattern: J | undefined): J.Identifier[] {
|
|
509
|
+
return patternBindings(pattern).map(bound => bound.identifier);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function unnamedMembers(bound: { identifier: J.Identifier }[]): { identifier: J.Identifier }[] {
|
|
513
|
+
return bound.map(({identifier}) => ({identifier}));
|
|
256
514
|
}
|
|
257
515
|
|
|
258
|
-
|
|
516
|
+
/**
|
|
517
|
+
* The block of members a class, object literal or type literal holds. A member is reached through
|
|
518
|
+
* the value or type holding it, so its name binds nowhere.
|
|
519
|
+
*/
|
|
520
|
+
function membersOf(node: Tree | undefined): J.Block | undefined {
|
|
521
|
+
switch (node?.kind) {
|
|
522
|
+
case J.Kind.ClassDeclaration:
|
|
523
|
+
return (node as J.ClassDeclaration).body;
|
|
524
|
+
case J.Kind.NewClass:
|
|
525
|
+
return (node as J.NewClass).body;
|
|
526
|
+
case JS.Kind.TypeLiteral:
|
|
527
|
+
return (node as JS.TypeLiteral).members;
|
|
528
|
+
default:
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function declaringScopeOf(cursor: Cursor, name: string, meaning?: Meaning): J | undefined {
|
|
259
534
|
for (let c: Cursor | undefined = cursor; c; c = c.parent) {
|
|
260
|
-
if (
|
|
535
|
+
if (binds(bindingsAt(c), name, meaning)) {
|
|
261
536
|
return c.value;
|
|
262
537
|
}
|
|
263
538
|
}
|
|
264
539
|
return undefined;
|
|
265
540
|
}
|
|
266
541
|
|
|
542
|
+
/** What the node a cursor stands on binds, which is nothing where it stands on padding or the root. */
|
|
543
|
+
function bindingsAt(cursor: Cursor): Bindings {
|
|
544
|
+
const parent = cursor.parent?.value;
|
|
545
|
+
return isTree(cursor.value) ? frameBindings(cursor.value, isTree(parent) ? parent : undefined) : noBindings;
|
|
546
|
+
}
|
|
547
|
+
|
|
267
548
|
/**
|
|
268
549
|
* What one node on the cursor path binds, `parent` being the node it hangs from. Only these two
|
|
269
550
|
* answers turn on the parent; a node alone settles the rest, which is what makes them cacheable.
|
|
270
551
|
*/
|
|
271
|
-
function frameBindings(node:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
return
|
|
552
|
+
function frameBindings(node: Tree, parent: Tree | undefined): Bindings {
|
|
553
|
+
if (node.kind === J.Kind.Block && membersOf(parent) === node) {
|
|
554
|
+
// An enum's members are in scope in its body, where an initializer may name an earlier one.
|
|
555
|
+
return isEnum(parent) ? toBindings(enumMemberNames(node as J.Block)) : noBindings;
|
|
556
|
+
}
|
|
557
|
+
// An arrow binds its lambda's parameters itself, since its return type reads them.
|
|
558
|
+
if (node.kind === J.Kind.Lambda && parent?.kind === JS.Kind.ArrowFunction) {
|
|
559
|
+
return noBindings;
|
|
275
560
|
}
|
|
276
561
|
// A function expression is the only function whose own name its body reaches: a declaration's
|
|
277
562
|
// name belongs to the enclosing block, a method's to an instance.
|
|
278
|
-
if (node
|
|
279
|
-
let
|
|
280
|
-
if (!
|
|
281
|
-
const self =
|
|
282
|
-
selfNamed.set(node,
|
|
563
|
+
if (node.kind === J.Kind.MethodDeclaration && parent?.kind === JS.Kind.StatementExpression) {
|
|
564
|
+
let bindings = selfNamed.get(node);
|
|
565
|
+
if (!bindings) {
|
|
566
|
+
const self = bound(boundIdentifiers((node as J.MethodDeclaration).name), 'value');
|
|
567
|
+
selfNamed.set(node, bindings = toBindings([...self, ...readBindings(node)]));
|
|
283
568
|
}
|
|
284
|
-
return
|
|
569
|
+
return bindings;
|
|
285
570
|
}
|
|
286
571
|
return ownBindings(node);
|
|
287
572
|
}
|
|
288
573
|
|
|
289
|
-
function ownBindings(node:
|
|
290
|
-
|
|
291
|
-
|
|
574
|
+
function ownBindings(node: Tree): Bindings {
|
|
575
|
+
let bindings = frames.get(node);
|
|
576
|
+
if (!bindings) {
|
|
577
|
+
frames.set(node, bindings = toBindings(readBindings(node)));
|
|
292
578
|
}
|
|
293
|
-
|
|
294
|
-
if (!names) {
|
|
295
|
-
frames.set(node, names = new Set(readBindings(node)));
|
|
296
|
-
}
|
|
297
|
-
return names;
|
|
579
|
+
return bindings;
|
|
298
580
|
}
|
|
299
581
|
|
|
300
|
-
|
|
301
|
-
function readBindings(node: any): string[] {
|
|
582
|
+
function readBindings(node: Tree): Bound[] {
|
|
302
583
|
switch (node.kind) {
|
|
303
584
|
case JS.Kind.CompilationUnit: {
|
|
304
585
|
const statements = (node as JS.CompilationUnit).statements;
|
|
305
586
|
return [...declaredNames(statements), ...hoistedNames(statements)];
|
|
306
587
|
}
|
|
307
|
-
case J.Kind.Block:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
588
|
+
case J.Kind.Block: {
|
|
589
|
+
const block = node as J.Block;
|
|
590
|
+
// A class static block is a var scope, as a function body is.
|
|
591
|
+
return block.static.element
|
|
592
|
+
? [...blockScopedNames(block.statements), ...hoistedNames(block.statements)]
|
|
593
|
+
: blockScopedNames(block.statements);
|
|
594
|
+
}
|
|
595
|
+
case J.Kind.MethodDeclaration:
|
|
596
|
+
case JS.Kind.ComputedPropertyMethodDeclaration: {
|
|
597
|
+
const method = node as J.MethodDeclaration | JS.ComputedPropertyMethodDeclaration;
|
|
598
|
+
return [
|
|
599
|
+
...typeParameterNames(method),
|
|
600
|
+
...declaredNames(method.parameters.elements),
|
|
601
|
+
...hoistedNames(method.body)
|
|
602
|
+
];
|
|
312
603
|
}
|
|
313
604
|
case J.Kind.Lambda: {
|
|
314
605
|
const lambda = node as J.Lambda;
|
|
315
606
|
return [...declaredNames(lambda.parameters.parameters), ...hoistedNames(lambda.body)];
|
|
316
607
|
}
|
|
317
608
|
case J.Kind.ClassDeclaration:
|
|
318
|
-
return
|
|
609
|
+
return [...declarationNames(node), ...typeParameterNames(node)];
|
|
610
|
+
// A namespace is where the functions and `var`s in its body hoist to, its block binding the rest.
|
|
611
|
+
case JS.Kind.NamespaceDeclaration:
|
|
612
|
+
return isGlobalAugmentation(node as JS.NamespaceDeclaration)
|
|
613
|
+
? []
|
|
614
|
+
: hoistedNames((node as JS.NamespaceDeclaration).body);
|
|
615
|
+
case JS.Kind.ArrowFunction:
|
|
616
|
+
return [...typeParameterNames(node), ...readBindings((node as JS.ArrowFunction).lambda)];
|
|
617
|
+
case JS.Kind.TypeDeclaration:
|
|
618
|
+
return typeParameterNames(node);
|
|
619
|
+
case JS.Kind.FunctionType:
|
|
620
|
+
return [...typeParameterNames(node), ...declaredNames((node as JS.FunctionType).parameters.elements)];
|
|
621
|
+
case JS.Kind.MappedType:
|
|
622
|
+
return declarationNames(unwrap((node as JS.MappedType).keysRemapping.typeParameter));
|
|
623
|
+
case JS.Kind.ConditionalType:
|
|
624
|
+
return inferredNames((node as JS.ConditionalType).condition.element.condition);
|
|
319
625
|
case J.Kind.TryCatch:
|
|
320
626
|
return declaredNames([(node as J.Try.Catch).parameter.tree]);
|
|
627
|
+
// A loop head's `var` hoists past the loop, as one anywhere in a function body does.
|
|
321
628
|
case J.Kind.ForLoop:
|
|
322
|
-
return
|
|
629
|
+
return blockScopedNames((node as J.ForLoop).control.init);
|
|
323
630
|
case J.Kind.ForEachLoop:
|
|
324
|
-
return
|
|
631
|
+
return blockScopedNames([(node as J.ForEachLoop).control.variable]);
|
|
325
632
|
case JS.Kind.ForInLoop:
|
|
326
|
-
return
|
|
633
|
+
return blockScopedNames([(node as JS.ForInLoop).control.variable]);
|
|
327
634
|
default:
|
|
328
635
|
return [];
|
|
329
636
|
}
|
|
@@ -333,7 +640,7 @@ function readBindings(node: any): string[] {
|
|
|
333
640
|
* The names statements bind in the block holding them — the complement of what
|
|
334
641
|
* {@link hoistedNames} claims, so `function (x) { var x = 1; }` reads as one binding, not two.
|
|
335
642
|
*/
|
|
336
|
-
function blockScopedNames(statements:
|
|
643
|
+
function blockScopedNames(statements: readonly unknown[]): Bound[] {
|
|
337
644
|
return statements.flatMap(statement => {
|
|
338
645
|
const element = unwrap(statement);
|
|
339
646
|
switch (element?.kind) {
|
|
@@ -350,37 +657,57 @@ function blockScopedNames(statements: any[]): string[] {
|
|
|
350
657
|
});
|
|
351
658
|
}
|
|
352
659
|
|
|
353
|
-
function isBlockScoped(declaration:
|
|
354
|
-
return ((declaration.modifiers ?? [])
|
|
660
|
+
function isBlockScoped(declaration: Tree): boolean {
|
|
661
|
+
return ((declaration as J.VariableDeclarations).modifiers ?? []).some(m => blockScoped.has(m.keyword!));
|
|
355
662
|
}
|
|
356
663
|
|
|
357
664
|
/** The names statements declare directly in the scope holding them. */
|
|
358
|
-
function declaredNames(statements:
|
|
665
|
+
function declaredNames(statements: readonly unknown[]): Bound[] {
|
|
359
666
|
return statements.flatMap(statement => declarationNames(unwrap(statement)));
|
|
360
667
|
}
|
|
361
668
|
|
|
362
|
-
|
|
669
|
+
/** The names a declaration binds, each with the meanings it has there. */
|
|
670
|
+
function declarationNames(statement: Tree | undefined): Bound[] {
|
|
363
671
|
switch (statement?.kind) {
|
|
364
672
|
case JS.Kind.Import:
|
|
365
673
|
return importNames(statement as JS.Import);
|
|
366
674
|
case J.Kind.VariableDeclarations:
|
|
367
|
-
return (statement as J.VariableDeclarations).variables
|
|
368
|
-
|
|
369
|
-
|
|
675
|
+
return bound((statement as J.VariableDeclarations).variables.flatMap(variable => {
|
|
676
|
+
const named = unwrap(variable) as J.VariableDeclarations.NamedVariable | undefined;
|
|
677
|
+
return boundIdentifiers(named?.name);
|
|
678
|
+
}), 'value');
|
|
370
679
|
case JS.Kind.ScopedVariableDeclarations:
|
|
371
680
|
return declaredNames((statement as JS.ScopedVariableDeclarations).variables);
|
|
372
681
|
case J.Kind.MethodDeclaration:
|
|
373
|
-
return
|
|
374
|
-
case J.Kind.ClassDeclaration:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
682
|
+
return bound(boundIdentifiers((statement as J.MethodDeclaration).name), 'value');
|
|
683
|
+
case J.Kind.ClassDeclaration: {
|
|
684
|
+
const declaration = statement as J.ClassDeclaration;
|
|
685
|
+
switch (declaration.classKind.type) {
|
|
686
|
+
case J.ClassDeclaration.Kind.Type.Interface:
|
|
687
|
+
return bound(boundIdentifiers(declaration.name), 'type');
|
|
688
|
+
case J.ClassDeclaration.Kind.Type.Enum:
|
|
689
|
+
// An enum's members are read as `E.A` in a type, so it is a namespace too.
|
|
690
|
+
return bound(boundIdentifiers(declaration.name), 'value', 'type', 'namespace');
|
|
691
|
+
default:
|
|
692
|
+
return bound(boundIdentifiers(declaration.name), 'value', 'type');
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
case JS.Kind.NamespaceDeclaration: {
|
|
696
|
+
const declaration = statement as JS.NamespaceDeclaration;
|
|
697
|
+
if (isGlobalAugmentation(declaration)) {
|
|
698
|
+
return [];
|
|
699
|
+
}
|
|
700
|
+
const names = boundIdentifiers(rootName(unwrap(declaration.name)));
|
|
701
|
+
return isInstantiated(declaration) ? bound(names, 'namespace', 'value') : bound(names, 'namespace');
|
|
702
|
+
}
|
|
378
703
|
case JS.Kind.TypeDeclaration:
|
|
379
|
-
return
|
|
704
|
+
return bound(boundIdentifiers(unwrap((statement as JS.TypeDeclaration).name)), 'type');
|
|
380
705
|
case J.Kind.TypeParameter:
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
return
|
|
706
|
+
// A type parameter binds across the declaration carrying it, not in the scope that one
|
|
707
|
+
// sits in, so no statement list leads here.
|
|
708
|
+
return bound(boundIdentifiers((statement as J.TypeParameter).name), 'type');
|
|
709
|
+
case JS.Kind.MappedTypeParameter:
|
|
710
|
+
return bound(boundIdentifiers((statement as JS.MappedType.Parameter).name as J), 'type');
|
|
384
711
|
case J.Kind.Case:
|
|
385
712
|
// The cases of a switch share the block it opens, so each one's declarations bind in all.
|
|
386
713
|
return declaredNames((statement as J.Case).statements.elements);
|
|
@@ -389,25 +716,93 @@ function declarationNames(statement: any): string[] {
|
|
|
389
716
|
}
|
|
390
717
|
}
|
|
391
718
|
|
|
719
|
+
/**
|
|
720
|
+
* Whether a namespace binds a value, which it does once its body holds something other than
|
|
721
|
+
* interfaces, type aliases, unexported imports and namespaces that do not. That is the binder's
|
|
722
|
+
* rule, under which a `const enum` counts, whatever the emitter later drops.
|
|
723
|
+
*/
|
|
724
|
+
function isInstantiated(declaration: JS.NamespaceDeclaration): boolean {
|
|
725
|
+
return (declaration.body?.statements ?? []).some(statement => {
|
|
726
|
+
const element = unwrap(statement);
|
|
727
|
+
switch (element?.kind) {
|
|
728
|
+
case JS.Kind.TypeDeclaration:
|
|
729
|
+
return false;
|
|
730
|
+
case J.Kind.ClassDeclaration:
|
|
731
|
+
return (element as J.ClassDeclaration).classKind.type !== J.ClassDeclaration.Kind.Type.Interface;
|
|
732
|
+
case JS.Kind.Import:
|
|
733
|
+
return (element as JS.Import).modifiers.some(modifier => modifier.keyword === 'export');
|
|
734
|
+
case JS.Kind.NamespaceDeclaration:
|
|
735
|
+
return isInstantiated(element as JS.NamespaceDeclaration);
|
|
736
|
+
default:
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/** `declare global { … }`, whose declarations the whole file sees, as if written at its top. */
|
|
743
|
+
function isGlobalAugmentation(declaration: JS.NamespaceDeclaration): boolean {
|
|
744
|
+
const name = unwrap(declaration.name);
|
|
745
|
+
return declaration.keywordType.element === JS.NamespaceDeclaration.KeywordType.Empty &&
|
|
746
|
+
name?.kind === J.Kind.Identifier && (name as J.Identifier).simpleName === 'global';
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function isEnum(node: Tree | undefined): boolean {
|
|
750
|
+
return node?.kind === J.Kind.ClassDeclaration &&
|
|
751
|
+
(node as J.ClassDeclaration).classKind.type === J.ClassDeclaration.Kind.Type.Enum;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function enumMemberNames(body: J.Block): Bound[] {
|
|
755
|
+
return body.statements.flatMap(statement => {
|
|
756
|
+
const element = unwrap(statement);
|
|
757
|
+
return element?.kind === J.Kind.EnumValueSet
|
|
758
|
+
? bound((element as J.EnumValueSet).enums.map(value => value.element.name), 'value')
|
|
759
|
+
: [];
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/** The identifier a dotted name `A.B.C` declares, which is `A`: the rest are members of it. */
|
|
764
|
+
function rootName(name: J | undefined): J | undefined {
|
|
765
|
+
return name?.kind === J.Kind.FieldAccess ? rootName((name as J.FieldAccess).target) : name;
|
|
766
|
+
}
|
|
767
|
+
|
|
392
768
|
/**
|
|
393
769
|
* The names a declaration's type parameters bind. A class keeps them in a container and everything
|
|
394
770
|
* else in a `J.TypeParameters`, and both hold the same right-padded list.
|
|
395
771
|
*/
|
|
396
|
-
function typeParameterNames(node:
|
|
397
|
-
const held = node
|
|
772
|
+
function typeParameterNames(node: Tree): Bound[] {
|
|
773
|
+
const held = (node as { typeParameters?: J.TypeParameters | J.Container<J.TypeParameter> }).typeParameters;
|
|
398
774
|
const parameters = held?.kind === J.Kind.TypeParameters
|
|
399
775
|
? (held as J.TypeParameters).typeParameters
|
|
400
776
|
: (held as J.Container<J.TypeParameter> | undefined)?.elements;
|
|
401
777
|
return (parameters ?? []).flatMap(parameter => declarationNames(unwrap(parameter)));
|
|
402
778
|
}
|
|
403
779
|
|
|
404
|
-
/**
|
|
405
|
-
|
|
780
|
+
/**
|
|
781
|
+
* The names the `infer` declarations in a conditional type's extends clause bind. A conditional
|
|
782
|
+
* type nested in the clause keeps its own `infer` names to itself.
|
|
783
|
+
*/
|
|
784
|
+
function inferredNames(extendsType: unknown): Bound[] {
|
|
785
|
+
const names: Bound[] = [];
|
|
786
|
+
walk(extendsType, node => {
|
|
787
|
+
if (node.kind === JS.Kind.InferType) {
|
|
788
|
+
names.push(...declarationNames(unwrap((node as JS.InferType).typeParameter)));
|
|
789
|
+
}
|
|
790
|
+
return node.kind !== JS.Kind.ConditionalType;
|
|
791
|
+
});
|
|
792
|
+
return names;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* The names an import binds, which for an aliased or namespace specifier is the alias, each with
|
|
797
|
+
* every meaning. A `type` import is no exception: `typeof X` reads it as a value from within a
|
|
798
|
+
* type, and a value use outside one is the checker's error rather than a read of a free name.
|
|
799
|
+
*/
|
|
800
|
+
function importNames(jsImport: JS.Import): Bound[] {
|
|
406
801
|
const importClause = jsImport.importClause;
|
|
407
802
|
if (!importClause) {
|
|
408
803
|
return [];
|
|
409
804
|
}
|
|
410
|
-
const names =
|
|
805
|
+
const names = boundIdentifiers(unwrap(importClause.name));
|
|
411
806
|
const namedBindings = importClause.namedBindings;
|
|
412
807
|
if (namedBindings?.kind === JS.Kind.NamedImports) {
|
|
413
808
|
for (const element of (namedBindings as JS.NamedImports).elements.elements) {
|
|
@@ -419,12 +814,12 @@ function importNames(jsImport: JS.Import): string[] {
|
|
|
419
814
|
} else {
|
|
420
815
|
names.push(...aliasedName(namedBindings));
|
|
421
816
|
}
|
|
422
|
-
return names;
|
|
817
|
+
return bound(names, 'value', 'type', 'namespace');
|
|
423
818
|
}
|
|
424
819
|
|
|
425
|
-
function aliasedName(specifier: J | undefined):
|
|
820
|
+
function aliasedName(specifier: J | undefined): J.Identifier[] {
|
|
426
821
|
const name = specifier?.kind === JS.Kind.Alias ? (specifier as JS.Alias).alias : specifier;
|
|
427
|
-
return
|
|
822
|
+
return boundIdentifiers(name);
|
|
428
823
|
}
|
|
429
824
|
|
|
430
825
|
const blockScoped = new Set(['let', 'const', 'using']);
|
|
@@ -433,19 +828,20 @@ const blockScoped = new Set(['let', 'const', 'using']);
|
|
|
433
828
|
// replaced one is walked afresh: every call site in a function asks what that body hoists, every
|
|
434
829
|
// import added to a file asks what that file declares, and every scope is asked what it binds by
|
|
435
830
|
// each of the call sites it encloses.
|
|
436
|
-
const hoisted = new WeakMap<object,
|
|
831
|
+
const hoisted = new WeakMap<object, Bound[]>();
|
|
437
832
|
const declared = new WeakMap<object, ReadonlySet<string>>();
|
|
438
833
|
const used = new WeakMap<object, ReadonlySet<string>>();
|
|
439
834
|
const referenced = new WeakMap<object, ReadonlySet<string>>();
|
|
440
|
-
const
|
|
441
|
-
const
|
|
835
|
+
const slots = new WeakMap<Tree, Map<UUID, string>>();
|
|
836
|
+
const frames = new WeakMap<object, Bindings>();
|
|
837
|
+
const selfNamed = new WeakMap<object, Bindings>();
|
|
442
838
|
|
|
443
839
|
/**
|
|
444
840
|
* The names blocks under `scope` hoist out to it. A `var` or function declaration reaches the whole
|
|
445
841
|
* function it sits in, so one nested in a block is in scope outside that block; only a `let`, `const`
|
|
446
842
|
* or `using` keyword says otherwise.
|
|
447
843
|
*/
|
|
448
|
-
function hoistedNames(scope:
|
|
844
|
+
function hoistedNames(scope: unknown): Bound[] {
|
|
449
845
|
if (typeof scope !== 'object' || scope === null) {
|
|
450
846
|
return [];
|
|
451
847
|
}
|
|
@@ -454,12 +850,14 @@ function hoistedNames(scope: any): string[] {
|
|
|
454
850
|
return cached;
|
|
455
851
|
}
|
|
456
852
|
|
|
457
|
-
const names:
|
|
458
|
-
const collect = (node:
|
|
853
|
+
const names: Bound[] = [];
|
|
854
|
+
const collect = (node: Tree): boolean => {
|
|
459
855
|
switch (node.kind) {
|
|
460
856
|
case J.Kind.MethodDeclaration:
|
|
461
857
|
names.push(...declarationNames(node));
|
|
462
858
|
return false;
|
|
859
|
+
case JS.Kind.ComputedPropertyMethodDeclaration:
|
|
860
|
+
return false;
|
|
463
861
|
case J.Kind.VariableDeclarations:
|
|
464
862
|
case JS.Kind.ScopedVariableDeclarations:
|
|
465
863
|
if (!isBlockScoped(node)) {
|
|
@@ -473,8 +871,14 @@ function hoistedNames(scope: any): string[] {
|
|
|
473
871
|
case JS.Kind.StatementExpression:
|
|
474
872
|
// A function or class used as an expression names itself for its own body alone.
|
|
475
873
|
return false;
|
|
874
|
+
case JS.Kind.NamespaceDeclaration:
|
|
875
|
+
// A namespace keeps its hoisting to itself, a global augmentation gives it to the file.
|
|
876
|
+
return isGlobalAugmentation(node as JS.NamespaceDeclaration);
|
|
476
877
|
case J.Kind.Lambda:
|
|
477
878
|
case J.Kind.ClassDeclaration:
|
|
879
|
+
case J.Kind.NewClass:
|
|
880
|
+
case JS.Kind.TypeLiteral:
|
|
881
|
+
// Nothing in an expression or a type hoists past it.
|
|
478
882
|
return false;
|
|
479
883
|
default:
|
|
480
884
|
return true;
|
|
@@ -491,7 +895,7 @@ function hoistedNames(scope: any): string[] {
|
|
|
491
895
|
* into, which a `JavaType` is not, so the walk stays in the tree the source spells rather than
|
|
492
896
|
* entering the type graph — cyclic and shared, where it would not terminate.
|
|
493
897
|
*/
|
|
494
|
-
export function walk(node: unknown, visit: (node:
|
|
898
|
+
export function walk(node: unknown, visit: (node: Tree) => boolean): void {
|
|
495
899
|
if (Array.isArray(node)) {
|
|
496
900
|
node.forEach(child => walk(child, visit));
|
|
497
901
|
return;
|
|
@@ -507,9 +911,13 @@ export function walk(node: unknown, visit: (node: any) => boolean): void {
|
|
|
507
911
|
}
|
|
508
912
|
}
|
|
509
913
|
|
|
510
|
-
/** The
|
|
511
|
-
function unwrap(node:
|
|
512
|
-
|
|
914
|
+
/** The tree a padding wrapper holds, or the node itself where it is a tree. */
|
|
915
|
+
function unwrap(node: unknown): J | undefined {
|
|
916
|
+
const kind = (node as { kind?: string } | undefined)?.kind;
|
|
917
|
+
if (kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded) {
|
|
918
|
+
return unwrap((node as { element?: unknown }).element);
|
|
919
|
+
}
|
|
920
|
+
return isTree(node) ? node as J : undefined;
|
|
513
921
|
}
|
|
514
922
|
|
|
515
923
|
/** `cursor` is protected on `TreeVisitor` and these APIs are free functions, so reaching it takes a cast. */
|
|
@@ -554,60 +962,188 @@ export function declarationsOf(statement: J | undefined): J.VariableDeclarations
|
|
|
554
962
|
}
|
|
555
963
|
|
|
556
964
|
/**
|
|
557
|
-
* Whether the identifier
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
propertyName?: unknown;
|
|
576
|
-
} | undefined;
|
|
577
|
-
|
|
578
|
-
// A call names a member of whatever it selects from. With nothing selected there is no member,
|
|
579
|
-
// and its `name` is a reference to the function being called.
|
|
580
|
-
if (owner?.kind === J.Kind.MethodInvocation && !owner.select) {
|
|
581
|
-
return true;
|
|
965
|
+
* Whether the identifier may be renamed in place, which is so where its slot reads rather than
|
|
966
|
+
* names. A shorthand property `{x}` answers false though it reads `x`, since a rename has to
|
|
967
|
+
* expand it to `{x: y}`. Position is all this reads, so a type position reads alike to a value,
|
|
968
|
+
* and what `x` reads is {@link resolve}'s question.
|
|
969
|
+
*/
|
|
970
|
+
export function isReference(cursor: Cursor, identifier: J.Identifier): boolean {
|
|
971
|
+
const path = pathTo(cursor, identifier);
|
|
972
|
+
return !isShorthand(path[path.length - 1]) && position(path, identifier)[1] !== undefined;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* The tree nodes from the nearest compilation unit on `cursor`, or its outermost tree, down to the
|
|
977
|
+
* one holding `identifier`, which is the cursor's own node or that node's parent.
|
|
978
|
+
*/
|
|
979
|
+
function pathTo(cursor: Cursor, identifier: J.Identifier): Tree[] {
|
|
980
|
+
const path = treePath(cursor);
|
|
981
|
+
if (path[path.length - 1] === identifier) {
|
|
982
|
+
path.pop();
|
|
582
983
|
}
|
|
984
|
+
return path;
|
|
985
|
+
}
|
|
583
986
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
987
|
+
/** The cursors on `cursor`'s chain holding tree nodes, down from the nearest compilation unit or outermost tree. */
|
|
988
|
+
function treeCursors(cursor: Cursor): Cursor[] {
|
|
989
|
+
const cursors: Cursor[] = [];
|
|
990
|
+
for (let c: Cursor | undefined = cursor; c; c = c.parent) {
|
|
991
|
+
if (isTree(c.value)) {
|
|
992
|
+
cursors.unshift(c);
|
|
993
|
+
}
|
|
994
|
+
if (c.value?.kind === JS.Kind.CompilationUnit) {
|
|
995
|
+
break;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return cursors;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function treePath(cursor: Cursor): Tree[] {
|
|
1002
|
+
return treeCursors(cursor).map(c => c.value as Tree);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* The scopes around `leaf` and what it reads with, folded down `path`, which ends at the node
|
|
1007
|
+
* holding `leaf`, and what each node of `path` reads with. The fold runs to the end since a naming
|
|
1008
|
+
* slot can hold a read deeper down.
|
|
1009
|
+
*/
|
|
1010
|
+
function position(path: Tree[], leaf: Tree): [Frames | undefined, Reading, Reading[]] {
|
|
1011
|
+
let scopes: Frames | undefined;
|
|
1012
|
+
let reading: Reading = 'value';
|
|
1013
|
+
const readings: Reading[] = [];
|
|
1014
|
+
for (let i = 0; i < path.length; i++) {
|
|
1015
|
+
readings.push(reading);
|
|
1016
|
+
const child = path[i + 1] ?? leaf;
|
|
1017
|
+
const key = slotOf(path[i], child);
|
|
1018
|
+
if (key === undefined) {
|
|
1019
|
+
const spelled = (child as Partial<J.Identifier>).simpleName ?? child.kind;
|
|
1020
|
+
throw new Error(`\`${spelled}\` is not under the cursor it is resolved from`);
|
|
1021
|
+
}
|
|
1022
|
+
[scopes, reading] = descend(path[i], path[i - 1], key, framesAt(path[i], path[i - 1], scopes), reading);
|
|
1023
|
+
}
|
|
1024
|
+
return [scopes, reading, readings];
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* The slot of `node` holding `child`, by id, so a copy or a rebuilt node is found as the original.
|
|
1029
|
+
* A node's slots are indexed once, through padding, containers and lists.
|
|
1030
|
+
*/
|
|
1031
|
+
function slotOf(node: Tree, child: Tree): string | undefined {
|
|
1032
|
+
let index = slots.get(node);
|
|
1033
|
+
if (!index) {
|
|
1034
|
+
index = new Map();
|
|
1035
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1036
|
+
if (key !== 'markers') {
|
|
1037
|
+
for (const held of treesIn(value)) {
|
|
1038
|
+
index.set(held.id, key);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
slots.set(node, index);
|
|
588
1043
|
}
|
|
1044
|
+
return index.get(child.id);
|
|
1045
|
+
}
|
|
589
1046
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
1047
|
+
/** The tree nodes a slot holds directly, through padding, containers and lists. */
|
|
1048
|
+
function treesIn(slot: unknown): Tree[] {
|
|
1049
|
+
if (Array.isArray(slot)) {
|
|
1050
|
+
return slot.flatMap(treesIn);
|
|
1051
|
+
}
|
|
1052
|
+
const kind = (slot as { kind?: string } | undefined)?.kind;
|
|
1053
|
+
if (kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded) {
|
|
1054
|
+
return treesIn((slot as J.RightPadded<any>).element);
|
|
1055
|
+
}
|
|
1056
|
+
if (kind === J.Kind.Container) {
|
|
1057
|
+
return treesIn((slot as J.Container<any>).elements);
|
|
1058
|
+
}
|
|
1059
|
+
return isTree(slot) ? [slot] : [];
|
|
593
1060
|
}
|
|
594
1061
|
|
|
595
|
-
/**
|
|
596
|
-
function
|
|
597
|
-
|
|
1062
|
+
/**
|
|
1063
|
+
* @deprecated Use {@link isReference}. Unlike this function once did, it answers false for an
|
|
1064
|
+
* import's own name, an alias and a re-export's specifier, which name rather than read.
|
|
1065
|
+
*/
|
|
1066
|
+
export const isValueReference = isReference;
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* The node owning the scope that binds what `identifier` reads, or undefined where it reads a
|
|
1070
|
+
* global or reads nothing. Position is what resolves it, so the cursor stands on the identifier
|
|
1071
|
+
* or on the node holding it, and a node the cursor does not hold is refused.
|
|
1072
|
+
*/
|
|
1073
|
+
export function resolve(cursor: Cursor, identifier: J.Identifier): J | undefined {
|
|
1074
|
+
const [scopes, reading] = position(pathTo(cursor, identifier), identifier);
|
|
1075
|
+
return reading && (bindingFrame(scopes, identifier.simpleName, reading)?.node as J | undefined);
|
|
598
1076
|
}
|
|
599
1077
|
|
|
600
1078
|
/**
|
|
601
|
-
*
|
|
602
|
-
*
|
|
1079
|
+
* Whether the identifier is assigned to where it stands. An assignment's target, plain or compound,
|
|
1080
|
+
* is, and so are the operand of `++` or `--`, the head of a for-in or for-of declaring nothing, and
|
|
1081
|
+
* a name in such a target's destructuring pattern. A write resolves as a value read, since
|
|
1082
|
+
* `x = 2` assigns to the `x` it reads. The cursor is as {@link resolve} takes it.
|
|
603
1083
|
*/
|
|
604
|
-
function
|
|
605
|
-
|
|
606
|
-
return (owner?.kind === JS.Kind.PropertyAssignment && owner.initializer === undefined) ||
|
|
607
|
-
references(identifier, parent);
|
|
1084
|
+
export function isWrite(cursor: Cursor, identifier: J.Identifier): boolean {
|
|
1085
|
+
return position(pathTo(cursor, identifier), identifier)[1] === 'write';
|
|
608
1086
|
}
|
|
609
1087
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
1088
|
+
/** One identifier referring to a binding, from {@link referencesOf}. */
|
|
1089
|
+
export interface Reference {
|
|
1090
|
+
/** The identifier's position, holding the tree nodes above it with no padding between them. */
|
|
1091
|
+
cursor: Cursor;
|
|
1092
|
+
identifier: J.Identifier;
|
|
1093
|
+
/** Whether the identifier is one declaring the binding. */
|
|
1094
|
+
declares: boolean;
|
|
1095
|
+
/** Whether the identifier is assigned to, as {@link isWrite} answers. */
|
|
1096
|
+
writes: boolean;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Every identifier referring to the binding `name` has at `cursor`, the ones declaring it and the
|
|
1101
|
+
* ones resolving to it, in the order the tree holds them, which a type annotation can depart from.
|
|
1102
|
+
* The binding is the innermost one of the name with `meaning`, or with any meaning where none is
|
|
1103
|
+
* asked for. A read with any meaning, as an export clause reads, refers to every meaning it finds.
|
|
1104
|
+
* A name bound nowhere has no references. Each reference's cursor continues the caller's chain.
|
|
1105
|
+
*/
|
|
1106
|
+
export function referencesOf(cursor: Cursor, name: string, meaning?: Meaning): Reference[] {
|
|
1107
|
+
const cursors = treeCursors(cursor);
|
|
1108
|
+
const path = cursors.map(c => c.value as Tree);
|
|
1109
|
+
if (path.length === 0) {
|
|
1110
|
+
return [];
|
|
1111
|
+
}
|
|
1112
|
+
const [scopes, reading, readings] = position(path.slice(0, -1), path[path.length - 1]);
|
|
1113
|
+
readings.push(reading);
|
|
1114
|
+
const here = framesAt(path[path.length - 1], path[path.length - 2], scopes);
|
|
1115
|
+
const frame = bindingFrame(here, name, meaning ?? 'any');
|
|
1116
|
+
if (!frame) {
|
|
1117
|
+
return [];
|
|
1118
|
+
}
|
|
1119
|
+
const declarations = new Set(frameBindings(frame.node, frame.parent).get(name)!.declarations
|
|
1120
|
+
.filter(declaration => meaning === undefined || declaration.meanings.includes(meaning))
|
|
1121
|
+
.map(declaration => declaration.identifier.id));
|
|
1122
|
+
// A class binds its own name in its body too, so a frame further out declaring the same
|
|
1123
|
+
// identifier holds the same binding, and its references sit under that frame.
|
|
1124
|
+
const sharesBinding = (scope: Frames | undefined): boolean => scope !== undefined &&
|
|
1125
|
+
(frameBindings(scope.node, scope.parent).get(name)?.declarations ?? [])
|
|
1126
|
+
.some(declaration => declarations.has(declaration.identifier.id));
|
|
1127
|
+
let root = frame;
|
|
1128
|
+
for (let outer = frame.outer; outer; outer = outer.outer) {
|
|
1129
|
+
if (sharesBinding(outer)) {
|
|
1130
|
+
root = outer;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
const at = path.indexOf(root.node);
|
|
1134
|
+
const references: Reference[] = [];
|
|
1135
|
+
readReferences(root.node, root.parent, root.outer, readings[at], (identifier, reads, scopes, trail) => {
|
|
1136
|
+
if (identifier.simpleName !== name) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const declares = declarations.has(identifier.id);
|
|
1140
|
+
const refers = !declares && reads !== undefined &&
|
|
1141
|
+
(meaning === undefined || reads === 'any' || meaningRead(reads) === meaning) &&
|
|
1142
|
+
sharesBinding(bindingFrame(scopes, name, reads));
|
|
1143
|
+
if (declares || refers) {
|
|
1144
|
+
const standing = trail.reduce((above, node) => new Cursor(node, above), cursors[at].parent);
|
|
1145
|
+
references.push({cursor: standing!, identifier, declares, writes: reads === 'write'});
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
return references;
|
|
613
1149
|
}
|