@openrewrite/rewrite 8.92.16 → 8.92.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/java/formatting-utils.d.ts +2 -1
- package/dist/java/formatting-utils.d.ts.map +1 -1
- package/dist/java/formatting-utils.js +4 -3
- package/dist/java/formatting-utils.js.map +1 -1
- package/dist/java/rpc.d.ts.map +1 -1
- package/dist/java/rpc.js +2 -0
- package/dist/java/rpc.js.map +1 -1
- package/dist/java/tree.d.ts +8 -0
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +11 -1
- package/dist/java/tree.js.map +1 -1
- package/dist/java/type.d.ts +7 -1
- package/dist/java/type.d.ts.map +1 -1
- package/dist/java/type.js +16 -1
- package/dist/java/type.js.map +1 -1
- package/dist/javascript/add-import.d.ts +27 -21
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +303 -143
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts.map +1 -1
- package/dist/javascript/amd.js +25 -10
- package/dist/javascript/amd.js.map +1 -1
- package/dist/javascript/autodetect.js +36 -45
- package/dist/javascript/autodetect.js.map +1 -1
- package/dist/javascript/binding.d.ts +6 -4
- package/dist/javascript/binding.d.ts.map +1 -1
- package/dist/javascript/binding.js +54 -18
- package/dist/javascript/binding.js.map +1 -1
- package/dist/javascript/comparator.d.ts +22 -5
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +120 -38
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts +2 -0
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +11 -1
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -1
- package/dist/javascript/format/tabs-and-indents-visitor.js +31 -4
- package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
- package/dist/javascript/index.d.ts +4 -2
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +10 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/markers.d.ts +12 -0
- package/dist/javascript/markers.d.ts.map +1 -1
- package/dist/javascript/markers.js +81 -2
- package/dist/javascript/markers.js.map +1 -1
- package/dist/javascript/method-matcher.d.ts +13 -2
- package/dist/javascript/method-matcher.d.ts.map +1 -1
- package/dist/javascript/method-matcher.js +32 -19
- package/dist/javascript/method-matcher.js.map +1 -1
- package/dist/javascript/node-resolution-result.js +3 -2
- package/dist/javascript/node-resolution-result.js.map +1 -1
- package/dist/javascript/package-name.d.ts +6 -0
- package/dist/javascript/package-name.d.ts.map +1 -0
- package/dist/javascript/package-name.js +47 -0
- package/dist/javascript/package-name.js.map +1 -0
- package/dist/javascript/parser.d.ts +9 -0
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +185 -88
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/preconditions.d.ts +4 -1
- package/dist/javascript/preconditions.d.ts.map +1 -1
- package/dist/javascript/preconditions.js +7 -4
- package/dist/javascript/preconditions.js.map +1 -1
- package/dist/javascript/print.d.ts +10 -1
- package/dist/javascript/print.d.ts.map +1 -1
- package/dist/javascript/print.js +120 -77
- package/dist/javascript/print.js.map +1 -1
- package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
- package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
- package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
- package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
- package/dist/javascript/recipes/index.d.ts +1 -0
- package/dist/javascript/recipes/index.d.ts.map +1 -1
- package/dist/javascript/recipes/index.js +1 -0
- package/dist/javascript/recipes/index.js.map +1 -1
- package/dist/javascript/scope.d.ts +60 -16
- package/dist/javascript/scope.d.ts.map +1 -1
- package/dist/javascript/scope.js +596 -131
- package/dist/javascript/scope.js.map +1 -1
- package/dist/javascript/search/uses-method.d.ts +1 -1
- package/dist/javascript/search/uses-method.d.ts.map +1 -1
- package/dist/javascript/search/uses-method.js +2 -2
- package/dist/javascript/search/uses-method.js.map +1 -1
- package/dist/javascript/search/uses-type.d.ts +14 -1
- package/dist/javascript/search/uses-type.d.ts.map +1 -1
- package/dist/javascript/search/uses-type.js +24 -9
- package/dist/javascript/search/uses-type.js.map +1 -1
- package/dist/javascript/templating/bindings.d.ts +1 -1
- package/dist/javascript/templating/bindings.d.ts.map +1 -1
- package/dist/javascript/templating/bindings.js +10 -50
- package/dist/javascript/templating/bindings.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +8 -11
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +12 -110
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +34 -24
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +208 -115
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +11 -5
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +86 -20
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +126 -60
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/precedence.d.ts +3 -1
- package/dist/javascript/templating/precedence.d.ts.map +1 -1
- package/dist/javascript/templating/precedence.js +28 -8
- package/dist/javascript/templating/precedence.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +7 -6
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +31 -28
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +6 -3
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +10 -1
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +22 -3
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/tree-debug.d.ts +2 -0
- package/dist/javascript/tree-debug.d.ts.map +1 -1
- package/dist/javascript/tree-debug.js +6 -0
- package/dist/javascript/tree-debug.js.map +1 -1
- package/dist/javascript/tsconfig.d.ts.map +1 -1
- package/dist/javascript/tsconfig.js +7 -17
- package/dist/javascript/tsconfig.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +35 -16
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +271 -252
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/javascript/type-report-cli.d.ts +7 -0
- package/dist/javascript/type-report-cli.d.ts.map +1 -0
- package/dist/javascript/type-report-cli.js +129 -0
- package/dist/javascript/type-report-cli.js.map +1 -0
- package/dist/javascript/type-report.d.ts +61 -0
- package/dist/javascript/type-report.d.ts.map +1 -0
- package/dist/javascript/type-report.js +488 -0
- package/dist/javascript/type-report.js.map +1 -0
- package/dist/markers.d.ts +4 -0
- package/dist/markers.d.ts.map +1 -1
- package/dist/markers.js +8 -0
- package/dist/markers.js.map +1 -1
- package/dist/print.d.ts +6 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +21 -7
- package/dist/print.js.map +1 -1
- package/dist/recipe.d.ts +5 -0
- package/dist/recipe.d.ts.map +1 -1
- package/dist/recipe.js +5 -0
- package/dist/recipe.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/java-rpc-client.d.ts +2 -0
- package/dist/rpc/java-rpc-client.d.ts.map +1 -1
- package/dist/rpc/java-rpc-client.js +1 -0
- package/dist/rpc/java-rpc-client.js.map +1 -1
- package/dist/rpc/queue.d.ts +24 -0
- package/dist/rpc/queue.d.ts.map +1 -1
- package/dist/rpc/queue.js +71 -11
- package/dist/rpc/queue.js.map +1 -1
- package/dist/rpc/request/get-object.d.ts +4 -0
- package/dist/rpc/request/get-object.d.ts.map +1 -1
- package/dist/rpc/request/get-object.js +23 -1
- package/dist/rpc/request/get-object.js.map +1 -1
- package/dist/rpc/request/parse.d.ts.map +1 -1
- package/dist/rpc/request/parse.js +3 -1
- package/dist/rpc/request/parse.js.map +1 -1
- package/dist/rpc/request/prepare-recipe.d.ts +3 -1
- package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
- package/dist/rpc/request/prepare-recipe.js +13 -6
- package/dist/rpc/request/prepare-recipe.js.map +1 -1
- package/dist/rpc/request/print.d.ts +8 -2
- package/dist/rpc/request/print.d.ts.map +1 -1
- package/dist/rpc/request/print.js +12 -9
- package/dist/rpc/request/print.js.map +1 -1
- package/dist/rpc/rewrite-rpc.d.ts +3 -2
- package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
- package/dist/rpc/rewrite-rpc.js +9 -3
- package/dist/rpc/rewrite-rpc.js.map +1 -1
- package/dist/rpc/server.d.ts.map +1 -1
- package/dist/rpc/server.js +19 -5
- package/dist/rpc/server.js.map +1 -1
- package/dist/search/is-source-file.d.ts +1 -0
- package/dist/search/is-source-file.d.ts.map +1 -1
- package/dist/search/is-source-file.js +7 -2
- package/dist/search/is-source-file.js.map +1 -1
- package/dist/test/java-rpc.d.ts.map +1 -1
- package/dist/test/java-rpc.js +3 -0
- package/dist/test/java-rpc.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +5 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +14 -3
- package/dist/test/rewrite-test.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +3 -1
- package/src/java/formatting-utils.ts +5 -4
- package/src/java/rpc.ts +2 -0
- package/src/java/tree.ts +15 -0
- package/src/java/type.ts +19 -2
- package/src/javascript/add-import.ts +338 -152
- package/src/javascript/amd.ts +28 -12
- package/src/javascript/autodetect.ts +32 -41
- package/src/javascript/binding.ts +64 -22
- package/src/javascript/comparator.ts +129 -43
- package/src/javascript/format/format.ts +9 -0
- package/src/javascript/format/tabs-and-indents-visitor.ts +30 -3
- package/src/javascript/index.ts +6 -3
- package/src/javascript/markers.ts +77 -5
- package/src/javascript/method-matcher.ts +34 -22
- package/src/javascript/node-resolution-result.ts +3 -2
- package/src/javascript/package-name.ts +45 -0
- package/src/javascript/parser.ts +200 -91
- package/src/javascript/preconditions.ts +7 -4
- package/src/javascript/print.ts +123 -76
- package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
- package/src/javascript/recipes/index.ts +1 -0
- package/src/javascript/scope.ts +705 -169
- package/src/javascript/search/uses-method.ts +2 -2
- package/src/javascript/search/uses-type.ts +30 -9
- package/src/javascript/templating/bindings.ts +13 -38
- package/src/javascript/templating/comparator.ts +16 -127
- package/src/javascript/templating/engine.ts +236 -129
- package/src/javascript/templating/pattern.ts +107 -28
- package/src/javascript/templating/placeholder-replacement.ts +154 -62
- package/src/javascript/templating/precedence.ts +33 -11
- package/src/javascript/templating/template.ts +35 -30
- package/src/javascript/templating/types.ts +6 -3
- package/src/javascript/templating/utils.ts +24 -2
- package/src/javascript/tree-debug.ts +9 -0
- package/src/javascript/tsconfig.ts +7 -17
- package/src/javascript/type-mapping.ts +277 -255
- package/src/javascript/type-report-cli.ts +96 -0
- package/src/javascript/type-report.ts +575 -0
- package/src/markers.ts +8 -0
- package/src/print.ts +23 -8
- package/src/recipe.ts +6 -0
- package/src/rpc/java-rpc-client.ts +3 -0
- package/src/rpc/queue.ts +80 -7
- package/src/rpc/request/get-object.ts +24 -0
- package/src/rpc/request/parse.ts +4 -1
- package/src/rpc/request/prepare-recipe.ts +16 -6
- package/src/rpc/request/print.ts +12 -8
- package/src/rpc/rewrite-rpc.ts +16 -5
- package/src/rpc/server.ts +18 -6
- package/src/search/is-source-file.ts +8 -3
- package/src/test/java-rpc.ts +3 -0
- package/src/test/rewrite-test.ts +14 -3
package/src/javascript/amd.ts
CHANGED
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
J,
|
|
25
25
|
rightPadded,
|
|
26
26
|
space,
|
|
27
|
-
spaceContainsNewline,
|
|
28
27
|
Statement,
|
|
29
28
|
TrailingComma
|
|
30
29
|
} from "../java";
|
|
@@ -193,16 +192,20 @@ export function parameterNames(block: AmdBlock): (string | undefined)[] {
|
|
|
193
192
|
}
|
|
194
193
|
|
|
195
194
|
export function identifierOf(parameter: J): J.Identifier | undefined {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
195
|
+
const name = declaredNameOf(parameter);
|
|
196
|
+
return name !== undefined && isIdentifier(name) ? name : undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** The name a factory parameter binds, which may be a destructuring pattern. */
|
|
200
|
+
function declaredNameOf(parameter: J): J | undefined {
|
|
201
|
+
return parameter.kind === J.Kind.VariableDeclarations ?
|
|
202
|
+
(parameter as J.VariableDeclarations).variables[0]?.element.name :
|
|
203
|
+
parameter;
|
|
201
204
|
}
|
|
202
205
|
|
|
203
206
|
/**
|
|
204
207
|
* Where an entry's leading whitespace sits. A dependency string carries it directly, while
|
|
205
|
-
* a factory parameter carries it on the
|
|
208
|
+
* a factory parameter carries it on the name its declaration wraps, even a destructuring one.
|
|
206
209
|
*/
|
|
207
210
|
interface Slot<T extends J> {
|
|
208
211
|
prefixOf(element: T): J.Space;
|
|
@@ -216,7 +219,7 @@ const dependencySlot: Slot<Expression> = {
|
|
|
216
219
|
};
|
|
217
220
|
|
|
218
221
|
const parameterSlot: Slot<J> = {
|
|
219
|
-
prefixOf: element =>
|
|
222
|
+
prefixOf: element => declaredNameOf(element)?.prefix ?? element.prefix,
|
|
220
223
|
withPrefix: (element, prefix) => {
|
|
221
224
|
if (element.kind !== J.Kind.VariableDeclarations) {
|
|
222
225
|
return {...element, prefix};
|
|
@@ -257,14 +260,27 @@ function moveTrailingComma<T extends J>(
|
|
|
257
260
|
|
|
258
261
|
/**
|
|
259
262
|
* The whitespace that separates one entry from the next, taken from the entries already
|
|
260
|
-
* there so that a block listing its dependencies one per line keeps doing so.
|
|
263
|
+
* there so that a block listing its dependencies one per line keeps doing so. Comments in an
|
|
264
|
+
* entry's prefix document that entry, so they stay behind.
|
|
261
265
|
*/
|
|
262
266
|
function separator<T extends J>(entries: readonly J.RightPadded<T>[], slot: Slot<T>): J.Space {
|
|
263
267
|
if (entries.length >= 2) {
|
|
264
|
-
return slot.prefixOf(entries[1].element);
|
|
268
|
+
return space(layoutOf(slot.prefixOf(entries[1].element)));
|
|
269
|
+
}
|
|
270
|
+
const first = layoutOf(slot.prefixOf(entries[0].element));
|
|
271
|
+
return space(first.includes("\n") ? first : " ");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* The prefix without its comments. That is the segment holding the line break, preferring the
|
|
276
|
+
* last comment's suffix, else the whitespace before the entry.
|
|
277
|
+
*/
|
|
278
|
+
function layoutOf(prefix: J.Space): string {
|
|
279
|
+
const last = prefix.comments[prefix.comments.length - 1];
|
|
280
|
+
if (last === undefined || (!last.suffix.includes("\n") && prefix.whitespace.includes("\n"))) {
|
|
281
|
+
return prefix.whitespace;
|
|
265
282
|
}
|
|
266
|
-
|
|
267
|
-
return spaceContainsNewline(first) ? first : space(" ");
|
|
283
|
+
return last.suffix;
|
|
268
284
|
}
|
|
269
285
|
|
|
270
286
|
/**
|
|
@@ -123,13 +123,13 @@ class TabsAndIndentsStatistics {
|
|
|
123
123
|
private totalSpaceIndents = 0;
|
|
124
124
|
private totalTabIndents = 0;
|
|
125
125
|
|
|
126
|
-
//
|
|
127
|
-
private
|
|
126
|
+
// How often a block was seen indenting its statements by each width past the enclosing block's
|
|
127
|
+
private readonly levelWidths = new Map<number, number>();
|
|
128
128
|
|
|
129
|
-
recordSpaceIndent(
|
|
129
|
+
recordSpaceIndent(width: number): void {
|
|
130
130
|
this.totalSpaceIndents++;
|
|
131
|
-
if (
|
|
132
|
-
this.
|
|
131
|
+
if (width > 0) {
|
|
132
|
+
this.levelWidths.set(width, (this.levelWidths.get(width) ?? 0) + 1);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -141,23 +141,14 @@ class TabsAndIndentsStatistics {
|
|
|
141
141
|
// Determine if using tabs or spaces
|
|
142
142
|
const useTabs = this.totalTabIndents > this.totalSpaceIndents;
|
|
143
143
|
|
|
144
|
-
//
|
|
145
|
-
// This correctly handles 2-space files where we see 2, 4, 6, 8... (all multiples of 2)
|
|
144
|
+
// The width most statements agree on, so a stray misindented line is outvoted
|
|
146
145
|
let detectedIndentSize = 4; // Default
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
// Only use common indent sizes (2, 4, 8)
|
|
155
|
-
if (gcd === 2 || gcd === 4 || gcd === 8) {
|
|
156
|
-
detectedIndentSize = gcd;
|
|
157
|
-
} else if (gcd > 0 && gcd % 4 === 0) {
|
|
158
|
-
detectedIndentSize = 4;
|
|
159
|
-
} else if (gcd > 0 && gcd % 2 === 0) {
|
|
160
|
-
detectedIndentSize = 2;
|
|
146
|
+
let votes = 0;
|
|
147
|
+
for (const size of [2, 4, 8]) {
|
|
148
|
+
const count = this.levelWidths.get(size) ?? 0;
|
|
149
|
+
if (count > votes) {
|
|
150
|
+
detectedIndentSize = size;
|
|
151
|
+
votes = count;
|
|
161
152
|
}
|
|
162
153
|
}
|
|
163
154
|
|
|
@@ -172,15 +163,6 @@ class TabsAndIndentsStatistics {
|
|
|
172
163
|
indentAllChainedCallsInAGroup: false
|
|
173
164
|
};
|
|
174
165
|
}
|
|
175
|
-
|
|
176
|
-
private computeGcd(a: number, b: number): number {
|
|
177
|
-
while (b !== 0) {
|
|
178
|
-
const temp = b;
|
|
179
|
-
b = a % b;
|
|
180
|
-
a = temp;
|
|
181
|
-
}
|
|
182
|
-
return a;
|
|
183
|
-
}
|
|
184
166
|
}
|
|
185
167
|
|
|
186
168
|
/**
|
|
@@ -272,23 +254,32 @@ class FindIndentVisitor extends JavaScriptVisitor<any> {
|
|
|
272
254
|
super();
|
|
273
255
|
}
|
|
274
256
|
|
|
257
|
+
/** The indent of the statements in each enclosing block, innermost last. */
|
|
258
|
+
private readonly enclosing: number[] = [];
|
|
259
|
+
|
|
275
260
|
protected async visitBlock(block: J.Block, p: any): Promise<J | undefined> {
|
|
276
|
-
|
|
261
|
+
const outer = this.enclosing[this.enclosing.length - 1] ?? 0;
|
|
262
|
+
let own: number | undefined;
|
|
277
263
|
for (const stmt of block.statements) {
|
|
278
264
|
const whitespace = stmt.element.prefix?.whitespace;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
265
|
+
const indent = whitespace ? this.analyzeIndent(whitespace, outer) : undefined;
|
|
266
|
+
own ??= indent;
|
|
267
|
+
}
|
|
268
|
+
this.enclosing.push(own ?? outer);
|
|
269
|
+
try {
|
|
270
|
+
return await super.visitBlock(block, p);
|
|
271
|
+
} finally {
|
|
272
|
+
this.enclosing.pop();
|
|
282
273
|
}
|
|
283
|
-
return super.visitBlock(block, p);
|
|
284
274
|
}
|
|
285
275
|
|
|
286
|
-
|
|
276
|
+
/** Records the indent `whitespace` ends in, relative to `outer`, and returns it if it is spaces. */
|
|
277
|
+
private analyzeIndent(whitespace: string, outer: number): number | undefined {
|
|
287
278
|
const newlineIndex = whitespace.lastIndexOf('\n');
|
|
288
|
-
if (newlineIndex < 0) return;
|
|
279
|
+
if (newlineIndex < 0) return undefined;
|
|
289
280
|
|
|
290
281
|
const indent = whitespace.substring(newlineIndex + 1);
|
|
291
|
-
if (indent.length === 0) return;
|
|
282
|
+
if (indent.length === 0) return 0;
|
|
292
283
|
|
|
293
284
|
// Check first character to determine type
|
|
294
285
|
if (indent[0] === '\t') {
|
|
@@ -300,10 +291,10 @@ class FindIndentVisitor extends JavaScriptVisitor<any> {
|
|
|
300
291
|
if (char === ' ') spaceCount++;
|
|
301
292
|
else break;
|
|
302
293
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
294
|
+
this.stats.recordSpaceIndent(spaceCount - outer);
|
|
295
|
+
return spaceCount;
|
|
306
296
|
}
|
|
297
|
+
return undefined;
|
|
307
298
|
}
|
|
308
299
|
}
|
|
309
300
|
|
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
import {J} from "../java";
|
|
17
17
|
import {JS} from "./tree";
|
|
18
18
|
import {JavaScriptVisitor} from "./visitor";
|
|
19
|
-
import {compilationUnitOf, cursorOf, declarationsOf, namesUsedIn, scopeOf} from "./scope";
|
|
19
|
+
import {compilationUnitOf, cursorOf, declarationsOf, isReference, namesUsedIn, scopeOf} from "./scope";
|
|
20
|
+
import {Cursor, isTree} from "../tree";
|
|
20
21
|
import {
|
|
21
|
-
AddImportOptions, bindImport, existingImportBinding, ExistingImportBinding, hasEsmSyntax, isCommonJs,
|
|
22
|
-
moduleNameOf, nameTaken, RebindImport, requiredModuleOfDeclaration
|
|
22
|
+
AddImportOptions, bindImport, bindingShape, existingImportBinding, ExistingImportBinding, hasEsmSyntax, isCommonJs,
|
|
23
|
+
memberName, moduleNameOf, nameTaken, RebindImport, requiredModuleOfDeclaration
|
|
23
24
|
} from "./add-import";
|
|
24
25
|
import {RemoveImport} from "./remove-import";
|
|
25
26
|
import {
|
|
@@ -229,7 +230,7 @@ function moduleObjectBindings(cu: JS.CompilationUnit): ModuleObjectBinding[] {
|
|
|
229
230
|
* never a type-only import for a value, which erases and would leave the reference unbound.
|
|
230
231
|
*/
|
|
231
232
|
function answersWholeModuleRequest(binding: ModuleObjectBinding, wantsNamespace: boolean, typeOnly: boolean): boolean {
|
|
232
|
-
return
|
|
233
|
+
return (typeOnly || !binding.typeOnly) &&
|
|
233
234
|
(binding.shape === "require" || binding.shape === (wantsNamespace ? "namespace" : "default"));
|
|
234
235
|
}
|
|
235
236
|
|
|
@@ -274,9 +275,10 @@ export function maybeBind(
|
|
|
274
275
|
}
|
|
275
276
|
}
|
|
276
277
|
|
|
277
|
-
if (isWholeModule && options.
|
|
278
|
-
|
|
279
|
-
//
|
|
278
|
+
if (isWholeModule && options.alias === undefined && options.preferredName === undefined &&
|
|
279
|
+
derivedBindingName(module) === undefined) {
|
|
280
|
+
// The module's last path segment is not a legal identifier, and the caller named none
|
|
281
|
+
// of its own — there is no name left to bind it to.
|
|
280
282
|
return undefined;
|
|
281
283
|
}
|
|
282
284
|
|
|
@@ -318,17 +320,13 @@ function sameCallees(a: readonly string[], b: readonly string[]): boolean {
|
|
|
318
320
|
return a.length === b.length && a.every((callee, i) => callee === b[i]);
|
|
319
321
|
}
|
|
320
322
|
|
|
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
323
|
/**
|
|
328
|
-
* Moves the binding for `from` to `to` and answers with the name it now carries
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
324
|
+
* Moves the binding for `from` to `to` and answers with the name it now carries, or `undefined`,
|
|
325
|
+
* changing nothing, where the move cannot be expressed safely. One call moves one binding.
|
|
326
|
+
* Named members of one import moved to one module, a call each, end up in one import of it,
|
|
327
|
+
* whether the calls share a visit or come from separate recipes.
|
|
328
|
+
* On an ES import, every type in the file naming what moved is renamed the way a parse of the result names it.
|
|
329
|
+
* It is renamed, not re-resolved, so a moved member keeps its old declaration's signature.
|
|
332
330
|
*/
|
|
333
331
|
export function maybeRebind(visitor: JavaScriptVisitor<any>, options: MaybeRebindOptions): string | undefined {
|
|
334
332
|
const amd = enclosingAmdBlock(visitor, options);
|
|
@@ -361,10 +359,9 @@ export function maybeRebind(visitor: JavaScriptVisitor<any>, options: MaybeRebin
|
|
|
361
359
|
if (existing === undefined) {
|
|
362
360
|
return undefined;
|
|
363
361
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (!existing.onlyMemberOfStatement && isCommonJs(cu)) {
|
|
362
|
+
// `RebindImport` replaces a statement it cannot rewrite in place, and a CommonJS file can gain no import.
|
|
363
|
+
if (isCommonJs(cu) &&
|
|
364
|
+
(!existing.onlyMemberOfStatement || bindingShape(options.from.member) !== bindingShape(options.to.member))) {
|
|
368
365
|
return undefined;
|
|
369
366
|
}
|
|
370
367
|
const boundName = rebindingName(visitor, cu, options, existing);
|
|
@@ -390,7 +387,8 @@ function rebindingName(
|
|
|
390
387
|
options: MaybeRebindOptions,
|
|
391
388
|
existing: ExistingImportBinding
|
|
392
389
|
): string | undefined {
|
|
393
|
-
const taken = (name: string) => nameTaken(name,
|
|
390
|
+
const taken = (name: string) => nameTaken(name, noNames, visitor) ||
|
|
391
|
+
(namesUsedIn(cu).has(name) && !reusesTargetImport(cu, name, options.to, existing));
|
|
394
392
|
const alias = options.to.alias;
|
|
395
393
|
if (alias !== undefined) {
|
|
396
394
|
return isBindableName(alias) && (alias === existing.localName || !taken(alias)) ? alias : undefined;
|
|
@@ -402,6 +400,50 @@ function rebindingName(
|
|
|
402
400
|
return taken(member) ? existing.localName : member;
|
|
403
401
|
}
|
|
404
402
|
|
|
403
|
+
const noNames: ReadonlySet<string> = new Set();
|
|
404
|
+
|
|
405
|
+
/** Whether the moved binding can merge into a value import of `to`'s named member that `name` binds. */
|
|
406
|
+
function reusesTargetImport(
|
|
407
|
+
cu: JS.CompilationUnit,
|
|
408
|
+
name: string,
|
|
409
|
+
to: MaybeRebindOptions["to"],
|
|
410
|
+
moved: ExistingImportBinding
|
|
411
|
+
): boolean {
|
|
412
|
+
const target = existingImportBinding(cu, to.module, to.member);
|
|
413
|
+
return target?.localName === name && bindingShape(to.member) === "named" && !target.typeOnly &&
|
|
414
|
+
!moved.typeOnly && onlyReferences(cu, name);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Whether every spelling of `name` outside the imports is a reference. Any binder spells its name
|
|
419
|
+
* in a declaring position, so each reference then reads the import, whatever kind of scope it is in.
|
|
420
|
+
*/
|
|
421
|
+
function onlyReferences(cu: JS.CompilationUnit, name: string): boolean {
|
|
422
|
+
let references = true;
|
|
423
|
+
const visit = (node: any, parent: Cursor): void => {
|
|
424
|
+
if (!references) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (Array.isArray(node)) {
|
|
428
|
+
node.forEach(child => visit(child, parent));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const cursor = new Cursor(node, parent);
|
|
432
|
+
if (node?.kind === J.Kind.Identifier && node.simpleName === name) {
|
|
433
|
+
references = isReference(cursor, node);
|
|
434
|
+
} else if (isTree(node) || node?.kind === J.Kind.RightPadded || node?.kind === J.Kind.LeftPadded ||
|
|
435
|
+
node?.kind === J.Kind.Container) {
|
|
436
|
+
Object.entries(node).forEach(([key, value]) => key !== 'markers' && visit(value, cursor));
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
const root = new Cursor(cu);
|
|
440
|
+
// An import or an `export {…}` binds nothing the name could collide with.
|
|
441
|
+
cu.statements
|
|
442
|
+
.filter(s => s.element?.kind !== JS.Kind.Import && s.element?.kind !== JS.Kind.ExportDeclaration)
|
|
443
|
+
.forEach(s => visit(s, root));
|
|
444
|
+
return references;
|
|
445
|
+
}
|
|
446
|
+
|
|
405
447
|
/**
|
|
406
448
|
* @deprecated Use {@link maybeUnbind} instead — this is a call-shape change, not a behaviour
|
|
407
449
|
* change: `maybeRemoveImport(v, module, member)` is `maybeUnbind(v, {module, member})`.
|
|
@@ -2347,15 +2347,15 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2347
2347
|
return true;
|
|
2348
2348
|
}
|
|
2349
2349
|
|
|
2350
|
-
/** Whether
|
|
2351
|
-
protected
|
|
2350
|
+
/** Whether a call declared on `declaringType` matches a call of the same name declared on any type. */
|
|
2351
|
+
protected declaresAnyCallee(_declaringType: Type.FullyQualified): boolean {
|
|
2352
2352
|
return false;
|
|
2353
2353
|
}
|
|
2354
2354
|
|
|
2355
2355
|
/**
|
|
2356
2356
|
* Override method invocation comparison to include type attribution checking.
|
|
2357
|
-
* When types match semantically,
|
|
2358
|
-
*
|
|
2357
|
+
* When types match semantically, a call without a receiver matches one with
|
|
2358
|
+
* (e.g., `isDate(x)` vs `util.isDate(x)`).
|
|
2359
2359
|
*/
|
|
2360
2360
|
override async visitMethodInvocation(method: J.MethodInvocation, other: J): Promise<J | undefined> {
|
|
2361
2361
|
if (other.kind !== J.Kind.MethodInvocation) {
|
|
@@ -2377,28 +2377,10 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2377
2377
|
// Check if we can skip name checking based on type attribution
|
|
2378
2378
|
// We can only skip the name check if both have method types AND they represent the SAME method
|
|
2379
2379
|
// (not just type-compatible methods, but the actual same function with same FQN)
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
const methodDeclaringType = method.methodType.declaringType;
|
|
2385
|
-
const otherDeclaringType = otherMethod.methodType.declaringType;
|
|
2386
|
-
|
|
2387
|
-
if (methodDeclaringType && otherDeclaringType &&
|
|
2388
|
-
Type.isFullyQualified(methodDeclaringType) && Type.isFullyQualified(otherDeclaringType)) {
|
|
2389
|
-
|
|
2390
|
-
const methodFQN = Type.FullyQualified.getFullyQualifiedName(methodDeclaringType as Type.FullyQualified);
|
|
2391
|
-
const otherFQN = Type.FullyQualified.getFullyQualifiedName(otherDeclaringType as Type.FullyQualified);
|
|
2392
|
-
|
|
2393
|
-
// Same module/class AND same method name in the type = same method (can be aliased)
|
|
2394
|
-
if (methodFQN === otherFQN && method.methodType.name === otherMethod.methodType.name) {
|
|
2395
|
-
canSkipNameCheck = true;
|
|
2396
|
-
}
|
|
2397
|
-
// If FQNs or method names don't match, we can't skip name check - fall through to name checking
|
|
2398
|
-
}
|
|
2399
|
-
// If one or both don't have fully qualified types, we can't safely skip name checking
|
|
2400
|
-
// Fall through to normal name comparison below
|
|
2401
|
-
}
|
|
2380
|
+
// Same module/class AND same method name in the type = same method (can be aliased)
|
|
2381
|
+
const canSkipNameCheck = !!method.methodType && !!otherMethod.methodType &&
|
|
2382
|
+
method.methodType.name === otherMethod.methodType.name &&
|
|
2383
|
+
this.sameFullyQualifiedName(method.methodType.declaringType, otherMethod.methodType.declaringType);
|
|
2402
2384
|
|
|
2403
2385
|
// Check names unless we determined we can skip based on type FQN matching
|
|
2404
2386
|
if (!canSkipNameCheck) {
|
|
@@ -2421,24 +2403,18 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2421
2403
|
const methodDeclaringType = method.methodType.declaringType;
|
|
2422
2404
|
const otherDeclaringType = otherMethod.methodType.declaringType;
|
|
2423
2405
|
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
// Different declaring types = different methods, even with same name
|
|
2431
|
-
if (methodFQN !== otherFQN) {
|
|
2432
|
-
return this.valueMismatch('methodType.declaringType');
|
|
2433
|
-
}
|
|
2406
|
+
// Different declaring types = different methods, even with same name
|
|
2407
|
+
if (Type.isFullyQualified(methodDeclaringType) && Type.isFullyQualified(otherDeclaringType) &&
|
|
2408
|
+
!this.sameFullyQualifiedName(methodDeclaringType, otherDeclaringType) &&
|
|
2409
|
+
!this.declaresAnyCallee(methodDeclaringType)) {
|
|
2410
|
+
return this.valueMismatch('methodType.declaringType');
|
|
2434
2411
|
}
|
|
2435
2412
|
}
|
|
2436
2413
|
}
|
|
2437
2414
|
|
|
2438
|
-
//
|
|
2439
|
-
//
|
|
2440
|
-
if (!canSkipNameCheck || this.
|
|
2441
|
-
// Types didn't provide a match - must compare receivers structurally
|
|
2415
|
+
// A written-out receiver still has to match. The method type proves which function
|
|
2416
|
+
// is called, not that the code reads the same.
|
|
2417
|
+
if (!canSkipNameCheck || method.select && !this.functionReceivers(method, otherMethod)) {
|
|
2442
2418
|
if ((method.select === undefined) !== (otherMethod.select === undefined)) {
|
|
2443
2419
|
return this.structuralMismatch('select');
|
|
2444
2420
|
}
|
|
@@ -2448,7 +2424,6 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2448
2424
|
if (!this.match) return method;
|
|
2449
2425
|
}
|
|
2450
2426
|
}
|
|
2451
|
-
// else: types matched, skip select comparison (allows namespace vs named imports)
|
|
2452
2427
|
|
|
2453
2428
|
// A pattern that spells out no type arguments says nothing about them, as with parentheses
|
|
2454
2429
|
if (method.typeParameters) {
|
|
@@ -2505,6 +2480,7 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2505
2480
|
* Override identifier comparison to include:
|
|
2506
2481
|
* 1. Type checking for field access
|
|
2507
2482
|
* 2. Semantic equivalence between `undefined` identifier and void expressions
|
|
2483
|
+
* 3. Names that differ but denote one declaration, as an import alias and its export do
|
|
2508
2484
|
*/
|
|
2509
2485
|
override async visitIdentifier(identifier: J.Identifier, other: J): Promise<J | undefined> {
|
|
2510
2486
|
// Check if this identifier is "undefined" and the other is a void expression
|
|
@@ -2519,9 +2495,8 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2519
2495
|
|
|
2520
2496
|
const otherIdentifier = other as J.Identifier;
|
|
2521
2497
|
|
|
2522
|
-
// Check name matches
|
|
2523
2498
|
if (identifier.simpleName !== otherIdentifier.simpleName) {
|
|
2524
|
-
return this.valueMismatch('simpleName');
|
|
2499
|
+
return this.denoteOneDeclaration(identifier, otherIdentifier) ? identifier : this.valueMismatch('simpleName');
|
|
2525
2500
|
}
|
|
2526
2501
|
|
|
2527
2502
|
// For identifiers with field types, check type attribution
|
|
@@ -2540,6 +2515,39 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2540
2515
|
return super.visitIdentifier(identifier, other);
|
|
2541
2516
|
}
|
|
2542
2517
|
|
|
2518
|
+
/**
|
|
2519
|
+
* Whether both calls have a bare function-valued receiver, such as a default import of a
|
|
2520
|
+
* callable module. The function type shell names no declaration, so the receivers are told
|
|
2521
|
+
* apart by the call alone.
|
|
2522
|
+
*/
|
|
2523
|
+
private functionReceivers(method: J.MethodInvocation, other: J.MethodInvocation): boolean {
|
|
2524
|
+
const isFunctionName = (select?: J.RightPadded<Expression>) =>
|
|
2525
|
+
select?.element.kind === J.Kind.Identifier &&
|
|
2526
|
+
!(select.element as J.Identifier).fieldType &&
|
|
2527
|
+
Type.isFunctionType((select.element as J.Identifier).type);
|
|
2528
|
+
return isFunctionName(method.select) && isFunctionName(other.select);
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
/** An import alias and its export denote one declaration, where a variable holding the same value does not. */
|
|
2532
|
+
private denoteOneDeclaration(identifier: J.Identifier, other: J.Identifier): boolean {
|
|
2533
|
+
if (identifier.fieldType || other.fieldType) {
|
|
2534
|
+
// An unresolved import has the placeholder name `unknown`, which identifies nothing
|
|
2535
|
+
return !!identifier.fieldType && !!other.fieldType &&
|
|
2536
|
+
identifier.fieldType.name === other.fieldType.name && identifier.fieldType.name !== 'unknown' &&
|
|
2537
|
+
this.sameFullyQualifiedName(identifier.fieldType.owner, other.fieldType.owner);
|
|
2538
|
+
}
|
|
2539
|
+
// Only a nominal type names a declaration. The function and object type shells are shared,
|
|
2540
|
+
// and a parameterized type is named after its base alone.
|
|
2541
|
+
return identifier.type?.kind === Type.Kind.Class && other.type?.kind === Type.Kind.Class &&
|
|
2542
|
+
!Type.isFunctionType(identifier.type) && !Type.isObjectType(identifier.type) &&
|
|
2543
|
+
this.sameFullyQualifiedName(identifier.type, other.type);
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
private sameFullyQualifiedName(type?: Type, other?: Type): boolean {
|
|
2547
|
+
return Type.isFullyQualified(type) && Type.isFullyQualified(other) &&
|
|
2548
|
+
Type.FullyQualified.getFullyQualifiedName(type) === Type.FullyQualified.getFullyQualifiedName(other);
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2543
2551
|
/**
|
|
2544
2552
|
* Override variable declarations comparison to handle lenient type matching.
|
|
2545
2553
|
* When lenientTypeMatching is true, patterns without typeExpression can match
|
|
@@ -2634,6 +2642,16 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2634
2642
|
);
|
|
2635
2643
|
if (!this.match) return methodDeclaration;
|
|
2636
2644
|
|
|
2645
|
+
// Visit name annotations
|
|
2646
|
+
await this.visitArrayProperty(
|
|
2647
|
+
methodDeclaration,
|
|
2648
|
+
'nameAnnotations',
|
|
2649
|
+
methodDeclaration.nameAnnotations,
|
|
2650
|
+
otherMethodDeclaration.nameAnnotations,
|
|
2651
|
+
async (ann1, ann2) => { await this.visit(ann1, ann2); }
|
|
2652
|
+
);
|
|
2653
|
+
if (!this.match) return methodDeclaration;
|
|
2654
|
+
|
|
2637
2655
|
// Visit type parameters if present
|
|
2638
2656
|
if (!!methodDeclaration.typeParameters !== !!otherMethodDeclaration.typeParameters) {
|
|
2639
2657
|
return this.structuralMismatch('typeParameters');
|
|
@@ -2742,3 +2760,71 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2742
2760
|
return literal;
|
|
2743
2761
|
}
|
|
2744
2762
|
}
|
|
2763
|
+
|
|
2764
|
+
/**
|
|
2765
|
+
* Whether two trees are the same code. Whitespace, comments, optional semicolons and trailing
|
|
2766
|
+
* commas, quote style (`'a'` and `"a"`), number spelling (`255` and `0xFF`) and type attribution
|
|
2767
|
+
* do not count. Markers count only where they are syntax that changes meaning, such as `?.`, a non-null
|
|
2768
|
+
* `!`, `function*` and `yield*`, so a marker a recipe attaches does not make trees differ.
|
|
2769
|
+
*/
|
|
2770
|
+
export async function isEqual(a: J, b: J): Promise<boolean> {
|
|
2771
|
+
return new CodeComparator().compare(a, b);
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
class CodeComparator extends JavaScriptComparatorVisitor {
|
|
2775
|
+
private readonly meaningfulMarkers: ReadonlySet<string> = new Set([
|
|
2776
|
+
JS.Markers.Optional, JS.Markers.NonNullAssertion, JS.Markers.Generator, JS.Markers.DelegatedYield
|
|
2777
|
+
]);
|
|
2778
|
+
|
|
2779
|
+
override async visit<R extends J>(j: Tree, p: J, parent?: Cursor): Promise<R | undefined> {
|
|
2780
|
+
if (this.match && this.meaningOf(j as J) !== this.meaningOf(p)) {
|
|
2781
|
+
return this.structuralMismatch('markers') as R;
|
|
2782
|
+
}
|
|
2783
|
+
return super.visit(j, p, parent);
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
protected override async visitProperty(j: any, other: any, propertyName?: string): Promise<any> {
|
|
2787
|
+
if (Type.isType(j) || Type.isType(other)) {
|
|
2788
|
+
return j;
|
|
2789
|
+
}
|
|
2790
|
+
return super.visitProperty(j, other, propertyName);
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
override async visitLiteral(literal: J.Literal, other: J): Promise<J | undefined> {
|
|
2794
|
+
if (!this.match) return literal;
|
|
2795
|
+
const otherLiteral = other as J.Literal;
|
|
2796
|
+
if (denotation(literal) !== denotation(otherLiteral)) {
|
|
2797
|
+
return this.valueMismatch('value', literal.value, otherLiteral.value);
|
|
2798
|
+
}
|
|
2799
|
+
return literal;
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
private meaningOf(tree: J): string {
|
|
2803
|
+
if (!tree.markers?.markers.length) {
|
|
2804
|
+
return '';
|
|
2805
|
+
}
|
|
2806
|
+
return tree.markers.markers
|
|
2807
|
+
.map(marker => marker.kind)
|
|
2808
|
+
.filter(kind => this.meaningfulMarkers.has(kind))
|
|
2809
|
+
.sort()
|
|
2810
|
+
.join(',');
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
/**
|
|
2815
|
+
* What a literal stands for, as a comparable key. A regex holds its source text as its value, so it
|
|
2816
|
+
* is told apart from a string by its slashes. A string with a `\uXXXX` surrogate escape holds no
|
|
2817
|
+
* decoded value, so its text between the quotes and its escapes stand in for one.
|
|
2818
|
+
*/
|
|
2819
|
+
function denotation(literal: J.Literal): string {
|
|
2820
|
+
const source = literal.valueSource ?? '';
|
|
2821
|
+
const primitive = literal.type?.keyword;
|
|
2822
|
+
if (source.startsWith('/')) {
|
|
2823
|
+
return JSON.stringify(['regex', source]);
|
|
2824
|
+
}
|
|
2825
|
+
if (literal.unicodeEscapes) {
|
|
2826
|
+
const quoted = source.startsWith("'") || source.startsWith('"');
|
|
2827
|
+
return JSON.stringify([primitive, quoted ? source.slice(1, -1) : source, literal.unicodeEscapes]);
|
|
2828
|
+
}
|
|
2829
|
+
return JSON.stringify([primitive, literal.value]);
|
|
2830
|
+
}
|
|
@@ -47,6 +47,15 @@ export const autoFormat = async <J2 extends J, P>(
|
|
|
47
47
|
): Promise<J2> =>
|
|
48
48
|
(await new AutoformatVisitor(stopAfter, styles).visit(j, p, parent) as J2);
|
|
49
49
|
|
|
50
|
+
/** `j` re-indented to the style of the file `parent` leads to, with its other whitespace as it is. */
|
|
51
|
+
export const autoIndent = async <J2 extends J, P>(j: J2, p: P, parent?: Cursor): Promise<J2> => {
|
|
52
|
+
const styleSource = (isSourceFile(j) ? j : parent?.firstEnclosing(isSourceFile)) ?? j;
|
|
53
|
+
const tabsAndIndents = getStyle(StyleKind.TabsAndIndentsStyle, styleSource) as TabsAndIndentsStyle;
|
|
54
|
+
const prettierStyle = getPrettierStyle(j, parent);
|
|
55
|
+
const style = prettierStyle ? tabsAndIndentsFrom(prettierStyle, tabsAndIndents) : tabsAndIndents;
|
|
56
|
+
return await new TabsAndIndentsVisitor(style).visit(j, p, parent) as J2;
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
/**
|
|
51
60
|
* Formats JavaScript/TypeScript code using a comprehensive set of formatting rules.
|
|
52
61
|
*
|
|
@@ -179,15 +179,42 @@ export class TabsAndIndentsVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
179
179
|
// TemplateExpressionSpan: reset indent context - template literal content determines its own indentation
|
|
180
180
|
// The expression inside ${...} should be indented based on where it appears in the template, not outer code
|
|
181
181
|
if (tree.kind === JS.Kind.TemplateExpressionSpan) {
|
|
182
|
-
//
|
|
182
|
+
// Whitespace after ${ belongs to the span, not its child expression.
|
|
183
183
|
const span = tree as JS.TemplateExpression.Span;
|
|
184
|
-
const prefix = span.
|
|
184
|
+
const prefix = lastWhitespace(span.prefix);
|
|
185
185
|
const lastNewline = prefix.lastIndexOf("\n");
|
|
186
186
|
if (lastNewline >= 0) {
|
|
187
|
-
return prefix.
|
|
187
|
+
return this.indentWidth(prefix.substring(lastNewline + 1));
|
|
188
|
+
}
|
|
189
|
+
const template = this.cursor.parentTree()?.value as JS.TemplateExpression | undefined;
|
|
190
|
+
if (template?.kind === JS.Kind.TemplateExpression) {
|
|
191
|
+
const index = template.spans.findIndex(s => s.element.id === span.id);
|
|
192
|
+
const preceding = index > 0 ? template.spans[index - 1].element.tail : template.head;
|
|
193
|
+
const text = preceding.valueSource ?? "";
|
|
194
|
+
const newline = text.lastIndexOf("\n");
|
|
195
|
+
if (newline >= 0) {
|
|
196
|
+
// A span on a literal's own line is anchored to that line's indentation.
|
|
197
|
+
const indent = text.substring(newline + 1).match(/^[ \t]*/)?.[0] ?? "";
|
|
198
|
+
return this.indentWidth(indent);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// An inline interpolation continues the surrounding code line, not column zero.
|
|
202
|
+
for (let c = this.cursor.parent; c; c = c.parent) {
|
|
203
|
+
const parent = c.value as J;
|
|
204
|
+
if (parent?.prefix && spaceContainsNewline(parent.prefix)) {
|
|
205
|
+
return (c.messages.get("indentContext") as IndentContext | undefined)?.[0] ?? parentMyIndent;
|
|
206
|
+
}
|
|
188
207
|
}
|
|
189
208
|
return 0;
|
|
190
209
|
}
|
|
210
|
+
// The tail prefix is the whitespace before }, not another continuation of the expression.
|
|
211
|
+
const parentTree = this.cursor.parentTree()?.value;
|
|
212
|
+
if (parentTree?.kind === JS.Kind.TemplateExpressionSpan &&
|
|
213
|
+
(parentTree as JS.TemplateExpression.Span).tail === tree) {
|
|
214
|
+
const prefix = lastWhitespace(tree.prefix);
|
|
215
|
+
const newline = prefix.lastIndexOf("\n");
|
|
216
|
+
return newline < 0 ? parentMyIndent : this.indentWidth(prefix.substring(newline + 1));
|
|
217
|
+
}
|
|
191
218
|
if (tree.kind === J.Kind.IfElse || parentIndentKind === 'align') {
|
|
192
219
|
return parentMyIndent;
|
|
193
220
|
}
|