@openrewrite/rewrite 8.69.0-20251207-160255 → 8.69.0-20251207-214914

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 (68) hide show
  1. package/dist/cli/cli-utils.d.ts.map +1 -1
  2. package/dist/cli/cli-utils.js +3 -2
  3. package/dist/cli/cli-utils.js.map +1 -1
  4. package/dist/cli/rewrite.js +2 -1
  5. package/dist/cli/rewrite.js.map +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/javascript/add-import.d.ts.map +1 -1
  10. package/dist/javascript/add-import.js +99 -56
  11. package/dist/javascript/add-import.js.map +1 -1
  12. package/dist/javascript/parser.d.ts.map +1 -1
  13. package/dist/javascript/parser.js +48 -8
  14. package/dist/javascript/parser.js.map +1 -1
  15. package/dist/javascript/recipes/async-callback-in-sync-array-method.d.ts +40 -0
  16. package/dist/javascript/recipes/async-callback-in-sync-array-method.d.ts.map +1 -0
  17. package/dist/javascript/recipes/async-callback-in-sync-array-method.js +232 -0
  18. package/dist/javascript/recipes/async-callback-in-sync-array-method.js.map +1 -0
  19. package/dist/javascript/recipes/change-import.d.ts +51 -0
  20. package/dist/javascript/recipes/change-import.d.ts.map +1 -0
  21. package/dist/javascript/recipes/change-import.js +658 -0
  22. package/dist/javascript/recipes/change-import.js.map +1 -0
  23. package/dist/javascript/recipes/index.d.ts +3 -0
  24. package/dist/javascript/recipes/index.d.ts.map +1 -1
  25. package/dist/javascript/recipes/index.js +3 -0
  26. package/dist/javascript/recipes/index.js.map +1 -1
  27. package/dist/javascript/recipes/order-imports.d.ts +10 -0
  28. package/dist/javascript/recipes/order-imports.d.ts.map +1 -0
  29. package/dist/javascript/recipes/order-imports.js +240 -0
  30. package/dist/javascript/recipes/order-imports.js.map +1 -0
  31. package/dist/javascript/templating/index.d.ts +1 -1
  32. package/dist/javascript/templating/index.d.ts.map +1 -1
  33. package/dist/javascript/templating/index.js +2 -1
  34. package/dist/javascript/templating/index.js.map +1 -1
  35. package/dist/javascript/templating/rewrite.d.ts +36 -2
  36. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  37. package/dist/javascript/templating/rewrite.js +76 -0
  38. package/dist/javascript/templating/rewrite.js.map +1 -1
  39. package/dist/json/parser.js +78 -30
  40. package/dist/json/parser.js.map +1 -1
  41. package/dist/run.d.ts.map +1 -1
  42. package/dist/run.js +7 -4
  43. package/dist/run.js.map +1 -1
  44. package/dist/version.txt +1 -1
  45. package/package.json +1 -1
  46. package/src/cli/cli-utils.ts +3 -2
  47. package/src/cli/rewrite.ts +2 -1
  48. package/src/index.ts +3 -2
  49. package/src/javascript/add-import.ts +125 -69
  50. package/src/javascript/parser.ts +49 -8
  51. package/src/javascript/recipes/async-callback-in-sync-array-method.ts +237 -0
  52. package/src/javascript/recipes/change-import.ts +700 -0
  53. package/src/javascript/recipes/index.ts +3 -0
  54. package/src/javascript/recipes/order-imports.ts +242 -0
  55. package/src/javascript/templating/index.ts +2 -1
  56. package/src/javascript/templating/rewrite.ts +89 -4
  57. package/src/json/parser.ts +69 -24
  58. package/src/run.ts +5 -4
  59. package/dist/recipe/index.d.ts +0 -2
  60. package/dist/recipe/index.d.ts.map +0 -1
  61. package/dist/recipe/index.js +0 -21
  62. package/dist/recipe/index.js.map +0 -1
  63. package/dist/recipe/order-imports.d.ts +0 -10
  64. package/dist/recipe/order-imports.d.ts.map +0 -1
  65. package/dist/recipe/order-imports.js +0 -213
  66. package/dist/recipe/order-imports.js.map +0 -1
  67. package/src/recipe/index.ts +0 -17
  68. package/src/recipe/order-imports.ts +0 -195
@@ -14,4 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
+ export * from "./async-callback-in-sync-array-method";
17
18
  export * from "./upgrade-dependency-version";
19
+ export * from "./order-imports";
20
+ export * from "./change-import";
@@ -0,0 +1,242 @@
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
+
17
+ import {Recipe} from "../../recipe";
18
+ import {produceAsync, TreeVisitor} from "../../visitor";
19
+ import {ExecutionContext} from "../../execution";
20
+ import {JavaScriptVisitor, JS} from "../index";
21
+ import {J} from "../../java";
22
+ import {Draft, produce} from "immer";
23
+ import {SpacesStyle, styleFromSourceFile, StyleKind} from "../style";
24
+
25
+ /**
26
+ * Import type categories for sorting order:
27
+ * 1. Side-effect imports (no specifier): import 'module';
28
+ * 2. Namespace imports: import * as foo from 'module';
29
+ * 3. Default imports: import foo from 'module';
30
+ * 4. Named imports: import { foo } from 'module';
31
+ * 5. Type imports: import type { Foo } from 'module';
32
+ */
33
+ enum ImportCategory {
34
+ SideEffect = 0,
35
+ Namespace = 1,
36
+ Default = 2,
37
+ Named = 3,
38
+ Type = 4
39
+ }
40
+
41
+ export class OrderImports extends Recipe {
42
+ readonly name = "org.openrewrite.javascript.cleanup.order-imports";
43
+ readonly displayName = "Order imports";
44
+ readonly description = "Sort imports by category and module path. Categories: side-effect, namespace, default, named, type. Within each category, imports are sorted alphabetically by module path. Named specifiers within each import are also sorted alphabetically.";
45
+
46
+
47
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
48
+ return new class extends JavaScriptVisitor<ExecutionContext> {
49
+
50
+ protected async visitJsCompilationUnit(cu: JS.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
51
+ const importCount = this.countImports(cu);
52
+ if (importCount === 0) {
53
+ return cu;
54
+ }
55
+
56
+ const imports = cu.statements.slice(0, importCount) as J.RightPadded<JS.Import>[];
57
+ const originalImportPosition = Object.fromEntries(imports.map((item, i) => [item.element.id, i]));
58
+ const restStatements = cu.statements.slice(importCount);
59
+
60
+ // Get style for consistent brace spacing
61
+ const spacesStyle = styleFromSourceFile(StyleKind.SpacesStyle, cu) as SpacesStyle | undefined;
62
+ const useBraceSpaces = spacesStyle?.within.es6ImportExportBraces ?? false;
63
+
64
+ // Sort named specifiers within each import
65
+ const sortedSpecifiers = this.sortNamedSpecifiersWithinImports(imports, useBraceSpaces);
66
+
67
+ // Sort imports by category and module path
68
+ sortedSpecifiers.sort((aPadded, bPadded) => {
69
+ const a = aPadded.element;
70
+ const b = bPadded.element;
71
+
72
+ // First, compare by category
73
+ const categoryA = this.getImportCategory(a);
74
+ const categoryB = this.getImportCategory(b);
75
+ if (categoryA !== categoryB) {
76
+ return categoryA - categoryB;
77
+ }
78
+
79
+ // Within same category, sort by module path (case-insensitive)
80
+ const modulePathA = this.getModulePath(a).toLowerCase();
81
+ const modulePathB = this.getModulePath(b).toLowerCase();
82
+ const pathComparison = modulePathA.localeCompare(modulePathB);
83
+ if (pathComparison !== 0) {
84
+ return pathComparison;
85
+ }
86
+
87
+ // Tiebreaker: keep original order for stability
88
+ return originalImportPosition[aPadded.element.id] - originalImportPosition[bPadded.element.id];
89
+ });
90
+
91
+ const cuWithImportsSorted = await produceAsync(cu, async draft => {
92
+ draft.statements = [...sortedSpecifiers, ...restStatements];
93
+ });
94
+
95
+ return produce(cuWithImportsSorted!, draft => {
96
+ for (let i = 0; i < importCount; i++) {
97
+ draft.statements[i].element.prefix.whitespace = i > 0 ? "\n" : "";
98
+ }
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Determine the category of an import for sorting purposes.
104
+ */
105
+ private getImportCategory(import_: JS.Import): ImportCategory {
106
+ // Type imports: import type { Foo } from 'module'
107
+ if (import_.importClause?.typeOnly) {
108
+ return ImportCategory.Type;
109
+ }
110
+
111
+ // Side-effect imports: import 'module'
112
+ if (import_.importClause === undefined) {
113
+ return ImportCategory.SideEffect;
114
+ }
115
+
116
+ // Namespace imports: import * as foo from 'module'
117
+ if (import_.importClause.namedBindings?.kind === JS.Kind.Alias) {
118
+ const alias = import_.importClause.namedBindings as JS.Alias;
119
+ if (alias.propertyName.element.simpleName === "*") {
120
+ return ImportCategory.Namespace;
121
+ }
122
+ }
123
+
124
+ // Default imports (without named imports): import foo from 'module'
125
+ if (import_.importClause.name && !import_.importClause.namedBindings) {
126
+ return ImportCategory.Default;
127
+ }
128
+
129
+ // Default with named imports or just named imports: import foo, { bar } from 'module' or import { foo } from 'module'
130
+ return ImportCategory.Named;
131
+ }
132
+
133
+ /**
134
+ * Extract the module path from an import statement.
135
+ */
136
+ private getModulePath(import_: JS.Import): string {
137
+ if (import_.moduleSpecifier?.element.kind === J.Kind.Literal) {
138
+ const literal = import_.moduleSpecifier.element as J.Literal;
139
+ // Remove quotes from the value
140
+ return String(literal.value ?? '');
141
+ }
142
+ return '';
143
+ }
144
+
145
+ private countImports(cu: JS.CompilationUnit): number {
146
+ let i = 0;
147
+ while ((i < cu.statements.length) && (cu.statements[i].element.kind === JS.Kind.Import)) {
148
+ i++;
149
+ }
150
+ return i;
151
+ }
152
+
153
+ /**
154
+ * Sort named specifiers within each import statement alphabetically.
155
+ */
156
+ private sortNamedSpecifiersWithinImports(imports: J.RightPadded<JS.Import>[], useBraceSpaces: boolean): J.RightPadded<JS.Import>[] {
157
+ const ret = [];
158
+ for (const importPadded of imports) {
159
+ const import_ = importPadded.element;
160
+ if (this.hasNamedImports(import_)) {
161
+ const importSorted = produce(import_, draft => {
162
+ const namedBindings = draft.importClause!.namedBindings as Draft<JS.NamedImports>;
163
+ let elements = namedBindings.elements.elements;
164
+
165
+ if (elements.length <= 1) {
166
+ return; // Nothing to sort
167
+ }
168
+
169
+ // Handle trailing comma
170
+ const trailingComma = elements.length > 0 &&
171
+ elements[elements.length - 1].markers?.markers.find(m => m.kind === J.Markers.TrailingComma);
172
+ if (trailingComma) {
173
+ elements[elements.length - 1].markers.markers =
174
+ elements[elements.length - 1].markers.markers.filter(m => m.kind !== J.Markers.TrailingComma);
175
+ }
176
+
177
+ // Sort by the imported name (not alias)
178
+ elements.sort((a, b) => {
179
+ const nameA = this.getSpecifierSortKey(a.element as JS.ImportSpecifier);
180
+ const nameB = this.getSpecifierSortKey(b.element as JS.ImportSpecifier);
181
+ return nameA.localeCompare(nameB);
182
+ });
183
+
184
+ // Normalize spacing based on es6ImportExportBraces style
185
+ const braceSpace = useBraceSpaces ? " " : "";
186
+ for (let i = 0; i < elements.length; i++) {
187
+ if (i === 0) {
188
+ // First element: space after opening brace based on style
189
+ elements[i].element.prefix = {kind: J.Kind.Space, whitespace: braceSpace, comments: []};
190
+ } else {
191
+ // Other elements: space after comma
192
+ elements[i].element.prefix = {kind: J.Kind.Space, whitespace: ' ', comments: []};
193
+ }
194
+ }
195
+ // Last element: space before closing brace based on style
196
+ elements[elements.length - 1].after = {kind: J.Kind.Space, whitespace: braceSpace, comments: []};
197
+
198
+ // Restore trailing comma to last element
199
+ if (trailingComma && elements.length > 0 &&
200
+ !elements[elements.length - 1].markers.markers.find(m => m.kind === J.Markers.TrailingComma)) {
201
+ elements[elements.length - 1].markers.markers.push(trailingComma);
202
+ }
203
+ });
204
+
205
+ ret.push(produce(importPadded, draft => {
206
+ draft.element = importSorted;
207
+ }));
208
+ } else {
209
+ ret.push(importPadded);
210
+ }
211
+ }
212
+ return ret;
213
+ }
214
+
215
+ /**
216
+ * Check if an import has named imports that can be sorted.
217
+ */
218
+ private hasNamedImports(import_: JS.Import): boolean {
219
+ if (import_.importClause?.namedBindings?.kind === JS.Kind.NamedImports) {
220
+ const namedImports = import_.importClause.namedBindings as JS.NamedImports;
221
+ return namedImports.elements.kind === J.Kind.Container &&
222
+ namedImports.elements.elements.length > 1;
223
+ }
224
+ return false;
225
+ }
226
+
227
+ /**
228
+ * Get the sort key for an import specifier (the original name, not alias).
229
+ */
230
+ private getSpecifierSortKey(specifier: JS.ImportSpecifier): string {
231
+ if (specifier.specifier.kind === JS.Kind.Alias) {
232
+ // import { foo as bar } - sort by 'foo'
233
+ return (specifier.specifier as JS.Alias).propertyName.element.simpleName;
234
+ } else if (specifier.specifier.kind === J.Kind.Identifier) {
235
+ // import { foo } - sort by 'foo'
236
+ return (specifier.specifier as J.Identifier).simpleName;
237
+ }
238
+ return '';
239
+ }
240
+ }
241
+ }
242
+ }
@@ -56,7 +56,8 @@ export {
56
56
  // Export rewrite functionality
57
57
  export {
58
58
  rewrite,
59
- fromRecipe
59
+ fromRecipe,
60
+ flattenBlock
60
61
  } from './rewrite';
61
62
 
62
63
  // Export template functionality
@@ -13,11 +13,12 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import {Cursor, ExecutionContext, Recipe} from '../..';
17
- import {J} from '../../java';
18
- import {RewriteRule, RewriteConfig, PreMatchContext, PostMatchContext} from './types';
19
- import {Pattern, MatchResult} from './pattern';
16
+ import {Cursor, ExecutionContext, Recipe, TreeVisitor} from '../..';
17
+ import {J, Statement} from '../../java';
18
+ import {PostMatchContext, PreMatchContext, RewriteConfig, RewriteRule} from './types';
19
+ import {MatchResult, Pattern} from './pattern';
20
20
  import {Template} from './template';
21
+ import {JavaScriptVisitor} from '../visitor';
21
22
 
22
23
  /**
23
24
  * Implementation of a replacement rule.
@@ -228,3 +229,87 @@ export const fromRecipe = (recipe: Recipe, ctx: ExecutionContext): RewriteRule =
228
229
  }
229
230
  })();
230
231
  }
232
+
233
+ /**
234
+ * Registers an after-visitor that will flatten a block's statements into its parent block.
235
+ *
236
+ * When a rewrite template produces a J.Block containing multiple statements, but you want
237
+ * those statements to be inserted directly into the parent block (not nested), use this
238
+ * function to register a follow-up visitor that performs the flattening.
239
+ *
240
+ * @param visitor The current visitor instance (to register the after-visitor)
241
+ * @param block The block whose statements should be flattened into its parent
242
+ * @returns The block (for chaining in return statements)
243
+ *
244
+ * @example
245
+ * ```typescript
246
+ * override async visitReturn(ret: J.Return, ctx: ExecutionContext): Promise<J | undefined> {
247
+ * const result = await rewrite(() => ({
248
+ * before: pattern`return #{cond} || #{arr}.some(#{cb})`,
249
+ * after: template`{
250
+ * if (#{cond}) return true;
251
+ * for (const item of #{arr}) {
252
+ * if (await #{cb}(item)) return true;
253
+ * }
254
+ * return false;
255
+ * }`
256
+ * })).tryOn(this.cursor, ret);
257
+ *
258
+ * if (result && result.kind === J.Kind.Block) {
259
+ * return flattenBlock(this, result as J.Block);
260
+ * }
261
+ * return result ?? ret;
262
+ * }
263
+ * ```
264
+ */
265
+ export function flattenBlock<P>(
266
+ visitor: TreeVisitor<any, P>,
267
+ block: J.Block
268
+ ): J.Block {
269
+ // Create a visitor that will flatten this specific block when found in a parent block
270
+ const flattenVisitor = new class extends JavaScriptVisitor<P> {
271
+ protected override async visitBlock(parentBlock: J.Block, p: P): Promise<J | undefined> {
272
+ let modified = false;
273
+ const newStatements: typeof parentBlock.statements = [];
274
+
275
+ for (const stmt of parentBlock.statements) {
276
+ // Check if this statement is the block we want to flatten
277
+ if (stmt.element === block || stmt.element.id === block.id) {
278
+ // Splice in the inner block's statements
279
+ for (let i = 0; i < block.statements.length; i++) {
280
+ const innerStmt = block.statements[i];
281
+ if (i === 0) {
282
+ // First statement inherits the outer statement's padding
283
+ newStatements.push({
284
+ ...innerStmt,
285
+ element: {
286
+ ...innerStmt.element,
287
+ prefix: stmt.element.prefix // Use the original statement's prefix
288
+ } as Statement
289
+ });
290
+ } else {
291
+ newStatements.push(innerStmt);
292
+ }
293
+ }
294
+ modified = true;
295
+ } else {
296
+ newStatements.push(stmt);
297
+ }
298
+ }
299
+
300
+ if (modified) {
301
+ return {
302
+ ...parentBlock,
303
+ statements: newStatements
304
+ } as J.Block;
305
+ }
306
+
307
+ return super.visitBlock(parentBlock, p);
308
+ }
309
+ }();
310
+
311
+ // Register the flatten visitor to run after the main visitor completes
312
+ visitor.afterVisit.push(flattenVisitor);
313
+
314
+ return block;
315
+ }
@@ -79,21 +79,38 @@ class ParseJsonReader extends ParserSourceReader {
79
79
  }
80
80
  if (Array.isArray(parsed)) {
81
81
  this.cursor++; // skip '['
82
- const values = parsed.map((p, i) => {
83
- const element = this.json(p) as Json.Value;
82
+ let values: Json.RightPadded<Json.Value>[];
83
+ if (parsed.length === 0) {
84
+ // Empty array - capture whitespace in an Empty element
84
85
  const afterWhitespace = this.whitespace();
85
- // Check if there's a comma after this element
86
- const hasComma = this.source[this.cursor] === ',';
87
- if (hasComma) {
88
- this.cursor++; // skip ','
89
- }
90
- return {
86
+ values = [{
91
87
  kind: Json.Kind.RightPadded,
92
- element,
88
+ element: {
89
+ kind: Json.Kind.Empty,
90
+ id: randomId(),
91
+ prefix: emptySpace,
92
+ markers: emptyMarkers
93
+ } satisfies Json.Empty as Json.Empty,
93
94
  after: space(afterWhitespace),
94
95
  markers: emptyMarkers
95
- } satisfies Json.RightPadded<Json.Value> as Json.RightPadded<Json.Value>;
96
- });
96
+ } satisfies Json.RightPadded<Json.Value> as Json.RightPadded<Json.Value>];
97
+ } else {
98
+ values = parsed.map((p, i) => {
99
+ const element = this.json(p) as Json.Value;
100
+ const afterWhitespace = this.whitespace();
101
+ // Check if there's a comma after this element
102
+ const hasComma = this.source[this.cursor] === ',';
103
+ if (hasComma) {
104
+ this.cursor++; // skip ','
105
+ }
106
+ return {
107
+ kind: Json.Kind.RightPadded,
108
+ element,
109
+ after: space(afterWhitespace),
110
+ markers: emptyMarkers
111
+ } satisfies Json.RightPadded<Json.Value> as Json.RightPadded<Json.Value>;
112
+ });
113
+ }
97
114
  this.cursor++; // skip ']'
98
115
  return {
99
116
  kind: Json.Kind.Array,
@@ -103,21 +120,38 @@ class ParseJsonReader extends ParserSourceReader {
103
120
  } else if (parsed !== null && typeof parsed === "object") {
104
121
  this.cursor++; // skip '{'
105
122
  const keys = Object.keys(parsed);
106
- const members = keys.map((key, i) => {
107
- const element = this.member(parsed, key);
123
+ let members: Json.RightPadded<Json.Member>[];
124
+ if (keys.length === 0) {
125
+ // Empty object - capture whitespace in an Empty element
108
126
  const afterWhitespace = this.whitespace();
109
- // Check if there's a comma after this element
110
- const hasComma = this.source[this.cursor] === ',';
111
- if (hasComma) {
112
- this.cursor++; // skip ','
113
- }
114
- return {
127
+ members = [{
115
128
  kind: Json.Kind.RightPadded,
116
- element,
129
+ element: {
130
+ kind: Json.Kind.Empty,
131
+ id: randomId(),
132
+ prefix: emptySpace,
133
+ markers: emptyMarkers
134
+ } satisfies Json.Empty as Json.Empty as unknown as Json.Member,
117
135
  after: space(afterWhitespace),
118
136
  markers: emptyMarkers
119
- } satisfies Json.RightPadded<Json.Member> as Json.RightPadded<Json.Member>;
120
- });
137
+ } satisfies Json.RightPadded<Json.Member> as Json.RightPadded<Json.Member>];
138
+ } else {
139
+ members = keys.map((key, i) => {
140
+ const element = this.member(parsed, key);
141
+ const afterWhitespace = this.whitespace();
142
+ // Check if there's a comma after this element
143
+ const hasComma = this.source[this.cursor] === ',';
144
+ if (hasComma) {
145
+ this.cursor++; // skip ','
146
+ }
147
+ return {
148
+ kind: Json.Kind.RightPadded,
149
+ element,
150
+ after: space(afterWhitespace),
151
+ markers: emptyMarkers
152
+ } satisfies Json.RightPadded<Json.Member> as Json.RightPadded<Json.Member>;
153
+ });
154
+ }
121
155
  this.cursor++; // skip '}'
122
156
  return {
123
157
  kind: Json.Kind.Object,
@@ -147,11 +181,22 @@ class ParseJsonReader extends ParserSourceReader {
147
181
  value: parsed
148
182
  } satisfies Json.Literal as Json.Literal;
149
183
  } else if (typeof parsed === "number") {
150
- this.cursor += parsed.toString().length;
184
+ // Extract original source to preserve precision for large numbers
185
+ const sourceStart = this.cursor;
186
+ // Numbers can have optional sign, digits, decimal point, and exponent
187
+ while (this.cursor < this.source.length) {
188
+ const char = this.source[this.cursor];
189
+ if (/[\d.eE+\-]/.test(char)) {
190
+ this.cursor++;
191
+ } else {
192
+ break;
193
+ }
194
+ }
195
+ const source = this.source.slice(sourceStart, this.cursor);
151
196
  return {
152
197
  kind: Json.Kind.Literal,
153
198
  ...base,
154
- source: parsed.toString(),
199
+ source,
155
200
  value: parsed,
156
201
  } satisfies Json.Literal as Json.Literal;
157
202
  } else if (typeof parsed === "boolean") {
package/src/run.ts CHANGED
@@ -41,7 +41,11 @@ export class Result {
41
41
  }
42
42
 
43
43
  async function hasScanningRecipe(recipe: Recipe): Promise<boolean> {
44
- return recipe instanceof ScanningRecipe || (await recipe.recipeList()).some(hasScanningRecipe);
44
+ if (recipe instanceof ScanningRecipe) return true;
45
+ for (const item of (await recipe.recipeList())) {
46
+ if (await hasScanningRecipe(item)) return true;
47
+ }
48
+ return false;
45
49
  }
46
50
 
47
51
  async function recurseRecipeList<T>(recipe: Recipe, initial: T, fn: (recipe: Recipe, t: T) => Promise<T | undefined>): Promise<T | undefined> {
@@ -145,10 +149,7 @@ export async function* scheduleRunStreaming(
145
149
  } else {
146
150
  // For non-scanning recipes, process files immediately as they come in
147
151
  const iterable = Array.isArray(before) ? before : before;
148
- let processCount = 0;
149
152
  for await (const b of iterable) {
150
- processCount++;
151
- onProgress?.('processing', processCount, -1, b.sourcePath);
152
153
  const editedB = await recurseRecipeList(recipe, b, async (recipe, b2) => (await recipe.editor()).visit(b2, ctx, cursor));
153
154
  // Always yield a result so the caller knows when each file is processed
154
155
  yield new Result(b, editedB !== b ? editedB : b);
@@ -1,2 +0,0 @@
1
- export { OrderImports } from "./order-imports";
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/recipe/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAC,YAAY,EAAC,MAAM,iBAAiB,CAAC"}
@@ -1,21 +0,0 @@
1
- "use strict";
2
- /*
3
- * Copyright 2025 the original author or authors.
4
- * <p>
5
- * Licensed under the Moderne Source Available License (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- * <p>
9
- * https://docs.moderne.io/licensing/moderne-source-available-license
10
- * <p>
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.OrderImports = void 0;
19
- var order_imports_1 = require("./order-imports");
20
- Object.defineProperty(exports, "OrderImports", { enumerable: true, get: function () { return order_imports_1.OrderImports; } });
21
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/recipe/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAEH,iDAA6C;AAArC,6GAAA,YAAY,OAAA"}
@@ -1,10 +0,0 @@
1
- import { Recipe } from "../recipe";
2
- import { TreeVisitor } from "../visitor";
3
- import { ExecutionContext } from "../execution";
4
- export declare class OrderImports extends Recipe {
5
- name: string;
6
- displayName: string;
7
- description: string;
8
- editor(): Promise<TreeVisitor<any, ExecutionContext>>;
9
- }
10
- //# sourceMappingURL=order-imports.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"order-imports.d.ts","sourceRoot":"","sources":["../../src/recipe/order-imports.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AACjC,OAAO,EAAe,WAAW,EAAC,MAAM,YAAY,CAAC;AACrD,OAAO,EAAC,gBAAgB,EAAC,MAAM,cAAc,CAAC;AAM9C,qBAAa,YAAa,SAAQ,MAAM;IACpC,IAAI,SAAkC;IACtC,WAAW,SAAmB;IAC9B,WAAW,SAAoG;IAGzG,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;CAoK9D"}