@openrewrite/rewrite 8.69.0-20251205-210445 → 8.69.0-20251207-092603
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 +12 -2
- package/dist/cli/cli-utils.d.ts.map +1 -1
- package/dist/cli/cli-utils.js +53 -13
- package/dist/cli/cli-utils.js.map +1 -1
- package/dist/cli/rewrite.d.ts.map +1 -1
- package/dist/cli/rewrite.js +230 -82
- package/dist/cli/rewrite.js.map +1 -1
- package/dist/cli/validate-parsing-recipe.d.ts +23 -0
- package/dist/cli/validate-parsing-recipe.d.ts.map +1 -0
- package/dist/cli/validate-parsing-recipe.js +149 -0
- package/dist/cli/validate-parsing-recipe.js.map +1 -0
- package/dist/javascript/format.d.ts.map +1 -1
- package/dist/javascript/format.js +7 -2
- package/dist/javascript/format.js.map +1 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +4 -2
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/tabs-and-indents-visitor.d.ts.map +1 -1
- package/dist/javascript/tabs-and-indents-visitor.js +32 -4
- package/dist/javascript/tabs-and-indents-visitor.js.map +1 -1
- package/dist/json/parser.js +35 -20
- package/dist/json/parser.js.map +1 -1
- package/dist/run.d.ts +8 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +78 -21
- package/dist/run.js.map +1 -1
- package/dist/version.txt +1 -1
- package/package.json +1 -1
- package/src/cli/cli-utils.ts +28 -9
- package/src/cli/rewrite.ts +244 -85
- package/src/cli/validate-parsing-recipe.ts +114 -0
- package/src/javascript/format.ts +7 -2
- package/src/javascript/parser.ts +6 -2
- package/src/javascript/tabs-and-indents-visitor.ts +33 -3
- package/src/json/parser.ts +35 -20
- package/src/run.ts +61 -25
package/src/json/parser.ts
CHANGED
|
@@ -59,35 +59,50 @@ class ParseJsonReader extends ParserSourceReader {
|
|
|
59
59
|
}
|
|
60
60
|
if (Array.isArray(parsed)) {
|
|
61
61
|
this.cursor++; // skip '['
|
|
62
|
+
const values = parsed.map((p, i) => {
|
|
63
|
+
const element = this.json(p) as Json.Value;
|
|
64
|
+
const afterWhitespace = this.whitespace();
|
|
65
|
+
// Check if there's a comma after this element
|
|
66
|
+
const hasComma = this.source[this.cursor] === ',';
|
|
67
|
+
if (hasComma) {
|
|
68
|
+
this.cursor++; // skip ','
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
kind: Json.Kind.RightPadded,
|
|
72
|
+
element,
|
|
73
|
+
after: space(afterWhitespace),
|
|
74
|
+
markers: emptyMarkers
|
|
75
|
+
} satisfies Json.RightPadded<Json.Value> as Json.RightPadded<Json.Value>;
|
|
76
|
+
});
|
|
77
|
+
this.cursor++; // skip ']'
|
|
62
78
|
return {
|
|
63
79
|
kind: Json.Kind.Array,
|
|
64
80
|
...base,
|
|
65
|
-
values
|
|
66
|
-
const value = {
|
|
67
|
-
kind: Json.Kind.RightPadded,
|
|
68
|
-
element: this.json(p) as Json.Value,
|
|
69
|
-
after: space(this.whitespace()),
|
|
70
|
-
markers: emptyMarkers
|
|
71
|
-
} satisfies Json.RightPadded<Json.Value> as Json.RightPadded<Json.Value>;
|
|
72
|
-
this.cursor++;
|
|
73
|
-
return value;
|
|
74
|
-
})
|
|
81
|
+
values
|
|
75
82
|
} satisfies Json.Array as Json.Array;
|
|
76
83
|
} else if (parsed !== null && typeof parsed === "object") {
|
|
77
84
|
this.cursor++; // skip '{'
|
|
85
|
+
const keys = Object.keys(parsed);
|
|
86
|
+
const members = keys.map((key, i) => {
|
|
87
|
+
const element = this.member(parsed, key);
|
|
88
|
+
const afterWhitespace = this.whitespace();
|
|
89
|
+
// Check if there's a comma after this element
|
|
90
|
+
const hasComma = this.source[this.cursor] === ',';
|
|
91
|
+
if (hasComma) {
|
|
92
|
+
this.cursor++; // skip ','
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
kind: Json.Kind.RightPadded,
|
|
96
|
+
element,
|
|
97
|
+
after: space(afterWhitespace),
|
|
98
|
+
markers: emptyMarkers
|
|
99
|
+
} satisfies Json.RightPadded<Json.Member> as Json.RightPadded<Json.Member>;
|
|
100
|
+
});
|
|
101
|
+
this.cursor++; // skip '}'
|
|
78
102
|
return {
|
|
79
103
|
kind: Json.Kind.Object,
|
|
80
104
|
...base,
|
|
81
|
-
members
|
|
82
|
-
const member = {
|
|
83
|
-
kind: Json.Kind.RightPadded,
|
|
84
|
-
element: this.member(parsed, key),
|
|
85
|
-
after: space(this.whitespace()),
|
|
86
|
-
markers: emptyMarkers
|
|
87
|
-
} satisfies Json.RightPadded<Json.Member> as Json.RightPadded<Json.Member>;
|
|
88
|
-
this.cursor++;
|
|
89
|
-
return member;
|
|
90
|
-
})
|
|
105
|
+
members
|
|
91
106
|
} satisfies Json.Object as Json.Object;
|
|
92
107
|
} else if (typeof parsed === "string") {
|
|
93
108
|
// Extract original source to preserve escape sequences
|
package/src/run.ts
CHANGED
|
@@ -68,51 +68,87 @@ export async function scheduleRun(recipe: Recipe, before: SourceFile[], ctx: Exe
|
|
|
68
68
|
return { changeset };
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
export type ProgressCallback = (phase: 'parsing' | 'scanning' | 'processing', current: number, total: number, sourcePath: string) => void;
|
|
72
|
+
|
|
71
73
|
/**
|
|
72
74
|
* Streaming version of scheduleRun that yields results as soon as each file is processed.
|
|
73
75
|
* This allows callers to print diffs immediately and free memory earlier.
|
|
74
76
|
*
|
|
77
|
+
* Accepts either an array or an async iterable of source files. When the recipe is not
|
|
78
|
+
* a scanning recipe, files are processed immediately as they're yielded from the iterable,
|
|
79
|
+
* avoiding the need to collect all files into memory first.
|
|
80
|
+
*
|
|
75
81
|
* For scanning recipes, the scan phase completes on all files before yielding any results.
|
|
82
|
+
*
|
|
83
|
+
* @param onProgress Optional callback for progress updates during parsing, scanning, and processing phases.
|
|
76
84
|
*/
|
|
77
85
|
export async function* scheduleRunStreaming(
|
|
78
86
|
recipe: Recipe,
|
|
79
|
-
before: SourceFile[]
|
|
80
|
-
ctx: ExecutionContext
|
|
87
|
+
before: SourceFile[] | AsyncIterable<SourceFile>,
|
|
88
|
+
ctx: ExecutionContext,
|
|
89
|
+
onProgress?: ProgressCallback
|
|
81
90
|
): AsyncGenerator<Result, void, undefined> {
|
|
82
91
|
const cursor = rootCursor();
|
|
92
|
+
const isScanning = await hasScanningRecipe(recipe);
|
|
93
|
+
|
|
94
|
+
if (isScanning) {
|
|
95
|
+
// For scanning recipes, we need to collect all files first for the scan phase
|
|
96
|
+
const files: SourceFile[] = Array.isArray(before) ? [...before] : [];
|
|
97
|
+
if (!Array.isArray(before)) {
|
|
98
|
+
let parseCount = 0;
|
|
99
|
+
for await (const sf of before) {
|
|
100
|
+
files.push(sf);
|
|
101
|
+
parseCount++;
|
|
102
|
+
onProgress?.('parsing', parseCount, -1, sf.sourcePath); // -1 = unknown total
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const totalFiles = files.length;
|
|
83
107
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
// Phase 1: Run scanners on all files
|
|
109
|
+
for (let i = 0; i < files.length; i++) {
|
|
110
|
+
const b = files[i];
|
|
111
|
+
onProgress?.('scanning', i + 1, totalFiles, b.sourcePath);
|
|
87
112
|
await recurseRecipeList(recipe, b, async (recipe, b2) => {
|
|
88
113
|
if (recipe instanceof ScanningRecipe) {
|
|
89
114
|
return (await recipe.scanner(recipe.accumulator(cursor, ctx))).visit(b2, ctx, cursor)
|
|
90
115
|
}
|
|
91
116
|
});
|
|
92
117
|
}
|
|
93
|
-
}
|
|
94
118
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
// Phase 2: Collect generated files
|
|
120
|
+
const generated = (await recurseRecipeList(recipe, [] as SourceFile[], async (recipe, generated) => {
|
|
121
|
+
if (recipe instanceof ScanningRecipe) {
|
|
122
|
+
generated.push(...await recipe.generate(recipe.accumulator(cursor, ctx), ctx));
|
|
123
|
+
}
|
|
124
|
+
return generated;
|
|
125
|
+
}))!;
|
|
126
|
+
|
|
127
|
+
// Phase 3: Edit existing files and yield results immediately
|
|
128
|
+
for (let i = 0; i < files.length; i++) {
|
|
129
|
+
const b = files[i];
|
|
130
|
+
onProgress?.('processing', i + 1, totalFiles, b.sourcePath);
|
|
131
|
+
const editedB = await recurseRecipeList(recipe, b, async (recipe, b2) => (await recipe.editor()).visit(b2, ctx, cursor));
|
|
132
|
+
// Always yield a result so the caller knows when each file is processed
|
|
133
|
+
yield new Result(b, editedB !== b ? editedB : b);
|
|
134
|
+
// Clear array entry to allow GC to free memory for this file
|
|
135
|
+
(files as any)[i] = null;
|
|
108
136
|
}
|
|
109
|
-
}
|
|
110
137
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
// Phase 4: Edit generated files and yield results
|
|
139
|
+
for (const g of generated) {
|
|
140
|
+
const editedG = await recurseRecipeList(recipe, g, async (recipe, g2) => (await recipe.editor()).visit(g2, ctx, cursor));
|
|
141
|
+
if (editedG) {
|
|
142
|
+
yield new Result(undefined, editedG);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
// For non-scanning recipes, process files immediately as they come in
|
|
147
|
+
const iterable = Array.isArray(before) ? before : before;
|
|
148
|
+
for await (const b of iterable) {
|
|
149
|
+
const editedB = await recurseRecipeList(recipe, b, async (recipe, b2) => (await recipe.editor()).visit(b2, ctx, cursor));
|
|
150
|
+
// Always yield a result so the caller knows when each file is processed
|
|
151
|
+
yield new Result(b, editedB !== b ? editedB : b);
|
|
116
152
|
}
|
|
117
153
|
}
|
|
118
154
|
|