@openrewrite/rewrite 8.70.0-20251219-180817 → 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.
Files changed (94) hide show
  1. package/dist/cli/cli-utils.d.ts +6 -6
  2. package/dist/cli/cli-utils.d.ts.map +1 -1
  3. package/dist/cli/cli-utils.js +50 -228
  4. package/dist/cli/cli-utils.js.map +1 -1
  5. package/dist/javascript/assertions.d.ts.map +1 -1
  6. package/dist/javascript/assertions.js +87 -12
  7. package/dist/javascript/assertions.js.map +1 -1
  8. package/dist/javascript/autodetect.d.ts +11 -11
  9. package/dist/javascript/autodetect.d.ts.map +1 -1
  10. package/dist/javascript/autodetect.js +18 -21
  11. package/dist/javascript/autodetect.js.map +1 -1
  12. package/dist/javascript/format/prettier-config-loader.d.ts.map +1 -1
  13. package/dist/javascript/format/prettier-config-loader.js +1 -1
  14. package/dist/javascript/format/prettier-config-loader.js.map +1 -1
  15. package/dist/javascript/index.d.ts +1 -0
  16. package/dist/javascript/index.d.ts.map +1 -1
  17. package/dist/javascript/index.js +1 -0
  18. package/dist/javascript/index.js.map +1 -1
  19. package/dist/javascript/markers.d.ts.map +1 -1
  20. package/dist/javascript/markers.js +135 -6
  21. package/dist/javascript/markers.js.map +1 -1
  22. package/dist/javascript/node-resolution-result.d.ts +4 -1
  23. package/dist/javascript/node-resolution-result.d.ts.map +1 -1
  24. package/dist/javascript/node-resolution-result.js +22 -1
  25. package/dist/javascript/node-resolution-result.js.map +1 -1
  26. package/dist/javascript/package-json-parser.d.ts +7 -0
  27. package/dist/javascript/package-json-parser.d.ts.map +1 -1
  28. package/dist/javascript/package-json-parser.js +19 -1
  29. package/dist/javascript/package-json-parser.js.map +1 -1
  30. package/dist/javascript/parser.d.ts.map +1 -1
  31. package/dist/javascript/parser.js +1 -13
  32. package/dist/javascript/parser.js.map +1 -1
  33. package/dist/javascript/preconditions.js +4 -4
  34. package/dist/javascript/preconditions.js.map +1 -1
  35. package/dist/javascript/project-parser.d.ts +137 -0
  36. package/dist/javascript/project-parser.d.ts.map +1 -0
  37. package/dist/javascript/project-parser.js +726 -0
  38. package/dist/javascript/project-parser.js.map +1 -0
  39. package/dist/javascript/style.d.ts +9 -26
  40. package/dist/javascript/style.d.ts.map +1 -1
  41. package/dist/javascript/style.js +18 -42
  42. package/dist/javascript/style.js.map +1 -1
  43. package/dist/json/parser.d.ts.map +1 -1
  44. package/dist/json/parser.js +1 -0
  45. package/dist/json/parser.js.map +1 -1
  46. package/dist/markers.d.ts +1 -1
  47. package/dist/markers.js +1 -1
  48. package/dist/markers.js.map +1 -1
  49. package/dist/parser.d.ts +1 -1
  50. package/dist/parser.d.ts.map +1 -1
  51. package/dist/rpc/index.d.ts +0 -1
  52. package/dist/rpc/index.d.ts.map +1 -1
  53. package/dist/rpc/index.js +4 -2
  54. package/dist/rpc/index.js.map +1 -1
  55. package/dist/rpc/request/index.d.ts +1 -0
  56. package/dist/rpc/request/index.d.ts.map +1 -1
  57. package/dist/rpc/request/index.js +1 -0
  58. package/dist/rpc/request/index.js.map +1 -1
  59. package/dist/rpc/request/parse-project.d.ts +25 -0
  60. package/dist/rpc/request/parse-project.d.ts.map +1 -0
  61. package/dist/rpc/request/parse-project.js +304 -0
  62. package/dist/rpc/request/parse-project.js.map +1 -0
  63. package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
  64. package/dist/rpc/rewrite-rpc.js +1 -0
  65. package/dist/rpc/rewrite-rpc.js.map +1 -1
  66. package/dist/text/parser.d.ts.map +1 -1
  67. package/dist/text/parser.js +1 -0
  68. package/dist/text/parser.js.map +1 -1
  69. package/dist/version.txt +1 -1
  70. package/dist/yaml/parser.d.ts.map +1 -1
  71. package/dist/yaml/parser.js +52 -4
  72. package/dist/yaml/parser.js.map +1 -1
  73. package/package.json +1 -1
  74. package/src/cli/cli-utils.ts +46 -237
  75. package/src/javascript/assertions.ts +74 -10
  76. package/src/javascript/autodetect.ts +22 -15
  77. package/src/javascript/format/prettier-config-loader.ts +2 -2
  78. package/src/javascript/index.ts +1 -0
  79. package/src/javascript/markers.ts +157 -7
  80. package/src/javascript/node-resolution-result.ts +23 -2
  81. package/src/javascript/package-json-parser.ts +19 -1
  82. package/src/javascript/parser.ts +1 -16
  83. package/src/javascript/preconditions.ts +1 -1
  84. package/src/javascript/project-parser.ts +657 -0
  85. package/src/javascript/style.ts +43 -28
  86. package/src/json/parser.ts +3 -1
  87. package/src/markers.ts +1 -1
  88. package/src/parser.ts +1 -1
  89. package/src/rpc/index.ts +7 -5
  90. package/src/rpc/request/index.ts +1 -0
  91. package/src/rpc/request/parse-project.ts +283 -0
  92. package/src/rpc/rewrite-rpc.ts +2 -0
  93. package/src/text/parser.ts +3 -1
  94. package/src/yaml/parser.ts +53 -5
@@ -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, spawnSync} from 'child_process';
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 files: string[] = [];
346
-
347
- if (verbose) {
348
- console.log(`Discovering files in ${projectRoot}...`);
349
- }
350
-
351
- // Get list of git-ignored files
352
- const ignoredFiles = new Set<string>();
353
- try {
354
- const result = spawnSync('git', ['ls-files', '--ignored', '--exclude-standard', '-o'], {
355
- cwd: projectRoot,
356
- encoding: 'utf8'
357
- });
358
- if (result.stdout) {
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
- * All lock file names (typed as string[] for easier comparison)
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 (including package.json which gets special parsing)
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 (some have non-standard extensions like yarn.lock)
480
- if (ALL_LOCK_FILE_NAMES.includes(basename)) {
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 { verbose = false, onProgress } = options;
554
- const total = filePaths.length;
555
- let current = 0;
405
+ const {verbose = false, onProgress} = options;
556
406
 
557
- // Group files by type
558
- const jsFiles: string[] = [];
559
- const packageJsonFiles: string[] = [];
560
- const jsonFiles: string[] = [];
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
- // Classify yarn.lock files by content (Yarn Berry uses YAML, Classic uses text)
595
- for (const yarnLockPath of yarnLockFiles) {
596
- const format = await classifyYarnLockFile(yarnLockPath);
597
- if (format === 'yaml') {
598
- yamlLockFiles.push(yarnLockPath);
599
- } else {
600
- textLockFiles.push(yarnLockPath);
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
- // Create parse context for tracking progress
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
- // Write non-JS/TS files to disk so tools like Prettier can find config files
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 class Autodetect implements NamedStyles {
40
- readonly kind = "org.openrewrite.marker.NamedStyles" as const;
41
- readonly id: string;
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
- readonly tags: string[] = [];
46
- readonly styles: Style[];
47
-
48
- constructor(id: string, styles: Style[]) {
49
- this.id = id;
50
- this.styles = styles;
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
- static detector(): Detector {
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 new Autodetect(randomId(), [
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 = new PrettierStyle(randomId(), config, this.detection.version, ignored);
339
+ marker = prettierStyle(randomId(), config, this.detection.version, ignored);
340
340
 
341
341
  // Cache and return
342
342
  this.configCache.set(configKey, marker);
@@ -29,6 +29,7 @@ export * from "./method-matcher";
29
29
  export * from "./format";
30
30
  export * from "./autodetect";
31
31
  export * from "./tree-debug";
32
+ export * from "./project-parser";
32
33
 
33
34
  export * from "./add-import";
34
35
  export * from "./remove-import";