@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.
- package/dist/java/type.d.ts +5 -0
- package/dist/java/type.d.ts.map +1 -1
- package/dist/java/type.js +12 -0
- package/dist/java/type.js.map +1 -1
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +9 -0
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +5 -9
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/{format.d.ts → format/format.d.ts} +15 -33
- package/dist/javascript/format/format.d.ts.map +1 -0
- package/dist/javascript/{format.js → format/format.js} +56 -313
- package/dist/javascript/format/format.js.map +1 -0
- package/dist/javascript/format/index.d.ts +3 -0
- package/dist/javascript/format/index.d.ts.map +1 -0
- package/dist/javascript/format/index.js +38 -0
- package/dist/javascript/format/index.js.map +1 -0
- package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts +28 -0
- package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts.map +1 -0
- package/dist/javascript/format/minimum-viable-spacing-visitor.js +308 -0
- package/dist/javascript/format/minimum-viable-spacing-visitor.js.map +1 -0
- package/dist/javascript/format/normalize-whitespace-visitor.d.ts +14 -0
- package/dist/javascript/format/normalize-whitespace-visitor.d.ts.map +1 -0
- package/dist/javascript/format/normalize-whitespace-visitor.js +65 -0
- package/dist/javascript/format/normalize-whitespace-visitor.js.map +1 -0
- package/dist/javascript/format/prettier-config-loader.d.ts +92 -0
- package/dist/javascript/format/prettier-config-loader.d.ts.map +1 -0
- package/dist/javascript/format/prettier-config-loader.js +419 -0
- package/dist/javascript/format/prettier-config-loader.js.map +1 -0
- package/dist/javascript/format/prettier-format.d.ts +111 -0
- package/dist/javascript/format/prettier-format.d.ts.map +1 -0
- package/dist/javascript/format/prettier-format.js +496 -0
- package/dist/javascript/format/prettier-format.js.map +1 -0
- package/dist/javascript/{tabs-and-indents-visitor.d.ts → format/tabs-and-indents-visitor.d.ts} +4 -4
- package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -0
- package/dist/javascript/{tabs-and-indents-visitor.js → format/tabs-and-indents-visitor.js} +7 -7
- package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -0
- package/dist/javascript/format/whitespace-reconciler.d.ts +106 -0
- package/dist/javascript/format/whitespace-reconciler.d.ts.map +1 -0
- package/dist/javascript/format/whitespace-reconciler.js +291 -0
- package/dist/javascript/format/whitespace-reconciler.js.map +1 -0
- package/dist/javascript/markers.d.ts.map +1 -1
- package/dist/javascript/markers.js +21 -0
- package/dist/javascript/markers.js.map +1 -1
- package/dist/javascript/parser.d.ts +15 -3
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +107 -24
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/recipes/auto-format.d.ts +3 -0
- package/dist/javascript/recipes/auto-format.d.ts.map +1 -1
- package/dist/javascript/recipes/auto-format.js +22 -1
- package/dist/javascript/recipes/auto-format.js.map +1 -1
- package/dist/javascript/style.d.ts +52 -1
- package/dist/javascript/style.d.ts.map +1 -1
- package/dist/javascript/style.js +43 -2
- package/dist/javascript/style.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +3 -4
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +6 -18
- package/dist/test/rewrite-test.js.map +1 -1
- package/dist/version.txt +1 -1
- package/dist/yaml/assertions.d.ts +4 -0
- package/dist/yaml/assertions.d.ts.map +1 -0
- package/dist/yaml/assertions.js +31 -0
- package/dist/yaml/assertions.js.map +1 -0
- package/dist/yaml/index.d.ts +2 -1
- package/dist/yaml/index.d.ts.map +1 -1
- package/dist/yaml/index.js +2 -1
- package/dist/yaml/index.js.map +1 -1
- package/package.json +5 -4
- package/src/java/type.ts +12 -0
- package/src/javascript/assertions.ts +9 -0
- package/src/javascript/comparator.ts +6 -11
- package/src/javascript/{format.ts → format/format.ts} +59 -267
- package/src/javascript/format/index.ts +21 -0
- package/src/javascript/format/minimum-viable-spacing-visitor.ts +256 -0
- package/src/javascript/format/normalize-whitespace-visitor.ts +42 -0
- package/src/javascript/format/prettier-config-loader.ts +422 -0
- package/src/javascript/format/prettier-format.ts +622 -0
- package/src/javascript/{tabs-and-indents-visitor.ts → format/tabs-and-indents-visitor.ts} +8 -8
- package/src/javascript/format/whitespace-reconciler.ts +345 -0
- package/src/javascript/markers.ts +19 -0
- package/src/javascript/parser.ts +107 -20
- package/src/javascript/recipes/auto-format.ts +28 -1
- package/src/javascript/style.ts +41 -2
- package/src/test/rewrite-test.ts +6 -18
- package/src/yaml/assertions.ts +28 -0
- package/src/yaml/index.ts +2 -1
- package/dist/javascript/format.d.ts.map +0 -1
- package/dist/javascript/format.js.map +0 -1
- package/dist/javascript/tabs-and-indents-visitor.d.ts.map +0 -1
- 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
|
+
}
|