@openrewrite/rewrite 8.91.1 → 8.91.3
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/dist/execution.d.ts +8 -0
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +31 -0
- package/dist/execution.js.map +1 -1
- package/dist/javascript/add-import.d.ts +127 -38
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +684 -195
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts +148 -0
- package/dist/javascript/amd.d.ts.map +1 -0
- package/dist/javascript/amd.js +802 -0
- package/dist/javascript/amd.js.map +1 -0
- package/dist/javascript/binding.d.ts +102 -0
- package/dist/javascript/binding.d.ts.map +1 -0
- package/dist/javascript/binding.js +308 -0
- package/dist/javascript/binding.js.map +1 -0
- package/dist/javascript/comparator.d.ts +2 -0
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +5 -1
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts +5 -2
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +51 -11
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts +19 -0
- package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts.map +1 -1
- package/dist/javascript/format/minimum-viable-spacing-visitor.js +180 -7
- package/dist/javascript/format/minimum-viable-spacing-visitor.js.map +1 -1
- package/dist/javascript/format/prettier-format.d.ts +2 -1
- package/dist/javascript/format/prettier-format.d.ts.map +1 -1
- package/dist/javascript/format/prettier-format.js +33 -23
- package/dist/javascript/format/prettier-format.js.map +1 -1
- package/dist/javascript/index.d.ts +8 -1
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +46 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/parser-utils.d.ts.map +1 -1
- package/dist/javascript/parser-utils.js +7 -9
- package/dist/javascript/parser-utils.js.map +1 -1
- package/dist/javascript/parser.d.ts +7 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +55 -4
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/print.d.ts +1 -0
- package/dist/javascript/print.d.ts.map +1 -1
- package/dist/javascript/print.js +9 -0
- package/dist/javascript/print.js.map +1 -1
- package/dist/javascript/recipes/change-import.d.ts +4 -3
- package/dist/javascript/recipes/change-import.d.ts.map +1 -1
- package/dist/javascript/recipes/change-import.js +12 -504
- package/dist/javascript/recipes/change-import.js.map +1 -1
- package/dist/javascript/remove-import.d.ts +2 -28
- package/dist/javascript/remove-import.d.ts.map +1 -1
- package/dist/javascript/remove-import.js +19 -57
- package/dist/javascript/remove-import.js.map +1 -1
- package/dist/javascript/rpc.js +2 -0
- package/dist/javascript/rpc.js.map +1 -1
- package/dist/javascript/scope.d.ts +82 -0
- package/dist/javascript/scope.d.ts.map +1 -0
- package/dist/javascript/scope.js +476 -0
- package/dist/javascript/scope.js.map +1 -0
- package/dist/javascript/templating/bindings.d.ts +0 -11
- package/dist/javascript/templating/bindings.d.ts.map +1 -1
- package/dist/javascript/templating/bindings.js +2 -49
- package/dist/javascript/templating/bindings.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +12 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +68 -14
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +22 -4
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +68 -24
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/index.d.ts +1 -1
- package/dist/javascript/templating/index.d.ts.map +1 -1
- package/dist/javascript/templating/index.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +2 -2
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +2 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +10 -5
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/precedence.d.ts +12 -4
- package/dist/javascript/templating/precedence.d.ts.map +1 -1
- package/dist/javascript/templating/precedence.js +18 -11
- package/dist/javascript/templating/precedence.js.map +1 -1
- package/dist/javascript/templating/rewrite.d.ts.map +1 -1
- package/dist/javascript/templating/rewrite.js +7 -1
- package/dist/javascript/templating/rewrite.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +7 -1
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +54 -28
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +15 -18
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +1 -1
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +3 -2
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/tree.d.ts +1 -0
- package/dist/javascript/tree.d.ts.map +1 -1
- package/dist/javascript/tree.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +14 -0
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/javascript/visitor.d.ts.map +1 -1
- package/dist/javascript/visitor.js +1 -0
- package/dist/javascript/visitor.js.map +1 -1
- package/dist/parser.d.ts +6 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +23 -1
- package/dist/parser.js.map +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +5 -2
- package/dist/print.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/request/parse-project.d.ts +11 -1
- package/dist/rpc/request/parse-project.d.ts.map +1 -1
- package/dist/rpc/request/parse-project.js +7 -2
- package/dist/rpc/request/parse-project.js.map +1 -1
- package/dist/rpc/request/parse.d.ts +13 -1
- package/dist/rpc/request/parse.d.ts.map +1 -1
- package/dist/rpc/request/parse.js +8 -2
- package/dist/rpc/request/parse.js.map +1 -1
- package/package.json +2 -2
- package/src/execution.ts +23 -0
- package/src/javascript/add-import.ts +753 -238
- package/src/javascript/amd.ts +954 -0
- package/src/javascript/binding.ts +435 -0
- package/src/javascript/comparator.ts +6 -1
- package/src/javascript/format/format.ts +56 -12
- package/src/javascript/format/minimum-viable-spacing-visitor.ts +196 -7
- package/src/javascript/format/prettier-format.ts +35 -25
- package/src/javascript/index.ts +19 -1
- package/src/javascript/parser-utils.ts +7 -11
- package/src/javascript/parser.ts +78 -6
- package/src/javascript/print.ts +10 -0
- package/src/javascript/recipes/change-import.ts +13 -593
- package/src/javascript/remove-import.ts +20 -56
- package/src/javascript/rpc.ts +2 -0
- package/src/javascript/scope.ts +517 -0
- package/src/javascript/templating/bindings.ts +2 -55
- package/src/javascript/templating/comparator.ts +81 -16
- package/src/javascript/templating/engine.ts +107 -38
- package/src/javascript/templating/index.ts +0 -1
- package/src/javascript/templating/pattern.ts +4 -2
- package/src/javascript/templating/placeholder-replacement.ts +16 -6
- package/src/javascript/templating/precedence.ts +21 -13
- package/src/javascript/templating/rewrite.ts +9 -1
- package/src/javascript/templating/template.ts +68 -29
- package/src/javascript/templating/types.ts +17 -18
- package/src/javascript/templating/utils.ts +4 -2
- package/src/javascript/tree.ts +1 -0
- package/src/javascript/type-mapping.ts +15 -0
- package/src/javascript/visitor.ts +1 -0
- package/src/parser.ts +24 -1
- package/src/print.ts +5 -2
- package/src/rpc/request/parse-project.ts +6 -2
- package/src/rpc/request/parse.ts +7 -2
|
@@ -1,44 +1,10 @@
|
|
|
1
1
|
import {JavaScriptVisitor} from "./visitor";
|
|
2
2
|
import {J} from "../java";
|
|
3
|
+
import {bindingNames} from "./scope";
|
|
3
4
|
import {JS, JSX} from "./tree";
|
|
4
5
|
import {mapAsync, updateIfChanged} from "../util";
|
|
5
6
|
import {ElementRemovalFormatter} from "../java";
|
|
6
7
|
|
|
7
|
-
/**
|
|
8
|
-
* @param visitor The visitor to add the import removal to
|
|
9
|
-
* @param module The module name (e.g., 'fs', 'react') to remove imports from
|
|
10
|
-
* @param member Optionally, the specific member to remove from the import.
|
|
11
|
-
* If not specified, removes all unused imports from the module.
|
|
12
|
-
* Special values:
|
|
13
|
-
* - 'default': Removes the default import from the module if unused,
|
|
14
|
-
* regardless of its local name (e.g., `import React from 'react'`)
|
|
15
|
-
* - '*': Removes the namespace import if unused (e.g., `import * as fs from 'fs'`)
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* // Remove a specific named import if unused
|
|
19
|
-
* maybeRemoveImport(visitor, 'fs', 'readFile');
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* // Remove the default import from 'react' if unused (regardless of local name)
|
|
23
|
-
* maybeRemoveImport(visitor, 'react', 'default');
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* // Remove all unused imports from 'react' module
|
|
27
|
-
* maybeRemoveImport(visitor, 'react');
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* // Remove namespace import if unused
|
|
31
|
-
* maybeRemoveImport(visitor, 'fs', '*');
|
|
32
|
-
*/
|
|
33
|
-
export function maybeRemoveImport(visitor: JavaScriptVisitor<any>, module: string, member?: string) {
|
|
34
|
-
for (const v of visitor.afterVisit || []) {
|
|
35
|
-
if (v instanceof RemoveImport && v.module === module && v.member === member) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
visitor.afterVisit.push(new RemoveImport(module, member));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
8
|
// Type alias for RightPadded elements to simplify type signatures
|
|
43
9
|
type RightPaddedElement<T extends J> = {
|
|
44
10
|
element?: T;
|
|
@@ -268,7 +234,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
268
234
|
shouldRemove = !usedIdentifiers.has(name) && !usedTypes.has(name);
|
|
269
235
|
} else {
|
|
270
236
|
// Regular case: check if the import name matches the removal criteria
|
|
271
|
-
shouldRemove = this.shouldRemoveImport(name, usedIdentifiers, usedTypes);
|
|
237
|
+
shouldRemove = this.shouldRemoveImport(name, usedIdentifiers, usedTypes, name);
|
|
272
238
|
}
|
|
273
239
|
|
|
274
240
|
if (shouldRemove) {
|
|
@@ -325,7 +291,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
325
291
|
}, p);
|
|
326
292
|
}
|
|
327
293
|
// Namespace is used, we can't remove individual members from it
|
|
328
|
-
} else if (this.shouldRemoveImport(name, usedIdentifiers, usedTypes)) {
|
|
294
|
+
} else if (this.shouldRemoveImport(name, usedIdentifiers, usedTypes, name)) {
|
|
329
295
|
// If there's no default import, remove the entire import
|
|
330
296
|
if (!importClause.name) {
|
|
331
297
|
return undefined;
|
|
@@ -355,7 +321,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
355
321
|
}, p);
|
|
356
322
|
}
|
|
357
323
|
// Namespace is used, we can't remove individual members from it
|
|
358
|
-
} else if (this.shouldRemoveImport(aliasName, usedIdentifiers, usedTypes)) {
|
|
324
|
+
} else if (this.shouldRemoveImport(aliasName, usedIdentifiers, usedTypes, aliasName)) {
|
|
359
325
|
// If there's no default import, remove the entire import
|
|
360
326
|
if (!importClause.name) {
|
|
361
327
|
return undefined;
|
|
@@ -490,7 +456,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
490
456
|
return true; // Keep imports that don't match the member
|
|
491
457
|
} else {
|
|
492
458
|
// We're removing based on the import name itself
|
|
493
|
-
return !this.shouldRemoveImport(importName, usedIdentifiers, usedTypes);
|
|
459
|
+
return !this.shouldRemoveImport(importName, usedIdentifiers, usedTypes, importName);
|
|
494
460
|
}
|
|
495
461
|
}
|
|
496
462
|
return true; // Keep non-ImportSpecifier elements
|
|
@@ -612,14 +578,10 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
612
578
|
const {filtered, allRemoved} = await this.filterElementsWithPrefixPreservation(
|
|
613
579
|
pattern.bindings.elements,
|
|
614
580
|
(elem: J) => {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const name = (elem as J.Identifier).simpleName;
|
|
620
|
-
return !this.shouldRemoveImport(name, usedIdentifiers, new Set());
|
|
621
|
-
}
|
|
622
|
-
return true; // Keep other element types
|
|
581
|
+
const bound = this.boundByPatternElement(elem);
|
|
582
|
+
// An element whose names cannot be read is left alone.
|
|
583
|
+
return bound.length === 0 ||
|
|
584
|
+
bound.some(b => !this.shouldRemoveImport(b.name, usedIdentifiers, new Set(), b.member));
|
|
623
585
|
},
|
|
624
586
|
async (elem: J, prefix: J.Space) => {
|
|
625
587
|
if (elem.kind === J.Kind.Identifier) {
|
|
@@ -687,23 +649,25 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
687
649
|
return undefined;
|
|
688
650
|
}
|
|
689
651
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
652
|
+
/** The names a pattern element binds, and for each the member of the module it reads. */
|
|
653
|
+
private boundByPatternElement(elem: J): { name: string; member?: string }[] {
|
|
654
|
+
// Shorthand, so the name it binds is the member it reads.
|
|
655
|
+
return elem.kind === J.Kind.Identifier
|
|
656
|
+
? [{name: (elem as J.Identifier).simpleName, member: (elem as J.Identifier).simpleName}]
|
|
657
|
+
: bindingNames(elem);
|
|
696
658
|
}
|
|
697
659
|
|
|
698
660
|
private shouldRemoveImport(
|
|
699
661
|
name: string,
|
|
700
662
|
usedIdentifiers: Set<string>,
|
|
701
|
-
usedTypes: Set<string
|
|
663
|
+
usedTypes: Set<string>,
|
|
664
|
+
member: string | undefined
|
|
702
665
|
): boolean {
|
|
703
666
|
// If member is specified, we're removing a specific member from the module
|
|
704
667
|
if (this.member !== undefined) {
|
|
705
|
-
//
|
|
706
|
-
|
|
668
|
+
// A name bound under an alias reads one member and is referenced by another, and a name
|
|
669
|
+
// a nested pattern binds reads a property of a property, so it reads no member at all.
|
|
670
|
+
if (this.member !== member) {
|
|
707
671
|
return false;
|
|
708
672
|
}
|
|
709
673
|
}
|
package/src/javascript/rpc.ts
CHANGED
|
@@ -229,6 +229,7 @@ class JavaScriptSender extends JavaScriptVisitor<RpcSendQueue> {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
override async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, q: RpcSendQueue): Promise<J | undefined> {
|
|
232
|
+
await q.getAndSendList(propertyAssignment, el => el.modifiers, el => el.id, el => this.visit(el, q));
|
|
232
233
|
await q.getAndSend(propertyAssignment, el => el.name, el => this.visitRightPadded(el, q));
|
|
233
234
|
await q.getAndSend(propertyAssignment, el => el.assigmentToken);
|
|
234
235
|
await q.getAndSend(propertyAssignment, el => el.initializer, el => this.visit(el, q));
|
|
@@ -816,6 +817,7 @@ class JavaScriptReceiver extends JavaScriptVisitor<RpcReceiveQueue> {
|
|
|
816
817
|
|
|
817
818
|
override async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, q: RpcReceiveQueue): Promise<J | undefined> {
|
|
818
819
|
const updates = {
|
|
820
|
+
modifiers: await q.receiveList(propertyAssignment.modifiers, el => this.visitDefined<J.Modifier>(el, q)),
|
|
819
821
|
name: await q.receive(propertyAssignment.name, el => this.visitRightPadded(el, q)),
|
|
820
822
|
assigmentToken: await q.receive(propertyAssignment.assigmentToken),
|
|
821
823
|
initializer: await q.receive(propertyAssignment.initializer, el => this.visitDefined<Expression>(el, q))
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 the original author or authors.
|
|
3
|
+
* <p>
|
|
4
|
+
* Licensed under the Moderne Source Available License (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
* <p>
|
|
8
|
+
* https://docs.moderne.io/licensing/moderne-source-available-license
|
|
9
|
+
* <p>
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import {Cursor, isTree} from "../tree";
|
|
17
|
+
import {J} from "../java";
|
|
18
|
+
import {JS} from "./tree";
|
|
19
|
+
// scope.ts sits below the visitor, so this stays type-only.
|
|
20
|
+
import type {JavaScriptVisitor} from "./visitor";
|
|
21
|
+
|
|
22
|
+
const noNames: ReadonlySet<string> = new Set();
|
|
23
|
+
|
|
24
|
+
/** One scope: the names it binds itself, the scopes around it, and what they answer together. */
|
|
25
|
+
export interface Scope {
|
|
26
|
+
/** The names this scope binds itself, which is not what it reaches: for that, ask `declares`. */
|
|
27
|
+
names(): ReadonlySet<string>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Visits this scope and then each one enclosing it, innermost first, stopping where `visit`
|
|
31
|
+
* returns false. Builds a scope per step, so `declares` is the cheaper way to ask about a
|
|
32
|
+
* name already in hand.
|
|
33
|
+
*/
|
|
34
|
+
walk(visit: (scope: Scope) => boolean): void;
|
|
35
|
+
|
|
36
|
+
/** Whether this scope or one enclosing it binds `name`. */
|
|
37
|
+
declares(name: string): boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The node owning the innermost scope that binds `name` — the compilation unit for a
|
|
41
|
+
* module-scope binding, otherwise the function, block or loop holding it — or undefined where
|
|
42
|
+
* nothing in scope does. A caller holding a declaration asks whether this is the node it came
|
|
43
|
+
* from: anything nearer shadows it.
|
|
44
|
+
*/
|
|
45
|
+
declaringScope(name: string): J | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The innermost scope holding `cursor`, which a visitor's own cursor rarely is: it stands on
|
|
50
|
+
* whatever node it is visiting. Where a declaration's shape leaves its reach unreadable the answer
|
|
51
|
+
* counts it rather than miss it, so a name reported here may not truly reach the cursor.
|
|
52
|
+
*/
|
|
53
|
+
export function scopeOf(cursor: Cursor): Scope {
|
|
54
|
+
return scopeAt(enclosingScopeCursor(cursor) ?? cursor);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function scopeAt(cursor: Cursor): Scope {
|
|
58
|
+
return {
|
|
59
|
+
names: () => frameBindings(cursor.value, cursor.parent?.value),
|
|
60
|
+
walk: visit => {
|
|
61
|
+
for (let c: Cursor | undefined = cursor; c; c = enclosingScopeCursor(c.parent)) {
|
|
62
|
+
if (!visit(scopeAt(c))) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
declares: name => declaringScopeOf(cursor, name) !== undefined,
|
|
68
|
+
declaringScope: name => declaringScopeOf(cursor, name)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function enclosingScopeCursor(from: Cursor | undefined): Cursor | undefined {
|
|
73
|
+
for (let c = from; c; c = c.parent) {
|
|
74
|
+
if (scopeKinds.has((c.value as { kind?: string } | undefined)?.kind!)) {
|
|
75
|
+
return c;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The nodes {@link readBindings} answers for; everything else binds nothing and is not a scope. */
|
|
82
|
+
const scopeKinds = new Set<string>([
|
|
83
|
+
JS.Kind.CompilationUnit, J.Kind.Block, J.Kind.MethodDeclaration, J.Kind.Lambda,
|
|
84
|
+
J.Kind.ClassDeclaration, J.Kind.TryCatch, J.Kind.ForLoop, J.Kind.ForEachLoop, JS.Kind.ForInLoop
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Every name the file declares, wherever it sits. A binding the whole file shares is referenced from
|
|
89
|
+
* sites that are not known when it is named, so it has to steer clear of every name that could
|
|
90
|
+
* shadow it at one of them.
|
|
91
|
+
*/
|
|
92
|
+
export function namesDeclaredIn(cu: JS.CompilationUnit): ReadonlySet<string> {
|
|
93
|
+
return namesDeclaredWithin(cu);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Every name the file spells, declared or not — what a name has to be absent from to be free. An
|
|
98
|
+
* ambient global is spelled and declared nowhere, and binding over one would capture its uses.
|
|
99
|
+
*/
|
|
100
|
+
export function namesUsedIn(cu: JS.CompilationUnit): ReadonlySet<string> {
|
|
101
|
+
return namesUsedWithin(cu);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** As {@link namesUsedIn}, over one subtree, for a name that only has to be free across that much. */
|
|
105
|
+
export function namesUsedWithin(node: unknown, cacheKey: object = node as object): ReadonlySet<string> {
|
|
106
|
+
if (cacheKey === null || typeof cacheKey !== 'object') {
|
|
107
|
+
return noNames;
|
|
108
|
+
}
|
|
109
|
+
const cached = used.get(cacheKey);
|
|
110
|
+
if (cached) {
|
|
111
|
+
return cached;
|
|
112
|
+
}
|
|
113
|
+
const names = new Set<string>();
|
|
114
|
+
walk(node, node => {
|
|
115
|
+
if (node.kind === J.Kind.Identifier) {
|
|
116
|
+
names.add((node as J.Identifier).simpleName);
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
});
|
|
120
|
+
used.set(cacheKey, names);
|
|
121
|
+
return names;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** As {@link namesDeclaredIn}, over one subtree, for a binding shared across only that much of a file. */
|
|
125
|
+
export function namesDeclaredWithin(node: unknown, cacheKey: object = node as object): ReadonlySet<string> {
|
|
126
|
+
if (cacheKey === null || typeof cacheKey !== 'object') {
|
|
127
|
+
return noNames;
|
|
128
|
+
}
|
|
129
|
+
const cached = declared.get(cacheKey);
|
|
130
|
+
if (cached) {
|
|
131
|
+
return cached;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const names = new Set<string>();
|
|
135
|
+
const collect = (node: any): boolean => {
|
|
136
|
+
declarationNames(node).forEach(name => names.add(name));
|
|
137
|
+
if (node.kind !== J.Kind.ClassDeclaration) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
// The walk ends at this branch, so the class's own type parameters are read here.
|
|
141
|
+
typeParameterNames(node).forEach(name => names.add(name));
|
|
142
|
+
// A member's name is not one the file binds, though the code inside one still declares names.
|
|
143
|
+
for (const member of (node as J.ClassDeclaration).body?.statements ?? []) {
|
|
144
|
+
const element = unwrap(member);
|
|
145
|
+
walk(element, node => node === element || collect(node));
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
};
|
|
149
|
+
walk(node, collect);
|
|
150
|
+
|
|
151
|
+
declared.set(cacheKey, names);
|
|
152
|
+
return names;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Every name a binding pattern introduces. `member` is the property a name takes its value from,
|
|
157
|
+
* which only a name an object pattern binds directly has: anything deeper reads a property of a
|
|
158
|
+
* property, an array element is chosen by position, and a rest name gathers what nothing claimed.
|
|
159
|
+
*/
|
|
160
|
+
export function bindingNames(pattern: J | undefined): { name: string; member?: string }[] {
|
|
161
|
+
switch (pattern?.kind) {
|
|
162
|
+
case J.Kind.Identifier: {
|
|
163
|
+
const simpleName = (pattern as J.Identifier).simpleName;
|
|
164
|
+
return simpleName ? [{name: simpleName}] : [];
|
|
165
|
+
}
|
|
166
|
+
case JS.Kind.Spread:
|
|
167
|
+
return bindingNames((pattern as JS.Spread).expression);
|
|
168
|
+
case JS.Kind.ArrayBindingPattern:
|
|
169
|
+
return unnamedMembers((pattern as JS.ArrayBindingPattern).elements.elements
|
|
170
|
+
.flatMap(element => bindingNames(unwrap(element))));
|
|
171
|
+
case JS.Kind.ObjectBindingPattern:
|
|
172
|
+
return (pattern as JS.ObjectBindingPattern).bindings.elements
|
|
173
|
+
.flatMap(element => bindingNames(unwrap(element)));
|
|
174
|
+
case JS.Kind.BindingElement: {
|
|
175
|
+
const element = pattern as JS.BindingElement;
|
|
176
|
+
if (element.name?.kind !== J.Kind.Identifier) {
|
|
177
|
+
return unnamedMembers(bindingNames(element.name as J));
|
|
178
|
+
}
|
|
179
|
+
const name = (element.name as J.Identifier).simpleName;
|
|
180
|
+
const propertyName = unwrap(element.propertyName);
|
|
181
|
+
return [{
|
|
182
|
+
name,
|
|
183
|
+
member: propertyName?.kind === J.Kind.Identifier ? (propertyName as J.Identifier).simpleName : name
|
|
184
|
+
}];
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function unnamedMembers(bound: { name: string }[]): { name: string }[] {
|
|
192
|
+
return bound.map(({name}) => ({name}));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function declaringScopeOf(cursor: Cursor, name: string): J | undefined {
|
|
196
|
+
for (let c: Cursor | undefined = cursor; c; c = c.parent) {
|
|
197
|
+
if (frameBindings(c.value, c.parent?.value).has(name)) {
|
|
198
|
+
return c.value;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* What one node on the cursor path binds, `parent` being the node it hangs from. Only these two
|
|
206
|
+
* answers turn on the parent; a node alone settles the rest, which is what makes them cacheable.
|
|
207
|
+
*/
|
|
208
|
+
function frameBindings(node: any, parent: any): ReadonlySet<string> {
|
|
209
|
+
// A class body holds members, which are reached through an instance rather than by name.
|
|
210
|
+
if (node?.kind === J.Kind.Block && parent?.kind === J.Kind.ClassDeclaration) {
|
|
211
|
+
return noNames;
|
|
212
|
+
}
|
|
213
|
+
// A function expression is the only function whose own name its body reaches: a declaration's
|
|
214
|
+
// name belongs to the enclosing block, a method's to an instance.
|
|
215
|
+
if (node?.kind === J.Kind.MethodDeclaration && parent?.kind === JS.Kind.StatementExpression) {
|
|
216
|
+
const self = bindingNames((node as J.MethodDeclaration).name).map(bound => bound.name);
|
|
217
|
+
return new Set([...self, ...ownBindings(node)]);
|
|
218
|
+
}
|
|
219
|
+
return ownBindings(node);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function ownBindings(node: any): ReadonlySet<string> {
|
|
223
|
+
if (typeof node !== 'object' || node === null) {
|
|
224
|
+
return noNames;
|
|
225
|
+
}
|
|
226
|
+
let names = frames.get(node);
|
|
227
|
+
if (!names) {
|
|
228
|
+
frames.set(node, names = new Set(readBindings(node)));
|
|
229
|
+
}
|
|
230
|
+
return names;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
function readBindings(node: any): string[] {
|
|
235
|
+
switch (node.kind) {
|
|
236
|
+
case JS.Kind.CompilationUnit: {
|
|
237
|
+
const statements = (node as JS.CompilationUnit).statements;
|
|
238
|
+
return [...declaredNames(statements), ...hoistedNames(statements)];
|
|
239
|
+
}
|
|
240
|
+
case J.Kind.Block:
|
|
241
|
+
return blockScopedNames((node as J.Block).statements);
|
|
242
|
+
case J.Kind.MethodDeclaration: {
|
|
243
|
+
const method = node as J.MethodDeclaration;
|
|
244
|
+
return [...declaredNames(method.parameters.elements), ...hoistedNames(method.body)];
|
|
245
|
+
}
|
|
246
|
+
case J.Kind.Lambda: {
|
|
247
|
+
const lambda = node as J.Lambda;
|
|
248
|
+
return [...declaredNames(lambda.parameters.parameters), ...hoistedNames(lambda.body)];
|
|
249
|
+
}
|
|
250
|
+
case J.Kind.ClassDeclaration:
|
|
251
|
+
return bindingNames((node as J.ClassDeclaration).name).map(bound => bound.name);
|
|
252
|
+
case J.Kind.TryCatch:
|
|
253
|
+
return declaredNames([(node as J.Try.Catch).parameter.tree]);
|
|
254
|
+
case J.Kind.ForLoop:
|
|
255
|
+
return declaredNames((node as J.ForLoop).control.init);
|
|
256
|
+
case J.Kind.ForEachLoop:
|
|
257
|
+
return declaredNames([(node as J.ForEachLoop).control.variable]);
|
|
258
|
+
case JS.Kind.ForInLoop:
|
|
259
|
+
return declaredNames([(node as JS.ForInLoop).control.variable]);
|
|
260
|
+
default:
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The names statements bind in the block holding them — the complement of what
|
|
267
|
+
* {@link hoistedNames} claims, so `function (x) { var x = 1; }` reads as one binding, not two.
|
|
268
|
+
*/
|
|
269
|
+
function blockScopedNames(statements: any[]): string[] {
|
|
270
|
+
return statements.flatMap(statement => {
|
|
271
|
+
const element = unwrap(statement);
|
|
272
|
+
switch (element?.kind) {
|
|
273
|
+
case J.Kind.MethodDeclaration:
|
|
274
|
+
return [];
|
|
275
|
+
case J.Kind.VariableDeclarations:
|
|
276
|
+
case JS.Kind.ScopedVariableDeclarations:
|
|
277
|
+
return isBlockScoped(element) ? declarationNames(element) : [];
|
|
278
|
+
case J.Kind.Case:
|
|
279
|
+
return blockScopedNames((element as J.Case).statements.elements);
|
|
280
|
+
default:
|
|
281
|
+
return declarationNames(element);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function isBlockScoped(declaration: any): boolean {
|
|
287
|
+
return ((declaration.modifiers ?? []) as J.Modifier[]).some(m => blockScoped.has(m.keyword!));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** The names statements declare directly in the scope holding them. */
|
|
291
|
+
function declaredNames(statements: any[]): string[] {
|
|
292
|
+
return statements.flatMap(statement => declarationNames(unwrap(statement)));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function declarationNames(statement: any): string[] {
|
|
296
|
+
switch (statement?.kind) {
|
|
297
|
+
case JS.Kind.Import:
|
|
298
|
+
return importNames(statement as JS.Import);
|
|
299
|
+
case J.Kind.VariableDeclarations:
|
|
300
|
+
return (statement as J.VariableDeclarations).variables
|
|
301
|
+
.flatMap(variable => bindingNames(unwrap(variable)?.name))
|
|
302
|
+
.map(bound => bound.name);
|
|
303
|
+
case JS.Kind.ScopedVariableDeclarations:
|
|
304
|
+
return declaredNames((statement as JS.ScopedVariableDeclarations).variables);
|
|
305
|
+
case J.Kind.MethodDeclaration:
|
|
306
|
+
return bindingNames((statement as J.MethodDeclaration).name).map(bound => bound.name);
|
|
307
|
+
case J.Kind.ClassDeclaration:
|
|
308
|
+
return bindingNames((statement as J.ClassDeclaration).name).map(bound => bound.name);
|
|
309
|
+
case JS.Kind.NamespaceDeclaration:
|
|
310
|
+
return bindingNames(unwrap((statement as JS.NamespaceDeclaration).name)).map(bound => bound.name);
|
|
311
|
+
case JS.Kind.TypeDeclaration:
|
|
312
|
+
return bindingNames(unwrap((statement as JS.TypeDeclaration).name)).map(bound => bound.name);
|
|
313
|
+
case J.Kind.TypeParameter:
|
|
314
|
+
// Reached by namesDeclaredWithin's walk, never through a statement list: a type
|
|
315
|
+
// parameter binds across the declaration carrying it, not in the scope that one sits in.
|
|
316
|
+
return bindingNames((statement as J.TypeParameter).name).map(bound => bound.name);
|
|
317
|
+
case J.Kind.Case:
|
|
318
|
+
// The cases of a switch share the block it opens, so each one's declarations bind in all.
|
|
319
|
+
return declaredNames((statement as J.Case).statements.elements);
|
|
320
|
+
default:
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* The names a declaration's type parameters bind. A class keeps them in a container and everything
|
|
327
|
+
* else in a `J.TypeParameters`, and both hold the same right-padded list.
|
|
328
|
+
*/
|
|
329
|
+
function typeParameterNames(node: any): string[] {
|
|
330
|
+
const held = node?.typeParameters;
|
|
331
|
+
const parameters = held?.kind === J.Kind.TypeParameters
|
|
332
|
+
? (held as J.TypeParameters).typeParameters
|
|
333
|
+
: (held as J.Container<J.TypeParameter> | undefined)?.elements;
|
|
334
|
+
return (parameters ?? []).flatMap(parameter => declarationNames(unwrap(parameter)));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** The names an import binds, which for an aliased or namespace specifier is the alias. */
|
|
338
|
+
function importNames(jsImport: JS.Import): string[] {
|
|
339
|
+
const importClause = jsImport.importClause;
|
|
340
|
+
if (!importClause) {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
const names = bindingNames(unwrap(importClause.name)).map(bound => bound.name);
|
|
344
|
+
const namedBindings = importClause.namedBindings;
|
|
345
|
+
if (namedBindings?.kind === JS.Kind.NamedImports) {
|
|
346
|
+
for (const element of (namedBindings as JS.NamedImports).elements.elements) {
|
|
347
|
+
const specifier = unwrap(element);
|
|
348
|
+
if (specifier?.kind === JS.Kind.ImportSpecifier) {
|
|
349
|
+
names.push(...aliasedName((specifier as JS.ImportSpecifier).specifier));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
names.push(...aliasedName(namedBindings));
|
|
354
|
+
}
|
|
355
|
+
return names;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function aliasedName(specifier: J | undefined): string[] {
|
|
359
|
+
const name = specifier?.kind === JS.Kind.Alias ? (specifier as JS.Alias).alias : specifier;
|
|
360
|
+
return bindingNames(name).map(bound => bound.name);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const blockScoped = new Set(['let', 'const', 'using']);
|
|
364
|
+
|
|
365
|
+
// A walk of an immutable subtree has one answer, so it runs once. Keyed on the subtree itself, a
|
|
366
|
+
// replaced one is walked afresh: every call site in a function asks what that body hoists, every
|
|
367
|
+
// import added to a file asks what that file declares, and every scope is asked what it binds by
|
|
368
|
+
// each of the call sites it encloses.
|
|
369
|
+
const hoisted = new WeakMap<object, string[]>();
|
|
370
|
+
const declared = new WeakMap<object, ReadonlySet<string>>();
|
|
371
|
+
const used = new WeakMap<object, ReadonlySet<string>>();
|
|
372
|
+
const frames = new WeakMap<object, ReadonlySet<string>>();
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* The names blocks under `scope` hoist out to it. A `var` or function declaration reaches the whole
|
|
376
|
+
* function it sits in, so one nested in a block is in scope outside that block; only a `let`, `const`
|
|
377
|
+
* or `using` keyword says otherwise.
|
|
378
|
+
*/
|
|
379
|
+
function hoistedNames(scope: any): string[] {
|
|
380
|
+
if (typeof scope !== 'object' || scope === null) {
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
const cached = hoisted.get(scope);
|
|
384
|
+
if (cached) {
|
|
385
|
+
return cached;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const names: string[] = [];
|
|
389
|
+
const collect = (node: any): boolean => {
|
|
390
|
+
switch (node.kind) {
|
|
391
|
+
case J.Kind.MethodDeclaration:
|
|
392
|
+
names.push(...declarationNames(node));
|
|
393
|
+
return false;
|
|
394
|
+
case J.Kind.VariableDeclarations:
|
|
395
|
+
case JS.Kind.ScopedVariableDeclarations:
|
|
396
|
+
if (!isBlockScoped(node)) {
|
|
397
|
+
names.push(...declarationNames(node));
|
|
398
|
+
}
|
|
399
|
+
return false;
|
|
400
|
+
case J.Kind.TryCatch:
|
|
401
|
+
// A catch parameter carries no keyword to read, and binds only in the catch block.
|
|
402
|
+
walk((node as J.Try.Catch).body, collect);
|
|
403
|
+
return false;
|
|
404
|
+
case JS.Kind.StatementExpression:
|
|
405
|
+
// A function or class used as an expression names itself for its own body alone.
|
|
406
|
+
return false;
|
|
407
|
+
case J.Kind.Lambda:
|
|
408
|
+
case J.Kind.ClassDeclaration:
|
|
409
|
+
return false;
|
|
410
|
+
default:
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
walk(scope, collect);
|
|
415
|
+
hoisted.set(scope, names);
|
|
416
|
+
return names;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Visits every LST node under `node`, leaving a subtree unvisited where `visit` returns false.
|
|
421
|
+
* `visit` sees nodes, never the padding holding them. Only what `isTree` accepts is descended
|
|
422
|
+
* into, which a `JavaType` is not, so the walk stays in the tree the source spells rather than
|
|
423
|
+
* entering the type graph — cyclic and shared, where it would not terminate.
|
|
424
|
+
*/
|
|
425
|
+
export function walk(node: unknown, visit: (node: any) => boolean): void {
|
|
426
|
+
if (Array.isArray(node)) {
|
|
427
|
+
node.forEach(child => walk(child, visit));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const kind = (node as any)?.kind;
|
|
431
|
+
if (kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded) {
|
|
432
|
+
walk((node as J.RightPadded<any>).element, visit);
|
|
433
|
+
} else if (kind === J.Kind.Container) {
|
|
434
|
+
walk((node as J.Container<any>).elements, visit);
|
|
435
|
+
} else if (isTree(node) && visit(node)) {
|
|
436
|
+
// Markers hang off a node rather than being part of the code it holds.
|
|
437
|
+
Object.entries(node).forEach(([key, value]) => key !== 'markers' && walk(value, visit));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/** The element a padding wrapper holds, or the node itself. */
|
|
442
|
+
function unwrap(node: any): any {
|
|
443
|
+
return node?.kind === J.Kind.RightPadded || node?.kind === J.Kind.LeftPadded ? unwrap(node.element) : node;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** `cursor` is protected on `TreeVisitor` and these APIs are free functions, so reaching it takes a cast. */
|
|
447
|
+
export function cursorOf(visitor: JavaScriptVisitor<any>): Cursor | undefined {
|
|
448
|
+
return (visitor as unknown as {cursor?: Cursor}).cursor;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** The compilation unit a cursor sits in, or the one a visitor is currently positioned in. */
|
|
452
|
+
export function compilationUnitOf(from: Cursor | JavaScriptVisitor<any>): JS.CompilationUnit | undefined {
|
|
453
|
+
const cursor = from instanceof Cursor ? from : cursorOf(from);
|
|
454
|
+
return cursor?.firstEnclosing((v): v is JS.CompilationUnit => v?.kind === JS.Kind.CompilationUnit);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** `preferred`, or the first `preferred_N` that `isTaken` rejects, so a new name never shadows one in scope. */
|
|
458
|
+
export function deconflict(preferred: string, isTaken: (name: string) => boolean): string {
|
|
459
|
+
if (!isTaken(preferred)) {
|
|
460
|
+
return preferred;
|
|
461
|
+
}
|
|
462
|
+
for (let suffix = 1; ; suffix++) {
|
|
463
|
+
const candidate = `${preferred}_${suffix}`;
|
|
464
|
+
if (!isTaken(candidate)) {
|
|
465
|
+
return candidate;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* The `J.VariableDeclarations` a statement declares — itself for a bare `const x = …`, one per
|
|
472
|
+
* declarator for `const a = …, b = …`, which the parser wraps in a `JS.ScopedVariableDeclarations`
|
|
473
|
+
* instead.
|
|
474
|
+
*/
|
|
475
|
+
export function declarationsOf(statement: J | undefined): J.VariableDeclarations[] {
|
|
476
|
+
if (statement?.kind === J.Kind.VariableDeclarations) {
|
|
477
|
+
return [statement as J.VariableDeclarations];
|
|
478
|
+
}
|
|
479
|
+
if (statement?.kind === JS.Kind.ScopedVariableDeclarations) {
|
|
480
|
+
return (statement as JS.ScopedVariableDeclarations).variables
|
|
481
|
+
.map(v => v.element)
|
|
482
|
+
.filter((v): v is J.VariableDeclarations => v?.kind === J.Kind.VariableDeclarations);
|
|
483
|
+
}
|
|
484
|
+
return [];
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Whether the identifier references a binding rather than naming one, which is what tells a rename
|
|
489
|
+
* which occurrences to follow. A name its parent introduces does not — a property, a method, a
|
|
490
|
+
* variable, a type parameter — nor one drawn from a namespace of its own: a statement label,
|
|
491
|
+
* declaring (`x:`) or referencing (`break x`), and a JSX attribute's prop. Position is all this
|
|
492
|
+
* reads: an import specifier's own name answers true, and a type position reads alike to a value.
|
|
493
|
+
*/
|
|
494
|
+
export function isValueReference(cursor: Cursor, identifier: J.Identifier): boolean {
|
|
495
|
+
let c: Cursor | undefined = cursor.parent;
|
|
496
|
+
while (c && isPadding(c.value)) {
|
|
497
|
+
c = c.parent;
|
|
498
|
+
}
|
|
499
|
+
const parent = c?.value as
|
|
500
|
+
{ kind?: string; name?: unknown; key?: unknown; label?: unknown; select?: unknown } | undefined;
|
|
501
|
+
|
|
502
|
+
// A call names a member of whatever it selects from. With nothing selected there is no member,
|
|
503
|
+
// and its `name` is a reference to the function being called.
|
|
504
|
+
if (parent?.kind === J.Kind.MethodInvocation && !parent.select) {
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// A JSX attribute keeps its prop name on `key`; the three label-bearing nodes keep theirs on
|
|
509
|
+
// `label`. Every other kind that names rather than references keeps it on `name`.
|
|
510
|
+
const named = parent?.kind === JS.Kind.JsxAttribute ? parent.key : (parent?.name ?? parent?.label);
|
|
511
|
+
return named !== identifier && (named as { element?: unknown } | undefined)?.element !== identifier;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function isPadding(value: unknown): boolean {
|
|
515
|
+
const kind = (value as { kind?: string } | undefined)?.kind;
|
|
516
|
+
return kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded || kind === J.Kind.Container;
|
|
517
|
+
}
|