@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.
- package/dist/cli/cli-utils.d.ts.map +1 -1
- package/dist/cli/cli-utils.js +3 -2
- package/dist/cli/cli-utils.js.map +1 -1
- package/dist/cli/rewrite.js +2 -1
- package/dist/cli/rewrite.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +99 -56
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +48 -8
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/recipes/async-callback-in-sync-array-method.d.ts +40 -0
- package/dist/javascript/recipes/async-callback-in-sync-array-method.d.ts.map +1 -0
- package/dist/javascript/recipes/async-callback-in-sync-array-method.js +232 -0
- package/dist/javascript/recipes/async-callback-in-sync-array-method.js.map +1 -0
- package/dist/javascript/recipes/change-import.d.ts +51 -0
- package/dist/javascript/recipes/change-import.d.ts.map +1 -0
- package/dist/javascript/recipes/change-import.js +658 -0
- package/dist/javascript/recipes/change-import.js.map +1 -0
- package/dist/javascript/recipes/index.d.ts +3 -0
- package/dist/javascript/recipes/index.d.ts.map +1 -1
- package/dist/javascript/recipes/index.js +3 -0
- package/dist/javascript/recipes/index.js.map +1 -1
- package/dist/javascript/recipes/order-imports.d.ts +10 -0
- package/dist/javascript/recipes/order-imports.d.ts.map +1 -0
- package/dist/javascript/recipes/order-imports.js +240 -0
- package/dist/javascript/recipes/order-imports.js.map +1 -0
- package/dist/javascript/templating/index.d.ts +1 -1
- package/dist/javascript/templating/index.d.ts.map +1 -1
- package/dist/javascript/templating/index.js +2 -1
- package/dist/javascript/templating/index.js.map +1 -1
- package/dist/javascript/templating/rewrite.d.ts +36 -2
- package/dist/javascript/templating/rewrite.d.ts.map +1 -1
- package/dist/javascript/templating/rewrite.js +76 -0
- package/dist/javascript/templating/rewrite.js.map +1 -1
- package/dist/json/parser.js +78 -30
- package/dist/json/parser.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +7 -4
- package/dist/run.js.map +1 -1
- package/dist/version.txt +1 -1
- package/package.json +1 -1
- package/src/cli/cli-utils.ts +3 -2
- package/src/cli/rewrite.ts +2 -1
- package/src/index.ts +3 -2
- package/src/javascript/add-import.ts +125 -69
- package/src/javascript/parser.ts +49 -8
- package/src/javascript/recipes/async-callback-in-sync-array-method.ts +237 -0
- package/src/javascript/recipes/change-import.ts +700 -0
- package/src/javascript/recipes/index.ts +3 -0
- package/src/javascript/recipes/order-imports.ts +242 -0
- package/src/javascript/templating/index.ts +2 -1
- package/src/javascript/templating/rewrite.ts +89 -4
- package/src/json/parser.ts +69 -24
- package/src/run.ts +5 -4
- package/dist/recipe/index.d.ts +0 -2
- package/dist/recipe/index.d.ts.map +0 -1
- package/dist/recipe/index.js +0 -21
- package/dist/recipe/index.js.map +0 -1
- package/dist/recipe/order-imports.d.ts +0 -10
- package/dist/recipe/order-imports.d.ts.map +0 -1
- package/dist/recipe/order-imports.js +0 -213
- package/dist/recipe/order-imports.js.map +0 -1
- package/src/recipe/index.ts +0 -17
- package/src/recipe/order-imports.ts +0 -195
|
@@ -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
|
+
}
|
|
@@ -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 {
|
|
19
|
-
import {
|
|
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
|
+
}
|
package/src/json/parser.ts
CHANGED
|
@@ -79,21 +79,38 @@ class ParseJsonReader extends ParserSourceReader {
|
|
|
79
79
|
}
|
|
80
80
|
if (Array.isArray(parsed)) {
|
|
81
81
|
this.cursor++; // skip '['
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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);
|
package/dist/recipe/index.d.ts
DELETED
|
@@ -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"}
|
package/dist/recipe/index.js
DELETED
|
@@ -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
|
package/dist/recipe/index.js.map
DELETED
|
@@ -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"}
|