@openrewrite/rewrite 8.96.0 → 8.96.1
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/javascript/amd.d.ts.map +1 -1
- package/dist/javascript/amd.js +21 -13
- package/dist/javascript/amd.js.map +1 -1
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +14 -5
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/recipes/order-imports.d.ts.map +1 -1
- package/dist/javascript/recipes/order-imports.js +20 -27
- package/dist/javascript/recipes/order-imports.js.map +1 -1
- package/dist/javascript/search/find-dependency.d.ts.map +1 -1
- package/dist/javascript/search/find-dependency.js +3 -0
- package/dist/javascript/search/find-dependency.js.map +1 -1
- package/dist/markers.js +4 -0
- package/dist/markers.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/test/rewrite-test.d.ts +20 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +156 -11
- package/dist/test/rewrite-test.js.map +1 -1
- package/package.json +1 -1
- package/src/javascript/amd.ts +22 -13
- package/src/javascript/format/format.ts +12 -5
- package/src/javascript/recipes/order-imports.ts +24 -28
- package/src/javascript/search/find-dependency.ts +3 -0
- package/src/markers.ts +4 -0
- package/src/test/rewrite-test.ts +154 -6
package/src/javascript/amd.ts
CHANGED
|
@@ -513,8 +513,11 @@ export function withoutDependencyAt(
|
|
|
513
513
|
block: AmdBlock,
|
|
514
514
|
index: number
|
|
515
515
|
): J.MethodInvocation {
|
|
516
|
-
const dependencies = withElements(block.dependencies, removeEntry(elementsOf(block), index, dependencySlot));
|
|
517
516
|
const parameters = parametersOf(block);
|
|
517
|
+
if (index < 0 || index >= Math.max(elementsOf(block).length, parameters.length)) {
|
|
518
|
+
return call;
|
|
519
|
+
}
|
|
520
|
+
const dependencies = withElements(block.dependencies, removeEntry(elementsOf(block), index, dependencySlot));
|
|
518
521
|
const factory = index >= parameters.length ?
|
|
519
522
|
block.factory :
|
|
520
523
|
withParameters(block.factory, removeEntry(parameters, index, parameterSlot));
|
|
@@ -644,6 +647,11 @@ function queuedFor(visitor: JavaScriptVisitor<any>, blockId: UUID): AddAmdDepend
|
|
|
644
647
|
return visitor.afterVisit.filter((v): v is AddAmdDependency<any> => v instanceof AddAmdDependency && v.blockId === blockId);
|
|
645
648
|
}
|
|
646
649
|
|
|
650
|
+
/** Whether `scope` is where the factory's parameters are declared, which for an arrow is the arrow wrapping its lambda. */
|
|
651
|
+
function declaresParameters(scope: J | undefined, factory: J.MethodDeclaration | J.Lambda): boolean {
|
|
652
|
+
return scope === factory || (scope?.kind === JS.Kind.ArrowFunction && (scope as JS.ArrowFunction).lambda === factory);
|
|
653
|
+
}
|
|
654
|
+
|
|
647
655
|
/**
|
|
648
656
|
* A local binding for `module` on the AMD lane, creating one where the block does not already
|
|
649
657
|
* have it, or `undefined` where no safe binding exists.
|
|
@@ -662,18 +670,19 @@ export function bindAmd(
|
|
|
662
670
|
const modules = dependencyNames(amd.block);
|
|
663
671
|
const bindings = parameterNames(amd.block);
|
|
664
672
|
|
|
665
|
-
const declared = module === "" ?
|
|
666
|
-
if (declared
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const
|
|
676
|
-
if (
|
|
673
|
+
const declared = module === "" ? [] : modules.flatMap((m, i) => m === module ? [i] : []);
|
|
674
|
+
if (declared.length > 0 && bindings[declared[0]] === undefined) {
|
|
675
|
+
// A parameter that is not a plain name gives the module no binding to hand back.
|
|
676
|
+
return undefined;
|
|
677
|
+
}
|
|
678
|
+
// Only code the factory encloses can shadow its parameter, so a cursor elsewhere in the
|
|
679
|
+
// block — on the `define` call, or on its dependency array — has nothing to measure.
|
|
680
|
+
const cursor = cursorOf(visitor);
|
|
681
|
+
const withinFactory = cursor?.firstEnclosing((v): v is J => v === amd.block.factory);
|
|
682
|
+
for (const index of declared) {
|
|
683
|
+
const bound = bindings[index];
|
|
684
|
+
if (bound !== undefined && (withinFactory === undefined ||
|
|
685
|
+
declaresParameters(scopeOf(cursor!).declaringScope(bound), amd.block.factory))) {
|
|
677
686
|
return bound;
|
|
678
687
|
}
|
|
679
688
|
}
|
|
@@ -255,6 +255,9 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
255
255
|
|
|
256
256
|
public async visitContainer<T extends J>(container: J.Container<T>, p: P): Promise<J.Container<T>> {
|
|
257
257
|
const ret = await super.visitContainer(container, p) as J.Container<T>;
|
|
258
|
+
if (this.cursor.value?.kind === J.Kind.TypeParameter) {
|
|
259
|
+
return ret;
|
|
260
|
+
}
|
|
258
261
|
return produce(ret, draft => {
|
|
259
262
|
if (draft.elements.length > 0) {
|
|
260
263
|
// Apply beforeComma rule to all elements except the last
|
|
@@ -321,7 +324,7 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
321
324
|
const ret = await super.visitForLoop(forLoop, p) as J.ForLoop;
|
|
322
325
|
return produceAsync(ret, async draft => {
|
|
323
326
|
draft.control.prefix.whitespace = this.style.beforeParentheses.forParentheses ? " " : "";
|
|
324
|
-
draft.control.init
|
|
327
|
+
draft.control.init.forEach((oneInit, index) => {
|
|
325
328
|
if (oneInit.element.kind === J.Kind.VariableDeclarations) {
|
|
326
329
|
const vd = oneInit.element as Draft<J.VariableDeclarations>;
|
|
327
330
|
if (vd.modifiers && vd.modifiers.length > 0) {
|
|
@@ -330,8 +333,7 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
330
333
|
}
|
|
331
334
|
oneInit.after.whitespace = "";
|
|
332
335
|
this.spaceBeforeRightPaddedElementDraft(oneInit, index === 0 ? this.style.within.forParentheses : true);
|
|
333
|
-
|
|
334
|
-
}));
|
|
336
|
+
});
|
|
335
337
|
if (draft.control.condition) {
|
|
336
338
|
draft.control.condition.element.prefix.whitespace = " ";
|
|
337
339
|
draft.control.condition.after.whitespace = this.style.other.beforeForSemicolon ? " " : "";
|
|
@@ -641,9 +643,14 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
|
|
|
641
643
|
const hasConstraint = constraintType.element.kind !== J.Kind.Empty;
|
|
642
644
|
const hasDefault = defaultType.element.kind !== J.Kind.Empty;
|
|
643
645
|
|
|
646
|
+
if (hasDefault && !defaultType.element.prefix.whitespace.includes("\n")) {
|
|
647
|
+
defaultType.element.prefix.whitespace = this.style.aroundOperators.assignment ? " " : "";
|
|
648
|
+
}
|
|
644
649
|
if (hasConstraint) {
|
|
645
|
-
|
|
646
|
-
|
|
650
|
+
if (!hasDefault) {
|
|
651
|
+
this.spaceAfterRightPaddedDraft(constraintType, this.style.other.beforeComma);
|
|
652
|
+
} else if (!constraintType.after.whitespace.includes("\n")) {
|
|
653
|
+
// Space before '=' for default type (in the `after` of constraint type)
|
|
647
654
|
constraintType.after.whitespace = this.style.aroundOperators.assignment ? " " : "";
|
|
648
655
|
}
|
|
649
656
|
} else if (hasDefault) {
|
|
@@ -172,43 +172,39 @@ export class OrderImports extends Recipe {
|
|
|
172
172
|
return; // Nothing to sort
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
elements[elements.length - 1].markers.
|
|
180
|
-
|
|
181
|
-
|
|
175
|
+
const bySortKey = (a: { element: unknown }, b: { element: unknown }) =>
|
|
176
|
+
this.getSpecifierSortKey(a.element as JS.ImportSpecifier)
|
|
177
|
+
.localeCompare(this.getSpecifierSortKey(b.element as JS.ImportSpecifier));
|
|
178
|
+
if (elements.some((e, i) => i > 0 && bySortKey(elements[i - 1], e) > 0)) {
|
|
179
|
+
const trailingComma = elements[elements.length - 1].markers?.markers.find(m => m.kind === J.Markers.TrailingComma);
|
|
180
|
+
if (trailingComma) {
|
|
181
|
+
elements[elements.length - 1].markers.markers =
|
|
182
|
+
elements[elements.length - 1].markers.markers.filter(m => m.kind !== J.Markers.TrailingComma);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
elements.sort(bySortKey);
|
|
182
186
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return nameA.localeCompare(nameB);
|
|
188
|
-
});
|
|
187
|
+
if (trailingComma) {
|
|
188
|
+
elements[elements.length - 1].markers.markers.push(trailingComma);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
189
191
|
|
|
190
|
-
// Normalize spacing based on es6ImportExportBraces style
|
|
191
192
|
const braceSpace = useBraceSpaces ? " " : "";
|
|
193
|
+
const differs = (space: J.Space, whitespace: string) =>
|
|
194
|
+
space.whitespace !== whitespace || space.comments.length > 0;
|
|
195
|
+
const spaceOf = (whitespace: string): J.Space => ({kind: J.Kind.Space, whitespace, comments: []});
|
|
192
196
|
for (let i = 0; i < elements.length; i++) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
elements[i].element.prefix =
|
|
196
|
-
} else {
|
|
197
|
-
// Other elements: space after comma
|
|
198
|
-
elements[i].element.prefix = {kind: J.Kind.Space, whitespace: ' ', comments: []};
|
|
197
|
+
const whitespace = i === 0 ? braceSpace : ' ';
|
|
198
|
+
if (differs(elements[i].element.prefix, whitespace)) {
|
|
199
|
+
elements[i].element.prefix = spaceOf(whitespace);
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// Restore trailing comma to last element
|
|
205
|
-
if (trailingComma && elements.length > 0 &&
|
|
206
|
-
!elements[elements.length - 1].markers.markers.find(m => m.kind === J.Markers.TrailingComma)) {
|
|
207
|
-
elements[elements.length - 1].markers.markers.push(trailingComma);
|
|
202
|
+
if (differs(elements[elements.length - 1].after, braceSpace)) {
|
|
203
|
+
elements[elements.length - 1].after = spaceOf(braceSpace);
|
|
208
204
|
}
|
|
209
205
|
});
|
|
210
206
|
|
|
211
|
-
ret.push(produce(importPadded, draft => {
|
|
207
|
+
ret.push(importSorted === import_ ? importPadded : produce(importPadded, draft => {
|
|
212
208
|
draft.element = importSorted;
|
|
213
209
|
}));
|
|
214
210
|
} else {
|
|
@@ -163,6 +163,9 @@ export class FindDependency extends Recipe {
|
|
|
163
163
|
private async markDependency(member: Json.Member, ctx: ExecutionContext): Promise<Json.Member> {
|
|
164
164
|
const visitedMember = await super.visitMember(member, ctx) as Json.Member;
|
|
165
165
|
const markedKey = foundSearchResult(visitedMember.key.element);
|
|
166
|
+
if (markedKey === visitedMember.key.element) {
|
|
167
|
+
return visitedMember;
|
|
168
|
+
}
|
|
166
169
|
return {
|
|
167
170
|
...visitedMember,
|
|
168
171
|
key: {
|
package/src/markers.ts
CHANGED
|
@@ -275,6 +275,10 @@ export function markupDebug<T extends { markers: Markers }>(t: T, message: strin
|
|
|
275
275
|
* Helper to add a markup marker to a tree node.
|
|
276
276
|
*/
|
|
277
277
|
function addMarkup<T extends { markers: Markers }>(t: T, markup: Markup): T {
|
|
278
|
+
if (t.markers.markers.some(m => m.kind === markup.kind &&
|
|
279
|
+
(m as Markup).message === markup.message && (m as Markup).detail === markup.detail)) {
|
|
280
|
+
return t;
|
|
281
|
+
}
|
|
278
282
|
return {
|
|
279
283
|
...t,
|
|
280
284
|
markers: {
|
package/src/test/rewrite-test.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {isNamedStyles} from "../style";
|
|
|
27
27
|
import {JavaScriptVisitor} from "../javascript";
|
|
28
28
|
import {attributionHint, dumpTypesIfRequested} from "../javascript/type-report";
|
|
29
29
|
import {J, Type} from "../java";
|
|
30
|
+
import {DATA_TABLE_STORE} from "../data-table";
|
|
30
31
|
|
|
31
32
|
export interface SourceSpec<T extends SourceFile> {
|
|
32
33
|
kind: string,
|
|
@@ -44,6 +45,8 @@ interface RecipeSpecConfiguration {
|
|
|
44
45
|
checkParsePrintIdempotence: boolean;
|
|
45
46
|
allowEmptyDiff: boolean;
|
|
46
47
|
checkReportingMarkersOnly: boolean;
|
|
48
|
+
cycles?: number;
|
|
49
|
+
expectedCyclesThatMakeChanges?: number;
|
|
47
50
|
recipe: Recipe;
|
|
48
51
|
recipeExecutionContext: ExecutionContext;
|
|
49
52
|
dataTableAssertions: { [key: string]: (rows: any[]) => void };
|
|
@@ -59,6 +62,19 @@ export class RecipeSpec {
|
|
|
59
62
|
*/
|
|
60
63
|
checkReportingMarkersOnly: boolean = true
|
|
61
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The cycles to run, by default one more than {@link expectedCyclesThatMakeChanges} and at least two. The
|
|
67
|
+
* cycles after the expected ones, and a run on a fresh parse of the output, fail the test if they change
|
|
68
|
+
* anything, so setting this to 1 turns the stability checks off.
|
|
69
|
+
*/
|
|
70
|
+
cycles?: number
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* How many cycles a recipe needs to settle, by default 1. A recipe that only reaches its result over
|
|
74
|
+
* several cycles declares how many here, and the test then compares the output of the last of them.
|
|
75
|
+
*/
|
|
76
|
+
expectedCyclesThatMakeChanges?: number
|
|
77
|
+
|
|
62
78
|
recipe: Recipe = new NoopRecipe()
|
|
63
79
|
|
|
64
80
|
/**
|
|
@@ -94,6 +110,8 @@ export class RecipeSpec {
|
|
|
94
110
|
checkParsePrintIdempotence: this.checkParsePrintIdempotence,
|
|
95
111
|
allowEmptyDiff: this.allowEmptyDiff,
|
|
96
112
|
checkReportingMarkersOnly: this.checkReportingMarkersOnly,
|
|
113
|
+
cycles: this.cycles,
|
|
114
|
+
expectedCyclesThatMakeChanges: this.expectedCyclesThatMakeChanges,
|
|
97
115
|
recipe: this.recipe,
|
|
98
116
|
recipeExecutionContext: this.recipeExecutionContext,
|
|
99
117
|
dataTableAssertions: {...this.dataTableAssertions}
|
|
@@ -194,19 +212,142 @@ export class RecipeSpec {
|
|
|
194
212
|
}
|
|
195
213
|
}
|
|
196
214
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
215
|
+
const expectedCycles = this.expectedCyclesThatMakeChanges ?? 1;
|
|
216
|
+
const cycles = this.cycles ?? Math.max(2, expectedCycles + 1);
|
|
217
|
+
const {changeset, unsettled} = await this.runCycles(allParsed.map(([_, sourceFile]) => sourceFile),
|
|
218
|
+
cycles, expectedCycles);
|
|
200
219
|
|
|
201
220
|
this.checkReportingMarkersOnly && await this.expectNoAddedMarkers(flattenedSpecs, allParsed, changeset);
|
|
202
221
|
await this.expectResultsToMatchAfter(flattenedSpecs, changeset, allParsed);
|
|
203
222
|
await this.expectGeneratedFiles(flattenedSpecs, changeset);
|
|
223
|
+
if (unsettled) {
|
|
224
|
+
await this.expectNoFurtherChanges(unsettled.changes,
|
|
225
|
+
`Expected recipe to complete in ${pluralCycles(expectedCycles)}, but cycle ${unsettled.cycle} changed`);
|
|
226
|
+
}
|
|
227
|
+
cycles > expectedCycles && await this.expectStableOnFreshParse(flattenedSpecs, allParsed, changeset);
|
|
204
228
|
|
|
205
229
|
// for (const [name, assertion] of Object.entries(this.dataTableAssertions)) {
|
|
206
230
|
// assertion(getRows(name, this.recipeExecutionContext));
|
|
207
231
|
// }
|
|
208
232
|
}
|
|
209
233
|
|
|
234
|
+
private async runCycles(sources: SourceFile[], cycles: number, expectedCycles: number):
|
|
235
|
+
Promise<{ changeset: Result[], unsettled?: { cycle: number, changes: Result[] } }> {
|
|
236
|
+
const lineages: { origin?: SourceFile, latest?: SourceFile }[] = sources.map(s => ({origin: s, latest: s}));
|
|
237
|
+
const changesetOf = () => lineages.map(({origin, latest}) => new Result(origin, latest));
|
|
238
|
+
let changeset: Result[] | undefined;
|
|
239
|
+
let cyclesThatMadeChanges = 0;
|
|
240
|
+
for (let cycle = 1; cycle <= cycles; cycle++) {
|
|
241
|
+
const live = lineages.filter(l => l.latest).map(l => l.latest!);
|
|
242
|
+
const ctx = cycle === 1 ? this.recipeExecutionContext : this.laterRunContext();
|
|
243
|
+
const changes = (await scheduleRun(this.recipe, live, ctx)).changeset.filter(r => r.before !== r.after);
|
|
244
|
+
if (changes.length === 0) {
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
if (changeset) {
|
|
248
|
+
return {changeset, unsettled: {cycle, changes}};
|
|
249
|
+
}
|
|
250
|
+
cyclesThatMadeChanges++;
|
|
251
|
+
for (const {before, after} of changes) {
|
|
252
|
+
const lineage = before && lineages.find(l => l.latest === before);
|
|
253
|
+
if (lineage) {
|
|
254
|
+
lineage.latest = after;
|
|
255
|
+
} else {
|
|
256
|
+
lineages.push({latest: after});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (cycle >= expectedCycles) {
|
|
260
|
+
changeset = changesetOf();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (cyclesThatMadeChanges < expectedCycles && expectedCycles > 1) {
|
|
264
|
+
throw new Error(`Expected recipe to complete in ${pluralCycles(expectedCycles)}, ` +
|
|
265
|
+
`but it settled after ${pluralCycles(cyclesThatMadeChanges)}. Lower expectedCyclesThatMakeChanges.`);
|
|
266
|
+
}
|
|
267
|
+
return {changeset: changeset ?? changesetOf()};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Data tables are written by the first cycle only, as in Java. */
|
|
271
|
+
private laterRunContext(): ExecutionContext {
|
|
272
|
+
const messages = {...this.recipeExecutionContext.messages};
|
|
273
|
+
delete messages[DATA_TABLE_STORE];
|
|
274
|
+
return new ExecutionContext(messages);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private async expectNoFurtherChanges(changes: Result[], headline: string, sanitized: boolean = false) {
|
|
278
|
+
const print = (sourceFile: SourceFile) => sanitized ? printSanitized(sourceFile) : TreePrinters.print(sourceFile);
|
|
279
|
+
const found: string[] = [];
|
|
280
|
+
for (const {before, after} of changes) {
|
|
281
|
+
if (!before) {
|
|
282
|
+
found.push(` ${after!.sourcePath}, which it generated`);
|
|
283
|
+
} else if (!after) {
|
|
284
|
+
found.push(` ${before.sourcePath}, which it deleted`);
|
|
285
|
+
} else {
|
|
286
|
+
const [expected, actual] = [await print(before), await print(after)];
|
|
287
|
+
if (expected !== actual) {
|
|
288
|
+
found.push(` ${after.sourcePath}\nExpected:\n${expected}\n\nActual:\n${actual}`);
|
|
289
|
+
} else if (!sanitized && !this.allowEmptyDiff) {
|
|
290
|
+
found.push(` ${after.sourcePath}, whose LST it changed without changing the printed source`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (found.length > 0) {
|
|
295
|
+
throw new Error([
|
|
296
|
+
`${headline} the sources again:`,
|
|
297
|
+
...found,
|
|
298
|
+
"A recipe should recognize code it has already changed and leave it alone, so that running it " +
|
|
299
|
+
"again changes nothing. A recipe that is meant to need more cycles declares how many with " +
|
|
300
|
+
"expectedCyclesThatMakeChanges."
|
|
301
|
+
].join("\n"));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Code that the next run reads is parsed from the printed output, not the LST this run produced, and a
|
|
307
|
+
* recipe that recognizes its own output only by the shape of that LST changes it again.
|
|
308
|
+
*/
|
|
309
|
+
private async expectStableOnFreshParse(specs: SourceSpec<any>[], parsed: [SourceSpec<any>, SourceFile][],
|
|
310
|
+
changeset: Result[]) {
|
|
311
|
+
if (!changeset.some(r => r.before !== r.after)) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const finals = changeset.filter(r => r.after).map(({before, after}): [SourceSpec<any> | undefined, SourceFile] => [
|
|
315
|
+
before ? parsed.find(([_, sourceFile]) => sourceFile === before)?.[0] :
|
|
316
|
+
specs.find(s => s.path === after!.sourcePath),
|
|
317
|
+
after!
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
const reparsedByFinal = new Map<SourceFile, SourceFile>();
|
|
321
|
+
const kinds = new Set(finals.filter(([spec]) => spec).map(([spec]) => spec!.kind));
|
|
322
|
+
for (const kind of kinds) {
|
|
323
|
+
const group = finals.filter(([spec]) => spec?.kind === kind);
|
|
324
|
+
const inputs = [];
|
|
325
|
+
for (const [_, sourceFile] of group) {
|
|
326
|
+
inputs.push({text: await printSanitized(sourceFile), sourcePath: sourceFile.sourcePath});
|
|
327
|
+
}
|
|
328
|
+
let i = 0;
|
|
329
|
+
try {
|
|
330
|
+
for await (const reparsed of group[0][0]!.parser(this.executionContext).parse(...inputs)) {
|
|
331
|
+
if (!isParseError(reparsed) && await printSanitized(reparsed) === inputs[i].text) {
|
|
332
|
+
// What the source file carries, like its styles, comes from outside its text
|
|
333
|
+
reparsedByFinal.set(group[i][1], {...reparsed, markers: group[i][1].markers});
|
|
334
|
+
}
|
|
335
|
+
i++;
|
|
336
|
+
}
|
|
337
|
+
} catch {
|
|
338
|
+
// files that do not parse again keep the LST the recipe produced
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (reparsedByFinal.size === 0) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const sources = finals.map(([_, sourceFile]) => reparsedByFinal.get(sourceFile) ?? sourceFile);
|
|
346
|
+
const changes = (await scheduleRun(this.recipe, sources, this.laterRunContext())).changeset
|
|
347
|
+
.filter(r => r.before !== r.after);
|
|
348
|
+
await this.expectNoFurtherChanges(changes, "Running the recipe on a fresh parse of its output changed", true);
|
|
349
|
+
}
|
|
350
|
+
|
|
210
351
|
private async expectNoParseFailures(parsed: [SourceSpec<any>, SourceFile][]) {
|
|
211
352
|
for (const [_, sourceFile] of parsed) {
|
|
212
353
|
if (sourceFile.kind === ParseErrorKind) {
|
|
@@ -291,9 +432,7 @@ export class RecipeSpec {
|
|
|
291
432
|
*/
|
|
292
433
|
private async freshParsePlacements(spec: SourceSpec<any>, after: SourceFile): Promise<Set<string>> {
|
|
293
434
|
try {
|
|
294
|
-
|
|
295
|
-
const out = new PrintOutputCapture(MarkerPrinter.SANITIZED);
|
|
296
|
-
const text = await TreePrinters.printer(after).print(after, out);
|
|
435
|
+
const text = await printSanitized(after);
|
|
297
436
|
const reparsed = await spec.parser(this.executionContext).parseOne({text, sourcePath: after.sourcePath});
|
|
298
437
|
return isParseError(reparsed) ? new Set() : new Set(markerPlacements(reparsed).keys());
|
|
299
438
|
} catch {
|
|
@@ -420,6 +559,15 @@ class ValidateWhitespaceVisitor extends JavaScriptVisitor<ExecutionContext> {
|
|
|
420
559
|
}
|
|
421
560
|
}
|
|
422
561
|
|
|
562
|
+
/** Without the comments that search results and markup print as. */
|
|
563
|
+
function printSanitized(sourceFile: SourceFile): Promise<string> {
|
|
564
|
+
return TreePrinters.printer(sourceFile).print(sourceFile, new PrintOutputCapture(MarkerPrinter.SANITIZED));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function pluralCycles(count: number): string {
|
|
568
|
+
return `${count} cycle${count === 1 ? "" : "s"}`;
|
|
569
|
+
}
|
|
570
|
+
|
|
423
571
|
/** Markers that report on an LST rather than describe it, which a recipe is free to leave. */
|
|
424
572
|
const reportingMarkerKinds = new Set<string>([
|
|
425
573
|
MarkersKind.SearchResult,
|