@openrewrite/rewrite 8.91.0 → 8.91.1
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/java/formatting-utils.d.ts +8 -5
- package/dist/java/formatting-utils.d.ts.map +1 -1
- package/dist/java/formatting-utils.js +14 -10
- package/dist/java/formatting-utils.js.map +1 -1
- package/dist/java/type.d.ts +2 -0
- package/dist/java/type.d.ts.map +1 -1
- package/dist/java/type.js +5 -0
- package/dist/java/type.js.map +1 -1
- package/dist/javascript/add-import.d.ts +39 -20
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +513 -243
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/assertions.d.ts +7 -0
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +31 -5
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/comparator.d.ts +5 -0
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +34 -9
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +8 -9
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/format/tabs-and-indents-visitor.d.ts +2 -0
- package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -1
- package/dist/javascript/format/tabs-and-indents-visitor.js +17 -11
- package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
- package/dist/javascript/node-resolution-result.d.ts +19 -1
- package/dist/javascript/node-resolution-result.d.ts.map +1 -1
- package/dist/javascript/node-resolution-result.js +37 -2
- package/dist/javascript/node-resolution-result.js.map +1 -1
- package/dist/javascript/package-exported-types.d.ts.map +1 -1
- package/dist/javascript/package-exported-types.js +56 -72
- package/dist/javascript/package-exported-types.js.map +1 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +10 -3
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/recipes/change-import.d.ts.map +1 -1
- package/dist/javascript/recipes/change-import.js +4 -8
- package/dist/javascript/recipes/change-import.js.map +1 -1
- package/dist/javascript/style.d.ts.map +1 -1
- package/dist/javascript/style.js +3 -10
- package/dist/javascript/style.js.map +1 -1
- package/dist/javascript/templating/bindings.d.ts +19 -0
- package/dist/javascript/templating/bindings.d.ts.map +1 -0
- package/dist/javascript/templating/bindings.js +121 -0
- package/dist/javascript/templating/bindings.js.map +1 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +5 -6
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +18 -7
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +70 -42
- 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 +13 -10
- 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 +14 -5
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/rewrite.d.ts.map +1 -1
- package/dist/javascript/templating/rewrite.js +14 -18
- package/dist/javascript/templating/rewrite.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +8 -0
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +43 -3
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +47 -1
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +5 -0
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +17 -0
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +1 -0
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +52 -2
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/style.d.ts +2 -0
- package/dist/style.d.ts.map +1 -1
- package/dist/style.js +5 -0
- package/dist/style.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +12 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +40 -3
- package/dist/test/rewrite-test.js.map +1 -1
- package/package.json +1 -1
- package/src/java/formatting-utils.ts +14 -10
- package/src/java/type.ts +5 -0
- package/src/javascript/add-import.ts +590 -230
- package/src/javascript/assertions.ts +33 -5
- package/src/javascript/comparator.ts +39 -10
- package/src/javascript/format/format.ts +9 -10
- package/src/javascript/format/tabs-and-indents-visitor.ts +18 -12
- package/src/javascript/node-resolution-result.ts +59 -4
- package/src/javascript/package-exported-types.ts +60 -74
- package/src/javascript/parser.ts +10 -3
- package/src/javascript/recipes/change-import.ts +4 -7
- package/src/javascript/style.ts +3 -11
- package/src/javascript/templating/bindings.ts +129 -0
- package/src/javascript/templating/comparator.ts +5 -6
- package/src/javascript/templating/engine.ts +82 -44
- package/src/javascript/templating/index.ts +2 -0
- package/src/javascript/templating/pattern.ts +16 -15
- package/src/javascript/templating/placeholder-replacement.ts +16 -5
- package/src/javascript/templating/rewrite.ts +16 -18
- package/src/javascript/templating/template.ts +45 -3
- package/src/javascript/templating/types.ts +53 -1
- package/src/javascript/templating/utils.ts +17 -0
- package/src/javascript/type-mapping.ts +54 -2
- package/src/style.ts +5 -0
- package/src/test/rewrite-test.ts +61 -3
|
@@ -34,6 +34,19 @@ import {Marker, replaceMarkerByKind} from "../markers";
|
|
|
34
34
|
*/
|
|
35
35
|
export const sourceFileCache: Map<string, ts.SourceFile> = new Map();
|
|
36
36
|
|
|
37
|
+
// Pooled so a spec reuses the program the previous spec left behind; see `JavaScriptParser.parse`.
|
|
38
|
+
const parserPool: Map<string, JavaScriptParser> = new Map();
|
|
39
|
+
|
|
40
|
+
function pooledParser(relativeTo?: string): JavaScriptParser {
|
|
41
|
+
const key = relativeTo ?? "";
|
|
42
|
+
let parser = parserPool.get(key);
|
|
43
|
+
if (!parser) {
|
|
44
|
+
parser = new JavaScriptParser({sourceFileCache, relativeTo});
|
|
45
|
+
parserPool.set(key, parser);
|
|
46
|
+
}
|
|
47
|
+
return parser;
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
// Automatically enable sourceFileCache for template parsing in tests
|
|
38
51
|
setTemplateSourceFileCache(sourceFileCache);
|
|
39
52
|
|
|
@@ -211,7 +224,7 @@ export async function* npm(relativeTo: string, ...sourceSpecs: SourceSpec<any>[]
|
|
|
211
224
|
|
|
212
225
|
yield {
|
|
213
226
|
...spec,
|
|
214
|
-
parser: () =>
|
|
227
|
+
parser: () => pooledParser(relativeTo),
|
|
215
228
|
// Add style marker before recipe runs if available
|
|
216
229
|
// Compose with existing beforeRecipe if present
|
|
217
230
|
beforeRecipe: styleMarker ? (sf: JS.CompilationUnit) => {
|
|
@@ -243,13 +256,28 @@ export function packageLockJson(before: string, after?: AfterRecipeText): Source
|
|
|
243
256
|
};
|
|
244
257
|
}
|
|
245
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Gives a source file the style its own text is written in. A spec that formats its output is
|
|
261
|
+
* measured against the built-in defaults unless it opts in with
|
|
262
|
+
* `{...typescript(src), beforeRecipe: withDetectedStyle}`; a file parsed through {@link npm} is
|
|
263
|
+
* sampled alongside its siblings and needs no opt-in.
|
|
264
|
+
*/
|
|
265
|
+
export async function withDetectedStyle(sourceFile: JS.CompilationUnit): Promise<JS.CompilationUnit> {
|
|
266
|
+
const detector = Autodetect.detector();
|
|
267
|
+
await detector.sample(sourceFile);
|
|
268
|
+
const detected = detector.build();
|
|
269
|
+
return produce(sourceFile, draft => {
|
|
270
|
+
draft.markers = replaceMarkerByKind(draft.markers, detected);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
246
274
|
export function javascript(before: string | null, after?: AfterRecipeText): SourceSpec<JS.CompilationUnit> {
|
|
247
275
|
return {
|
|
248
276
|
kind: JS.Kind.CompilationUnit,
|
|
249
277
|
before: before,
|
|
250
278
|
after: dedentAfter(after),
|
|
251
279
|
ext: 'js',
|
|
252
|
-
parser: ctx =>
|
|
280
|
+
parser: ctx => pooledParser()
|
|
253
281
|
};
|
|
254
282
|
}
|
|
255
283
|
|
|
@@ -259,7 +287,7 @@ export function typescript(before: string | null, after?: AfterRecipeText): Sour
|
|
|
259
287
|
before: before,
|
|
260
288
|
after: dedentAfter(after),
|
|
261
289
|
ext: 'ts',
|
|
262
|
-
parser: () =>
|
|
290
|
+
parser: () => pooledParser()
|
|
263
291
|
};
|
|
264
292
|
}
|
|
265
293
|
|
|
@@ -269,7 +297,7 @@ export function tsx(before: string | null, after?: AfterRecipeText): SourceSpec<
|
|
|
269
297
|
before: before,
|
|
270
298
|
after: dedentAfter(after),
|
|
271
299
|
ext: 'tsx',
|
|
272
|
-
parser: () =>
|
|
300
|
+
parser: () => pooledParser()
|
|
273
301
|
};
|
|
274
302
|
}
|
|
275
303
|
|
|
@@ -279,6 +307,6 @@ export function jsx(before: string | null, after?: AfterRecipeText): SourceSpec<
|
|
|
279
307
|
before: before,
|
|
280
308
|
after: dedentAfter(after),
|
|
281
309
|
ext: 'jsx',
|
|
282
|
-
parser: () =>
|
|
310
|
+
parser: () => pooledParser()
|
|
283
311
|
};
|
|
284
312
|
}
|
|
@@ -2410,11 +2410,6 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2410
2410
|
}
|
|
2411
2411
|
}
|
|
2412
2412
|
|
|
2413
|
-
// If neither has type, use structural comparison
|
|
2414
|
-
if (!method.methodType && !otherMethod.methodType) {
|
|
2415
|
-
return super.visitMethodInvocation(method, other);
|
|
2416
|
-
}
|
|
2417
|
-
|
|
2418
2413
|
// If both have types with FQ declaring types, verify they're compatible
|
|
2419
2414
|
// (This prevents matching completely different methods like util.isArray vs util.isBoolean)
|
|
2420
2415
|
if (method.methodType && otherMethod.methodType) {
|
|
@@ -2450,12 +2445,12 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2450
2445
|
}
|
|
2451
2446
|
// else: types matched, skip select comparison (allows namespace vs named imports)
|
|
2452
2447
|
|
|
2453
|
-
//
|
|
2454
|
-
if (
|
|
2455
|
-
|
|
2456
|
-
|
|
2448
|
+
// A pattern that spells out no type arguments says nothing about them, as with parentheses
|
|
2449
|
+
if (method.typeParameters) {
|
|
2450
|
+
if (!otherMethod.typeParameters) {
|
|
2451
|
+
return this.structuralMismatch('typeParameters');
|
|
2452
|
+
}
|
|
2457
2453
|
|
|
2458
|
-
if (method.typeParameters && otherMethod.typeParameters) {
|
|
2459
2454
|
await this.visitContainerProperty('typeParameters', method.typeParameters, otherMethod.typeParameters);
|
|
2460
2455
|
if (!this.match) return method;
|
|
2461
2456
|
}
|
|
@@ -2475,6 +2470,32 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2475
2470
|
return method;
|
|
2476
2471
|
}
|
|
2477
2472
|
|
|
2473
|
+
/**
|
|
2474
|
+
* A call whose callee is not a plain identifier, such as `getFn()(x)`, `arr[0](x)` or `fn?.(x)`.
|
|
2475
|
+
* Type arguments constrain it under the same rule as `visitMethodInvocation`.
|
|
2476
|
+
*/
|
|
2477
|
+
override async visitFunctionCall(functionCall: JS.FunctionCall, other: J): Promise<J | undefined> {
|
|
2478
|
+
if (other.kind !== JS.Kind.FunctionCall) {
|
|
2479
|
+
return this.kindMismatch();
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
const otherFunctionCall = other as JS.FunctionCall;
|
|
2483
|
+
if (functionCall.typeParameters) {
|
|
2484
|
+
if (!otherFunctionCall.typeParameters) {
|
|
2485
|
+
return this.structuralMismatch('typeParameters');
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
await this.visitContainerProperty('typeParameters', functionCall.typeParameters, otherFunctionCall.typeParameters);
|
|
2489
|
+
if (!this.match) return functionCall;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
// Type arguments are settled, so they come off both sides and the rest compares property by property
|
|
2493
|
+
const patternCall: JS.FunctionCall = {...functionCall, typeParameters: undefined};
|
|
2494
|
+
const targetCall: JS.FunctionCall = {...otherFunctionCall, typeParameters: undefined};
|
|
2495
|
+
await super.visitFunctionCall(patternCall, targetCall);
|
|
2496
|
+
return functionCall;
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2478
2499
|
/**
|
|
2479
2500
|
* Override identifier comparison to include:
|
|
2480
2501
|
* 1. Type checking for field access
|
|
@@ -2520,6 +2541,10 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2520
2541
|
* code with typeExpression.
|
|
2521
2542
|
*/
|
|
2522
2543
|
override async visitVariableDeclarations(variableDeclarations: J.VariableDeclarations, other: J): Promise<J | undefined> {
|
|
2544
|
+
if (other.kind !== J.Kind.VariableDeclarations) {
|
|
2545
|
+
return this.kindMismatch();
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2523
2548
|
const otherVariableDeclarations = other as J.VariableDeclarations;
|
|
2524
2549
|
|
|
2525
2550
|
// Visit leading annotations
|
|
@@ -2578,6 +2603,10 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
|
|
|
2578
2603
|
* code with returnTypeExpression.
|
|
2579
2604
|
*/
|
|
2580
2605
|
override async visitMethodDeclaration(methodDeclaration: J.MethodDeclaration, other: J): Promise<J | undefined> {
|
|
2606
|
+
if (other.kind !== J.Kind.MethodDeclaration) {
|
|
2607
|
+
return this.kindMismatch();
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2581
2610
|
const otherMethodDeclaration = other as J.MethodDeclaration;
|
|
2582
2611
|
|
|
2583
2612
|
// Visit leading annotations
|
|
@@ -17,7 +17,7 @@ import {JS} from "../tree";
|
|
|
17
17
|
import {JavaScriptVisitor} from "../visitor";
|
|
18
18
|
import {Comment, J, lastWhitespace, replaceLastWhitespace, Statement} from "../../java";
|
|
19
19
|
import {create as produce, Draft} from "mutative";
|
|
20
|
-
import {Cursor, isScope, Tree} from "../../tree";
|
|
20
|
+
import {Cursor, isScope, isSourceFile, Tree} from "../../tree";
|
|
21
21
|
import {BlankLinesStyle, getStyle, SpacesStyle, StyleKind, TabsAndIndentsStyle, WrappingAndBracesStyle} from "../style";
|
|
22
22
|
import {NamedStyles} from "../../style";
|
|
23
23
|
import {produceAsync} from "../../visitor";
|
|
@@ -74,13 +74,16 @@ export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
74
74
|
return applyPrettierFormatting(tree as R, prettierStyle, p, cursor, this.stopAfter);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// Style markers live on the source file, which is `tree` itself only when a whole file is being formatted
|
|
78
|
+
const styleSource = (isSourceFile(tree) ? tree : cursor?.firstEnclosing(isSourceFile)) ?? tree;
|
|
79
|
+
|
|
77
80
|
const visitors = [
|
|
78
81
|
new NormalizeWhitespaceVisitor(this.stopAfter),
|
|
79
82
|
new MinimumViableSpacingVisitor(this.stopAfter),
|
|
80
|
-
new BlankLinesVisitor(getStyle(StyleKind.BlankLinesStyle,
|
|
81
|
-
new WrappingAndBracesVisitor(getStyle(StyleKind.WrappingAndBracesStyle,
|
|
82
|
-
new SpacesVisitor(getStyle(StyleKind.SpacesStyle,
|
|
83
|
-
new TabsAndIndentsVisitor(getStyle(StyleKind.TabsAndIndentsStyle,
|
|
83
|
+
new BlankLinesVisitor(getStyle(StyleKind.BlankLinesStyle, styleSource, this.styles) as BlankLinesStyle, this.stopAfter),
|
|
84
|
+
new WrappingAndBracesVisitor(getStyle(StyleKind.WrappingAndBracesStyle, styleSource, this.styles) as WrappingAndBracesStyle, this.stopAfter),
|
|
85
|
+
new SpacesVisitor(getStyle(StyleKind.SpacesStyle, styleSource, this.styles) as SpacesStyle, this.stopAfter),
|
|
86
|
+
new TabsAndIndentsVisitor(getStyle(StyleKind.TabsAndIndentsStyle, styleSource, this.styles) as TabsAndIndentsStyle, this.stopAfter),
|
|
84
87
|
]
|
|
85
88
|
|
|
86
89
|
let t: R | undefined = tree as R;
|
|
@@ -227,11 +230,7 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
227
230
|
// Apply beforeComma rule to all elements except the last
|
|
228
231
|
// (last element's after is before closing bracket, not a comma)
|
|
229
232
|
for (let i = 0; i < draft.elements.length - 1; i++) {
|
|
230
|
-
|
|
231
|
-
// Preserve newlines - only adjust when on same line
|
|
232
|
-
if (!afterWs.includes("\n")) {
|
|
233
|
-
draft.elements[i].after.whitespace = this.style.other.beforeComma ? " " : "";
|
|
234
|
-
}
|
|
233
|
+
this.spaceAfterRightPaddedDraft(draft.elements[i], this.style.other.beforeComma);
|
|
235
234
|
}
|
|
236
235
|
}
|
|
237
236
|
if (draft.elements.length > 1) {
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
lastWhitespace,
|
|
23
23
|
normalizeSpaceIndent,
|
|
24
24
|
replaceIndentAfterLastNewline,
|
|
25
|
-
replaceLastWhitespace,
|
|
26
25
|
spaceContainsNewline,
|
|
27
26
|
stripLeadingIndent
|
|
28
27
|
} from "../../java";
|
|
@@ -45,6 +44,11 @@ export class TabsAndIndentsVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
45
44
|
this.useTabCharacter = this.tabsAndIndentsStyle.useTabCharacter;
|
|
46
45
|
}
|
|
47
46
|
|
|
47
|
+
/** Width of existing indent whitespace in the unit {@link indentString} takes, where a tab is one indent. */
|
|
48
|
+
private indentWidth(indent: string): number {
|
|
49
|
+
return [...indent].reduce((width, ch) => width + (ch === "\t" ? this.indentSize : 1), 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
private indentString(indent: number): string {
|
|
49
53
|
if (this.useTabCharacter) {
|
|
50
54
|
return "\t".repeat(Math.floor(indent / this.indentSize));
|
|
@@ -395,12 +399,12 @@ export class TabsAndIndentsVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
395
399
|
}
|
|
396
400
|
|
|
397
401
|
private normalizeBlockEnd(block: J.Block, myIndent: number): J.Block {
|
|
398
|
-
|
|
399
|
-
if (!effectiveLastWs.includes("\n")) {
|
|
402
|
+
if (!spaceContainsNewline(block.end)) {
|
|
400
403
|
return block;
|
|
401
404
|
}
|
|
402
|
-
|
|
403
|
-
|
|
405
|
+
const end = normalizeSpaceIndent(block.end, this.indentString(myIndent), this.indentString(myIndent + this.indentSize));
|
|
406
|
+
return end === block.end ? block : produce(block, draft => {
|
|
407
|
+
draft.end = end;
|
|
404
408
|
});
|
|
405
409
|
}
|
|
406
410
|
|
|
@@ -440,12 +444,14 @@ export class TabsAndIndentsVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
440
444
|
// Normalize the last element's after whitespace (closing delimiter like `)`)
|
|
441
445
|
// The closing delimiter should align with the parent's indent level
|
|
442
446
|
if (container.elements.length > 0) {
|
|
443
|
-
const
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
447
|
+
const last = container.elements[container.elements.length - 1];
|
|
448
|
+
if (spaceContainsNewline(last.after)) {
|
|
449
|
+
const after = normalizeSpaceIndent(last.after, this.indentString(parentIndent), this.indentString(parentIndent + this.indentSize));
|
|
450
|
+
if (after !== last.after) {
|
|
451
|
+
return produce(container, draft => {
|
|
452
|
+
draft.elements[draft.elements.length - 1].after = after;
|
|
453
|
+
});
|
|
454
|
+
}
|
|
449
455
|
}
|
|
450
456
|
}
|
|
451
457
|
|
|
@@ -646,7 +652,7 @@ export class TabsAndIndentsVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
646
652
|
const idx = ws.lastIndexOf('\n');
|
|
647
653
|
if (idx !== -1) {
|
|
648
654
|
anchorCursor = c;
|
|
649
|
-
anchorIndent = ws.
|
|
655
|
+
anchorIndent = this.indentWidth(ws.substring(idx + 1));
|
|
650
656
|
}
|
|
651
657
|
}
|
|
652
658
|
|
|
@@ -60,13 +60,65 @@ export interface PackageLockEntry {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
63
|
+
* Entry in a legacy npm lockfileVersion 1 `dependencies` tree (npm 5/6), with its
|
|
64
|
+
* requirements under `requires` and nested conflicting versions under `dependencies`.
|
|
65
|
+
*/
|
|
66
|
+
export interface LockfileV1Entry {
|
|
67
|
+
readonly version?: string;
|
|
68
|
+
readonly resolved?: string;
|
|
69
|
+
readonly integrity?: string;
|
|
70
|
+
readonly license?: string | string[] | { type?: string; url?: string };
|
|
71
|
+
readonly engines?: Record<string, string> | string[];
|
|
72
|
+
readonly requires?: Record<string, string>;
|
|
73
|
+
readonly dependencies?: Record<string, LockfileV1Entry>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parsed package-lock.json content structure. The `packages` map is the modern
|
|
78
|
+
* (lockfileVersion 2/3) shape; `dependencies` is the legacy lockfileVersion 1 tree.
|
|
64
79
|
*/
|
|
65
80
|
export interface PackageLockContent {
|
|
66
81
|
readonly name?: string;
|
|
67
82
|
readonly version?: string;
|
|
68
83
|
readonly lockfileVersion?: number;
|
|
69
84
|
readonly packages?: Record<string, PackageLockEntry>;
|
|
85
|
+
readonly dependencies?: Record<string, LockfileV1Entry>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Converts a legacy npm lockfileVersion 1 `dependencies` tree into the modern
|
|
90
|
+
* `packages` map keyed by node_modules path, so the resolver can walk transitive
|
|
91
|
+
* dependencies the same way it does for lockfileVersion 2/3 files. Returns
|
|
92
|
+
* undefined when the tree is absent or empty.
|
|
93
|
+
*/
|
|
94
|
+
function convertV1DependencyTree(
|
|
95
|
+
tree: Record<string, LockfileV1Entry> | undefined
|
|
96
|
+
): Record<string, PackageLockEntry> | undefined {
|
|
97
|
+
if (!tree || Object.keys(tree).length === 0) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const packages: Record<string, PackageLockEntry> = {};
|
|
101
|
+
|
|
102
|
+
function walk(deps: Record<string, LockfileV1Entry>, pathPrefix: string): void {
|
|
103
|
+
for (const [name, entry] of Object.entries(deps)) {
|
|
104
|
+
const pkgPath = `${pathPrefix}node_modules/${name}`;
|
|
105
|
+
packages[pkgPath] = {
|
|
106
|
+
version: entry.version,
|
|
107
|
+
resolved: entry.resolved,
|
|
108
|
+
integrity: entry.integrity,
|
|
109
|
+
license: entry.license,
|
|
110
|
+
engines: entry.engines,
|
|
111
|
+
// v1 folds all declared deps into `requires`; the resolver reads `dependencies`.
|
|
112
|
+
dependencies: entry.requires,
|
|
113
|
+
};
|
|
114
|
+
if (entry.dependencies) {
|
|
115
|
+
walk(entry.dependencies, `${pkgPath}/`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
walk(tree, "");
|
|
121
|
+
return packages;
|
|
70
122
|
}
|
|
71
123
|
|
|
72
124
|
/**
|
|
@@ -524,9 +576,12 @@ export function createNodeResolutionResultMarker(
|
|
|
524
576
|
function parseResolutions(
|
|
525
577
|
lockContent: PackageLockContent
|
|
526
578
|
): ResolvedDependency[] {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const packages = lockContent.packages
|
|
579
|
+
// Prefer the modern `packages` map; fall back to converting a legacy
|
|
580
|
+
// lockfileVersion 1 `dependencies` tree when no `packages` map is present.
|
|
581
|
+
const packages = lockContent.packages && Object.keys(lockContent.packages).length > 0
|
|
582
|
+
? lockContent.packages
|
|
583
|
+
: convertV1DependencyTree(lockContent.dependencies);
|
|
584
|
+
if (!packages) return [];
|
|
530
585
|
|
|
531
586
|
// First pass: Create all ResolvedDependency placeholders and build path map
|
|
532
587
|
const packageInfos: Array<{ path: string; name: string; version: string; entry: PackageLockEntry }> = [];
|
|
@@ -18,6 +18,8 @@ import * as path from "path";
|
|
|
18
18
|
import {Type} from "../java";
|
|
19
19
|
import {JavaScriptTypeMapping} from "./type-mapping";
|
|
20
20
|
|
|
21
|
+
const TYPESCRIPT_FILE = /\.[cm]?tsx?$/;
|
|
22
|
+
|
|
21
23
|
// Free functions are not exported-type entries; only nominal types (and value modules, which are
|
|
22
24
|
// descended into for nested types) qualify.
|
|
23
25
|
const TYPE_SYMBOL_FLAGS =
|
|
@@ -38,21 +40,13 @@ const TYPE_SYMBOL_FLAGS =
|
|
|
38
40
|
* @param references node_modules roots / sibling packages used only for symbol resolution
|
|
39
41
|
*/
|
|
40
42
|
export function exportedTypes(ownArtifacts: string[], references: string[]): Type.FullyQualified[] {
|
|
41
|
-
const entries: string[] = [];
|
|
42
|
-
for (const artifact of ownArtifacts) {
|
|
43
|
-
const entry = declarationFileEntry(artifact) ?? resolvePackageEntry(artifact);
|
|
44
|
-
if (entry) {
|
|
45
|
-
entries.push(path.normalize(entry));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (entries.length === 0) {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
43
|
const compilerOptions: ts.CompilerOptions = {
|
|
53
44
|
target: ts.ScriptTarget.Latest,
|
|
54
|
-
module
|
|
55
|
-
|
|
45
|
+
// Mirrors JavaScriptParser's module resolution, so a dependency's own imports resolve here
|
|
46
|
+
// as they do when its consumers are parsed; the rationale for each option lives there.
|
|
47
|
+
module: ts.ModuleKind.Preserve,
|
|
48
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
49
|
+
customConditions: ["node"],
|
|
56
50
|
noEmit: true,
|
|
57
51
|
allowJs: true,
|
|
58
52
|
checkJs: false,
|
|
@@ -80,6 +74,17 @@ export function exportedTypes(ownArtifacts: string[], references: string[]): Typ
|
|
|
80
74
|
return resolved;
|
|
81
75
|
});
|
|
82
76
|
|
|
77
|
+
const entries: string[] = [];
|
|
78
|
+
for (const artifact of ownArtifacts) {
|
|
79
|
+
const entry = declarationFileEntry(artifact) ?? resolvePackageEntry(artifact, compilerOptions, host);
|
|
80
|
+
if (entry) {
|
|
81
|
+
entries.push(path.normalize(entry));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (entries.length === 0) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
|
|
83
88
|
const program = ts.createProgram(entries, compilerOptions, host);
|
|
84
89
|
const checker = program.getTypeChecker();
|
|
85
90
|
const typeMapping = new JavaScriptTypeMapping(checker);
|
|
@@ -156,7 +161,7 @@ function arity(cls: Type.Class): number {
|
|
|
156
161
|
|
|
157
162
|
/** A TypeScript declaration/source FILE (e.g. a `lib.*.d.ts` of ambient globals), used directly as a program entry. */
|
|
158
163
|
function declarationFileEntry(artifact: string): string | undefined {
|
|
159
|
-
return ts.sys.fileExists(artifact) &&
|
|
164
|
+
return ts.sys.fileExists(artifact) && TYPESCRIPT_FILE.test(artifact) ? artifact : undefined;
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
/** Top-level ambient type declarations of a script (non-module) file — a `lib.*.d.ts`'s globals; namespaces descended for nested types. */
|
|
@@ -186,30 +191,34 @@ function globalTypeSymbols(checker: ts.TypeChecker, sourceFile: ts.SourceFile):
|
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* else a top-level `index.d.ts` (then `index.ts`).
|
|
194
|
+
* A package directory's declaration entry, found by resolving the package's own name from inside
|
|
195
|
+
* it so `types`, `exports` conditions and `typesVersions` all apply; else the declared `types`
|
|
196
|
+
* file; else a top-level `index.d.ts` (then `index.ts`). Reaching none of them means the package
|
|
197
|
+
* ships no types, which leaves the caller to fall back to its DefinitelyTyped package.
|
|
192
198
|
*/
|
|
193
|
-
function resolvePackageEntry(dir: string): string | undefined {
|
|
194
|
-
const
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
:
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
199
|
+
function resolvePackageEntry(dir: string, options: ts.CompilerOptions, host: ts.ModuleResolutionHost): string | undefined {
|
|
200
|
+
const manifest = packageManifest(dir);
|
|
201
|
+
if (typeof manifest?.name === "string") {
|
|
202
|
+
const conditions = options.customConditions ?? [];
|
|
203
|
+
// A package's declarations count under whichever condition carries them, since no
|
|
204
|
+
// consumer's import mode is choosing between `import` and `require` here.
|
|
205
|
+
for (const attempt of [conditions, [...conditions, "require"]]) {
|
|
206
|
+
const resolved = ts.resolveModuleName(manifest.name, path.join(dir, "__entry__.ts"),
|
|
207
|
+
{...options, customConditions: attempt}, host).resolvedModule?.resolvedFileName;
|
|
208
|
+
if (resolved && TYPESCRIPT_FILE.test(resolved) && containedIn(dir, resolved)) {
|
|
209
|
+
return resolved;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// An `exports` map without a `types` condition takes resolution to the runtime entry and stops
|
|
214
|
+
// it there, while `types` still names the declarations.
|
|
215
|
+
const declared = manifest?.types ?? manifest?.typings;
|
|
216
|
+
if (typeof declared === "string") {
|
|
217
|
+
const resolved = path.resolve(dir, declared);
|
|
218
|
+
for (const candidate of [resolved, resolved + ".d.ts"]) {
|
|
219
|
+
if (ts.sys.fileExists(candidate)) {
|
|
220
|
+
return candidate;
|
|
210
221
|
}
|
|
211
|
-
} catch {
|
|
212
|
-
// Malformed package.json; fall through to conventional entry points.
|
|
213
222
|
}
|
|
214
223
|
}
|
|
215
224
|
for (const candidate of ["index.d.ts", "index.ts", "index.tsx"]) {
|
|
@@ -221,49 +230,26 @@ function resolvePackageEntry(dir: string): string | undefined {
|
|
|
221
230
|
return undefined;
|
|
222
231
|
}
|
|
223
232
|
|
|
224
|
-
/** The declaration file for the package's `.` subpath from its `exports` map, if any. */
|
|
225
|
-
function exportsTypesEntry(exports: unknown): string | undefined {
|
|
226
|
-
if (typeof exports === "string") {
|
|
227
|
-
return typesFromCondition(exports);
|
|
228
|
-
}
|
|
229
|
-
if (!exports || typeof exports !== "object") {
|
|
230
|
-
return undefined;
|
|
231
|
-
}
|
|
232
|
-
const map = exports as Record<string, unknown>;
|
|
233
|
-
// `exports["."]`, or the whole object when it is condition-only sugar (no subpath keys).
|
|
234
|
-
const dot = "." in map
|
|
235
|
-
? map["."]
|
|
236
|
-
: (Object.keys(map).some(k => k.startsWith(".")) ? undefined : map);
|
|
237
|
-
return dot === undefined ? undefined : typesFromCondition(dot);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
233
|
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
234
|
+
* Whether `file` belongs to the package installed at `dir`. Resolution by name walks up the tree,
|
|
235
|
+
* so a same-named package elsewhere can answer — which an npm alias (`"a": "npm:b@1"`) installs by
|
|
236
|
+
* construction.
|
|
244
237
|
*/
|
|
245
|
-
function
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
238
|
+
function containedIn(dir: string, file: string): boolean {
|
|
239
|
+
return path.resolve(file).startsWith(path.resolve(dir) + path.sep);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function packageManifest(dir: string): {name?: unknown, types?: unknown, typings?: unknown} | undefined {
|
|
243
|
+
const packageJson = path.join(dir, "package.json");
|
|
244
|
+
if (!ts.sys.fileExists(packageJson)) {
|
|
250
245
|
return undefined;
|
|
251
246
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (nestedTypes) {
|
|
258
|
-
return nestedTypes;
|
|
259
|
-
}
|
|
260
|
-
for (const cond of ["import", "require", "default"]) {
|
|
261
|
-
const nested = typesFromCondition(o[cond]);
|
|
262
|
-
if (nested) {
|
|
263
|
-
return nested;
|
|
264
|
-
}
|
|
247
|
+
try {
|
|
248
|
+
return JSON.parse(ts.sys.readFile(packageJson) || "{}");
|
|
249
|
+
} catch {
|
|
250
|
+
// Malformed package.json; fall through to conventional entry points.
|
|
251
|
+
return undefined;
|
|
265
252
|
}
|
|
266
|
-
return undefined;
|
|
267
253
|
}
|
|
268
254
|
|
|
269
255
|
/**
|
package/src/javascript/parser.ts
CHANGED
|
@@ -91,8 +91,14 @@ export class JavaScriptParser extends Parser {
|
|
|
91
91
|
super({ctx, relativeTo});
|
|
92
92
|
this.compilerOptions = {
|
|
93
93
|
target: ts.ScriptTarget.Latest,
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
// Bundler matches `exports` conditions leniently, so packages that publish types only under
|
|
95
|
+
// an `import` condition resolve; it pairs with `preserve`, which `commonjs` cannot (TS5095).
|
|
96
|
+
// A `node16` module kind makes TS1479 reachable, and that error costs the whole LST.
|
|
97
|
+
module: ts.ModuleKind.Preserve,
|
|
98
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
99
|
+
// Bundler's own condition set omits `node`, which takes the browser branch of packages
|
|
100
|
+
// that publish one, typing them against an API surface the source does not use.
|
|
101
|
+
customConditions: ["node"],
|
|
96
102
|
noEmit: true,
|
|
97
103
|
allowJs: true,
|
|
98
104
|
checkJs: true,
|
|
@@ -326,7 +332,8 @@ export class JavaScriptParser extends Parser {
|
|
|
326
332
|
return resolvedModules;
|
|
327
333
|
};
|
|
328
334
|
|
|
329
|
-
//
|
|
335
|
+
// TypeScript carries the previous program's bound files forward when the root paths match,
|
|
336
|
+
// so a parser held across parses of one path costs a fraction of a freshly built one.
|
|
330
337
|
const program = ts.createProgram([...inputFiles.keys()], this.compilerOptions, host, this.oldProgram);
|
|
331
338
|
|
|
332
339
|
// Update the oldProgram reference
|
|
@@ -164,17 +164,14 @@ export class ChangeImport extends Recipe {
|
|
|
164
164
|
alias: aliasToUse,
|
|
165
165
|
onlyIfReferenced: false
|
|
166
166
|
});
|
|
167
|
-
} else if (aliasToUse && aliasToUse !== newMember) {
|
|
168
|
-
maybeAddImport(this, {
|
|
169
|
-
module: newModule,
|
|
170
|
-
member: newMember,
|
|
171
|
-
alias: aliasToUse,
|
|
172
|
-
onlyIfReferenced: false
|
|
173
|
-
});
|
|
174
167
|
} else {
|
|
175
168
|
maybeAddImport(this, {
|
|
176
169
|
module: newModule,
|
|
177
170
|
member: newMember,
|
|
171
|
+
// A moved binding keeps the local name it had. Pinning it also tells
|
|
172
|
+
// `maybeAddImport` not to deconflict against the import being replaced,
|
|
173
|
+
// which is still present in the tree it reads.
|
|
174
|
+
alias: aliasToUse ?? newMember,
|
|
178
175
|
onlyIfReferenced: false
|
|
179
176
|
});
|
|
180
177
|
}
|
package/src/javascript/style.ts
CHANGED
|
@@ -14,10 +14,9 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {NamedStyles, Style} from "../style";
|
|
17
|
+
import {isNamedStyles, NamedStyles, Style} from "../style";
|
|
18
18
|
import {randomId} from "../uuid";
|
|
19
19
|
import {Tree} from "../tree";
|
|
20
|
-
import {MarkersKind} from "../markers";
|
|
21
20
|
|
|
22
21
|
export const JavaScriptStyles = {
|
|
23
22
|
IntelliJ: "org.openrewrite.javascript.style.IntelliJ",
|
|
@@ -562,13 +561,7 @@ export namespace IntelliJ {
|
|
|
562
561
|
}
|
|
563
562
|
|
|
564
563
|
export function styleFromSourceFile(styleKind: string, sourceFile: Tree): Style | undefined {
|
|
565
|
-
|
|
566
|
-
const candidate = namedStyles.map(namedStyle => namedStyle.styles).flat()
|
|
567
|
-
.find(style => style.kind === styleKind)
|
|
568
|
-
if (candidate) {
|
|
569
|
-
return candidate;
|
|
570
|
-
}
|
|
571
|
-
return IntelliJ.TypeScript.defaults.styles.find(style => style.kind === styleKind) as Style;
|
|
564
|
+
return getStyle(styleKind, sourceFile);
|
|
572
565
|
}
|
|
573
566
|
|
|
574
567
|
/**
|
|
@@ -591,8 +584,7 @@ export function getStyle(styleKind: string, sourceFile: Tree, styles?: NamedStyl
|
|
|
591
584
|
}
|
|
592
585
|
|
|
593
586
|
// Then check source file markers
|
|
594
|
-
const
|
|
595
|
-
for (const namedStyle of namedStyles) {
|
|
587
|
+
for (const namedStyle of sourceFile.markers.markers.filter(isNamedStyles)) {
|
|
596
588
|
const found = namedStyle.styles.find(s => s.kind === styleKind);
|
|
597
589
|
if (found) {
|
|
598
590
|
return found;
|