@openrewrite/rewrite 8.91.0 → 8.91.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/java/formatting-utils.d.ts +8 -5
- package/dist/java/formatting-utils.d.ts.map +1 -1
- package/dist/java/formatting-utils.js +14 -10
- package/dist/java/formatting-utils.js.map +1 -1
- package/dist/java/type.d.ts +2 -0
- package/dist/java/type.d.ts.map +1 -1
- package/dist/java/type.js +5 -0
- package/dist/java/type.js.map +1 -1
- package/dist/javascript/add-import.d.ts +39 -20
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +513 -243
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/assertions.d.ts +7 -0
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +31 -5
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/comparator.d.ts +5 -0
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +34 -9
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +8 -9
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/format/tabs-and-indents-visitor.d.ts +2 -0
- package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -1
- package/dist/javascript/format/tabs-and-indents-visitor.js +17 -11
- package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
- package/dist/javascript/node-resolution-result.d.ts +19 -1
- package/dist/javascript/node-resolution-result.d.ts.map +1 -1
- package/dist/javascript/node-resolution-result.js +37 -2
- package/dist/javascript/node-resolution-result.js.map +1 -1
- package/dist/javascript/package-exported-types.d.ts.map +1 -1
- package/dist/javascript/package-exported-types.js +56 -72
- package/dist/javascript/package-exported-types.js.map +1 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +10 -3
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/recipes/change-import.d.ts.map +1 -1
- package/dist/javascript/recipes/change-import.js +4 -8
- package/dist/javascript/recipes/change-import.js.map +1 -1
- package/dist/javascript/style.d.ts.map +1 -1
- package/dist/javascript/style.js +3 -10
- package/dist/javascript/style.js.map +1 -1
- package/dist/javascript/templating/bindings.d.ts +19 -0
- package/dist/javascript/templating/bindings.d.ts.map +1 -0
- package/dist/javascript/templating/bindings.js +121 -0
- package/dist/javascript/templating/bindings.js.map +1 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +5 -6
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +18 -7
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +70 -42
- package/dist/javascript/templating/engine.js.map +1 -1
- 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.map +1 -1
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +13 -10
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +2 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +14 -5
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/rewrite.d.ts.map +1 -1
- package/dist/javascript/templating/rewrite.js +14 -18
- package/dist/javascript/templating/rewrite.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +8 -0
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +43 -3
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +47 -1
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +5 -0
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +17 -0
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +1 -0
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +52 -2
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/style.d.ts +2 -0
- package/dist/style.d.ts.map +1 -1
- package/dist/style.js +5 -0
- package/dist/style.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +12 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +40 -3
- package/dist/test/rewrite-test.js.map +1 -1
- package/package.json +1 -1
- package/src/java/formatting-utils.ts +14 -10
- package/src/java/type.ts +5 -0
- package/src/javascript/add-import.ts +590 -230
- package/src/javascript/assertions.ts +33 -5
- package/src/javascript/comparator.ts +39 -10
- package/src/javascript/format/format.ts +9 -10
- package/src/javascript/format/tabs-and-indents-visitor.ts +18 -12
- package/src/javascript/node-resolution-result.ts +59 -4
- package/src/javascript/package-exported-types.ts +60 -74
- package/src/javascript/parser.ts +10 -3
- package/src/javascript/recipes/change-import.ts +4 -7
- package/src/javascript/style.ts +3 -11
- package/src/javascript/templating/bindings.ts +129 -0
- package/src/javascript/templating/comparator.ts +5 -6
- package/src/javascript/templating/engine.ts +82 -44
- package/src/javascript/templating/index.ts +2 -0
- package/src/javascript/templating/pattern.ts +16 -15
- package/src/javascript/templating/placeholder-replacement.ts +16 -5
- package/src/javascript/templating/rewrite.ts +16 -18
- package/src/javascript/templating/template.ts +45 -3
- package/src/javascript/templating/types.ts +53 -1
- package/src/javascript/templating/utils.ts +17 -0
- package/src/javascript/type-mapping.ts +54 -2
- package/src/style.ts +5 -0
- package/src/test/rewrite-test.ts +61 -3
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
import {Cursor, Tree} from '../..';
|
|
17
17
|
import {J} from '../../java';
|
|
18
18
|
import {ApplyOptions, Parameter, TemplateOptions, TemplateParameter} from './types';
|
|
19
|
+
import {bindingContextStatement, isResolvable} from './bindings';
|
|
20
|
+
import {maybeAddImport} from '../add-import';
|
|
21
|
+
import {JavaScriptVisitor} from '../visitor';
|
|
19
22
|
import {MatchResult} from './pattern';
|
|
20
23
|
import {generateCacheKey, globalAstCache, WRAPPERS_MAP_SYMBOL} from './utils';
|
|
21
24
|
import {CAPTURE_NAME_SYMBOL, RAW_CODE_SYMBOL} from './capture';
|
|
@@ -246,7 +249,11 @@ export class Template {
|
|
|
246
249
|
// Generate cache key for global lookup
|
|
247
250
|
// For raw() parameters, we need to include their code values in the key
|
|
248
251
|
// since they're spliced at construction time, not application time
|
|
249
|
-
const contextStatements =
|
|
252
|
+
const contextStatements = [
|
|
253
|
+
...(this.options.context || this.options.imports || []),
|
|
254
|
+
...Object.entries(this.options.bindings ?? {})
|
|
255
|
+
.map(([name, b]) => bindingContextStatement(name, b, this.options.dependencies ?? {}))
|
|
256
|
+
];
|
|
250
257
|
const parametersKey = this.parameters.map((p, i) => {
|
|
251
258
|
const value = p.value;
|
|
252
259
|
// Include raw code values in the cache key using the symbol
|
|
@@ -258,7 +265,8 @@ export class Template {
|
|
|
258
265
|
const cacheKey = generateCacheKey(
|
|
259
266
|
this.templateParts,
|
|
260
267
|
parametersKey,
|
|
261
|
-
|
|
268
|
+
// As in Pattern.getAstPattern: a parameter's type reaches the parse as a declaration
|
|
269
|
+
[...contextStatements, ...TemplateEngine.parameterPreamble(this.parameters)],
|
|
262
270
|
this.options.dependencies || {}
|
|
263
271
|
);
|
|
264
272
|
|
|
@@ -284,6 +292,24 @@ export class Template {
|
|
|
284
292
|
return result;
|
|
285
293
|
}
|
|
286
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Binds every module this template declares in the file `visitor` is traversing, and returns
|
|
297
|
+
* the local names to hand back through {@link ApplyOptions.bindings}. A module the dependencies
|
|
298
|
+
* cannot resolve is bound whether or not the template goes on to reference it, so call this
|
|
299
|
+
* where the template is known to apply — {@link RewriteRule.tryOn} does, once a pattern matched.
|
|
300
|
+
*/
|
|
301
|
+
resolveBindings(visitor: JavaScriptVisitor<any>): Record<string, string> {
|
|
302
|
+
const resolved: Record<string, string> = {};
|
|
303
|
+
const dependencies = this.options.dependencies ?? {};
|
|
304
|
+
for (const [name, binding] of Object.entries(this.options.bindings ?? {})) {
|
|
305
|
+
// Recognising the reference the template splices in takes attribution, which only the
|
|
306
|
+
// import form of a context statement carries. Without one there is nothing to look for.
|
|
307
|
+
const onlyIfReferenced = isResolvable(binding.module, dependencies);
|
|
308
|
+
resolved[name] = maybeAddImport(visitor, {...binding, preferredName: name, onlyIfReferenced});
|
|
309
|
+
}
|
|
310
|
+
return resolved;
|
|
311
|
+
}
|
|
312
|
+
|
|
287
313
|
/**
|
|
288
314
|
* Applies this template and returns the resulting tree.
|
|
289
315
|
*
|
|
@@ -346,6 +372,19 @@ export class Template {
|
|
|
346
372
|
}
|
|
347
373
|
}
|
|
348
374
|
|
|
375
|
+
const declared = this.options.bindings ?? {};
|
|
376
|
+
const renames: Record<string, string> = {};
|
|
377
|
+
const modules: Record<string, string> = {};
|
|
378
|
+
for (const [name, binding] of Object.entries(declared)) {
|
|
379
|
+
const bound = options?.bindings?.[name];
|
|
380
|
+
if (bound === undefined) {
|
|
381
|
+
throw new Error(`Template declares a binding for '${name}' but was applied without a local name for it. ` +
|
|
382
|
+
`Pass bindings: template.resolveBindings(visitor) to apply().`);
|
|
383
|
+
}
|
|
384
|
+
renames[name] = bound;
|
|
385
|
+
modules[name] = binding.module;
|
|
386
|
+
}
|
|
387
|
+
|
|
349
388
|
// Use instance-level cache to get the template tree
|
|
350
389
|
const ast = await this.getTemplateTree();
|
|
351
390
|
|
|
@@ -359,7 +398,10 @@ export class Template {
|
|
|
359
398
|
mode: JavaCoordinates.Mode.Replace
|
|
360
399
|
},
|
|
361
400
|
normalizedValues,
|
|
362
|
-
wrappersMap
|
|
401
|
+
wrappersMap,
|
|
402
|
+
options?.format ?? true,
|
|
403
|
+
renames,
|
|
404
|
+
modules
|
|
363
405
|
);
|
|
364
406
|
}
|
|
365
407
|
}
|
|
@@ -18,6 +18,8 @@ import {J, Type} from '../../java';
|
|
|
18
18
|
import type {Pattern} from "./pattern";
|
|
19
19
|
import type {Template} from "./template";
|
|
20
20
|
import type {CaptureValue, RawCode} from "./capture";
|
|
21
|
+
import type {AddImportOptions} from "../add-import";
|
|
22
|
+
import type {JavaScriptVisitor} from "../visitor";
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Options for variadic captures that match zero or more nodes in a sequence.
|
|
@@ -481,8 +483,29 @@ export interface TemplateOptions {
|
|
|
481
483
|
* The template engine will create a package.json with these dependencies.
|
|
482
484
|
*/
|
|
483
485
|
dependencies?: Record<string, string>;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Modules the template's code refers to, keyed by the identifier its source uses for each.
|
|
489
|
+
* The key is a preferred name: {@link Template.resolveBindings} deconflicts it against the
|
|
490
|
+
* file, and applying the template rewrites the template's references to whatever it settled on.
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* ```typescript
|
|
494
|
+
* template`Theming.setTheme(${capture('theme')})`
|
|
495
|
+
* .configure({bindings: {Theming: {module: 'sap/ui/core/Theming', member: 'default'}}})
|
|
496
|
+
* ```
|
|
497
|
+
*/
|
|
498
|
+
bindings?: Record<string, ModuleBinding>;
|
|
484
499
|
}
|
|
485
500
|
|
|
501
|
+
/**
|
|
502
|
+
* A module a template's code depends on. The local name comes from the key it is declared under,
|
|
503
|
+
* and {@link Template.resolveBindings} settles the rest. `member` and `typeOnly` shape the import
|
|
504
|
+
* it creates, so a caller passing {@link ApplyOptions.bindings} of its own reads neither.
|
|
505
|
+
*/
|
|
506
|
+
export type ModuleBinding = Omit<AddImportOptions,
|
|
507
|
+
'module' | 'alias' | 'preferredName' | 'onlyIfReferenced' | 'sideEffectOnly'> & { module: string };
|
|
508
|
+
|
|
486
509
|
/**
|
|
487
510
|
* Options for template application.
|
|
488
511
|
*/
|
|
@@ -509,6 +532,32 @@ export interface ApplyOptions {
|
|
|
509
532
|
* ```
|
|
510
533
|
*/
|
|
511
534
|
values?: Map<Capture | string, J> | MatchResult | Record<string, J>;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Whether the result is fitted to where it lands. Defaults to `true`; pass `false` to assemble
|
|
538
|
+
* several results into one subtree and format that subtree once, rather than once per application.
|
|
539
|
+
* The anchor supplies the result's prefix either way.
|
|
540
|
+
*/
|
|
541
|
+
format?: boolean;
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Local names for the bindings the template declares, keyed as they are declared — normally
|
|
545
|
+
* {@link Template.resolveBindings}. Every declared binding needs one.
|
|
546
|
+
*/
|
|
547
|
+
bindings?: Record<string, string>;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Options a caller supplies per node, as against the ones {@link RewriteConfig} fixes for the rule. */
|
|
551
|
+
export interface TryOnOptions {
|
|
552
|
+
/**
|
|
553
|
+
* The visitor to bind the applied template's declared modules in. The rule holds that template,
|
|
554
|
+
* so it resolves them itself, and only once a pattern has matched — a rule that does not fire
|
|
555
|
+
* leaves the file's imports alone. {@link TryOnOptions.bindings} overrides this.
|
|
556
|
+
*/
|
|
557
|
+
visitor?: JavaScriptVisitor<any>;
|
|
558
|
+
|
|
559
|
+
/** As {@link ApplyOptions.bindings}, for a caller that resolves the modules some other way. */
|
|
560
|
+
bindings?: Record<string, string>;
|
|
512
561
|
}
|
|
513
562
|
|
|
514
563
|
/**
|
|
@@ -524,7 +573,7 @@ export interface RewriteRule {
|
|
|
524
573
|
* When using in a visitor, always use the `|| node` pattern to return the original
|
|
525
574
|
* node when there's no match: `return await rule.tryOn(this.cursor, node) || node;`
|
|
526
575
|
*/
|
|
527
|
-
tryOn(cursor: Cursor, node: J): Promise<J | undefined>;
|
|
576
|
+
tryOn(cursor: Cursor, node: J, options?: TryOnOptions): Promise<J | undefined>;
|
|
528
577
|
|
|
529
578
|
/**
|
|
530
579
|
* Chains this rule with another rule, creating a composite rule that applies both transformations sequentially.
|
|
@@ -624,6 +673,9 @@ export interface RewriteConfig {
|
|
|
624
673
|
before: Pattern | Pattern[];
|
|
625
674
|
after: Template | ((match: MatchResult) => Template);
|
|
626
675
|
|
|
676
|
+
/** As {@link ApplyOptions.format}, applied to every node the rule rewrites. */
|
|
677
|
+
format?: boolean;
|
|
678
|
+
|
|
627
679
|
/**
|
|
628
680
|
* Optional predicate evaluated BEFORE pattern matching.
|
|
629
681
|
* Use for efficient early filtering based on AST context when captures aren't needed.
|
|
@@ -182,6 +182,23 @@ export function retainIds<T extends J>(tree: T, retainable: ReadonlySet<string>)
|
|
|
182
182
|
return new RetainIdVisitor(retainable).visit(tree, null) as Promise<T>;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Strips the indentation a template carries from the recipe source it was written in. Its first
|
|
187
|
+
* line starts at the opening backtick, so the common indent is the one shared by the lines below.
|
|
188
|
+
*/
|
|
189
|
+
export function dedentTemplate(code: string): string {
|
|
190
|
+
const lines = code.split("\n");
|
|
191
|
+
let indent = Infinity;
|
|
192
|
+
for (const line of lines.slice(1)) {
|
|
193
|
+
if (line.trim().length > 0) {
|
|
194
|
+
indent = Math.min(indent, line.length - line.trimStart().length);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return indent === Infinity || indent === 0 ?
|
|
198
|
+
code :
|
|
199
|
+
[lines[0], ...lines.slice(1).map(line => line.slice(indent))].join("\n");
|
|
200
|
+
}
|
|
201
|
+
|
|
185
202
|
/**
|
|
186
203
|
* Marker that stores capture metadata on pattern AST nodes.
|
|
187
204
|
* This avoids the need to parse capture names from identifiers during matching.
|
|
@@ -17,6 +17,7 @@ import ts from "typescript";
|
|
|
17
17
|
import * as path from "path";
|
|
18
18
|
import {Type} from "../java";
|
|
19
19
|
import FUNCTION_TYPE_NAME = Type.FUNCTION_TYPE_NAME;
|
|
20
|
+
import OBJECT_TYPE_NAME = Type.OBJECT_TYPE_NAME;
|
|
20
21
|
|
|
21
22
|
export class JavaScriptTypeMapping {
|
|
22
23
|
// Primary cache: Use type signatures (preferring type.id) as cache keys
|
|
@@ -461,14 +462,25 @@ export class JavaScriptTypeMapping {
|
|
|
461
462
|
return functionType;
|
|
462
463
|
}
|
|
463
464
|
|
|
464
|
-
//
|
|
465
|
+
// A structural type has no nominal name, so every object literal `{a: 1}` and type
|
|
466
|
+
// literal `{a: number}` shares the synthetic FQN `{}`, with `members` carrying the shape.
|
|
465
467
|
if (type.flags & ts.TypeFlags.Object) {
|
|
466
468
|
const objectFlags = (type as ts.ObjectType).objectFlags;
|
|
467
469
|
if (objectFlags & ts.ObjectFlags.Anonymous) {
|
|
468
|
-
|
|
470
|
+
const objectType = this.createEmptyObjectType();
|
|
471
|
+
this.typeCache.set(signature, objectType);
|
|
472
|
+
this.populateClassType(objectType, type);
|
|
473
|
+
return objectType;
|
|
469
474
|
}
|
|
470
475
|
}
|
|
471
476
|
|
|
477
|
+
// The `object` keyword: an object of unknown shape.
|
|
478
|
+
if (type.flags & ts.TypeFlags.NonPrimitive) {
|
|
479
|
+
const objectType = this.createEmptyObjectType();
|
|
480
|
+
this.typeCache.set(signature, objectType);
|
|
481
|
+
return objectType;
|
|
482
|
+
}
|
|
483
|
+
|
|
472
484
|
// Pre-cache as unknownType before resolving type aliases to prevent infinite
|
|
473
485
|
// recursion when alias resolution cycles back through getType with different type.ids
|
|
474
486
|
this.typeCache.set(signature, Type.unknownType);
|
|
@@ -820,6 +832,29 @@ export class JavaScriptTypeMapping {
|
|
|
820
832
|
}
|
|
821
833
|
|
|
822
834
|
methodType(node: ts.Node): Type.Method | undefined {
|
|
835
|
+
// An object literal is a `J.NewClass`, and `J.NewClass.getType()` reads
|
|
836
|
+
// `constructorType.returnType`, as it does for a Java anonymous class.
|
|
837
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
838
|
+
const objectType = this.type(node);
|
|
839
|
+
if (!Type.isFullyQualified(objectType)) {
|
|
840
|
+
return undefined;
|
|
841
|
+
}
|
|
842
|
+
return {
|
|
843
|
+
kind: Type.Kind.Method,
|
|
844
|
+
flags: 0,
|
|
845
|
+
declaringType: objectType,
|
|
846
|
+
name: '<constructor>',
|
|
847
|
+
returnType: objectType,
|
|
848
|
+
parameterNames: [],
|
|
849
|
+
parameterTypes: [],
|
|
850
|
+
thrownExceptions: [],
|
|
851
|
+
annotations: [],
|
|
852
|
+
declaredFormalTypeNames: [],
|
|
853
|
+
toJSON: function () {
|
|
854
|
+
return Type.signature(this);
|
|
855
|
+
}
|
|
856
|
+
} as Type.Method;
|
|
857
|
+
}
|
|
823
858
|
|
|
824
859
|
let signature: ts.Signature | undefined;
|
|
825
860
|
let methodName: string;
|
|
@@ -1644,6 +1679,23 @@ export class JavaScriptTypeMapping {
|
|
|
1644
1679
|
return gtv;
|
|
1645
1680
|
}
|
|
1646
1681
|
|
|
1682
|
+
private createEmptyObjectType(): Type.Class {
|
|
1683
|
+
return {
|
|
1684
|
+
kind: Type.Kind.Class,
|
|
1685
|
+
flags: 0,
|
|
1686
|
+
classKind: Type.Class.Kind.Interface,
|
|
1687
|
+
fullyQualifiedName: OBJECT_TYPE_NAME,
|
|
1688
|
+
typeParameters: [],
|
|
1689
|
+
annotations: [],
|
|
1690
|
+
interfaces: [],
|
|
1691
|
+
members: [],
|
|
1692
|
+
methods: [],
|
|
1693
|
+
toJSON: function () {
|
|
1694
|
+
return Type.signature(this);
|
|
1695
|
+
}
|
|
1696
|
+
} as Type.Class;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1647
1699
|
/**
|
|
1648
1700
|
* Create an empty function type shell with FQN 𝑓.
|
|
1649
1701
|
* The shell will be populated later to handle circular references.
|
package/src/style.ts
CHANGED
|
@@ -20,6 +20,11 @@ export interface Style {
|
|
|
20
20
|
readonly kind: string
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** Style markers carry their own kind (e.g. Autodetect, PrettierStyle), so `styles` is what identifies one. */
|
|
24
|
+
export function isNamedStyles(marker: Marker): marker is NamedStyles<string> {
|
|
25
|
+
return Array.isArray((marker as Partial<NamedStyles<string>>).styles);
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
export interface NamedStyles<K extends string = typeof MarkersKind.NamedStyles> extends Marker {
|
|
24
29
|
readonly kind: K
|
|
25
30
|
readonly name: string;
|
package/src/test/rewrite-test.ts
CHANGED
|
@@ -20,7 +20,6 @@ import {Parser} from "../parser";
|
|
|
20
20
|
import {TreePrinters} from "../print";
|
|
21
21
|
import {SourceFile} from "../tree";
|
|
22
22
|
import {Result, scheduleRun} from "../run";
|
|
23
|
-
import {SnowflakeId} from "@akashrajpurohit/snowflake-id";
|
|
24
23
|
import {mapAsync, trimIndent} from "../util";
|
|
25
24
|
import {ParseErrorKind} from "../parse-error";
|
|
26
25
|
import {MarkersKind, ParseExceptionResult} from "../markers";
|
|
@@ -38,6 +37,15 @@ export interface SourceSpec<T extends SourceFile> {
|
|
|
38
37
|
ext: string
|
|
39
38
|
}
|
|
40
39
|
|
|
40
|
+
/** The fields a test configures on a spec, as opposed to the machinery it runs with. */
|
|
41
|
+
interface RecipeSpecConfiguration {
|
|
42
|
+
checkParsePrintIdempotence: boolean;
|
|
43
|
+
allowEmptyDiff: boolean;
|
|
44
|
+
recipe: Recipe;
|
|
45
|
+
recipeExecutionContext: ExecutionContext;
|
|
46
|
+
dataTableAssertions: { [key: string]: (rows: any[]) => void };
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
export class RecipeSpec {
|
|
42
50
|
checkParsePrintIdempotence: boolean = true
|
|
43
51
|
allowEmptyDiff: boolean = false
|
|
@@ -57,10 +65,58 @@ export class RecipeSpec {
|
|
|
57
65
|
|
|
58
66
|
private dataTableAssertions: { [key: string]: (rows: any[]) => void } = {}
|
|
59
67
|
|
|
68
|
+
// A suite typically declares one spec and configures it per test, which makes the spec a
|
|
69
|
+
// channel between tests unless each test starts from the same configuration. A consumer opts
|
|
70
|
+
// into that reset by calling `trackSuiteConfiguration` from a vitest setup file, as
|
|
71
|
+
// `test/setup.ts` does; until it does, `live` is undefined and no spec is retained here.
|
|
72
|
+
private static live?: RecipeSpec[];
|
|
73
|
+
private suiteConfiguration?: RecipeSpecConfiguration;
|
|
74
|
+
|
|
75
|
+
constructor() {
|
|
76
|
+
RecipeSpec.live?.push(this);
|
|
77
|
+
}
|
|
78
|
+
|
|
60
79
|
dataTable<Row>(name: string, allRows: (rows: Row[]) => void) {
|
|
61
80
|
this.dataTableAssertions[name] = allRows;
|
|
62
81
|
}
|
|
63
82
|
|
|
83
|
+
private configuration(): RecipeSpecConfiguration {
|
|
84
|
+
return {
|
|
85
|
+
checkParsePrintIdempotence: this.checkParsePrintIdempotence,
|
|
86
|
+
allowEmptyDiff: this.allowEmptyDiff,
|
|
87
|
+
recipe: this.recipe,
|
|
88
|
+
recipeExecutionContext: this.recipeExecutionContext,
|
|
89
|
+
dataTableAssertions: {...this.dataTableAssertions}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Starts recording specs, so that the calls below have something to act on. */
|
|
94
|
+
static trackSuiteConfiguration(): void {
|
|
95
|
+
RecipeSpec.live = [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Records the configuration each test in the current suite starts from. */
|
|
99
|
+
static captureSuiteConfiguration(): void {
|
|
100
|
+
for (const spec of RecipeSpec.live ?? []) {
|
|
101
|
+
spec.suiteConfiguration = spec.configuration();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Undoes whatever the previous test configured. */
|
|
106
|
+
static restoreSuiteConfiguration(): void {
|
|
107
|
+
for (const spec of RecipeSpec.live ?? []) {
|
|
108
|
+
if (spec.suiteConfiguration) {
|
|
109
|
+
Object.assign(spec, spec.suiteConfiguration);
|
|
110
|
+
spec.dataTableAssertions = {...spec.suiteConfiguration.dataTableAssertions};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Bounds the registry to the suite that is running, which is all it has to span. */
|
|
116
|
+
static forgetSuiteSpecs(): void {
|
|
117
|
+
RecipeSpec.live &&= [];
|
|
118
|
+
}
|
|
119
|
+
|
|
64
120
|
async rewriteRun(...sourceSpecs: (SourceSpec<any> | Generator<SourceSpec<any>, void, unknown> | AsyncGenerator<SourceSpec<any>, void, unknown>)[]): Promise<void> {
|
|
65
121
|
// Flatten generators into a list of sourceSpecs
|
|
66
122
|
const flattenedSpecs: SourceSpec<any>[] = [];
|
|
@@ -230,11 +286,13 @@ export class RecipeSpec {
|
|
|
230
286
|
* Parse the whole group together so the sources can reference once another.
|
|
231
287
|
*/
|
|
232
288
|
private async parse(specs: SourceSpec<any>[]): Promise<[SourceSpec<any>, SourceFile][]> {
|
|
233
|
-
|
|
289
|
+
// The path is a function of position, so a test parses under the same path on every run,
|
|
290
|
+
// which is what lets a parser pooled across specs reuse its program.
|
|
291
|
+
let unnamed = 0;
|
|
234
292
|
const before: [SourceSpec<any>, { text: string, sourcePath: string }][] = [];
|
|
235
293
|
for (const spec of specs) {
|
|
236
294
|
if (spec.before) {
|
|
237
|
-
const sourcePath = spec.path ||
|
|
295
|
+
const sourcePath = spec.path || `source-${unnamed++}.${spec.ext}`;
|
|
238
296
|
before.push([spec, {text: dedent(spec.before), sourcePath: sourcePath}]);
|
|
239
297
|
}
|
|
240
298
|
}
|