@openrewrite/rewrite 8.70.0-20251219-160440 → 8.70.0-20251219-215811
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/cli/cli-utils.d.ts +6 -6
- package/dist/cli/cli-utils.d.ts.map +1 -1
- package/dist/cli/cli-utils.js +50 -228
- package/dist/cli/cli-utils.js.map +1 -1
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +87 -12
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/autodetect.d.ts +11 -11
- package/dist/javascript/autodetect.d.ts.map +1 -1
- package/dist/javascript/autodetect.js +18 -21
- package/dist/javascript/autodetect.js.map +1 -1
- package/dist/javascript/format/prettier-config-loader.d.ts.map +1 -1
- package/dist/javascript/format/prettier-config-loader.js +1 -1
- package/dist/javascript/format/prettier-config-loader.js.map +1 -1
- package/dist/javascript/index.d.ts +1 -0
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +1 -0
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/markers.d.ts.map +1 -1
- package/dist/javascript/markers.js +135 -6
- package/dist/javascript/markers.js.map +1 -1
- package/dist/javascript/node-resolution-result.d.ts +4 -1
- package/dist/javascript/node-resolution-result.d.ts.map +1 -1
- package/dist/javascript/node-resolution-result.js +22 -1
- package/dist/javascript/node-resolution-result.js.map +1 -1
- package/dist/javascript/package-json-parser.d.ts +7 -0
- package/dist/javascript/package-json-parser.d.ts.map +1 -1
- package/dist/javascript/package-json-parser.js +19 -1
- package/dist/javascript/package-json-parser.js.map +1 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +1 -13
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/preconditions.js +4 -4
- package/dist/javascript/preconditions.js.map +1 -1
- package/dist/javascript/project-parser.d.ts +137 -0
- package/dist/javascript/project-parser.d.ts.map +1 -0
- package/dist/javascript/project-parser.js +726 -0
- package/dist/javascript/project-parser.js.map +1 -0
- package/dist/javascript/style.d.ts +9 -26
- package/dist/javascript/style.d.ts.map +1 -1
- package/dist/javascript/style.js +18 -42
- package/dist/javascript/style.js.map +1 -1
- package/dist/json/parser.d.ts.map +1 -1
- package/dist/json/parser.js +1 -0
- package/dist/json/parser.js.map +1 -1
- package/dist/markers.d.ts +1 -1
- package/dist/markers.js +1 -1
- package/dist/markers.js.map +1 -1
- package/dist/parser.d.ts +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/rpc/index.d.ts +0 -1
- package/dist/rpc/index.d.ts.map +1 -1
- package/dist/rpc/index.js +4 -2
- package/dist/rpc/index.js.map +1 -1
- package/dist/rpc/request/index.d.ts +1 -0
- package/dist/rpc/request/index.d.ts.map +1 -1
- package/dist/rpc/request/index.js +1 -0
- package/dist/rpc/request/index.js.map +1 -1
- package/dist/rpc/request/parse-project.d.ts +25 -0
- package/dist/rpc/request/parse-project.d.ts.map +1 -0
- package/dist/rpc/request/parse-project.js +304 -0
- package/dist/rpc/request/parse-project.js.map +1 -0
- package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
- package/dist/rpc/rewrite-rpc.js +1 -0
- package/dist/rpc/rewrite-rpc.js.map +1 -1
- package/dist/text/parser.d.ts.map +1 -1
- package/dist/text/parser.js +1 -0
- package/dist/text/parser.js.map +1 -1
- package/dist/version.txt +1 -1
- package/dist/yaml/parser.d.ts.map +1 -1
- package/dist/yaml/parser.js +52 -4
- package/dist/yaml/parser.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/cli-utils.ts +46 -237
- package/src/javascript/assertions.ts +74 -10
- package/src/javascript/autodetect.ts +22 -15
- package/src/javascript/format/prettier-config-loader.ts +2 -2
- package/src/javascript/index.ts +1 -0
- package/src/javascript/markers.ts +157 -7
- package/src/javascript/node-resolution-result.ts +23 -2
- package/src/javascript/package-json-parser.ts +19 -1
- package/src/javascript/parser.ts +1 -16
- package/src/javascript/preconditions.ts +1 -1
- package/src/javascript/project-parser.ts +657 -0
- package/src/javascript/style.ts +43 -28
- package/src/json/parser.ts +3 -1
- package/src/markers.ts +1 -1
- package/src/parser.ts +1 -1
- package/src/rpc/index.ts +7 -5
- package/src/rpc/request/index.ts +1 -0
- package/src/rpc/request/parse-project.ts +283 -0
- package/src/rpc/rewrite-rpc.ts +2 -0
- package/src/text/parser.ts +3 -1
- package/src/yaml/parser.ts +53 -5
package/src/cli/cli-utils.ts
CHANGED
|
@@ -14,22 +14,11 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import * as fs from 'fs';
|
|
17
|
-
import * as fsp from 'fs/promises';
|
|
18
17
|
import * as path from 'path';
|
|
19
|
-
import {spawn
|
|
18
|
+
import {spawn} from 'child_process';
|
|
20
19
|
import {Recipe, RecipeRegistry} from '../recipe';
|
|
21
20
|
import {SourceFile} from '../tree';
|
|
22
|
-
import {
|
|
23
|
-
isYarnBerryLockFile,
|
|
24
|
-
JavaScriptParser,
|
|
25
|
-
JSON_LOCK_FILE_NAMES,
|
|
26
|
-
PackageJsonParser,
|
|
27
|
-
TEXT_LOCK_FILE_NAMES,
|
|
28
|
-
YAML_LOCK_FILE_NAMES
|
|
29
|
-
} from '../javascript';
|
|
30
|
-
import {JsonParser} from '../json';
|
|
31
|
-
import {PlainTextParser} from '../text';
|
|
32
|
-
import {YamlParser} from '../yaml';
|
|
21
|
+
import {ProjectParser} from '../javascript/project-parser';
|
|
33
22
|
|
|
34
23
|
// ANSI color codes
|
|
35
24
|
const colors = {
|
|
@@ -339,123 +328,28 @@ export function findRecipe(
|
|
|
339
328
|
}
|
|
340
329
|
|
|
341
330
|
/**
|
|
342
|
-
* Discover source files in a project directory, respecting .gitignore
|
|
331
|
+
* Discover source files in a project directory, respecting .gitignore.
|
|
332
|
+
* Delegates to ProjectParser for file discovery.
|
|
343
333
|
*/
|
|
344
334
|
export async function discoverFiles(projectRoot: string, verbose: boolean = false): Promise<string[]> {
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
for (const line of result.stdout.split('\n')) {
|
|
360
|
-
if (line.trim()) {
|
|
361
|
-
ignoredFiles.add(path.join(projectRoot, line.trim()));
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
} catch {
|
|
366
|
-
// Git not available or not a git repository
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Get tracked and untracked (but not ignored) files
|
|
370
|
-
const trackedFiles = new Set<string>();
|
|
371
|
-
try {
|
|
372
|
-
// Get tracked files
|
|
373
|
-
const tracked = spawnSync('git', ['ls-files'], {
|
|
374
|
-
cwd: projectRoot,
|
|
375
|
-
encoding: 'utf8'
|
|
376
|
-
});
|
|
377
|
-
// Check if git command failed (not a git repository)
|
|
378
|
-
if (tracked.status !== 0 || tracked.error) {
|
|
379
|
-
// Not a git repository, fall back to recursive directory scan
|
|
380
|
-
await walkDirectory(projectRoot, files, ignoredFiles, projectRoot);
|
|
381
|
-
return files.filter(isAcceptedFile);
|
|
382
|
-
}
|
|
383
|
-
if (tracked.stdout) {
|
|
384
|
-
for (const line of tracked.stdout.split('\n')) {
|
|
385
|
-
if (line.trim()) {
|
|
386
|
-
trackedFiles.add(path.join(projectRoot, line.trim()));
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Get untracked but not ignored files
|
|
392
|
-
const untracked = spawnSync('git', ['ls-files', '--others', '--exclude-standard'], {
|
|
393
|
-
cwd: projectRoot,
|
|
394
|
-
encoding: 'utf8'
|
|
395
|
-
});
|
|
396
|
-
if (untracked.stdout) {
|
|
397
|
-
for (const line of untracked.stdout.split('\n')) {
|
|
398
|
-
if (line.trim()) {
|
|
399
|
-
trackedFiles.add(path.join(projectRoot, line.trim()));
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
} catch {
|
|
404
|
-
// Not a git repository, fall back to recursive directory scan
|
|
405
|
-
await walkDirectory(projectRoot, files, ignoredFiles, projectRoot);
|
|
406
|
-
return files.filter(isAcceptedFile);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Filter to accepted file types that exist on disk
|
|
410
|
-
// (git ls-files returns deleted files that are still tracked)
|
|
411
|
-
for (const file of trackedFiles) {
|
|
412
|
-
if (!ignoredFiles.has(file) && isAcceptedFile(file) && fs.existsSync(file)) {
|
|
413
|
-
files.push(file);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return files;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Walk a directory recursively, collecting files
|
|
422
|
-
*/
|
|
423
|
-
export async function walkDirectory(
|
|
424
|
-
dir: string,
|
|
425
|
-
files: string[],
|
|
426
|
-
ignored: Set<string>,
|
|
427
|
-
projectRoot: string
|
|
428
|
-
): Promise<void> {
|
|
429
|
-
const entries = await fsp.readdir(dir, {withFileTypes: true});
|
|
430
|
-
|
|
431
|
-
for (const entry of entries) {
|
|
432
|
-
const fullPath = path.join(dir, entry.name);
|
|
433
|
-
|
|
434
|
-
// Skip hidden files and common ignore patterns
|
|
435
|
-
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist' ||
|
|
436
|
-
entry.name === 'build' || entry.name === 'coverage') {
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (ignored.has(fullPath)) {
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (entry.isDirectory()) {
|
|
445
|
-
await walkDirectory(fullPath, files, ignored, projectRoot);
|
|
446
|
-
} else if (entry.isFile() && isAcceptedFile(fullPath)) {
|
|
447
|
-
files.push(fullPath);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
335
|
+
const parser = new ProjectParser(projectRoot, {verbose});
|
|
336
|
+
const discovered = await parser.discoverFiles();
|
|
337
|
+
|
|
338
|
+
// Flatten all discovered files into a single array
|
|
339
|
+
return [
|
|
340
|
+
...discovered.packageJsonFiles,
|
|
341
|
+
...discovered.lockFiles.json,
|
|
342
|
+
...discovered.lockFiles.yaml,
|
|
343
|
+
...discovered.lockFiles.text,
|
|
344
|
+
...discovered.jsFiles,
|
|
345
|
+
...discovered.jsonFiles,
|
|
346
|
+
...discovered.yamlFiles,
|
|
347
|
+
...discovered.textFiles
|
|
348
|
+
];
|
|
450
349
|
}
|
|
451
350
|
|
|
452
351
|
/**
|
|
453
|
-
*
|
|
454
|
-
*/
|
|
455
|
-
const ALL_LOCK_FILE_NAMES: readonly string[] = [...JSON_LOCK_FILE_NAMES, ...YAML_LOCK_FILE_NAMES, ...TEXT_LOCK_FILE_NAMES];
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Check if a file is accepted for parsing based on its extension
|
|
352
|
+
* Check if a file is accepted for parsing based on its extension.
|
|
459
353
|
*/
|
|
460
354
|
export function isAcceptedFile(filePath: string): boolean {
|
|
461
355
|
const ext = path.extname(filePath).toLowerCase();
|
|
@@ -466,7 +360,7 @@ export function isAcceptedFile(filePath: string): boolean {
|
|
|
466
360
|
return true;
|
|
467
361
|
}
|
|
468
362
|
|
|
469
|
-
// JSON files
|
|
363
|
+
// JSON files
|
|
470
364
|
if (ext === '.json') {
|
|
471
365
|
return true;
|
|
472
366
|
}
|
|
@@ -476,8 +370,13 @@ export function isAcceptedFile(filePath: string): boolean {
|
|
|
476
370
|
return true;
|
|
477
371
|
}
|
|
478
372
|
|
|
479
|
-
// Lock files (
|
|
480
|
-
if (
|
|
373
|
+
// Lock files (yarn.lock has no extension)
|
|
374
|
+
if (['yarn.lock', 'pnpm-lock.yaml', 'package-lock.json', 'bun.lock'].includes(basename)) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Text config files
|
|
379
|
+
if (['.prettierignore', '.gitignore', '.npmignore', '.eslintignore'].includes(basename)) {
|
|
481
380
|
return true;
|
|
482
381
|
}
|
|
483
382
|
|
|
@@ -491,127 +390,37 @@ export interface ParseFilesOptions {
|
|
|
491
390
|
onProgress?: ProgressCallback;
|
|
492
391
|
}
|
|
493
392
|
|
|
494
|
-
/**
|
|
495
|
-
* Internal context for file parsing progress tracking.
|
|
496
|
-
*/
|
|
497
|
-
interface ParseContext {
|
|
498
|
-
current: number;
|
|
499
|
-
total: number;
|
|
500
|
-
verbose: boolean;
|
|
501
|
-
onProgress?: ProgressCallback;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
* Helper to parse files with a given parser, handling verbose logging and progress.
|
|
506
|
-
*/
|
|
507
|
-
async function* parseWithParser(
|
|
508
|
-
files: string[],
|
|
509
|
-
parser: { parse(...files: string[]): AsyncGenerator<SourceFile> },
|
|
510
|
-
fileType: string,
|
|
511
|
-
ctx: ParseContext
|
|
512
|
-
): AsyncGenerator<SourceFile, ParseContext> {
|
|
513
|
-
if (files.length === 0) {
|
|
514
|
-
return ctx;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (ctx.verbose) {
|
|
518
|
-
console.log(`Parsing ${files.length} ${fileType} files...`);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
for await (const sf of parser.parse(...files)) {
|
|
522
|
-
ctx.current++;
|
|
523
|
-
ctx.onProgress?.(ctx.current, ctx.total, sf.sourcePath);
|
|
524
|
-
yield sf;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return ctx;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Classifies a yarn.lock file as YAML (Berry) or text (Classic) based on its content.
|
|
532
|
-
* Returns 'yaml' for Yarn Berry (v2+) and 'text' for Yarn Classic (v1).
|
|
533
|
-
*/
|
|
534
|
-
async function classifyYarnLockFile(filePath: string): Promise<'yaml' | 'text'> {
|
|
535
|
-
try {
|
|
536
|
-
const content = await fsp.readFile(filePath, 'utf-8');
|
|
537
|
-
return isYarnBerryLockFile(content) ? 'yaml' : 'text';
|
|
538
|
-
} catch {
|
|
539
|
-
// Default to text format if we can't read the file
|
|
540
|
-
return 'text';
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
393
|
/**
|
|
545
394
|
* Parse source files using appropriate parsers (streaming version).
|
|
546
395
|
* Yields source files as they are parsed, allowing immediate processing.
|
|
396
|
+
*
|
|
397
|
+
* Uses ProjectParser with a file filter to parse only the specified files.
|
|
398
|
+
* This handles Prettier detection, file classification, and appropriate parser selection.
|
|
547
399
|
*/
|
|
548
400
|
export async function* parseFilesStreaming(
|
|
549
401
|
filePaths: string[],
|
|
550
402
|
projectRoot: string,
|
|
551
403
|
options: ParseFilesOptions = {}
|
|
552
404
|
): AsyncGenerator<SourceFile, void, undefined> {
|
|
553
|
-
const {
|
|
554
|
-
const total = filePaths.length;
|
|
555
|
-
let current = 0;
|
|
405
|
+
const {verbose = false, onProgress} = options;
|
|
556
406
|
|
|
557
|
-
//
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
const jsonLockFiles: string[] = [];
|
|
562
|
-
const yamlLockFiles: string[] = [];
|
|
563
|
-
const yamlFiles: string[] = [];
|
|
564
|
-
const textLockFiles: string[] = [];
|
|
565
|
-
|
|
566
|
-
// Collect yarn.lock files for content-based classification
|
|
567
|
-
const yarnLockFiles: string[] = [];
|
|
568
|
-
|
|
569
|
-
for (const filePath of filePaths) {
|
|
570
|
-
const basename = path.basename(filePath);
|
|
571
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
572
|
-
|
|
573
|
-
if (basename === 'package.json') {
|
|
574
|
-
packageJsonFiles.push(filePath);
|
|
575
|
-
} else if (['.js', '.jsx', '.ts', '.tsx', '.mjs', '.mts', '.cjs', '.cts'].includes(ext)) {
|
|
576
|
-
jsFiles.push(filePath);
|
|
577
|
-
} else if ((JSON_LOCK_FILE_NAMES as readonly string[]).includes(basename)) {
|
|
578
|
-
jsonLockFiles.push(filePath);
|
|
579
|
-
} else if ((YAML_LOCK_FILE_NAMES as readonly string[]).includes(basename)) {
|
|
580
|
-
yamlLockFiles.push(filePath);
|
|
581
|
-
} else if (basename === 'yarn.lock') {
|
|
582
|
-
// yarn.lock needs content-based classification
|
|
583
|
-
yarnLockFiles.push(filePath);
|
|
584
|
-
} else if ((TEXT_LOCK_FILE_NAMES as readonly string[]).includes(basename)) {
|
|
585
|
-
// Other text lock files (if any besides yarn.lock)
|
|
586
|
-
textLockFiles.push(filePath);
|
|
587
|
-
} else if (ext === '.json') {
|
|
588
|
-
jsonFiles.push(filePath);
|
|
589
|
-
} else if (['.yaml', '.yml'].includes(ext)) {
|
|
590
|
-
yamlFiles.push(filePath);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
407
|
+
// Create a set for fast lookup
|
|
408
|
+
const fileSet = new Set(filePaths.map(f => path.resolve(f)));
|
|
409
|
+
let current = 0;
|
|
410
|
+
const total = filePaths.length;
|
|
593
411
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
412
|
+
const parser = new ProjectParser(projectRoot, {
|
|
413
|
+
verbose,
|
|
414
|
+
fileFilter: (absolutePath) => fileSet.has(absolutePath),
|
|
415
|
+
onProgress: onProgress ? (phase, cur, tot, filePath) => {
|
|
416
|
+
if (phase === "parsing" && filePath) {
|
|
417
|
+
current++;
|
|
418
|
+
onProgress(current, total, filePath);
|
|
419
|
+
}
|
|
420
|
+
} : undefined
|
|
421
|
+
});
|
|
603
422
|
|
|
604
|
-
|
|
605
|
-
const ctx: ParseContext = { current, total, verbose, onProgress };
|
|
606
|
-
|
|
607
|
-
// Parse files by type using helper
|
|
608
|
-
yield* parseWithParser(jsFiles, new JavaScriptParser({relativeTo: projectRoot}), 'JavaScript/TypeScript', ctx);
|
|
609
|
-
yield* parseWithParser(packageJsonFiles, new PackageJsonParser({relativeTo: projectRoot}), 'package.json', ctx);
|
|
610
|
-
yield* parseWithParser(jsonLockFiles, new JsonParser({relativeTo: projectRoot}), 'JSON lock', ctx);
|
|
611
|
-
yield* parseWithParser(yamlLockFiles, new YamlParser({relativeTo: projectRoot}), 'YAML lock', ctx);
|
|
612
|
-
yield* parseWithParser(textLockFiles, new PlainTextParser({relativeTo: projectRoot}), 'text lock', ctx);
|
|
613
|
-
yield* parseWithParser(yamlFiles, new YamlParser({relativeTo: projectRoot}), 'YAML', ctx);
|
|
614
|
-
yield* parseWithParser(jsonFiles, new JsonParser({relativeTo: projectRoot}), 'JSON', ctx);
|
|
423
|
+
yield* parser.parse();
|
|
615
424
|
}
|
|
616
425
|
|
|
617
426
|
/**
|
|
@@ -21,8 +21,12 @@ import ts from 'typescript';
|
|
|
21
21
|
import {json, Json} from "../json";
|
|
22
22
|
import {DependencyWorkspace} from "./dependency-workspace";
|
|
23
23
|
import {setTemplateSourceFileCache} from "./templating/engine";
|
|
24
|
+
import {PrettierConfigLoader} from "./format/prettier-config-loader";
|
|
25
|
+
import {produce} from "immer";
|
|
24
26
|
import * as fs from "fs";
|
|
25
27
|
import * as path from "path";
|
|
28
|
+
import {Autodetect} from "./autodetect";
|
|
29
|
+
import {Marker} from "../markers";
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
32
|
* Shared TypeScript source file cache for test parsers.
|
|
@@ -51,6 +55,20 @@ export async function* npm(relativeTo: string, ...sourceSpecs: SourceSpec<any>[]
|
|
|
51
55
|
);
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
// Write non-JS/TS files to disk FIRST so Prettier config detection can find them
|
|
59
|
+
for (const spec of sourceSpecs) {
|
|
60
|
+
if (spec.path !== 'package.json' && spec.path !== 'package-lock.json' && spec.kind !== JS.Kind.CompilationUnit) {
|
|
61
|
+
if (spec.before && spec.path) {
|
|
62
|
+
const filePath = path.join(relativeTo, spec.path);
|
|
63
|
+
const dir = path.dirname(filePath);
|
|
64
|
+
if (!fs.existsSync(dir)) {
|
|
65
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
fs.writeFileSync(filePath, spec.before);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
// Yield package.json FIRST so its PackageJsonParser is used for all JSON specs
|
|
55
73
|
// (The test framework uses the first spec's parser for all specs of the same kind)
|
|
56
74
|
if (packageJsonSpec) {
|
|
@@ -100,23 +118,69 @@ export async function* npm(relativeTo: string, ...sourceSpecs: SourceSpec<any>[]
|
|
|
100
118
|
yield packageLockSpec;
|
|
101
119
|
}
|
|
102
120
|
|
|
121
|
+
// Detect Prettier configuration for the project
|
|
122
|
+
const prettierLoader = new PrettierConfigLoader(relativeTo);
|
|
123
|
+
const detection = await prettierLoader.detectPrettier();
|
|
124
|
+
|
|
125
|
+
// Collect JS/TS specs for potential auto-detection
|
|
126
|
+
const jsSpecs = sourceSpecs.filter(
|
|
127
|
+
spec => spec.path !== 'package.json' && spec.path !== 'package-lock.json' && spec.kind === JS.Kind.CompilationUnit
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Build style marker: either PrettierStyle per-file or Autodetect for all
|
|
131
|
+
let autodetectMarker: Marker | null = null;
|
|
132
|
+
if (!detection.available && jsSpecs.length > 0) {
|
|
133
|
+
// Prettier is NOT available: auto-detect styles from the source files
|
|
134
|
+
// Pre-parse all JS specs to sample them
|
|
135
|
+
const detector = Autodetect.detector();
|
|
136
|
+
const tempParser = new JavaScriptParser({sourceFileCache, relativeTo});
|
|
137
|
+
|
|
138
|
+
for (const spec of jsSpecs) {
|
|
139
|
+
if (spec.before) {
|
|
140
|
+
const fileName = spec.path ?? `file.${spec.ext}`;
|
|
141
|
+
const filePath = path.join(relativeTo, fileName);
|
|
142
|
+
for await (const sf of tempParser.parse({text: spec.before, sourcePath: filePath})) {
|
|
143
|
+
if (sf.kind === JS.Kind.CompilationUnit) {
|
|
144
|
+
await detector.sample(sf as JS.CompilationUnit);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
autodetectMarker = detector.build();
|
|
150
|
+
}
|
|
151
|
+
|
|
103
152
|
for (const spec of sourceSpecs) {
|
|
104
153
|
if (spec.path !== 'package.json' && spec.path !== 'package-lock.json') {
|
|
105
154
|
if (spec.kind === JS.Kind.CompilationUnit) {
|
|
155
|
+
// For JS/TS files, use a parser that adds style marker if available
|
|
156
|
+
// spec.path may be undefined, so generate a reasonable path from the extension
|
|
157
|
+
const fileName = spec.path ?? `file.${spec.ext}`;
|
|
158
|
+
const filePath = path.join(relativeTo, fileName);
|
|
159
|
+
|
|
160
|
+
// Get the appropriate style marker
|
|
161
|
+
let styleMarker: Marker | undefined;
|
|
162
|
+
if (detection.available) {
|
|
163
|
+
// Use per-file PrettierStyle marker
|
|
164
|
+
styleMarker = await prettierLoader.getConfigMarker(filePath);
|
|
165
|
+
} else {
|
|
166
|
+
// Use shared Autodetect marker
|
|
167
|
+
styleMarker = autodetectMarker ?? undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
106
170
|
yield {
|
|
107
171
|
...spec,
|
|
108
|
-
parser: () => new JavaScriptParser({sourceFileCache, relativeTo})
|
|
172
|
+
parser: () => new JavaScriptParser({sourceFileCache, relativeTo}),
|
|
173
|
+
// Add style marker before recipe runs if available
|
|
174
|
+
// Compose with existing beforeRecipe if present
|
|
175
|
+
beforeRecipe: styleMarker ? (sf: JS.CompilationUnit) => {
|
|
176
|
+
const withMarker = produce(sf, draft => {
|
|
177
|
+
draft.markers.markers = draft.markers.markers.concat([styleMarker]);
|
|
178
|
+
});
|
|
179
|
+
return spec.beforeRecipe ? spec.beforeRecipe(withMarker) : withMarker;
|
|
180
|
+
} : spec.beforeRecipe
|
|
109
181
|
}
|
|
110
182
|
} else {
|
|
111
|
-
//
|
|
112
|
-
if (spec.before && spec.path) {
|
|
113
|
-
const filePath = path.join(relativeTo, spec.path);
|
|
114
|
-
const dir = path.dirname(filePath);
|
|
115
|
-
if (!fs.existsSync(dir)) {
|
|
116
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
117
|
-
}
|
|
118
|
-
fs.writeFileSync(filePath, spec.before);
|
|
119
|
-
}
|
|
183
|
+
// Non-JS/TS files were already written to disk above
|
|
120
184
|
yield spec;
|
|
121
185
|
}
|
|
122
186
|
}
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
WrappingAndBracesStyle,
|
|
29
29
|
WrappingAndBracesStyleDetailKind
|
|
30
30
|
} from "./style";
|
|
31
|
+
import {UUID} from "../uuid";
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Auto-detected styles for JavaScript/TypeScript code.
|
|
@@ -36,21 +37,27 @@ import {
|
|
|
36
37
|
* - Indent size (2, 4, etc.)
|
|
37
38
|
* - Spaces within ES6 import/export braces
|
|
38
39
|
*/
|
|
39
|
-
export
|
|
40
|
-
readonly kind
|
|
41
|
-
readonly
|
|
42
|
-
readonly
|
|
43
|
-
readonly
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
export interface Autodetect extends NamedStyles<typeof StyleKind.Autodetect> {
|
|
41
|
+
readonly kind: typeof StyleKind.Autodetect;
|
|
42
|
+
readonly name: "org.openrewrite.javascript.Autodetect";
|
|
43
|
+
readonly displayName: "Auto-detected";
|
|
44
|
+
readonly description: "Automatically detect styles from a repository's existing code.";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function autodetect(id: UUID, styles: Style[]): Autodetect {
|
|
48
|
+
return {
|
|
49
|
+
kind: StyleKind.Autodetect,
|
|
50
|
+
id,
|
|
51
|
+
name: "org.openrewrite.javascript.Autodetect",
|
|
52
|
+
displayName: "Auto-detected",
|
|
53
|
+
description: "Automatically detect styles from a repository's existing code.",
|
|
54
|
+
tags: [],
|
|
55
|
+
styles
|
|
56
|
+
};
|
|
57
|
+
}
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
export namespace Autodetect {
|
|
60
|
+
export function detector(): Detector {
|
|
54
61
|
return new Detector();
|
|
55
62
|
}
|
|
56
63
|
}
|
|
@@ -85,7 +92,7 @@ export class Detector {
|
|
|
85
92
|
* Build the auto-detected styles from collected statistics.
|
|
86
93
|
*/
|
|
87
94
|
build(): Autodetect {
|
|
88
|
-
return
|
|
95
|
+
return autodetect(randomId(), [
|
|
89
96
|
this.tabsAndIndentsStats.getTabsAndIndentsStyle(),
|
|
90
97
|
this.spacesStats.getSpacesStyle(),
|
|
91
98
|
this.getWrappingAndBracesStyle(),
|
|
@@ -18,7 +18,7 @@ import * as fs from 'fs';
|
|
|
18
18
|
import * as fsp from 'fs/promises';
|
|
19
19
|
import * as os from 'os';
|
|
20
20
|
import {spawnSync} from 'child_process';
|
|
21
|
-
import {PrettierStyle} from '../style';
|
|
21
|
+
import {prettierStyle, PrettierStyle} from '../style';
|
|
22
22
|
import {randomId} from '../../uuid';
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -336,7 +336,7 @@ export class PrettierConfigLoader {
|
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
// Create new PrettierStyle instance
|
|
339
|
-
marker =
|
|
339
|
+
marker = prettierStyle(randomId(), config, this.detection.version, ignored);
|
|
340
340
|
|
|
341
341
|
// Cache and return
|
|
342
342
|
this.configCache.set(configKey, marker);
|
package/src/javascript/index.ts
CHANGED