@openrewrite/rewrite 8.69.0 → 8.69.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 (93) hide show
  1. package/dist/java/type.d.ts +5 -0
  2. package/dist/java/type.d.ts.map +1 -1
  3. package/dist/java/type.js +12 -0
  4. package/dist/java/type.js.map +1 -1
  5. package/dist/javascript/assertions.d.ts.map +1 -1
  6. package/dist/javascript/assertions.js +9 -0
  7. package/dist/javascript/assertions.js.map +1 -1
  8. package/dist/javascript/comparator.d.ts.map +1 -1
  9. package/dist/javascript/comparator.js +5 -9
  10. package/dist/javascript/comparator.js.map +1 -1
  11. package/dist/javascript/{format.d.ts → format/format.d.ts} +15 -33
  12. package/dist/javascript/format/format.d.ts.map +1 -0
  13. package/dist/javascript/{format.js → format/format.js} +56 -313
  14. package/dist/javascript/format/format.js.map +1 -0
  15. package/dist/javascript/format/index.d.ts +3 -0
  16. package/dist/javascript/format/index.d.ts.map +1 -0
  17. package/dist/javascript/format/index.js +38 -0
  18. package/dist/javascript/format/index.js.map +1 -0
  19. package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts +28 -0
  20. package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts.map +1 -0
  21. package/dist/javascript/format/minimum-viable-spacing-visitor.js +308 -0
  22. package/dist/javascript/format/minimum-viable-spacing-visitor.js.map +1 -0
  23. package/dist/javascript/format/normalize-whitespace-visitor.d.ts +14 -0
  24. package/dist/javascript/format/normalize-whitespace-visitor.d.ts.map +1 -0
  25. package/dist/javascript/format/normalize-whitespace-visitor.js +65 -0
  26. package/dist/javascript/format/normalize-whitespace-visitor.js.map +1 -0
  27. package/dist/javascript/format/prettier-config-loader.d.ts +92 -0
  28. package/dist/javascript/format/prettier-config-loader.d.ts.map +1 -0
  29. package/dist/javascript/format/prettier-config-loader.js +419 -0
  30. package/dist/javascript/format/prettier-config-loader.js.map +1 -0
  31. package/dist/javascript/format/prettier-format.d.ts +111 -0
  32. package/dist/javascript/format/prettier-format.d.ts.map +1 -0
  33. package/dist/javascript/format/prettier-format.js +496 -0
  34. package/dist/javascript/format/prettier-format.js.map +1 -0
  35. package/dist/javascript/{tabs-and-indents-visitor.d.ts → format/tabs-and-indents-visitor.d.ts} +4 -4
  36. package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -0
  37. package/dist/javascript/{tabs-and-indents-visitor.js → format/tabs-and-indents-visitor.js} +7 -7
  38. package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -0
  39. package/dist/javascript/format/whitespace-reconciler.d.ts +106 -0
  40. package/dist/javascript/format/whitespace-reconciler.d.ts.map +1 -0
  41. package/dist/javascript/format/whitespace-reconciler.js +291 -0
  42. package/dist/javascript/format/whitespace-reconciler.js.map +1 -0
  43. package/dist/javascript/markers.d.ts.map +1 -1
  44. package/dist/javascript/markers.js +21 -0
  45. package/dist/javascript/markers.js.map +1 -1
  46. package/dist/javascript/parser.d.ts +15 -3
  47. package/dist/javascript/parser.d.ts.map +1 -1
  48. package/dist/javascript/parser.js +107 -24
  49. package/dist/javascript/parser.js.map +1 -1
  50. package/dist/javascript/recipes/auto-format.d.ts +3 -0
  51. package/dist/javascript/recipes/auto-format.d.ts.map +1 -1
  52. package/dist/javascript/recipes/auto-format.js +22 -1
  53. package/dist/javascript/recipes/auto-format.js.map +1 -1
  54. package/dist/javascript/style.d.ts +52 -1
  55. package/dist/javascript/style.d.ts.map +1 -1
  56. package/dist/javascript/style.js +43 -2
  57. package/dist/javascript/style.js.map +1 -1
  58. package/dist/test/rewrite-test.d.ts +3 -4
  59. package/dist/test/rewrite-test.d.ts.map +1 -1
  60. package/dist/test/rewrite-test.js +6 -18
  61. package/dist/test/rewrite-test.js.map +1 -1
  62. package/dist/version.txt +1 -1
  63. package/dist/yaml/assertions.d.ts +4 -0
  64. package/dist/yaml/assertions.d.ts.map +1 -0
  65. package/dist/yaml/assertions.js +31 -0
  66. package/dist/yaml/assertions.js.map +1 -0
  67. package/dist/yaml/index.d.ts +2 -1
  68. package/dist/yaml/index.d.ts.map +1 -1
  69. package/dist/yaml/index.js +2 -1
  70. package/dist/yaml/index.js.map +1 -1
  71. package/package.json +5 -4
  72. package/src/java/type.ts +12 -0
  73. package/src/javascript/assertions.ts +9 -0
  74. package/src/javascript/comparator.ts +6 -11
  75. package/src/javascript/{format.ts → format/format.ts} +59 -267
  76. package/src/javascript/format/index.ts +21 -0
  77. package/src/javascript/format/minimum-viable-spacing-visitor.ts +256 -0
  78. package/src/javascript/format/normalize-whitespace-visitor.ts +42 -0
  79. package/src/javascript/format/prettier-config-loader.ts +422 -0
  80. package/src/javascript/format/prettier-format.ts +622 -0
  81. package/src/javascript/{tabs-and-indents-visitor.ts → format/tabs-and-indents-visitor.ts} +8 -8
  82. package/src/javascript/format/whitespace-reconciler.ts +345 -0
  83. package/src/javascript/markers.ts +19 -0
  84. package/src/javascript/parser.ts +107 -20
  85. package/src/javascript/recipes/auto-format.ts +28 -1
  86. package/src/javascript/style.ts +41 -2
  87. package/src/test/rewrite-test.ts +6 -18
  88. package/src/yaml/assertions.ts +28 -0
  89. package/src/yaml/index.ts +2 -1
  90. package/dist/javascript/format.d.ts.map +0 -1
  91. package/dist/javascript/format.js.map +0 -1
  92. package/dist/javascript/tabs-and-indents-visitor.d.ts.map +0 -1
  93. package/dist/javascript/tabs-and-indents-visitor.js.map +0 -1
@@ -0,0 +1,622 @@
1
+ /*
2
+ * Copyright 2025 the original author or authors.
3
+ * <p>
4
+ * Licensed under the Moderne Source Available License (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ * <p>
8
+ * https://docs.moderne.io/licensing/moderne-source-available-license
9
+ * <p>
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import {JS} from '../tree';
17
+ import {J, Statement} from '../../java';
18
+ import {Cursor, Tree} from '../../tree';
19
+ import {TreePrinters} from '../../print';
20
+ import {JavaScriptParser} from '../parser';
21
+ import {WhitespaceReconciler} from './whitespace-reconciler';
22
+ import {produce} from 'immer';
23
+ import {randomId} from '../../uuid';
24
+ import {PrettierStyle, StyleKind} from '../style';
25
+ import {NamedStyles} from '../../style';
26
+ import {findMarker} from '../../markers';
27
+ import {NormalizeWhitespaceVisitor} from './normalize-whitespace-visitor';
28
+ import {MinimumViableSpacingVisitor} from './minimum-viable-spacing-visitor';
29
+ import {loadPrettierVersion} from './prettier-config-loader';
30
+
31
+ /**
32
+ * Loads Prettier for formatting.
33
+ *
34
+ * We use the main Prettier module (not standalone) because:
35
+ * 1. It automatically handles parser resolution
36
+ * 2. Works better with CommonJS (avoids ESM issues in Jest)
37
+ * 3. Simpler - no need to manually load plugins
38
+ */
39
+ async function loadPrettierFormatting(version?: string): Promise<typeof import('prettier')> {
40
+ if (version) {
41
+ // Ensure the version is installed and get it from cache
42
+ return await loadPrettierVersion(version);
43
+ }
44
+
45
+ // Use bundled Prettier
46
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
47
+ return require('prettier');
48
+ }
49
+
50
+ /**
51
+ * Options for Prettier formatting.
52
+ */
53
+ export interface PrettierFormatOptions {
54
+ /**
55
+ * Tab width for indentation. Defaults to 2.
56
+ */
57
+ tabWidth?: number;
58
+
59
+ /**
60
+ * Use tabs instead of spaces. Defaults to false.
61
+ */
62
+ useTabs?: boolean;
63
+
64
+ /**
65
+ * Print semicolons at the ends of statements. Defaults to true.
66
+ */
67
+ semi?: boolean;
68
+
69
+ /**
70
+ * Use single quotes instead of double quotes. Defaults to false.
71
+ */
72
+ singleQuote?: boolean;
73
+
74
+ /**
75
+ * Print trailing commas wherever possible. Defaults to 'all'.
76
+ */
77
+ trailingComma?: 'all' | 'es5' | 'none';
78
+
79
+ /**
80
+ * Print width for line wrapping. Defaults to 80.
81
+ */
82
+ printWidth?: number;
83
+
84
+ /**
85
+ * Change when properties in objects are quoted.
86
+ * - "as-needed" - Only add quotes around object properties where required.
87
+ * - "consistent" - If at least one property in an object requires quotes, quote all properties.
88
+ * - "preserve" - Respect the input use of quotes in object properties.
89
+ * Defaults to "as-needed".
90
+ */
91
+ quoteProps?: 'as-needed' | 'consistent' | 'preserve';
92
+
93
+ /**
94
+ * The Prettier version to use (e.g., "3.4.2").
95
+ * If specified, loads that version from cache or installs it.
96
+ * If not specified, uses the bundled Prettier.
97
+ */
98
+ prettierVersion?: string;
99
+ }
100
+
101
+ /**
102
+ * Formats a JavaScript/TypeScript AST using Prettier.
103
+ *
104
+ * This function:
105
+ * 1. Prints the AST to a string
106
+ * 2. Formats the string using Prettier
107
+ * 3. Parses the formatted string back to an AST (without type attribution for performance)
108
+ * 4. Reconciles the whitespace from the formatted AST back into the original AST
109
+ *
110
+ * The result preserves the original AST's structure, types, and markers while
111
+ * applying Prettier's formatting rules for whitespace.
112
+ *
113
+ * @param sourceFile The source file to format
114
+ * @param options Prettier formatting options
115
+ * @param stopAfter Optional node to stop formatting after. Once this node is exited,
116
+ * no more whitespace changes are applied to subsequent nodes.
117
+ * @returns The formatted source file with reconciled whitespace
118
+ */
119
+ export async function prettierFormat(
120
+ sourceFile: JS.CompilationUnit,
121
+ options: PrettierFormatOptions = {},
122
+ stopAfter?: J
123
+ ): Promise<JS.CompilationUnit> {
124
+ // Load Prettier - either specific version or bundled
125
+ let prettier: typeof import('prettier');
126
+
127
+ try {
128
+ prettier = await loadPrettierFormatting(options.prettierVersion);
129
+ } catch (e) {
130
+ console.error('Failed to load Prettier:', e);
131
+ throw new Error(
132
+ `Prettier is not installed or failed to load. Please install it with: npm install prettier. Error: ${e}`
133
+ );
134
+ }
135
+
136
+ // Step 1: Print the AST to string
137
+ const originalSource = await TreePrinters.print(sourceFile);
138
+
139
+ // Step 2: Determine parser based on source path
140
+ const parser = getParserForPath(sourceFile.sourcePath);
141
+
142
+ // Step 3: Format with Prettier
143
+ // Using the main Prettier module - parsers are resolved automatically
144
+ // Only set parser and filepath - pass through all other options without defaults
145
+ // This lets Prettier use its own defaults for any unspecified options
146
+ const prettierOptions: Record<string, unknown> = {
147
+ parser,
148
+ filepath: sourceFile.sourcePath, // Important: tells Prettier the file type for proper formatting
149
+ ...options,
150
+ };
151
+ // Remove our internal option that Prettier doesn't understand
152
+ delete prettierOptions.prettierVersion;
153
+
154
+ const formattedSource = await prettier.format(originalSource, prettierOptions);
155
+
156
+ // Step 4: Parse the formatted string using parseOnly() for maximum performance
157
+ // (bypasses TypeScript's type checker entirely)
158
+ const formattedParser = new JavaScriptParser();
159
+ const formattedAst = await formattedParser.parseOnly({
160
+ sourcePath: sourceFile.sourcePath,
161
+ text: formattedSource
162
+ });
163
+
164
+ if (!formattedAst) {
165
+ console.warn('Prettier formatting: Failed to parse formatted output, returning original');
166
+ return sourceFile;
167
+ }
168
+
169
+ // Step 5: Reconcile whitespace from formatted AST to original AST
170
+ // Note: For subtree formatting with pruned trees, the structure may differ
171
+ // (e.g., Prettier removes empty placeholder statements). In such cases,
172
+ // we return the formatted AST directly and let the caller handle
173
+ // subtree-level reconciliation.
174
+ const reconciler = new WhitespaceReconciler();
175
+ const formattedCu = formattedAst as JS.CompilationUnit;
176
+ const result = reconciler.reconcile(sourceFile, formattedCu, undefined, stopAfter);
177
+
178
+ // If reconciliation succeeded, return the reconciled original with updated whitespace
179
+ // If it failed (structure mismatch), return the formatted AST for subtree reconciliation
180
+ return reconciler.isCompatible() ? result as JS.CompilationUnit : formattedCu;
181
+ }
182
+
183
+ /**
184
+ * Maps file extensions to Prettier parser names.
185
+ */
186
+ const PRETTIER_PARSER_BY_EXTENSION: Record<string, string> = {
187
+ '.ts': 'typescript',
188
+ '.tsx': 'typescript',
189
+ '.js': 'babel',
190
+ '.jsx': 'babel',
191
+ '.mjs': 'babel',
192
+ '.cjs': 'babel',
193
+ };
194
+
195
+ /**
196
+ * Determines the Prettier parser to use based on file extension.
197
+ */
198
+ function getParserForPath(filePath: string): string {
199
+ const lower = filePath.toLowerCase();
200
+ for (const [ext, parser] of Object.entries(PRETTIER_PARSER_BY_EXTENSION)) {
201
+ if (lower.endsWith(ext)) {
202
+ return parser;
203
+ }
204
+ }
205
+ return 'babel'; // Default parser
206
+ }
207
+
208
+ /**
209
+ * Represents a segment of the path from root to a target node.
210
+ */
211
+ interface PathSegment {
212
+ /** The property name containing the child */
213
+ property: string;
214
+ /** For array properties, the index of the element */
215
+ index?: number;
216
+ }
217
+
218
+ /**
219
+ * Result of extracting a path from cursor.
220
+ */
221
+ interface PathExtractionResult {
222
+ /** The compilation unit (root of the tree) */
223
+ compilationUnit: JS.CompilationUnit | undefined;
224
+ /** The path from root to target */
225
+ path: PathSegment[];
226
+ }
227
+
228
+ /**
229
+ * Extracts the path from a CompilationUnit to a target node using the cursor.
230
+ * Returns the path segments in order from root to target.
231
+ *
232
+ * @param cursor The cursor, which may not include the target (e.g., when passing cursor.parent)
233
+ * @param target The target node we're looking for
234
+ */
235
+ function extractPathFromCursor(cursor: Cursor, target: any): PathExtractionResult {
236
+ const pathNodes = cursor.asArray().reverse(); // root to target
237
+ const segments: PathSegment[] = [];
238
+ let compilationUnit: JS.CompilationUnit | undefined;
239
+
240
+ // Helper to check if two nodes are the same (by identity or ID)
241
+ const isSameNode = (a: any, b: any): boolean => {
242
+ if (a === b) return true;
243
+ if (a && b && typeof a === 'object' && typeof b === 'object' && 'id' in a && 'id' in b) {
244
+ return a.id === b.id;
245
+ }
246
+ return false;
247
+ };
248
+
249
+ // Helper to find a child in a parent and return the segment
250
+ const findChildInParent = (parent: any, child: any): PathSegment | undefined => {
251
+ if (!parent || typeof parent !== 'object') return undefined;
252
+
253
+ for (const key of Object.keys(parent)) {
254
+ const value = (parent as any)[key];
255
+ if (value == null) continue;
256
+
257
+ if (Array.isArray(value)) {
258
+ for (let idx = 0; idx < value.length; idx++) {
259
+ const item = value[idx];
260
+ if (isSameNode(item, child)) {
261
+ return { property: key, index: idx };
262
+ }
263
+ }
264
+ } else if (isSameNode(value, child)) {
265
+ return { property: key };
266
+ }
267
+ }
268
+ return undefined;
269
+ };
270
+
271
+ for (let i = 0; i < pathNodes.length - 1; i++) {
272
+ const parent = pathNodes[i];
273
+ const child = pathNodes[i + 1];
274
+
275
+ // Check if this node is the CompilationUnit
276
+ if (parent?.kind === JS.Kind.CompilationUnit) {
277
+ compilationUnit = parent as JS.CompilationUnit;
278
+ }
279
+
280
+ const segment = findChildInParent(parent, child);
281
+ if (segment) {
282
+ segments.push(segment);
283
+ }
284
+ }
285
+
286
+ // Check the last node for CompilationUnit
287
+ const lastNode = pathNodes[pathNodes.length - 1];
288
+ if (lastNode?.kind === JS.Kind.CompilationUnit) {
289
+ compilationUnit = lastNode as JS.CompilationUnit;
290
+ }
291
+
292
+ // If the cursor doesn't include the target, add the final segment
293
+ // This handles the case when autoFormat is called with cursor.parent
294
+ if (lastNode && !isSameNode(lastNode, target)) {
295
+ const finalSegment = findChildInParent(lastNode, target);
296
+ if (finalSegment) {
297
+ segments.push(finalSegment);
298
+ }
299
+ }
300
+
301
+ return { compilationUnit, path: segments };
302
+ }
303
+
304
+ /**
305
+ * Creates a "null" identifier placeholder for use in pruned trees.
306
+ * Using "null" instead of an empty statement ensures Prettier sees similar
307
+ * line lengths and doesn't collapse multi-line code to single-line.
308
+ */
309
+ function createNullPlaceholder(prefix: J.Space): J.Identifier {
310
+ return {
311
+ kind: J.Kind.Identifier,
312
+ id: randomId(),
313
+ markers: { kind: "org.openrewrite.marker.Markers", id: randomId(), markers: [] },
314
+ prefix: prefix,
315
+ annotations: [],
316
+ simpleName: "null",
317
+ type: undefined,
318
+ fieldType: undefined
319
+ };
320
+ }
321
+
322
+ /**
323
+ * Prunes a compilation unit for efficient Prettier formatting of a subtree,
324
+ * and substitutes the (potentially modified) target at the path location.
325
+ *
326
+ * For J.Block#statements along the path to the target:
327
+ * - Prior siblings are replaced with "null" identifier placeholders (to maintain line length)
328
+ * - Following siblings are omitted entirely
329
+ *
330
+ * This optimization reduces the amount of code Prettier needs to process
331
+ * while maintaining approximate line positions so Prettier doesn't collapse
332
+ * multi-line code.
333
+ *
334
+ * @param cu The compilation unit to prune
335
+ * @param path The path from root to the target subtree
336
+ * @param target The (potentially modified) target to substitute at the path location
337
+ * @returns A pruned copy of the compilation unit with the target substituted
338
+ */
339
+ function pruneTreeForSubtree(cu: JS.CompilationUnit, path: PathSegment[], target: any): JS.CompilationUnit {
340
+ return pruneNode(cu, path, 0, target) as JS.CompilationUnit;
341
+ }
342
+
343
+ /**
344
+ * Recursively prunes a node, following the path, pruning J.Block#statements,
345
+ * and substituting the target at the final location.
346
+ */
347
+ function pruneNode(node: any, path: PathSegment[], pathIndex: number, target: any): any {
348
+ if (pathIndex >= path.length) {
349
+ // Reached the target location - substitute with the (potentially modified) target
350
+ return target;
351
+ }
352
+
353
+ const segment = path[pathIndex];
354
+ const value = node[segment.property];
355
+
356
+ if (value == null) {
357
+ return node;
358
+ }
359
+
360
+ // Handle J.Block#statements specially - prune siblings
361
+ if (node.kind === J.Kind.Block && segment.property === 'statements' && segment.index !== undefined) {
362
+ const statements = value as J.RightPadded<Statement>[];
363
+ const targetIndex = segment.index;
364
+
365
+ // Create pruned statements array:
366
+ // - Prior siblings: replace with "null" placeholders (to maintain line length)
367
+ // - Target: recurse into it (following the path through RightPadded.element)
368
+ // - Following siblings: omit entirely
369
+ const prunedStatements: J.RightPadded<Statement>[] = [];
370
+
371
+ for (let i = 0; i <= targetIndex; i++) {
372
+ if (i < targetIndex) {
373
+ // Prior sibling - replace with "null" placeholder
374
+ // Preserve the original prefix to maintain line positions
375
+ const originalPrefix = statements[i].element.prefix;
376
+ const placeholder = createNullPlaceholder(originalPrefix);
377
+ prunedStatements.push({
378
+ kind: J.Kind.RightPadded,
379
+ element: placeholder,
380
+ after: statements[i].after,
381
+ markers: statements[i].markers
382
+ } as J.RightPadded<Statement>);
383
+ } else {
384
+ // Target - recurse into the RightPadded (path will handle .element)
385
+ const prunedRightPadded = pruneNode(statements[i], path, pathIndex + 1, target);
386
+ prunedStatements.push(prunedRightPadded);
387
+ }
388
+ }
389
+ // Following siblings are omitted
390
+
391
+ return produce(node, (draft: any) => {
392
+ draft.statements = prunedStatements;
393
+ });
394
+ }
395
+
396
+ // For other properties, just recurse without pruning
397
+ if (Array.isArray(value) && segment.index !== undefined) {
398
+ const childNode = value[segment.index];
399
+ const prunedChild = pruneNode(childNode, path, pathIndex + 1, target);
400
+
401
+ if (prunedChild !== childNode) {
402
+ return produce(node, (draft: any) => {
403
+ draft[segment.property][segment.index!] = prunedChild;
404
+ });
405
+ }
406
+ } else if (!Array.isArray(value)) {
407
+ const prunedChild = pruneNode(value, path, pathIndex + 1, target);
408
+
409
+ if (prunedChild !== value) {
410
+ return produce(node, (draft: any) => {
411
+ draft[segment.property] = prunedChild;
412
+ });
413
+ }
414
+ }
415
+
416
+ return node;
417
+ }
418
+
419
+ /**
420
+ * Finds a node in a tree by following a path of segments.
421
+ * Used to locate the target node in the formatted tree.
422
+ *
423
+ * For block statements, the target is always at the last index since
424
+ * following siblings are omitted during pruning.
425
+ */
426
+ function findByPath(tree: any, path: PathSegment[]): any {
427
+ let current = tree;
428
+
429
+ for (const segment of path) {
430
+ if (current == null) return undefined;
431
+
432
+ const value = current[segment.property];
433
+ if (value == null) return undefined;
434
+
435
+ if (Array.isArray(value) && segment.index !== undefined) {
436
+ // For block statements, target is always at the last index
437
+ // since following siblings are omitted during pruning
438
+ const isBlockStatements = current.kind === J.Kind.Block && segment.property === 'statements';
439
+ const index = isBlockStatements ? value.length - 1 : segment.index;
440
+ const item = value[index];
441
+ if (item == null) return undefined;
442
+ current = item;
443
+ } else {
444
+ current = value;
445
+ }
446
+ }
447
+
448
+ return current;
449
+ }
450
+
451
+ /**
452
+ * Formats a subtree of a JavaScript/TypeScript AST using Prettier.
453
+ *
454
+ * This function is optimized for formatting a small part of a larger tree:
455
+ * 1. Extracts the path from compilation unit to target
456
+ * 2. Prunes the tree (replaces siblings with placeholders)
457
+ * 3. Formats the pruned tree with Prettier
458
+ * 4. Finds the target in the formatted tree
459
+ * 5. Reconciles only the target subtree's whitespace
460
+ *
461
+ * @param target The subtree to format
462
+ * @param cursor The cursor pointing to or near the target
463
+ * @param options Prettier formatting options
464
+ * @param stopAfter Optional node to stop formatting after
465
+ * @returns The formatted subtree, or undefined if formatting failed
466
+ */
467
+ export async function prettierFormatSubtree<T extends J>(
468
+ target: T,
469
+ cursor: Cursor,
470
+ options: PrettierFormatOptions = {},
471
+ stopAfter?: J
472
+ ): Promise<T | undefined> {
473
+ // Extract the path and compilation unit in a single cursor traversal
474
+ const { compilationUnit: cu, path } = extractPathFromCursor(cursor, target);
475
+
476
+ if (!cu) {
477
+ return undefined;
478
+ }
479
+
480
+ // Prune the tree for efficient formatting and substitute the (potentially modified) target.
481
+ // This ensures that if the visitor modified the target before calling autoFormat,
482
+ // we format the modified content, not the original from the cursor.
483
+ const prunedCu = pruneTreeForSubtree(cu, path, target);
484
+
485
+ // Format the pruned compilation unit with Prettier
486
+ const formattedPrunedCu = await prettierFormat(prunedCu, options);
487
+
488
+ // Find the target node in the formatted tree using the path
489
+ const formattedTarget = findByPath(formattedPrunedCu, path);
490
+ if (!formattedTarget) {
491
+ return undefined;
492
+ }
493
+
494
+ // Reconcile only the target subtree, optionally stopping after a specific node
495
+ const reconciler = new WhitespaceReconciler();
496
+ const reconciled = reconciler.reconcile(target as J, formattedTarget as J, undefined, stopAfter);
497
+
498
+ return reconciled as T;
499
+ }
500
+
501
+ /**
502
+ * Gets the PrettierStyle from the styles array or source file markers.
503
+ *
504
+ * @param tree The tree being formatted
505
+ * @param cursor Optional cursor for walking up to find source file
506
+ * @param styles Optional styles array to check first
507
+ * @returns PrettierStyle if found, undefined otherwise
508
+ */
509
+ export function getPrettierStyle(
510
+ tree: Tree,
511
+ cursor?: Cursor,
512
+ styles?: NamedStyles<string>[]
513
+ ): PrettierStyle | undefined {
514
+ // First check the styles array
515
+ if (styles) {
516
+ const fromStyles = styles.find(s => (s as any).kind === StyleKind.PrettierStyle);
517
+ if (fromStyles) {
518
+ return fromStyles as unknown as PrettierStyle;
519
+ }
520
+ }
521
+
522
+ // Then check for PrettierStyle marker on source file
523
+ let sourceFile: JS.CompilationUnit | undefined;
524
+
525
+ if (tree.kind === JS.Kind.CompilationUnit) {
526
+ sourceFile = tree as JS.CompilationUnit;
527
+ } else if (cursor) {
528
+ // Walk up the cursor to find the compilation unit
529
+ let current: Cursor | undefined = cursor;
530
+ while (current) {
531
+ if (current.value?.kind === JS.Kind.CompilationUnit) {
532
+ sourceFile = current.value as JS.CompilationUnit;
533
+ break;
534
+ }
535
+ current = current.parent;
536
+ }
537
+ }
538
+
539
+ if (!sourceFile) {
540
+ return undefined;
541
+ }
542
+
543
+ return findMarker(sourceFile, StyleKind.PrettierStyle) as PrettierStyle | undefined;
544
+ }
545
+
546
+ /**
547
+ * Applies Prettier formatting to a tree.
548
+ *
549
+ * Configuration is resolved from the PrettierStyle marker on the source file.
550
+ *
551
+ * For compilation units, formats and reconciles the entire tree.
552
+ * For subtrees, uses prettierFormatSubtree which prunes the tree for efficiency,
553
+ * formats the pruned tree, and reconciles only the target subtree.
554
+ *
555
+ * @param tree The tree to format
556
+ * @param prettierStyle The PrettierStyle containing config
557
+ * @param p The visitor parameter
558
+ * @param cursor Optional cursor for subtree formatting
559
+ * @param stopAfter Optional tree to stop after
560
+ * @returns The formatted tree
561
+ */
562
+ export async function applyPrettierFormatting<R extends J, P>(
563
+ tree: R,
564
+ prettierStyle: PrettierStyle,
565
+ p: P,
566
+ cursor?: Cursor,
567
+ stopAfter?: Tree
568
+ ): Promise<R | undefined> {
569
+ // Run only the essential visitors first
570
+ const essentialVisitors = [
571
+ new NormalizeWhitespaceVisitor(stopAfter),
572
+ new MinimumViableSpacingVisitor(stopAfter),
573
+ ];
574
+
575
+ let t: R | undefined = tree;
576
+ for (const visitor of essentialVisitors) {
577
+ t = await visitor.visit(t, p, cursor);
578
+ if (t === undefined) {
579
+ return undefined;
580
+ }
581
+ }
582
+
583
+ // If file is in .prettierignore, skip formatting entirely
584
+ if (prettierStyle.ignored) {
585
+ return t;
586
+ }
587
+
588
+ // Build options for Prettier
589
+ // Pass through the entire resolved config - let Prettier use its own defaults for unspecified options
590
+ const prettierOpts: PrettierFormatOptions = {
591
+ ...prettierStyle.config as PrettierFormatOptions,
592
+ prettierVersion: prettierStyle.prettierVersion,
593
+ };
594
+
595
+ try {
596
+ if (t.kind === JS.Kind.CompilationUnit) {
597
+ // Format and reconcile the entire compilation unit
598
+ const formatted = await prettierFormat(t as unknown as JS.CompilationUnit, prettierOpts, stopAfter as J | undefined);
599
+ return formatted as unknown as R;
600
+ }
601
+
602
+ if (!cursor) {
603
+ // No cursor provided - can't use subtree formatting, return with essential formatting
604
+ console.warn('Prettier formatting: No cursor provided for subtree, returning with essential formatting only');
605
+ return t;
606
+ }
607
+
608
+ // Use prettierFormatSubtree for subtree formatting
609
+ const formatted = await prettierFormatSubtree(t, cursor, prettierOpts, stopAfter as J | undefined);
610
+ if (formatted) {
611
+ return formatted as R;
612
+ }
613
+
614
+ // Subtree formatting failed, return with essential formatting applied
615
+ console.warn('Prettier formatting: Subtree formatting failed, returning with essential formatting only');
616
+ return t;
617
+ } catch (e) {
618
+ // If Prettier fails, return tree with essential formatting applied
619
+ console.warn('Prettier formatting failed, returning with essential formatting only:', e);
620
+ return t;
621
+ }
622
+ }
@@ -13,8 +13,8 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import {JS, JSX} from "./tree";
17
- import {JavaScriptVisitor} from "./visitor";
16
+ import {JS, JSX} from "../tree";
17
+ import {JavaScriptVisitor} from "../visitor";
18
18
  import {
19
19
  isJava,
20
20
  isSpace,
@@ -25,13 +25,13 @@ import {
25
25
  replaceLastWhitespace,
26
26
  spaceContainsNewline,
27
27
  stripLeadingIndent
28
- } from "../java";
28
+ } from "../../java";
29
29
  import {produce} from "immer";
30
- import {Cursor, isScope, isTree, Tree} from "../tree";
31
- import {mapAsync} from "../util";
32
- import {produceAsync} from "../visitor";
33
- import {TabsAndIndentsStyle} from "./style";
34
- import {findMarker} from "../markers";
30
+ import {Cursor, isScope, isTree, Tree} from "../../tree";
31
+ import {mapAsync} from "../../util";
32
+ import {produceAsync} from "../../visitor";
33
+ import {TabsAndIndentsStyle} from "../style";
34
+ import {findMarker} from "../../markers";
35
35
 
36
36
  type IndentKind = 'block' | 'continuation' | 'align';
37
37
  type IndentContext = [number, IndentKind]; // [indent, kind]