@openrewrite/rewrite 8.91.2 → 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 +64 -9
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +398 -71
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts +11 -1
- package/dist/javascript/amd.d.ts.map +1 -1
- package/dist/javascript/amd.js +43 -9
- package/dist/javascript/amd.js.map +1 -1
- package/dist/javascript/binding.d.ts +10 -2
- package/dist/javascript/binding.d.ts.map +1 -1
- package/dist/javascript/binding.js +44 -71
- package/dist/javascript/binding.js.map +1 -1
- 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 +3 -3
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +21 -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 +5 -9
- package/dist/javascript/parser-utils.js.map +1 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +48 -3
- 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 +6 -3
- package/dist/javascript/recipes/change-import.d.ts.map +1 -1
- package/dist/javascript/recipes/change-import.js +17 -277
- package/dist/javascript/recipes/change-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 +40 -7
- package/dist/javascript/scope.d.ts.map +1 -1
- package/dist/javascript/scope.js +182 -30
- package/dist/javascript/scope.js.map +1 -1
- package/dist/javascript/templating/bindings.js +2 -20
- package/dist/javascript/templating/bindings.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +2 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +25 -0
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +5 -0
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +30 -12
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +6 -0
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +5 -0
- package/dist/javascript/templating/types.d.ts.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 +1 -1
- package/src/execution.ts +23 -0
- package/src/javascript/add-import.ts +449 -78
- package/src/javascript/amd.ts +49 -10
- package/src/javascript/binding.ts +60 -74
- 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 +10 -3
- package/src/javascript/parser-utils.ts +5 -11
- package/src/javascript/parser.ts +65 -6
- package/src/javascript/print.ts +10 -0
- package/src/javascript/recipes/change-import.ts +19 -349
- package/src/javascript/rpc.ts +2 -0
- package/src/javascript/scope.ts +210 -31
- package/src/javascript/templating/bindings.ts +2 -25
- package/src/javascript/templating/comparator.ts +29 -2
- package/src/javascript/templating/engine.ts +41 -14
- package/src/javascript/templating/template.ts +6 -0
- package/src/javascript/templating/types.ts +6 -0
- 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
package/src/javascript/amd.ts
CHANGED
|
@@ -29,7 +29,8 @@ import {
|
|
|
29
29
|
TrailingComma
|
|
30
30
|
} from "../java";
|
|
31
31
|
import {JS} from "./tree";
|
|
32
|
-
import {cursorOf, deconflict, namesDeclaredWithin} from "./scope";
|
|
32
|
+
import {cursorOf, deconflict, namesDeclaredWithin, scopeOf} from "./scope";
|
|
33
|
+
import {Cursor} from "../tree";
|
|
33
34
|
import {JavaScriptVisitor} from "./visitor";
|
|
34
35
|
import {ExecutionContext} from "../execution";
|
|
35
36
|
|
|
@@ -374,17 +375,19 @@ function withParameters(
|
|
|
374
375
|
}
|
|
375
376
|
|
|
376
377
|
/**
|
|
377
|
-
* A bare arrow parameter has no closing delimiter of its own — the space before `=>`
|
|
378
|
-
*
|
|
379
|
-
*
|
|
378
|
+
* A bare arrow parameter has no closing delimiter of its own — the space before `=>` is the
|
|
379
|
+
* parameter's trailing padding, so parenthesizing moves its whitespace to `arrow` and leaves any
|
|
380
|
+
* comment with the parameter. The padding comes from the original list, since `elements` stands
|
|
381
|
+
* in for an emptied list with a placeholder that carries none.
|
|
380
382
|
*/
|
|
381
383
|
function parenthesize(lambda: J.Lambda, elements: J.RightPadded<J>[]): J.Lambda {
|
|
384
|
+
const original = normalizeArrowParameters(lambda.parameters.parameters);
|
|
385
|
+
const trailing = original[original.length - 1]?.after ?? lambda.arrow;
|
|
382
386
|
const last = elements[elements.length - 1];
|
|
383
|
-
const arrowSpace = last.after.whitespace === "" ? lambda.arrow : last.after;
|
|
384
387
|
return {
|
|
385
388
|
...lambda,
|
|
386
389
|
parameters: {...lambda.parameters, parenthesized: true, parameters: [...elements.slice(0, -1), {...last, after: emptySpace}]},
|
|
387
|
-
arrow:
|
|
390
|
+
arrow: space(trailing.whitespace)
|
|
388
391
|
};
|
|
389
392
|
}
|
|
390
393
|
|
|
@@ -468,6 +471,26 @@ export function withDependency(
|
|
|
468
471
|
return withParts(call, block, dependencies, factory);
|
|
469
472
|
}
|
|
470
473
|
|
|
474
|
+
/**
|
|
475
|
+
* Adds a dependency the factory takes no parameter for, as AMD allows for a module loaded only
|
|
476
|
+
* for its side effects. Refuses where parameters outnumber dependencies, since the appended one
|
|
477
|
+
* would land on a surplus parameter and bind after all — as it would in `define(factory)`, whose
|
|
478
|
+
* parameters come from the loader.
|
|
479
|
+
*/
|
|
480
|
+
export function withUnboundDependency(
|
|
481
|
+
call: J.MethodInvocation,
|
|
482
|
+
block: AmdBlock,
|
|
483
|
+
module: string
|
|
484
|
+
): J.MethodInvocation | undefined {
|
|
485
|
+
if (parametersOf(block).length > elementsOf(block).length) {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
const dependencies = withElements(
|
|
489
|
+
block.dependencies,
|
|
490
|
+
appendEntry(elementsOf(block), dependencyLiteral(module, quoteOf(block)), dependencySlot));
|
|
491
|
+
return withParts(call, block, dependencies, block.factory);
|
|
492
|
+
}
|
|
493
|
+
|
|
471
494
|
export function withoutDependencyAt(
|
|
472
495
|
call: J.MethodInvocation,
|
|
473
496
|
block: AmdBlock,
|
|
@@ -549,11 +572,11 @@ export function calleesOf(options?: AmdCalleeOptions): readonly string[] {
|
|
|
549
572
|
|
|
550
573
|
/** The nearest AMD block the cursor sits inside, which is the one a binding belongs to. */
|
|
551
574
|
export function enclosingAmdBlock(
|
|
552
|
-
|
|
575
|
+
from: Cursor | JavaScriptVisitor<any>,
|
|
553
576
|
options?: AmdCalleeOptions
|
|
554
577
|
): {call: J.MethodInvocation, block: AmdBlock} | undefined {
|
|
555
578
|
const callees = calleesOf(options);
|
|
556
|
-
let cursor = cursorOf(
|
|
579
|
+
let cursor = from instanceof Cursor ? from : cursorOf(from);
|
|
557
580
|
while (cursor !== undefined) {
|
|
558
581
|
const value = cursor.value as J | undefined;
|
|
559
582
|
if (value?.kind === J.Kind.MethodInvocation) {
|
|
@@ -591,7 +614,12 @@ const RESERVED_WORDS = new Set([
|
|
|
591
614
|
*/
|
|
592
615
|
export function derivedBindingName(module: string): string | undefined {
|
|
593
616
|
const segment = lastSegment(module);
|
|
594
|
-
return
|
|
617
|
+
return isBindableName(segment) ? segment : undefined;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** Whether `name` is one a binding can take. ASCII identifiers only, so a legal name may be refused. */
|
|
621
|
+
export function isBindableName(name: string): boolean {
|
|
622
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_WORDS.has(name);
|
|
595
623
|
}
|
|
596
624
|
|
|
597
625
|
/** Queued reservations already targeting this block: the shared source for both dedup and deconfliction. */
|
|
@@ -619,7 +647,18 @@ export function bindAmd(
|
|
|
619
647
|
|
|
620
648
|
const declared = module === "" ? -1 : modules.indexOf(module);
|
|
621
649
|
if (declared >= 0) {
|
|
622
|
-
|
|
650
|
+
const bound = bindings[declared];
|
|
651
|
+
if (bound === undefined) {
|
|
652
|
+
// A parameter that is not a plain name gives the module no binding to hand back.
|
|
653
|
+
return undefined;
|
|
654
|
+
}
|
|
655
|
+
// Only code the factory encloses can shadow its parameter, so a cursor elsewhere in the
|
|
656
|
+
// block — on the `define` call, or on its dependency array — has nothing to measure.
|
|
657
|
+
const cursor = cursorOf(visitor);
|
|
658
|
+
const withinFactory = cursor?.firstEnclosing((v): v is J => v === amd.block.factory);
|
|
659
|
+
if (withinFactory === undefined || scopeOf(cursor!).declaringScope(bound) === amd.block.factory) {
|
|
660
|
+
return bound;
|
|
661
|
+
}
|
|
623
662
|
}
|
|
624
663
|
|
|
625
664
|
// Two calls naming the same module at the same block are the same request (ADR 0013),
|
|
@@ -16,12 +16,15 @@
|
|
|
16
16
|
import {J} from "../java";
|
|
17
17
|
import {JS} from "./tree";
|
|
18
18
|
import {JavaScriptVisitor} from "./visitor";
|
|
19
|
-
import {compilationUnitOf, cursorOf, declarationsOf,
|
|
20
|
-
import {
|
|
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";
|
|
21
24
|
import {RemoveImport} from "./remove-import";
|
|
22
25
|
import {
|
|
23
26
|
AmdCalleeOptions, amdBlockOf, bindAmd, calleesOf, dependencyNames, derivedBindingName, enclosingAmdBlock,
|
|
24
|
-
parameterNames, RebindAmdDependency, RemoveAmdDependency
|
|
27
|
+
isBindableName, parameterNames, RebindAmdDependency, RemoveAmdDependency
|
|
25
28
|
} from "./amd";
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -48,7 +51,14 @@ export interface MaybeUnbindOptions {
|
|
|
48
51
|
|
|
49
52
|
export interface MaybeRebindOptions {
|
|
50
53
|
from: {module: string; member?: string};
|
|
51
|
-
|
|
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};
|
|
52
62
|
|
|
53
63
|
/** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
|
|
54
64
|
amdCallee?: string | readonly string[];
|
|
@@ -117,40 +127,6 @@ export function isAmdBlock(node: J, options?: AmdCalleeOptions): boolean {
|
|
|
117
127
|
amdBlockOf(node as J.MethodInvocation, calleesOf(options)) !== undefined;
|
|
118
128
|
}
|
|
119
129
|
|
|
120
|
-
/** Whether a top-level statement already marks the file as a module: an import or any export form. */
|
|
121
|
-
function hasEsmSyntax(cu: JS.CompilationUnit): boolean {
|
|
122
|
-
return cu.statements.some(stmt => {
|
|
123
|
-
const element = stmt.element;
|
|
124
|
-
// `export {a, b}`, `export * from` and `export default` are their own statement kinds;
|
|
125
|
-
// `export class`/`function`/`const` instead carry `export` as a modifier on the
|
|
126
|
-
// declaration itself, the same way TypeScript's own AST models it.
|
|
127
|
-
const modifiers = (element as {modifiers?: J.Modifier[]} | undefined)?.modifiers;
|
|
128
|
-
return element?.kind === JS.Kind.Import ||
|
|
129
|
-
element?.kind === JS.Kind.ExportDeclaration ||
|
|
130
|
-
element?.kind === JS.Kind.ExportAssignment ||
|
|
131
|
-
(modifiers?.some(m => m.keyword === "export") ?? false);
|
|
132
|
-
}) || hasTopLevelAwait(cu);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Whether a statement holds an `await` outside any function of its own — legal only at module
|
|
137
|
-
* top level, unlike an `await` inside an `async function`, which says nothing about the file.
|
|
138
|
-
*/
|
|
139
|
-
function hasTopLevelAwait(cu: JS.CompilationUnit): boolean {
|
|
140
|
-
let found = false;
|
|
141
|
-
walk(cu.statements, node => {
|
|
142
|
-
if (found) {
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
if (node.kind === JS.Kind.Await) {
|
|
146
|
-
found = true;
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
return node.kind !== J.Kind.MethodDeclaration && node.kind !== J.Kind.Lambda;
|
|
150
|
-
});
|
|
151
|
-
return found;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
130
|
interface ModuleObjectBinding {
|
|
155
131
|
name: string;
|
|
156
132
|
module: string;
|
|
@@ -167,33 +143,6 @@ interface ModuleObjectBinding {
|
|
|
167
143
|
typeOnly: boolean;
|
|
168
144
|
}
|
|
169
145
|
|
|
170
|
-
/** Whether the file binds its modules with `require`, which decides whether a create is possible. */
|
|
171
|
-
function isCommonJs(cu: JS.CompilationUnit): boolean {
|
|
172
|
-
if (cu.sourcePath.endsWith(".cjs") || cu.sourcePath.endsWith(".cts")) {
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
// Node treats these as ES modules regardless of what they contain, the same way
|
|
176
|
-
// `AddImport`'s own `determineImportStyle` reads them as ES6-preferring; `.js`/`.ts`/`.tsx`
|
|
177
|
-
// stay ambiguous and fall through to the statements below.
|
|
178
|
-
if (cu.sourcePath.endsWith(".mjs") || cu.sourcePath.endsWith(".mts")) {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
if (hasEsmSyntax(cu)) {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
return cu.statements.some(stmt =>
|
|
185
|
-
declarationsOf(stmt.element).some(d => requiredModule(d) !== undefined));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/** The module a `const X = require("m")` declaration names, for the one variable it declares. */
|
|
189
|
-
function requiredModule(declaration: J.VariableDeclarations): string | undefined {
|
|
190
|
-
const variables = declaration.variables;
|
|
191
|
-
const initializer = variables.length === 1 ? variables[0].element?.initializer?.element : undefined;
|
|
192
|
-
return initializer?.kind === J.Kind.MethodInvocation
|
|
193
|
-
? requiredModuleOf(initializer as J.MethodInvocation)
|
|
194
|
-
: undefined;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
146
|
/**
|
|
198
147
|
* The module a `const X = await import("m")` declaration names, for the one variable it declares.
|
|
199
148
|
* A dynamic import resolves to the module namespace object, the same value `import * as X`
|
|
@@ -270,7 +219,7 @@ function moduleObjectBindings(cu: JS.CompilationUnit): ModuleObjectBinding[] {
|
|
|
270
219
|
}
|
|
271
220
|
}
|
|
272
221
|
}
|
|
273
|
-
bindings.push(...wholeModuleBindingsVia(cu,
|
|
222
|
+
bindings.push(...wholeModuleBindingsVia(cu, requiredModuleOfDeclaration, "require"));
|
|
274
223
|
bindings.push(...wholeModuleBindingsVia(cu, dynamicallyImportedModule, "namespace"));
|
|
275
224
|
return bindings;
|
|
276
225
|
}
|
|
@@ -312,12 +261,14 @@ export function maybeBind(
|
|
|
312
261
|
|
|
313
262
|
const cu = compilationUnitOf(visitor);
|
|
314
263
|
const isWholeModule = !options.sideEffectOnly && (key === undefined || key === "*");
|
|
315
|
-
if (isWholeModule) {
|
|
316
|
-
const
|
|
264
|
+
if (isWholeModule && cu) {
|
|
265
|
+
const scope = scopeOf(cursorOf(visitor)!);
|
|
266
|
+
const bound = moduleObjectBindings(cu).find(b =>
|
|
317
267
|
b.module === module && answersWholeModuleRequest(b, key === "*", options.typeOnly ?? false) &&
|
|
318
268
|
// A pinned alias asks for a binding of that name, so another name for the same
|
|
319
269
|
// module does not answer it; `bindImport`'s own lookup applies the same rule.
|
|
320
|
-
(options.alias === undefined || b.name === options.alias)
|
|
270
|
+
(options.alias === undefined || b.name === options.alias) &&
|
|
271
|
+
scope.declaringScope(b.name) === cu);
|
|
321
272
|
if (bound !== undefined) {
|
|
322
273
|
return bound.name;
|
|
323
274
|
}
|
|
@@ -374,9 +325,10 @@ function bindingShape(member: string | undefined): "default" | "namespace" | "na
|
|
|
374
325
|
}
|
|
375
326
|
|
|
376
327
|
/**
|
|
377
|
-
* Moves the binding for `from` to `to
|
|
328
|
+
* Moves the binding for `from` to `to` and answers with the name it now carries — the primitive
|
|
378
329
|
* behind a member rename or a module move. Returns `undefined`, changing nothing, where the move
|
|
379
|
-
* is not safely expressible; the refusals are listed in CLAUDE.md:
|
|
330
|
+
* is not safely expressible; the refusals and the choice of name are listed in CLAUDE.md:
|
|
331
|
+
* JavaScript module bindings.
|
|
380
332
|
*/
|
|
381
333
|
export function maybeRebind(visitor: JavaScriptVisitor<any>, options: MaybeRebindOptions): string | undefined {
|
|
382
334
|
const amd = enclosingAmdBlock(visitor, options);
|
|
@@ -386,7 +338,10 @@ export function maybeRebind(visitor: JavaScriptVisitor<any>, options: MaybeRebin
|
|
|
386
338
|
}
|
|
387
339
|
const index = dependencyNames(amd.block).indexOf(options.from.module);
|
|
388
340
|
const binding = index < 0 ? undefined : parameterNames(amd.block)[index];
|
|
389
|
-
if (binding === undefined
|
|
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)) {
|
|
390
345
|
return undefined;
|
|
391
346
|
}
|
|
392
347
|
const callees = calleesOf(options);
|
|
@@ -412,8 +367,39 @@ export function maybeRebind(visitor: JavaScriptVisitor<any>, options: MaybeRebin
|
|
|
412
367
|
if (!existing.onlyMemberOfStatement && isCommonJs(cu)) {
|
|
413
368
|
return undefined;
|
|
414
369
|
}
|
|
415
|
-
|
|
416
|
-
|
|
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;
|
|
417
403
|
}
|
|
418
404
|
|
|
419
405
|
/**
|
|
@@ -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 => {
|