@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
|
@@ -0,0 +1,435 @@
|
|
|
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 {J} from "../java";
|
|
17
|
+
import {JS} from "./tree";
|
|
18
|
+
import {JavaScriptVisitor} from "./visitor";
|
|
19
|
+
import {compilationUnitOf, cursorOf, declarationsOf, namesUsedIn, scopeOf} from "./scope";
|
|
20
|
+
import {
|
|
21
|
+
AddImportOptions, bindImport, existingImportBinding, ExistingImportBinding, hasEsmSyntax, isCommonJs, memberName,
|
|
22
|
+
moduleNameOf, nameTaken, RebindImport, requiredModuleOfDeclaration
|
|
23
|
+
} from "./add-import";
|
|
24
|
+
import {RemoveImport} from "./remove-import";
|
|
25
|
+
import {
|
|
26
|
+
AmdCalleeOptions, amdBlockOf, bindAmd, calleesOf, dependencyNames, derivedBindingName, enclosingAmdBlock,
|
|
27
|
+
isBindableName, parameterNames, RebindAmdDependency, RemoveAmdDependency
|
|
28
|
+
} from "./amd";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A bare string is shorthand for `{module}`, the overwhelmingly common call with nothing else to
|
|
32
|
+
* configure. A factory parameter binds a whole module under a name and nothing else, so on the AMD
|
|
33
|
+
* lane `member`, `typeOnly`, `sideEffectOnly`, `onlyIfReferenced`, `quoteStyle` and `style` do not
|
|
34
|
+
* apply: the first three refuse, and the rest have nothing to shape.
|
|
35
|
+
*/
|
|
36
|
+
export interface MaybeBindOptions extends AddImportOptions {
|
|
37
|
+
/** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
|
|
38
|
+
amdCallee?: string | readonly string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A bare string is shorthand for `{module}`, the overwhelmingly common call with nothing else to configure. */
|
|
42
|
+
export interface MaybeUnbindOptions {
|
|
43
|
+
module: string;
|
|
44
|
+
|
|
45
|
+
/** The member to remove; unset removes every unused binding of the module. */
|
|
46
|
+
member?: string;
|
|
47
|
+
|
|
48
|
+
/** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
|
|
49
|
+
amdCallee?: string | readonly string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MaybeRebindOptions {
|
|
53
|
+
from: {module: string; member?: string};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* `alias` names the moved binding, taken verbatim. Left unset, an alias on the `from` side
|
|
57
|
+
* survives the move — `{Old as X}` becomes `{New as X}`, since `X` is a name the file chose —
|
|
58
|
+
* while an unaliased binding takes the new member's name where that name is free. Pin `alias`
|
|
59
|
+
* to the old local name to keep it. See CLAUDE.md: Which name a rebind binds.
|
|
60
|
+
*/
|
|
61
|
+
to: {module: string; member?: string; alias?: string};
|
|
62
|
+
|
|
63
|
+
/** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
|
|
64
|
+
amdCallee?: string | readonly string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ModuleBindings {
|
|
68
|
+
/**
|
|
69
|
+
* The module `localName` refers to, or undefined when it is not a module binding — of any
|
|
70
|
+
* shape, so a namespace import's name answers here even though `maybeBind` will not reuse it
|
|
71
|
+
* for a plain `{module}` request. See `bindingOf`.
|
|
72
|
+
*/
|
|
73
|
+
moduleOf(localName: string): string | undefined;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The local name bound to `module`, or undefined when nothing binds it — of any shape: a
|
|
77
|
+
* namespace import counts, even though `maybeBind` will not treat it as answering a plain
|
|
78
|
+
* `{module}` request. What a name can stand in for is `maybeBind`'s question, not this one's.
|
|
79
|
+
*/
|
|
80
|
+
bindingOf(module: string): string | undefined;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The lane these bindings come from, and the one `maybeBind` would use. `"none"` is a
|
|
84
|
+
* plain script — no import, export, `require` binding, or enclosing AMD block — which
|
|
85
|
+
* `maybeBind` still turns into a module on request; a caller that must not do that checks
|
|
86
|
+
* for `"none"` itself.
|
|
87
|
+
*/
|
|
88
|
+
readonly moduleSystem: "esm" | "amd" | "commonjs" | "none";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function moduleBindings(
|
|
92
|
+
visitor: JavaScriptVisitor<any>,
|
|
93
|
+
options?: AmdCalleeOptions
|
|
94
|
+
): ModuleBindings {
|
|
95
|
+
const amd = enclosingAmdBlock(visitor, options);
|
|
96
|
+
if (amd !== undefined) {
|
|
97
|
+
const modules = dependencyNames(amd.block);
|
|
98
|
+
const bindings = parameterNames(amd.block);
|
|
99
|
+
return {
|
|
100
|
+
moduleSystem: "amd",
|
|
101
|
+
moduleOf: localName => {
|
|
102
|
+
const index = bindings.indexOf(localName);
|
|
103
|
+
// `dependencyNames` pads a non-literal element with "" to hold its position; that
|
|
104
|
+
// filler names no module, so it answers neither lookup.
|
|
105
|
+
return index < 0 || modules[index] === "" ? undefined : modules[index];
|
|
106
|
+
},
|
|
107
|
+
bindingOf: module => {
|
|
108
|
+
const index = module === "" ? -1 : modules.indexOf(module);
|
|
109
|
+
return index < 0 ? undefined : bindings[index];
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const cu = compilationUnitOf(visitor);
|
|
115
|
+
const bound = cu === undefined ? [] : moduleObjectBindings(cu);
|
|
116
|
+
return {
|
|
117
|
+
moduleSystem: cu === undefined ? "none" :
|
|
118
|
+
isCommonJs(cu) ? "commonjs" :
|
|
119
|
+
hasEsmSyntax(cu) ? "esm" : "none",
|
|
120
|
+
moduleOf: localName => bound.find(b => b.name === localName)?.module,
|
|
121
|
+
bindingOf: module => bound.find(b => b.module === module)?.name
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function isAmdBlock(node: J, options?: AmdCalleeOptions): boolean {
|
|
126
|
+
return node.kind === J.Kind.MethodInvocation &&
|
|
127
|
+
amdBlockOf(node as J.MethodInvocation, calleesOf(options)) !== undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface ModuleObjectBinding {
|
|
131
|
+
name: string;
|
|
132
|
+
module: string;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* `"default"` and `"namespace"` bind different values — a namespace object's default sits at
|
|
136
|
+
* `.default` — so only one answers a whole-module request of the matching form. CommonJS has
|
|
137
|
+
* no such split: `"require"` answers either. A caller wanting the namespace asks for
|
|
138
|
+
* `member: "*"`, which is what a module exporting no default is bound by.
|
|
139
|
+
*/
|
|
140
|
+
shape: "default" | "namespace" | "require";
|
|
141
|
+
|
|
142
|
+
/** A type-only import erases, so the name it binds stands for no value at runtime. */
|
|
143
|
+
typeOnly: boolean;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The module a `const X = await import("m")` declaration names, for the one variable it declares.
|
|
148
|
+
* A dynamic import resolves to the module namespace object, the same value `import * as X`
|
|
149
|
+
* binds, so it shares that shape rather than getting one of its own.
|
|
150
|
+
*/
|
|
151
|
+
function dynamicallyImportedModule(declaration: J.VariableDeclarations): string | undefined {
|
|
152
|
+
const variables = declaration.variables;
|
|
153
|
+
const initializer = variables.length === 1 ? variables[0].element?.initializer?.element : undefined;
|
|
154
|
+
const awaited = initializer?.kind === JS.Kind.Await ? (initializer as JS.Await).expression : undefined;
|
|
155
|
+
// `import(...)` is a keyword, not an identifier, so the parser maps it to `JS.FunctionCall`
|
|
156
|
+
// rather than the `J.MethodInvocation` an ordinary call gets.
|
|
157
|
+
if (awaited?.kind !== JS.Kind.FunctionCall) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const call = awaited as JS.FunctionCall;
|
|
161
|
+
const callee = call.function?.element;
|
|
162
|
+
if (callee?.kind !== J.Kind.Identifier || (callee as J.Identifier).simpleName !== "import") {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
const argument = call.arguments.elements[0]?.element;
|
|
166
|
+
return argument?.kind === J.Kind.Literal && typeof (argument as J.Literal).value === "string"
|
|
167
|
+
? (argument as J.Literal).value as string
|
|
168
|
+
: undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Bindings from top-level `const X = <moduleOf-recognised call>` declarations, all of one shape. */
|
|
172
|
+
function wholeModuleBindingsVia(
|
|
173
|
+
cu: JS.CompilationUnit,
|
|
174
|
+
moduleOf: (declaration: J.VariableDeclarations) => string | undefined,
|
|
175
|
+
shape: ModuleObjectBinding["shape"]
|
|
176
|
+
): ModuleObjectBinding[] {
|
|
177
|
+
const bindings: ModuleObjectBinding[] = [];
|
|
178
|
+
for (const stmt of cu.statements) {
|
|
179
|
+
for (const declaration of declarationsOf(stmt.element)) {
|
|
180
|
+
const module = moduleOf(declaration);
|
|
181
|
+
const name = declaration.variables[0]?.element?.name;
|
|
182
|
+
if (module !== undefined && name?.kind === J.Kind.Identifier) {
|
|
183
|
+
bindings.push({name: (name as J.Identifier).simpleName, module, shape, typeOnly: false});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return bindings;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Only whole-module bindings: a named member does not name the module object. */
|
|
191
|
+
function moduleObjectBindings(cu: JS.CompilationUnit): ModuleObjectBinding[] {
|
|
192
|
+
const bindings: ModuleObjectBinding[] = [];
|
|
193
|
+
for (const stmt of cu.statements) {
|
|
194
|
+
const statement = stmt.element;
|
|
195
|
+
if (statement?.kind !== JS.Kind.Import) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const jsImport = statement as JS.Import;
|
|
199
|
+
const specifier = jsImport.moduleSpecifier?.element;
|
|
200
|
+
if (specifier?.kind !== J.Kind.Literal) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const module = (specifier as J.Literal).value;
|
|
204
|
+
if (typeof module !== "string") {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const clause = jsImport.importClause;
|
|
208
|
+
const typeOnly = clause?.typeOnly ?? false;
|
|
209
|
+
if (clause?.name?.element?.kind === J.Kind.Identifier) {
|
|
210
|
+
bindings.push({name: (clause.name.element as J.Identifier).simpleName, module, shape: "default", typeOnly});
|
|
211
|
+
}
|
|
212
|
+
// `namedBindings` is a `JS.Alias` only for `import * as X from "m"`; a renamed
|
|
213
|
+
// named import (`{a as b}`) nests its alias inside `NamedImports` instead.
|
|
214
|
+
const named = clause?.namedBindings;
|
|
215
|
+
if (named?.kind === JS.Kind.Alias) {
|
|
216
|
+
const alias = (named as JS.Alias).alias;
|
|
217
|
+
if (alias?.kind === J.Kind.Identifier) {
|
|
218
|
+
bindings.push({name: (alias as J.Identifier).simpleName, module, shape: "namespace", typeOnly});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
bindings.push(...wholeModuleBindingsVia(cu, requiredModuleOfDeclaration, "require"));
|
|
223
|
+
bindings.push(...wholeModuleBindingsVia(cu, dynamicallyImportedModule, "namespace"));
|
|
224
|
+
return bindings;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Whether `binding` answers a whole-module request: the namespace form when `wantsNamespace`, and
|
|
229
|
+
* never a type-only import for a value, which erases and would leave the reference unbound.
|
|
230
|
+
*/
|
|
231
|
+
function answersWholeModuleRequest(binding: ModuleObjectBinding, wantsNamespace: boolean, typeOnly: boolean): boolean {
|
|
232
|
+
return binding.typeOnly === typeOnly &&
|
|
233
|
+
(binding.shape === "require" || binding.shape === (wantsNamespace ? "namespace" : "default"));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* A local binding for `module` or one of its members, creating one where none exists, or
|
|
238
|
+
* `undefined` where no safe binding is possible. The lane — AMD or ESM/CommonJS — is decided
|
|
239
|
+
* from the cursor; AMD binds only the whole module, so a `member` request there refuses rather
|
|
240
|
+
* than guess. `onlyIfReferenced` defaults to true, so the import may never appear.
|
|
241
|
+
*/
|
|
242
|
+
export function maybeBind(
|
|
243
|
+
visitor: JavaScriptVisitor<any>,
|
|
244
|
+
options: MaybeBindOptions | string
|
|
245
|
+
): string | undefined {
|
|
246
|
+
if (typeof options === "string") {
|
|
247
|
+
options = {module: options};
|
|
248
|
+
}
|
|
249
|
+
const module = moduleNameOf(options.module);
|
|
250
|
+
const key = memberName(options.member);
|
|
251
|
+
|
|
252
|
+
const amd = enclosingAmdBlock(visitor, options);
|
|
253
|
+
if (amd !== undefined) {
|
|
254
|
+
if (key !== undefined || options.sideEffectOnly) {
|
|
255
|
+
// Nor can a factory parameter load a module without binding it to a name.
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
return bindAmd(visitor, amd, module, options.alias ?? options.preferredName,
|
|
259
|
+
calleesOf(options), options.alias !== undefined);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const cu = compilationUnitOf(visitor);
|
|
263
|
+
const isWholeModule = !options.sideEffectOnly && (key === undefined || key === "*");
|
|
264
|
+
if (isWholeModule && cu) {
|
|
265
|
+
const scope = scopeOf(cursorOf(visitor)!);
|
|
266
|
+
const bound = moduleObjectBindings(cu).find(b =>
|
|
267
|
+
b.module === module && answersWholeModuleRequest(b, key === "*", options.typeOnly ?? false) &&
|
|
268
|
+
// A pinned alias asks for a binding of that name, so another name for the same
|
|
269
|
+
// module does not answer it; `bindImport`'s own lookup applies the same rule.
|
|
270
|
+
(options.alias === undefined || b.name === options.alias) &&
|
|
271
|
+
scope.declaringScope(b.name) === cu);
|
|
272
|
+
if (bound !== undefined) {
|
|
273
|
+
return bound.name;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (isWholeModule && options.preferredName === undefined && derivedBindingName(module) === undefined) {
|
|
278
|
+
// The module's last path segment is not a legal identifier, and the caller named no
|
|
279
|
+
// preference of its own — there is no name left to bind it to.
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// `bindImport`'s own lookup finds and reuses a member-specific binding on its own, so
|
|
284
|
+
// refusal here only has to gate the point where it would create a new one.
|
|
285
|
+
const refuseCreate = cu !== undefined && isCommonJs(cu);
|
|
286
|
+
return bindImport(visitor, {
|
|
287
|
+
...options,
|
|
288
|
+
preferredName: options.preferredName ?? (isWholeModule ? derivedBindingName(module) : undefined)
|
|
289
|
+
}, refuseCreate);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Removes `module`'s import(s) where unused, or one `member` of it — `'default'` and `'*'` select
|
|
294
|
+
* the default and namespace import regardless of local name. A member-scoped request does not
|
|
295
|
+
* apply to an AMD dependency, which binds a module rather than one of its members.
|
|
296
|
+
*/
|
|
297
|
+
export function maybeUnbind(visitor: JavaScriptVisitor<any>, options: MaybeUnbindOptions | string): void {
|
|
298
|
+
if (typeof options === "string") {
|
|
299
|
+
options = {module: options};
|
|
300
|
+
}
|
|
301
|
+
const callees = calleesOf(options);
|
|
302
|
+
const queued = visitor.afterVisit || [];
|
|
303
|
+
if (!queued.some(v => v instanceof RemoveImport && v.module === options.module && v.member === options.member)) {
|
|
304
|
+
visitor.afterVisit.push(new RemoveImport(options.module, options.member));
|
|
305
|
+
}
|
|
306
|
+
// Both queue unconditionally so the caller need not know which lane the file uses: each
|
|
307
|
+
// visitor removes whatever matching construct it finds, ESM import or AMD dependency, and a
|
|
308
|
+
// file with both gets both removed. `amdCallee` says which block a call means, not how
|
|
309
|
+
// anything prints, so — unlike `preferredName`/`quoteStyle` on the bind side — it is part of
|
|
310
|
+
// the dedup key.
|
|
311
|
+
if (!queued.some(v => v instanceof RemoveAmdDependency && v.module === options.module &&
|
|
312
|
+
v.member === options.member && sameCallees(v.callees, callees))) {
|
|
313
|
+
visitor.afterVisit.push(new RemoveAmdDependency(options.module, options.member, callees));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function sameCallees(a: readonly string[], b: readonly string[]): boolean {
|
|
318
|
+
return a.length === b.length && a.every((callee, i) => callee === b[i]);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Which of an import clause's three slots `member` binds — `import`, `import *`, or `import {}`. */
|
|
322
|
+
function bindingShape(member: string | undefined): "default" | "namespace" | "named" {
|
|
323
|
+
const key = memberName(member);
|
|
324
|
+
return key === undefined ? "default" : key === "*" ? "namespace" : "named";
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Moves the binding for `from` to `to` and answers with the name it now carries — the primitive
|
|
329
|
+
* behind a member rename or a module move. Returns `undefined`, changing nothing, where the move
|
|
330
|
+
* is not safely expressible; the refusals and the choice of name are listed in CLAUDE.md:
|
|
331
|
+
* JavaScript module bindings.
|
|
332
|
+
*/
|
|
333
|
+
export function maybeRebind(visitor: JavaScriptVisitor<any>, options: MaybeRebindOptions): string | undefined {
|
|
334
|
+
const amd = enclosingAmdBlock(visitor, options);
|
|
335
|
+
if (amd !== undefined) {
|
|
336
|
+
if (memberName(options.from.member) !== undefined || memberName(options.to.member) !== undefined) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
const index = dependencyNames(amd.block).indexOf(options.from.module);
|
|
340
|
+
const binding = index < 0 ? undefined : parameterNames(amd.block)[index];
|
|
341
|
+
if (binding === undefined ||
|
|
342
|
+
// An AMD binding's name is its factory parameter, so naming it something else means
|
|
343
|
+
// rewriting the factory's references, which this lane's dependency swap does not reach.
|
|
344
|
+
(options.to.alias !== undefined && options.to.alias !== binding)) {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
const callees = calleesOf(options);
|
|
348
|
+
const from = options.from.module;
|
|
349
|
+
if (!(visitor.afterVisit || []).some(v => v instanceof RebindAmdDependency &&
|
|
350
|
+
v.blockId === amd.call.id && v.fromModule === from && sameCallees(v.callees, callees))) {
|
|
351
|
+
visitor.afterVisit.push(new RebindAmdDependency(amd.call.id, from, options.to.module, callees));
|
|
352
|
+
}
|
|
353
|
+
return binding;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const cu = compilationUnitOf(visitor);
|
|
357
|
+
if (cu === undefined) {
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
const existing = existingImportBinding(cu, options.from.module, options.from.member);
|
|
361
|
+
if (existing === undefined) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
if (existing.onlyMemberOfStatement && bindingShape(options.from.member) !== bindingShape(options.to.member)) {
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
if (!existing.onlyMemberOfStatement && isCommonJs(cu)) {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
const boundName = rebindingName(visitor, cu, options, existing);
|
|
371
|
+
if (boundName === undefined) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
if (!(visitor.afterVisit || []).some(v => v instanceof RebindImport &&
|
|
375
|
+
v.from.module === options.from.module && v.from.member === options.from.member &&
|
|
376
|
+
v.localName === existing.localName)) {
|
|
377
|
+
visitor.afterVisit.push(new RebindImport(options.from, options.to, existing.localName, boundName));
|
|
378
|
+
}
|
|
379
|
+
return boundName;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* The name the moved binding takes, or undefined where a pinned alias cannot be bound verbatim,
|
|
384
|
+
* since deconflicting it would leave the references the caller emits unbound. The rule is
|
|
385
|
+
* CLAUDE.md: Which name a rebind binds.
|
|
386
|
+
*/
|
|
387
|
+
function rebindingName(
|
|
388
|
+
visitor: JavaScriptVisitor<any>,
|
|
389
|
+
cu: JS.CompilationUnit,
|
|
390
|
+
options: MaybeRebindOptions,
|
|
391
|
+
existing: ExistingImportBinding
|
|
392
|
+
): string | undefined {
|
|
393
|
+
const taken = (name: string) => nameTaken(name, namesUsedIn(cu), visitor);
|
|
394
|
+
const alias = options.to.alias;
|
|
395
|
+
if (alias !== undefined) {
|
|
396
|
+
return isBindableName(alias) && (alias === existing.localName || !taken(alias)) ? alias : undefined;
|
|
397
|
+
}
|
|
398
|
+
const member = memberName(options.to.member);
|
|
399
|
+
if (existing.aliased || member === undefined || member === '*' || member === existing.localName) {
|
|
400
|
+
return existing.localName;
|
|
401
|
+
}
|
|
402
|
+
return taken(member) ? existing.localName : member;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* @deprecated Use {@link maybeUnbind} instead — this is a call-shape change, not a behaviour
|
|
407
|
+
* change: `maybeRemoveImport(v, module, member)` is `maybeUnbind(v, {module, member})`.
|
|
408
|
+
*/
|
|
409
|
+
export function maybeRemoveImport(visitor: JavaScriptVisitor<any>, module: string, member?: string): void {
|
|
410
|
+
maybeUnbind(visitor, {module, member});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* @deprecated Use {@link maybeBind} instead. Beyond binding through an AMD factory parameter,
|
|
415
|
+
* `maybeBind` returns `undefined` rather than creating an import where the file binds its modules
|
|
416
|
+
* with `require`, or where no legal identifier can be derived from the module and none was named.
|
|
417
|
+
*/
|
|
418
|
+
export function maybeAddImport(
|
|
419
|
+
visitor: JavaScriptVisitor<any>,
|
|
420
|
+
options: AddImportOptions & { sideEffectOnly: true }
|
|
421
|
+
): undefined;
|
|
422
|
+
export function maybeAddImport(
|
|
423
|
+
visitor: JavaScriptVisitor<any>,
|
|
424
|
+
options: AddImportOptions & { sideEffectOnly?: false }
|
|
425
|
+
): string | undefined;
|
|
426
|
+
export function maybeAddImport(
|
|
427
|
+
visitor: JavaScriptVisitor<any>,
|
|
428
|
+
options: AddImportOptions
|
|
429
|
+
): string | undefined;
|
|
430
|
+
export function maybeAddImport(
|
|
431
|
+
visitor: JavaScriptVisitor<any>,
|
|
432
|
+
options: AddImportOptions
|
|
433
|
+
): string | undefined {
|
|
434
|
+
return maybeBind(visitor, options);
|
|
435
|
+
}
|
|
@@ -2347,6 +2347,11 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2347
2347
|
return true;
|
|
2348
2348
|
}
|
|
2349
2349
|
|
|
2350
|
+
/** Whether the receiver has to be walked even where attribution alone settles the call. */
|
|
2351
|
+
protected selectMustBeVisited(_method: J.MethodInvocation): boolean {
|
|
2352
|
+
return false;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2350
2355
|
/**
|
|
2351
2356
|
* Override method invocation comparison to include type attribution checking.
|
|
2352
2357
|
* When types match semantically, we allow matching even if one has a receiver
|
|
@@ -2432,7 +2437,7 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2432
2437
|
|
|
2433
2438
|
// When types match (canSkipNameCheck = true), we can skip select comparison entirely.
|
|
2434
2439
|
// This allows matching forwardRef() vs React.forwardRef() where types indicate same method.
|
|
2435
|
-
if (!canSkipNameCheck) {
|
|
2440
|
+
if (!canSkipNameCheck || this.selectMustBeVisited(method)) {
|
|
2436
2441
|
// Types didn't provide a match - must compare receivers structurally
|
|
2437
2442
|
if ((method.select === undefined) !== (otherMethod.select === undefined)) {
|
|
2438
2443
|
return this.structuralMismatch('select');
|
|
@@ -18,7 +18,7 @@ import {JavaScriptVisitor} from "../visitor";
|
|
|
18
18
|
import {Comment, J, lastWhitespace, replaceLastWhitespace, Statement} from "../../java";
|
|
19
19
|
import {create as produce, Draft} from "mutative";
|
|
20
20
|
import {Cursor, isScope, isSourceFile, Tree} from "../../tree";
|
|
21
|
-
import {BlankLinesStyle, getStyle, SpacesStyle, StyleKind, TabsAndIndentsStyle, WrappingAndBracesStyle} from "../style";
|
|
21
|
+
import {BlankLinesStyle, getStyle, PrettierStyle, SpacesStyle, StyleKind, TabsAndIndentsStyle, WrappingAndBracesStyle} from "../style";
|
|
22
22
|
import {NamedStyles} from "../../style";
|
|
23
23
|
import {produceAsync} from "../../visitor";
|
|
24
24
|
import {Generator} from "../markers";
|
|
@@ -55,8 +55,9 @@ export const autoFormat = async <J2 extends J, P>(
|
|
|
55
55
|
* 2. Styles from source file markers (NamedStyles)
|
|
56
56
|
* 3. IntelliJ defaults
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
58
|
+
* A PrettierStyle, in the styles array or as a marker on the source file, formats through Prettier.
|
|
59
|
+
* The built-in visitors lay out a generated subtree Prettier cannot carry back, and any file
|
|
60
|
+
* without such a style.
|
|
60
61
|
*/
|
|
61
62
|
export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
|
|
62
63
|
private readonly styles?: NamedStyles<string>[];
|
|
@@ -67,23 +68,27 @@ export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
async visit<R extends J>(tree: Tree, p: P, cursor?: Cursor): Promise<R | undefined> {
|
|
70
|
-
// Check for PrettierStyle in styles array or as marker on source file
|
|
71
|
-
// If found, delegate entirely to Prettier (skip other formatting visitors)
|
|
72
71
|
const prettierStyle = getPrettierStyle(tree, cursor, this.styles);
|
|
73
72
|
if (prettierStyle) {
|
|
74
|
-
|
|
73
|
+
const formatted = await applyPrettierFormatting(tree as R, prettierStyle, p, cursor, this.stopAfter);
|
|
74
|
+
if (formatted !== undefined) {
|
|
75
|
+
return formatted;
|
|
76
|
+
}
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
// Style markers live on the source file, which is `tree` itself only when a whole file is being formatted
|
|
78
80
|
const styleSource = (isSourceFile(tree) ? tree : cursor?.firstEnclosing(isSourceFile)) ?? tree;
|
|
81
|
+
const tabsAndIndents = getStyle(StyleKind.TabsAndIndentsStyle, styleSource, this.styles) as TabsAndIndentsStyle;
|
|
82
|
+
const spaces = getStyle(StyleKind.SpacesStyle, styleSource, this.styles) as SpacesStyle;
|
|
79
83
|
|
|
80
84
|
const visitors = [
|
|
81
85
|
new NormalizeWhitespaceVisitor(this.stopAfter),
|
|
82
86
|
new MinimumViableSpacingVisitor(this.stopAfter),
|
|
83
87
|
new BlankLinesVisitor(getStyle(StyleKind.BlankLinesStyle, styleSource, this.styles) as BlankLinesStyle, this.stopAfter),
|
|
84
88
|
new WrappingAndBracesVisitor(getStyle(StyleKind.WrappingAndBracesStyle, styleSource, this.styles) as WrappingAndBracesStyle, this.stopAfter),
|
|
85
|
-
new SpacesVisitor(
|
|
86
|
-
new TabsAndIndentsVisitor(
|
|
89
|
+
new SpacesVisitor(prettierStyle ? spacesFrom(prettierStyle, spaces) : spaces, this.stopAfter),
|
|
90
|
+
new TabsAndIndentsVisitor(
|
|
91
|
+
prettierStyle ? tabsAndIndentsFrom(prettierStyle, tabsAndIndents) : tabsAndIndents, this.stopAfter),
|
|
87
92
|
]
|
|
88
93
|
|
|
89
94
|
let t: R | undefined = tree as R;
|
|
@@ -98,6 +103,22 @@ export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
106
|
+
/** The brace spacing a Prettier configuration states, over the style the rest of the spacing comes from. */
|
|
107
|
+
function spacesFrom(prettierStyle: PrettierStyle, fallback: SpacesStyle): SpacesStyle {
|
|
108
|
+
// Prettier's default, in force for a configuration that does not name it
|
|
109
|
+
const bracketSpacing = prettierStyle.config.bracketSpacing !== false;
|
|
110
|
+
return {...fallback, within: {...fallback.within, objectLiteralBraces: bracketSpacing, es6ImportExportBraces: bracketSpacing}};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The widths a Prettier configuration states, over the style the rest of the layout comes from. */
|
|
114
|
+
function tabsAndIndentsFrom(prettierStyle: PrettierStyle, fallback: TabsAndIndentsStyle): TabsAndIndentsStyle {
|
|
115
|
+
const config = prettierStyle.config;
|
|
116
|
+
// Prettier's defaults, in force for a configuration that names neither
|
|
117
|
+
const tabWidth = typeof config.tabWidth === 'number' ? config.tabWidth : 2;
|
|
118
|
+
const useTabCharacter = config.useTabs === true;
|
|
119
|
+
return {...fallback, useTabCharacter, tabSize: tabWidth, indentSize: tabWidth, continuationIndent: tabWidth};
|
|
120
|
+
}
|
|
121
|
+
|
|
101
122
|
export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
102
123
|
constructor(private style: SpacesStyle, private stopAfter?: Tree) {
|
|
103
124
|
super();
|
|
@@ -279,6 +300,14 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
279
300
|
})
|
|
280
301
|
}
|
|
281
302
|
|
|
303
|
+
protected async visitExportSpecifier(exportSpecifier: JS.ExportSpecifier, p: P): Promise<J | undefined> {
|
|
304
|
+
const ret = await super.visitExportSpecifier(exportSpecifier, p) as JS.ExportSpecifier;
|
|
305
|
+
return produce(ret, draft => {
|
|
306
|
+
// `type` is separated from the name it qualifies by that name's own prefix
|
|
307
|
+
draft.specifier.prefix.whitespace = draft.typeOnly.element ? " " : "";
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
282
311
|
protected async visitForLoop(forLoop: J.ForLoop, p: P): Promise<J | undefined> {
|
|
283
312
|
const ret = await super.visitForLoop(forLoop, p) as J.ForLoop;
|
|
284
313
|
return produceAsync(ret, async draft => {
|
|
@@ -337,12 +366,16 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
337
366
|
: "";
|
|
338
367
|
}
|
|
339
368
|
if (draft.importClause.namedBindings) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
draft.importClause.namedBindings.prefix.whitespace = " ";
|
|
343
|
-
}
|
|
369
|
+
// `type`, or a default name and its comma, stands between keyword and bindings
|
|
370
|
+
const afterClause = draft.importClause.name || draft.importClause.typeOnly ? " " : "";
|
|
344
371
|
if (draft.importClause.namedBindings.kind == JS.Kind.NamedImports) {
|
|
345
372
|
const ni = draft.importClause.namedBindings as Draft<JS.NamedImports>;
|
|
373
|
+
// For named imports the space before `{` sits in the container, as the parser
|
|
374
|
+
// writes it; a newline there is layout, which spacing does not decide
|
|
375
|
+
if (!ni.prefix.whitespace.includes("\n") && !ni.elements.before.whitespace.includes("\n")) {
|
|
376
|
+
ni.prefix.whitespace = "";
|
|
377
|
+
ni.elements.before.whitespace = afterClause;
|
|
378
|
+
}
|
|
346
379
|
const isMultiLine = ni.elements.elements.some(e => e.element.prefix.whitespace.includes("\n"));
|
|
347
380
|
if (!isMultiLine) {
|
|
348
381
|
const braceSpace = this.style.within.es6ImportExportBraces ? " " : "";
|
|
@@ -355,6 +388,8 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
355
388
|
lastAfter.whitespace = this.style.other.beforeComma ? " " : "";
|
|
356
389
|
}
|
|
357
390
|
}
|
|
391
|
+
} else if (afterClause) {
|
|
392
|
+
draft.importClause.namedBindings.prefix.whitespace = afterClause;
|
|
358
393
|
}
|
|
359
394
|
}
|
|
360
395
|
}
|
|
@@ -365,6 +400,15 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
365
400
|
})
|
|
366
401
|
}
|
|
367
402
|
|
|
403
|
+
protected async visitImportSpecifier(importSpecifier: JS.ImportSpecifier, p: P): Promise<J | undefined> {
|
|
404
|
+
const ret = await super.visitImportSpecifier(importSpecifier, p) as JS.ImportSpecifier;
|
|
405
|
+
return produce(ret, draft => {
|
|
406
|
+
// The specifier's leading space is the brace and comma rules'; this is the
|
|
407
|
+
// gap between `type` and the name, which the name's own prefix carries
|
|
408
|
+
draft.specifier.prefix.whitespace = draft.importType.element ? " " : "";
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
368
412
|
protected async visitIndexSignatureDeclaration(indexSignatureDeclaration: JS.IndexSignatureDeclaration, p: P): Promise<J | undefined> {
|
|
369
413
|
const ret = await super.visitIndexSignatureDeclaration(indexSignatureDeclaration, p) as JS.IndexSignatureDeclaration;
|
|
370
414
|
return produce(ret, draft => {
|