@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,42 @@
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 {JavaScriptVisitor} from "../visitor";
17
+ import {J} from "../../java";
18
+ import {Cursor, isScope, Tree} from "../../tree";
19
+
20
+ /**
21
+ * Ensures that whitespace is on the outermost AST element possible.
22
+ * Called NormalizeFormat in Java.
23
+ */
24
+ export class NormalizeWhitespaceVisitor<P> extends JavaScriptVisitor<P> {
25
+ constructor(private stopAfter?: Tree) {
26
+ super();
27
+ }
28
+
29
+ override async visit<R extends J>(tree: Tree, p: P, parent?: Cursor): Promise<R | undefined> {
30
+ if (this.cursor?.getNearestMessage("stop") != null) {
31
+ return tree as R;
32
+ }
33
+ return super.visit(tree, p, parent);
34
+ }
35
+
36
+ override async postVisit(tree: J, p: P): Promise<J | undefined> {
37
+ if (this.stopAfter != null && isScope(this.stopAfter, tree)) {
38
+ this.cursor?.root.messages.set("stop", true);
39
+ }
40
+ return super.postVisit(tree, p);
41
+ }
42
+ }
@@ -0,0 +1,422 @@
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 * as path from 'path';
17
+ import * as fs from 'fs';
18
+ import * as fsp from 'fs/promises';
19
+ import * as os from 'os';
20
+ import {spawnSync} from 'child_process';
21
+ import {PrettierStyle} from '../style';
22
+ import {randomId} from '../../uuid';
23
+
24
+ /**
25
+ * Cache of loaded Prettier modules by version.
26
+ * This ensures we don't reload the same version multiple times.
27
+ *
28
+ * Use `clearPrettierModuleCache()` to clear this cache if needed
29
+ * (e.g., in long-running processes to free memory).
30
+ */
31
+ const prettierModuleCache: Map<string, typeof import('prettier')> = new Map();
32
+
33
+ /**
34
+ * Cache of pending Prettier load operations.
35
+ * Prevents concurrent requests for the same version from duplicating work.
36
+ */
37
+ const pendingLoads: Map<string, Promise<typeof import('prettier')>> = new Map();
38
+
39
+ /**
40
+ * Clears the in-memory Prettier module cache.
41
+ * Call this in long-running processes to free memory when Prettier
42
+ * versions are no longer needed.
43
+ */
44
+ export function clearPrettierModuleCache(): void {
45
+ prettierModuleCache.clear();
46
+ pendingLoads.clear();
47
+ }
48
+
49
+ /**
50
+ * Gets the cache directory for a specific Prettier version.
51
+ * Uses ~/.cache/openrewrite/prettier/<version>/
52
+ */
53
+ function getPrettierCacheDir(version: string): string {
54
+ const cacheBase = path.join(os.homedir(), '.cache', 'openrewrite', 'prettier');
55
+ return path.join(cacheBase, version);
56
+ }
57
+
58
+ /**
59
+ * Checks if a Prettier version is installed in the cache.
60
+ */
61
+ function isPrettierCached(version: string): boolean {
62
+ const cacheDir = getPrettierCacheDir(version);
63
+ const prettierPath = path.join(cacheDir, 'node_modules', 'prettier', 'package.json');
64
+ if (!fs.existsSync(prettierPath)) {
65
+ return false;
66
+ }
67
+ // Verify the installed version matches
68
+ try {
69
+ const pkg = JSON.parse(fs.readFileSync(prettierPath, 'utf8'));
70
+ return pkg.version === version;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Installs a specific Prettier version to the cache directory.
78
+ */
79
+ async function installPrettierToCache(version: string): Promise<void> {
80
+ const cacheDir = getPrettierCacheDir(version);
81
+
82
+ // Create directory structure
83
+ await fsp.mkdir(cacheDir, {recursive: true});
84
+
85
+ // Create minimal package.json
86
+ const packageJson = {
87
+ name: `prettier-cache-${version}`,
88
+ version: '1.0.0',
89
+ private: true,
90
+ dependencies: {
91
+ prettier: version
92
+ }
93
+ };
94
+ await fsp.writeFile(
95
+ path.join(cacheDir, 'package.json'),
96
+ JSON.stringify(packageJson, null, 2)
97
+ );
98
+
99
+ // Run npm install
100
+ const result = spawnSync('npm', ['install', '--silent'], {
101
+ cwd: cacheDir,
102
+ encoding: 'utf-8',
103
+ stdio: ['pipe', 'pipe', 'pipe'],
104
+ timeout: 120000 // 2 minutes
105
+ });
106
+
107
+ if (result.error) {
108
+ throw new Error(`Failed to install Prettier ${version}: ${result.error.message}`);
109
+ }
110
+ if (result.status !== 0) {
111
+ const stderr = result.stderr?.trim() || '';
112
+ throw new Error(`Failed to install Prettier ${version}: npm exited with code ${result.status}${stderr ? '\n' + stderr : ''}`);
113
+ }
114
+
115
+ // Verify installation
116
+ if (!isPrettierCached(version)) {
117
+ throw new Error(`Prettier ${version} installation verification failed`);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Loads Prettier from the cache directory for a specific version.
123
+ */
124
+ function loadPrettierFromCache(version: string): typeof import('prettier') {
125
+ const cacheDir = getPrettierCacheDir(version);
126
+ const prettierPath = path.join(cacheDir, 'node_modules', 'prettier');
127
+
128
+ // Clear require cache for this path to ensure fresh load
129
+ // (in case the version was reinstalled)
130
+ const resolvedPath = require.resolve(prettierPath);
131
+ delete require.cache[resolvedPath];
132
+
133
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
134
+ return require(prettierPath);
135
+ }
136
+
137
+ /**
138
+ * Result of detecting Prettier in a project.
139
+ */
140
+ export interface PrettierDetectionResult {
141
+ /**
142
+ * Whether Prettier is available in the project.
143
+ */
144
+ available: boolean;
145
+ /**
146
+ * The Prettier version from the project's node_modules.
147
+ */
148
+ version?: string;
149
+ /**
150
+ * Reference to the bundled Prettier for resolving configs.
151
+ * We use our bundled version to resolve configs (stable API),
152
+ * but at formatting time, the project's version will be loaded dynamically.
153
+ */
154
+ bundledPrettier?: typeof import('prettier');
155
+ }
156
+
157
+ /**
158
+ * Manages Prettier configuration detection and loading for a parse session.
159
+ *
160
+ * This class:
161
+ * 1. Detects if Prettier is installed in the project
162
+ * 2. Resolves Prettier config per-file (with overrides)
163
+ * 3. Caches resolved configs for marker deduplication
164
+ * 4. Creates PrettierStyle markers for source files
165
+ *
166
+ * The marker only stores the version and resolved config - at formatting time,
167
+ * the correct version of Prettier will be loaded dynamically (like npx).
168
+ */
169
+ export class PrettierConfigLoader {
170
+ private detection?: PrettierDetectionResult;
171
+ private configCache: Map<string, PrettierStyle> = new Map();
172
+
173
+ /**
174
+ * Creates a new PrettierConfigLoader for a project.
175
+ *
176
+ * @param projectRoot The root directory of the project (where package.json is)
177
+ */
178
+ constructor(private readonly projectRoot: string) {}
179
+
180
+ /**
181
+ * Detects Prettier in the project and loads our bundled version for config resolution.
182
+ * Call this once at the start of parsing.
183
+ */
184
+ async detectPrettier(): Promise<PrettierDetectionResult> {
185
+ if (this.detection) {
186
+ return this.detection;
187
+ }
188
+
189
+ this.detection = { available: false };
190
+
191
+ try {
192
+ // Get the project's Prettier version from package.json or node_modules
193
+ const version = this.getPrettierVersionFromProject();
194
+ if (!version) {
195
+ return this.detection;
196
+ }
197
+
198
+ // Load our bundled Prettier for config resolution
199
+ // We use dynamic require to handle cases where Prettier isn't installed
200
+ let bundledPrettier: typeof import('prettier');
201
+ try {
202
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
203
+ bundledPrettier = require('prettier');
204
+ } catch (e) {
205
+ // Our bundled Prettier isn't available
206
+ console.warn('PrettierConfigLoader: Failed to load bundled Prettier:', e);
207
+ return this.detection;
208
+ }
209
+
210
+ this.detection = {
211
+ available: true,
212
+ version,
213
+ bundledPrettier
214
+ };
215
+ } catch {
216
+ // Any error means Prettier isn't properly set up
217
+ this.detection = { available: false };
218
+ }
219
+
220
+ return this.detection;
221
+ }
222
+
223
+ /**
224
+ * Gets the Prettier version from the project's package.json or node_modules.
225
+ * Scans upward from projectRoot to support monorepo structures where Prettier
226
+ * is installed at the repository root.
227
+ */
228
+ private getPrettierVersionFromProject(): string | undefined {
229
+ // Scan upward from projectRoot to find Prettier
230
+ // This handles monorepos where Prettier is at the root but files are in subdirectories
231
+ let dir = this.projectRoot;
232
+
233
+ while (true) {
234
+ // First, check for prettier in node_modules (actual installation)
235
+ const prettierPackageJson = path.join(dir, 'node_modules', 'prettier', 'package.json');
236
+ if (fs.existsSync(prettierPackageJson)) {
237
+ try {
238
+ const pkg = JSON.parse(fs.readFileSync(prettierPackageJson, 'utf8'));
239
+ return pkg.version;
240
+ } catch {
241
+ // Corrupted package.json, continue scanning
242
+ }
243
+ }
244
+
245
+ // Check package.json for prettier dependency (might be a range like ^3.0.0)
246
+ const packageJsonPath = path.join(dir, 'package.json');
247
+ if (fs.existsSync(packageJsonPath)) {
248
+ try {
249
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
250
+ const deps = {
251
+ ...packageJson.dependencies,
252
+ ...packageJson.devDependencies
253
+ };
254
+ if (deps.prettier) {
255
+ // Found prettier as a dependency, but node_modules wasn't found
256
+ // This might mean dependencies aren't installed yet
257
+ // Continue scanning upward in case there's a parent with node_modules
258
+ }
259
+ } catch {
260
+ // package.json parse error, continue
261
+ }
262
+ }
263
+
264
+ // Move up to parent directory
265
+ const parent = path.dirname(dir);
266
+ if (parent === dir) {
267
+ // Reached filesystem root
268
+ break;
269
+ }
270
+ dir = parent;
271
+ }
272
+
273
+ return undefined;
274
+ }
275
+
276
+ /**
277
+ * Finds the .prettierignore file by scanning upward from projectRoot.
278
+ * Returns undefined if no .prettierignore file is found.
279
+ */
280
+ private findPrettierIgnore(): string | undefined {
281
+ let dir = this.projectRoot;
282
+
283
+ while (true) {
284
+ const ignorePath = path.join(dir, '.prettierignore');
285
+ if (fs.existsSync(ignorePath)) {
286
+ return ignorePath;
287
+ }
288
+
289
+ const parent = path.dirname(dir);
290
+ if (parent === dir) {
291
+ // Reached filesystem root
292
+ break;
293
+ }
294
+ dir = parent;
295
+ }
296
+
297
+ return undefined;
298
+ }
299
+
300
+ /**
301
+ * Gets or creates a PrettierStyle marker for the given source file.
302
+ * Returns undefined if Prettier is not available or no config applies to this file.
303
+ *
304
+ * @param filePath Absolute path to the source file
305
+ */
306
+ async getConfigMarker(filePath: string): Promise<PrettierStyle | undefined> {
307
+ if (!this.detection?.available || !this.detection.bundledPrettier) {
308
+ return undefined;
309
+ }
310
+
311
+ try {
312
+ // Resolve path against projectRoot if not absolute
313
+ const absolutePath = path.isAbsolute(filePath)
314
+ ? filePath
315
+ : path.join(this.projectRoot, filePath);
316
+
317
+ // Check if file is ignored by .prettierignore
318
+ const ignorePath = this.findPrettierIgnore();
319
+ let ignored = false;
320
+ if (ignorePath) {
321
+ const fileInfo = await this.detection.bundledPrettier.getFileInfo(absolutePath, { ignorePath });
322
+ ignored = fileInfo.ignored;
323
+ }
324
+
325
+ // Resolve config for this specific file (applies overrides)
326
+ // If no config file exists, use empty config (Prettier defaults)
327
+ const config = await this.detection.bundledPrettier.resolveConfig(absolutePath) ?? {};
328
+
329
+ // Create a cache key from the resolved config + version + ignored status
330
+ const configKey = JSON.stringify({ config, version: this.detection.version, ignored });
331
+
332
+ // Check cache for existing marker with same config
333
+ let marker = this.configCache.get(configKey);
334
+ if (marker) {
335
+ return marker;
336
+ }
337
+
338
+ // Create new PrettierStyle instance
339
+ marker = new PrettierStyle(randomId(), config, this.detection.version, ignored);
340
+
341
+ // Cache and return
342
+ this.configCache.set(configKey, marker);
343
+ return marker;
344
+ } catch (e) {
345
+ // Config resolution failed for this file
346
+ console.warn(`PrettierConfigLoader: Failed to resolve config for ${filePath}:`, e);
347
+ return undefined;
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Clears the config cache. Call this between parse batches if needed.
353
+ */
354
+ clearCache(): void {
355
+ this.configCache.clear();
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Dynamically loads a specific version of Prettier for formatting.
361
+ *
362
+ * This function will attempt to load Prettier in this order:
363
+ * 1. From the in-memory cache (if already loaded)
364
+ * 2. From the current working directory's node_modules (if version matches)
365
+ * 3. From the cached npm project at ~/.cache/openrewrite/prettier/<version>/
366
+ * 4. Install to cache and load from there
367
+ *
368
+ * Concurrent requests for the same version are deduplicated.
369
+ *
370
+ * @param version The Prettier version to load (e.g., "3.4.2")
371
+ * @returns The loaded Prettier module
372
+ */
373
+ export async function loadPrettierVersion(version: string): Promise<typeof import('prettier')> {
374
+ // Check in-memory cache first
375
+ const cached = prettierModuleCache.get(version);
376
+ if (cached) {
377
+ return cached;
378
+ }
379
+
380
+ // Check if there's already a pending load for this version
381
+ const pending = pendingLoads.get(version);
382
+ if (pending) {
383
+ return pending;
384
+ }
385
+
386
+ // Create and cache the load promise to prevent concurrent duplicate loads
387
+ const loadPromise = loadPrettierVersionInternal(version);
388
+ pendingLoads.set(version, loadPromise);
389
+
390
+ try {
391
+ const prettier = await loadPromise;
392
+ prettierModuleCache.set(version, prettier);
393
+ return prettier;
394
+ } finally {
395
+ pendingLoads.delete(version);
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Internal implementation of Prettier version loading.
401
+ */
402
+ async function loadPrettierVersionInternal(version: string): Promise<typeof import('prettier')> {
403
+ // Try to load from local node_modules if version matches
404
+ try {
405
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
406
+ const localPrettier = require('prettier');
407
+ if (localPrettier.version === version) {
408
+ return localPrettier;
409
+ }
410
+ } catch {
411
+ // Local prettier not available
412
+ }
413
+
414
+ // Check if version is cached on disk
415
+ if (isPrettierCached(version)) {
416
+ return loadPrettierFromCache(version);
417
+ }
418
+
419
+ // Install to cache and load
420
+ await installPrettierToCache(version);
421
+ return loadPrettierFromCache(version);
422
+ }