@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.
Files changed (116) hide show
  1. package/dist/java/formatting-utils.d.ts +8 -5
  2. package/dist/java/formatting-utils.d.ts.map +1 -1
  3. package/dist/java/formatting-utils.js +14 -10
  4. package/dist/java/formatting-utils.js.map +1 -1
  5. package/dist/java/type.d.ts +2 -0
  6. package/dist/java/type.d.ts.map +1 -1
  7. package/dist/java/type.js +5 -0
  8. package/dist/java/type.js.map +1 -1
  9. package/dist/javascript/add-import.d.ts +39 -20
  10. package/dist/javascript/add-import.d.ts.map +1 -1
  11. package/dist/javascript/add-import.js +513 -243
  12. package/dist/javascript/add-import.js.map +1 -1
  13. package/dist/javascript/assertions.d.ts +7 -0
  14. package/dist/javascript/assertions.d.ts.map +1 -1
  15. package/dist/javascript/assertions.js +31 -5
  16. package/dist/javascript/assertions.js.map +1 -1
  17. package/dist/javascript/comparator.d.ts +5 -0
  18. package/dist/javascript/comparator.d.ts.map +1 -1
  19. package/dist/javascript/comparator.js +34 -9
  20. package/dist/javascript/comparator.js.map +1 -1
  21. package/dist/javascript/format/format.d.ts.map +1 -1
  22. package/dist/javascript/format/format.js +8 -9
  23. package/dist/javascript/format/format.js.map +1 -1
  24. package/dist/javascript/format/tabs-and-indents-visitor.d.ts +2 -0
  25. package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -1
  26. package/dist/javascript/format/tabs-and-indents-visitor.js +17 -11
  27. package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
  28. package/dist/javascript/node-resolution-result.d.ts +19 -1
  29. package/dist/javascript/node-resolution-result.d.ts.map +1 -1
  30. package/dist/javascript/node-resolution-result.js +37 -2
  31. package/dist/javascript/node-resolution-result.js.map +1 -1
  32. package/dist/javascript/package-exported-types.d.ts.map +1 -1
  33. package/dist/javascript/package-exported-types.js +56 -72
  34. package/dist/javascript/package-exported-types.js.map +1 -1
  35. package/dist/javascript/parser.d.ts.map +1 -1
  36. package/dist/javascript/parser.js +10 -3
  37. package/dist/javascript/parser.js.map +1 -1
  38. package/dist/javascript/recipes/change-import.d.ts.map +1 -1
  39. package/dist/javascript/recipes/change-import.js +4 -8
  40. package/dist/javascript/recipes/change-import.js.map +1 -1
  41. package/dist/javascript/style.d.ts.map +1 -1
  42. package/dist/javascript/style.js +3 -10
  43. package/dist/javascript/style.js.map +1 -1
  44. package/dist/javascript/templating/bindings.d.ts +19 -0
  45. package/dist/javascript/templating/bindings.d.ts.map +1 -0
  46. package/dist/javascript/templating/bindings.js +121 -0
  47. package/dist/javascript/templating/bindings.js.map +1 -0
  48. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  49. package/dist/javascript/templating/comparator.js +5 -6
  50. package/dist/javascript/templating/comparator.js.map +1 -1
  51. package/dist/javascript/templating/engine.d.ts +18 -7
  52. package/dist/javascript/templating/engine.d.ts.map +1 -1
  53. package/dist/javascript/templating/engine.js +70 -42
  54. package/dist/javascript/templating/engine.js.map +1 -1
  55. package/dist/javascript/templating/index.d.ts +1 -1
  56. package/dist/javascript/templating/index.d.ts.map +1 -1
  57. package/dist/javascript/templating/index.js.map +1 -1
  58. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  59. package/dist/javascript/templating/pattern.js +13 -10
  60. package/dist/javascript/templating/pattern.js.map +1 -1
  61. package/dist/javascript/templating/placeholder-replacement.d.ts +2 -0
  62. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  63. package/dist/javascript/templating/placeholder-replacement.js +14 -5
  64. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  65. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  66. package/dist/javascript/templating/rewrite.js +14 -18
  67. package/dist/javascript/templating/rewrite.js.map +1 -1
  68. package/dist/javascript/templating/template.d.ts +8 -0
  69. package/dist/javascript/templating/template.d.ts.map +1 -1
  70. package/dist/javascript/templating/template.js +43 -3
  71. package/dist/javascript/templating/template.js.map +1 -1
  72. package/dist/javascript/templating/types.d.ts +47 -1
  73. package/dist/javascript/templating/types.d.ts.map +1 -1
  74. package/dist/javascript/templating/utils.d.ts +5 -0
  75. package/dist/javascript/templating/utils.d.ts.map +1 -1
  76. package/dist/javascript/templating/utils.js +17 -0
  77. package/dist/javascript/templating/utils.js.map +1 -1
  78. package/dist/javascript/type-mapping.d.ts +1 -0
  79. package/dist/javascript/type-mapping.d.ts.map +1 -1
  80. package/dist/javascript/type-mapping.js +52 -2
  81. package/dist/javascript/type-mapping.js.map +1 -1
  82. package/dist/rewrite-javascript-version.txt +1 -1
  83. package/dist/style.d.ts +2 -0
  84. package/dist/style.d.ts.map +1 -1
  85. package/dist/style.js +5 -0
  86. package/dist/style.js.map +1 -1
  87. package/dist/test/rewrite-test.d.ts +12 -0
  88. package/dist/test/rewrite-test.d.ts.map +1 -1
  89. package/dist/test/rewrite-test.js +40 -3
  90. package/dist/test/rewrite-test.js.map +1 -1
  91. package/package.json +1 -1
  92. package/src/java/formatting-utils.ts +14 -10
  93. package/src/java/type.ts +5 -0
  94. package/src/javascript/add-import.ts +590 -230
  95. package/src/javascript/assertions.ts +33 -5
  96. package/src/javascript/comparator.ts +39 -10
  97. package/src/javascript/format/format.ts +9 -10
  98. package/src/javascript/format/tabs-and-indents-visitor.ts +18 -12
  99. package/src/javascript/node-resolution-result.ts +59 -4
  100. package/src/javascript/package-exported-types.ts +60 -74
  101. package/src/javascript/parser.ts +10 -3
  102. package/src/javascript/recipes/change-import.ts +4 -7
  103. package/src/javascript/style.ts +3 -11
  104. package/src/javascript/templating/bindings.ts +129 -0
  105. package/src/javascript/templating/comparator.ts +5 -6
  106. package/src/javascript/templating/engine.ts +82 -44
  107. package/src/javascript/templating/index.ts +2 -0
  108. package/src/javascript/templating/pattern.ts +16 -15
  109. package/src/javascript/templating/placeholder-replacement.ts +16 -5
  110. package/src/javascript/templating/rewrite.ts +16 -18
  111. package/src/javascript/templating/template.ts +45 -3
  112. package/src/javascript/templating/types.ts +53 -1
  113. package/src/javascript/templating/utils.ts +17 -0
  114. package/src/javascript/type-mapping.ts +54 -2
  115. package/src/style.ts +5 -0
  116. 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: () => new JavaScriptParser({sourceFileCache, relativeTo}),
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 => new JavaScriptParser({sourceFileCache})
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: () => new JavaScriptParser({sourceFileCache})
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: () => new JavaScriptParser({sourceFileCache})
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: () => new JavaScriptParser({sourceFileCache})
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
- // Compare type parameters
2454
- if ((method.typeParameters === undefined) !== (otherMethod.typeParameters === undefined)) {
2455
- return this.structuralMismatch('typeParameters');
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, tree, this.styles) as BlankLinesStyle, this.stopAfter),
81
- new WrappingAndBracesVisitor(getStyle(StyleKind.WrappingAndBracesStyle, tree, this.styles) as WrappingAndBracesStyle, this.stopAfter),
82
- new SpacesVisitor(getStyle(StyleKind.SpacesStyle, tree, this.styles) as SpacesStyle, this.stopAfter),
83
- new TabsAndIndentsVisitor(getStyle(StyleKind.TabsAndIndentsStyle, tree, this.styles) as TabsAndIndentsStyle, this.stopAfter),
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
- const afterWs = draft.elements[i].after.whitespace;
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
- const effectiveLastWs = lastWhitespace(block.end);
399
- if (!effectiveLastWs.includes("\n")) {
402
+ if (!spaceContainsNewline(block.end)) {
400
403
  return block;
401
404
  }
402
- return produce(block, draft => {
403
- draft.end = replaceLastWhitespace(draft.end, ws => replaceIndentAfterLastNewline(ws, this.indentString(myIndent)));
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 effectiveLastWs = lastWhitespace(container.elements[container.elements.length - 1].after);
444
- if (effectiveLastWs.includes("\n")) {
445
- return produce(container, draft => {
446
- const lastDraft = draft.elements[draft.elements.length - 1];
447
- lastDraft.after = replaceLastWhitespace(lastDraft.after, ws => replaceIndentAfterLastNewline(ws, this.indentString(parentIndent)));
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.length - idx - 1;
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
- * Parsed package-lock.json content structure (npm lockfile v3 format).
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
- if (!lockContent.packages) return [];
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: ts.ModuleKind.CommonJS,
55
- moduleResolution: ts.ModuleResolutionKind.Node10,
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) && /\.[cm]?tsx?$/.test(artifact) ? artifact : undefined;
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
- * Resolve a package directory to its declaration entry: the package.json `types`/`typings`
190
- * field, else the `exports["."]` types condition (modern packages declare types only there),
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 packageJson = path.join(dir, "package.json");
195
- if (ts.sys.fileExists(packageJson)) {
196
- try {
197
- const pkg = JSON.parse(ts.sys.readFile(packageJson) || "{}");
198
- const typesField: unknown = pkg.types ?? pkg.typings;
199
- const candidate = typeof typesField === "string"
200
- ? typesField
201
- : exportsTypesEntry(pkg.exports);
202
- if (typeof candidate === "string") {
203
- const resolved = path.resolve(dir, candidate);
204
- if (ts.sys.fileExists(resolved)) {
205
- return resolved;
206
- }
207
- if (ts.sys.fileExists(resolved + ".d.ts")) {
208
- return resolved + ".d.ts";
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
- * Pull a declaration file out of an `exports` condition value: an explicit `types` condition,
242
- * else the `types` nested under `import`/`require`/`default`. A bare target string is a runtime
243
- * (JS) entry, not types, unless it is itself a declaration file.
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 typesFromCondition(entry: unknown): string | undefined {
246
- if (typeof entry === "string") {
247
- return /\.d\.[cm]?ts$/.test(entry) ? entry : undefined;
248
- }
249
- if (!entry || typeof entry !== "object") {
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
- const o = entry as Record<string, unknown>;
253
- if (typeof o.types === "string") {
254
- return o.types;
255
- }
256
- const nestedTypes = typesFromCondition(o.types);
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
  /**
@@ -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
- module: ts.ModuleKind.CommonJS,
95
- moduleResolution: ts.ModuleResolutionKind.Node10,
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
- // Create a new Program, passing the oldProgram for incremental parsing
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
  }
@@ -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
- const namedStyles = sourceFile.markers.markers.filter(marker => marker.kind === MarkersKind.NamedStyles) as NamedStyles[];
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 namedStyles = sourceFile.markers.markers.filter(marker => marker.kind === MarkersKind.NamedStyles) as NamedStyles[];
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;