@openrewrite/rewrite 8.91.1 → 8.91.3
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/execution.d.ts +8 -0
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +31 -0
- package/dist/execution.js.map +1 -1
- package/dist/javascript/add-import.d.ts +127 -38
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +684 -195
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts +148 -0
- package/dist/javascript/amd.d.ts.map +1 -0
- package/dist/javascript/amd.js +802 -0
- package/dist/javascript/amd.js.map +1 -0
- package/dist/javascript/binding.d.ts +102 -0
- package/dist/javascript/binding.d.ts.map +1 -0
- package/dist/javascript/binding.js +308 -0
- package/dist/javascript/binding.js.map +1 -0
- package/dist/javascript/comparator.d.ts +2 -0
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +5 -1
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts +5 -2
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +51 -11
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts +19 -0
- package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts.map +1 -1
- package/dist/javascript/format/minimum-viable-spacing-visitor.js +180 -7
- package/dist/javascript/format/minimum-viable-spacing-visitor.js.map +1 -1
- package/dist/javascript/format/prettier-format.d.ts +2 -1
- package/dist/javascript/format/prettier-format.d.ts.map +1 -1
- package/dist/javascript/format/prettier-format.js +33 -23
- package/dist/javascript/format/prettier-format.js.map +1 -1
- package/dist/javascript/index.d.ts +8 -1
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +46 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/parser-utils.d.ts.map +1 -1
- package/dist/javascript/parser-utils.js +7 -9
- package/dist/javascript/parser-utils.js.map +1 -1
- package/dist/javascript/parser.d.ts +7 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +55 -4
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/print.d.ts +1 -0
- package/dist/javascript/print.d.ts.map +1 -1
- package/dist/javascript/print.js +9 -0
- package/dist/javascript/print.js.map +1 -1
- package/dist/javascript/recipes/change-import.d.ts +4 -3
- package/dist/javascript/recipes/change-import.d.ts.map +1 -1
- package/dist/javascript/recipes/change-import.js +12 -504
- package/dist/javascript/recipes/change-import.js.map +1 -1
- package/dist/javascript/remove-import.d.ts +2 -28
- package/dist/javascript/remove-import.d.ts.map +1 -1
- package/dist/javascript/remove-import.js +19 -57
- package/dist/javascript/remove-import.js.map +1 -1
- package/dist/javascript/rpc.js +2 -0
- package/dist/javascript/rpc.js.map +1 -1
- package/dist/javascript/scope.d.ts +82 -0
- package/dist/javascript/scope.d.ts.map +1 -0
- package/dist/javascript/scope.js +476 -0
- package/dist/javascript/scope.js.map +1 -0
- package/dist/javascript/templating/bindings.d.ts +0 -11
- package/dist/javascript/templating/bindings.d.ts.map +1 -1
- package/dist/javascript/templating/bindings.js +2 -49
- package/dist/javascript/templating/bindings.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +12 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +68 -14
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +22 -4
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +68 -24
- 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 +2 -2
- 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 +10 -5
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/precedence.d.ts +12 -4
- package/dist/javascript/templating/precedence.d.ts.map +1 -1
- package/dist/javascript/templating/precedence.js +18 -11
- package/dist/javascript/templating/precedence.js.map +1 -1
- package/dist/javascript/templating/rewrite.d.ts.map +1 -1
- package/dist/javascript/templating/rewrite.js +7 -1
- package/dist/javascript/templating/rewrite.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +7 -1
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +54 -28
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +15 -18
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +1 -1
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +3 -2
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/tree.d.ts +1 -0
- package/dist/javascript/tree.d.ts.map +1 -1
- package/dist/javascript/tree.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +14 -0
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/javascript/visitor.d.ts.map +1 -1
- package/dist/javascript/visitor.js +1 -0
- package/dist/javascript/visitor.js.map +1 -1
- package/dist/parser.d.ts +6 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +23 -1
- package/dist/parser.js.map +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +5 -2
- package/dist/print.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/request/parse-project.d.ts +11 -1
- package/dist/rpc/request/parse-project.d.ts.map +1 -1
- package/dist/rpc/request/parse-project.js +7 -2
- package/dist/rpc/request/parse-project.js.map +1 -1
- package/dist/rpc/request/parse.d.ts +13 -1
- package/dist/rpc/request/parse.d.ts.map +1 -1
- package/dist/rpc/request/parse.js +8 -2
- package/dist/rpc/request/parse.js.map +1 -1
- package/package.json +2 -2
- package/src/execution.ts +23 -0
- package/src/javascript/add-import.ts +753 -238
- package/src/javascript/amd.ts +954 -0
- package/src/javascript/binding.ts +435 -0
- package/src/javascript/comparator.ts +6 -1
- package/src/javascript/format/format.ts +56 -12
- package/src/javascript/format/minimum-viable-spacing-visitor.ts +196 -7
- package/src/javascript/format/prettier-format.ts +35 -25
- package/src/javascript/index.ts +19 -1
- package/src/javascript/parser-utils.ts +7 -11
- package/src/javascript/parser.ts +78 -6
- package/src/javascript/print.ts +10 -0
- package/src/javascript/recipes/change-import.ts +13 -593
- package/src/javascript/remove-import.ts +20 -56
- package/src/javascript/rpc.ts +2 -0
- package/src/javascript/scope.ts +517 -0
- package/src/javascript/templating/bindings.ts +2 -55
- package/src/javascript/templating/comparator.ts +81 -16
- package/src/javascript/templating/engine.ts +107 -38
- package/src/javascript/templating/index.ts +0 -1
- package/src/javascript/templating/pattern.ts +4 -2
- package/src/javascript/templating/placeholder-replacement.ts +16 -6
- package/src/javascript/templating/precedence.ts +21 -13
- package/src/javascript/templating/rewrite.ts +9 -1
- package/src/javascript/templating/template.ts +68 -29
- package/src/javascript/templating/types.ts +17 -18
- package/src/javascript/templating/utils.ts +4 -2
- package/src/javascript/tree.ts +1 -0
- package/src/javascript/type-mapping.ts +15 -0
- package/src/javascript/visitor.ts +1 -0
- package/src/parser.ts +24 -1
- package/src/print.ts +5 -2
- package/src/rpc/request/parse-project.ts +6 -2
- package/src/rpc/request/parse.ts +7 -2
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import {isTree} from '../..';
|
|
17
17
|
import {emptySpace, J} from '../../java';
|
|
18
|
-
import {emptyMarkers, Marker, markers} from '../../markers';
|
|
18
|
+
import {emptyMarkers, Marker, markers, Markers} from '../../markers';
|
|
19
19
|
import {randomId} from '../../uuid';
|
|
20
20
|
import {JS} from '..';
|
|
21
21
|
|
|
@@ -319,9 +319,12 @@ export function requiredPrecedence(parent: J, childId: string): number | undefin
|
|
|
319
319
|
return slotConstraints(parent, childId)?.precedence;
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
/**
|
|
322
|
+
/**
|
|
323
|
+
* Parenthesizes `expression` if the slot of `parent` holding `childId` would otherwise reparse it.
|
|
324
|
+
* `slotMarkers` are the markers the slot contributed; {@link parenthesize} says what they decide.
|
|
325
|
+
*/
|
|
323
326
|
export function maybeParenthesize(parent: J | undefined, childId: string, expression: J,
|
|
324
|
-
|
|
327
|
+
slotMarkers?: Markers): J {
|
|
325
328
|
if (!parent) {
|
|
326
329
|
return expression;
|
|
327
330
|
}
|
|
@@ -334,10 +337,10 @@ export function maybeParenthesize(parent: J | undefined, childId: string, expres
|
|
|
334
337
|
// A statement wrapper is transparent here: the parentheses belong around the expression
|
|
335
338
|
if (expression.kind === JS.Kind.ExpressionStatement) {
|
|
336
339
|
const inner = (expression as JS.ExpressionStatement).expression;
|
|
337
|
-
const wrapped = wrapIfNeeded(parent, childId, constraints, inner,
|
|
340
|
+
const wrapped = wrapIfNeeded(parent, childId, constraints, inner, slotMarkers);
|
|
338
341
|
return wrapped === inner ? expression : {...expression, expression: wrapped} as JS.ExpressionStatement;
|
|
339
342
|
}
|
|
340
|
-
return wrapIfNeeded(parent, childId, constraints, expression,
|
|
343
|
+
return wrapIfNeeded(parent, childId, constraints, expression, slotMarkers);
|
|
341
344
|
}
|
|
342
345
|
|
|
343
346
|
/** The nearest enclosing LST node in a cursor path, skipping the padding wrappers visitors push. */
|
|
@@ -352,18 +355,23 @@ export function enclosingTree(cursor: { value: any, parent?: any } | undefined):
|
|
|
352
355
|
return undefined;
|
|
353
356
|
}
|
|
354
357
|
|
|
355
|
-
/**
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
358
|
+
/**
|
|
359
|
+
* Wraps in `J.Parentheses`, moving the prefix out so the surrounding whitespace survives. A trailing marker
|
|
360
|
+
* `slotMarkers` also carries belongs to the slot around the expression, so it moves out too: `${x}!` with `x`
|
|
361
|
+
* bound to `a + b` gives `(a + b)!`, while a bare `${x}` bound to `a!` gives `(a!)`.
|
|
362
|
+
*/
|
|
363
|
+
export function parenthesize(expression: J, slotMarkers?: Markers): J.Parentheses<J> {
|
|
364
|
+
const hoisted = expression.markers.markers.filter(
|
|
365
|
+
m => isTrailingMarker(m) && slotMarkers?.markers.some(slot => slot.kind === m.kind));
|
|
366
|
+
const inner = hoisted.length === 0 ? expression : {
|
|
359
367
|
...expression,
|
|
360
|
-
markers: markers(...expression.markers.markers.filter(m => !
|
|
368
|
+
markers: markers(...expression.markers.markers.filter(m => !hoisted.includes(m)))
|
|
361
369
|
};
|
|
362
370
|
return {
|
|
363
371
|
kind: J.Kind.Parentheses,
|
|
364
372
|
id: randomId(),
|
|
365
373
|
prefix: expression.prefix,
|
|
366
|
-
markers:
|
|
374
|
+
markers: hoisted.length === 0 ? emptyMarkers : markers(...hoisted),
|
|
367
375
|
tree: {
|
|
368
376
|
kind: J.Kind.RightPadded,
|
|
369
377
|
element: {...inner, prefix: emptySpace},
|
|
@@ -374,7 +382,7 @@ export function parenthesize(expression: J, slotOwnsTrailingMarkers: boolean = f
|
|
|
374
382
|
}
|
|
375
383
|
|
|
376
384
|
function wrapIfNeeded(parent: J, childId: string, constraints: SlotConstraints, expression: J,
|
|
377
|
-
|
|
385
|
+
slotMarkers: Markers | undefined): J {
|
|
378
386
|
if (precedenceOf(expression) < constraints.precedence ||
|
|
379
387
|
(constraints.noCallShape && isCallShaped(expression)) ||
|
|
380
388
|
(constraints.noOptionalChain && hasOptionalChain(expression)) ||
|
|
@@ -383,7 +391,7 @@ function wrapIfNeeded(parent: J, childId: string, constraints: SlotConstraints,
|
|
|
383
391
|
(constraints.followedByDot && isDotAdjacentNumber(expression)) ||
|
|
384
392
|
mixesNullishWithLogical(parent, expression) ||
|
|
385
393
|
wouldFuseSigns(parent, childId, expression)) {
|
|
386
|
-
return parenthesize(expression,
|
|
394
|
+
return parenthesize(expression, slotMarkers);
|
|
387
395
|
}
|
|
388
396
|
return expression;
|
|
389
397
|
}
|
|
@@ -58,7 +58,15 @@ class RewriteRuleImpl implements RewriteRule {
|
|
|
58
58
|
let result: J | undefined;
|
|
59
59
|
|
|
60
60
|
const template = typeof this.after === 'function' ? this.after(match) : this.after;
|
|
61
|
-
const bindings = options?.bindings ??
|
|
61
|
+
const bindings = options?.bindings ??
|
|
62
|
+
(options?.visitor ? await template.resolveBindings(options.visitor) : undefined);
|
|
63
|
+
// Applying without them would splice the context's own names in unbound, which
|
|
64
|
+
// reads as a working edit and is not one.
|
|
65
|
+
if (bindings === undefined && await template.bindsModules()) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"Template binds modules in its context, so applying it needs their local names. " +
|
|
68
|
+
"Pass {visitor: this} to tryOn, or bindings you resolved yourself.");
|
|
69
|
+
}
|
|
62
70
|
result = await template.apply(node, cursor,
|
|
63
71
|
{ values: match, format: this.format, bindings: bindings || undefined });
|
|
64
72
|
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
import {Cursor, Tree} from '../..';
|
|
17
17
|
import {J} from '../../java';
|
|
18
18
|
import {ApplyOptions, Parameter, TemplateOptions, TemplateParameter} from './types';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
19
|
+
import {maybeBind} from '../binding';
|
|
20
|
+
import {ContextBinding} from './engine';
|
|
21
21
|
import {JavaScriptVisitor} from '../visitor';
|
|
22
22
|
import {MatchResult} from './pattern';
|
|
23
23
|
import {generateCacheKey, globalAstCache, WRAPPERS_MAP_SYMBOL} from './utils';
|
|
@@ -177,6 +177,7 @@ export class TemplateBuilder {
|
|
|
177
177
|
export class Template {
|
|
178
178
|
private options: TemplateOptions = {};
|
|
179
179
|
private _cachedTemplate?: J;
|
|
180
|
+
private _contextBindings?: Promise<ContextBinding[]>;
|
|
180
181
|
|
|
181
182
|
/**
|
|
182
183
|
* Creates a new template.
|
|
@@ -221,9 +222,16 @@ export class Template {
|
|
|
221
222
|
* })
|
|
222
223
|
*/
|
|
223
224
|
configure(options: TemplateOptions): Template {
|
|
225
|
+
// The `never` applies only where a caller typechecks against it, so JavaScript and pre-#8685
|
|
226
|
+
// typings reach here, where a silently dropped option leaves the template's names unbound.
|
|
227
|
+
if ((options as { bindings?: unknown }).bindings !== undefined) {
|
|
228
|
+
throw new Error("Template option 'bindings' is now 'context': name each module as the " +
|
|
229
|
+
"import it stands for, e.g. context: [`import {vi} from 'vitest';`].");
|
|
230
|
+
}
|
|
224
231
|
this.options = {...this.options, ...options};
|
|
225
232
|
// Invalidate cache when configuration changes
|
|
226
233
|
this._cachedTemplate = undefined;
|
|
234
|
+
this._contextBindings = undefined;
|
|
227
235
|
return this;
|
|
228
236
|
}
|
|
229
237
|
|
|
@@ -240,20 +248,16 @@ export class Template {
|
|
|
240
248
|
* @returns The cached or newly computed template tree
|
|
241
249
|
* @internal
|
|
242
250
|
*/
|
|
243
|
-
private async getTemplateTree(): Promise<
|
|
251
|
+
private async getTemplateTree(): Promise<J> {
|
|
244
252
|
// Level 1: Instance cache (fastest path)
|
|
245
253
|
if (this._cachedTemplate) {
|
|
246
|
-
return this._cachedTemplate
|
|
254
|
+
return this._cachedTemplate;
|
|
247
255
|
}
|
|
248
256
|
|
|
249
257
|
// Generate cache key for global lookup
|
|
250
258
|
// For raw() parameters, we need to include their code values in the key
|
|
251
259
|
// since they're spliced at construction time, not application time
|
|
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
|
-
];
|
|
260
|
+
const contextStatements = this.options.context || this.options.imports || [];
|
|
257
261
|
const parametersKey = this.parameters.map((p, i) => {
|
|
258
262
|
const value = p.value;
|
|
259
263
|
// Include raw code values in the cache key using the symbol
|
|
@@ -267,14 +271,15 @@ export class Template {
|
|
|
267
271
|
parametersKey,
|
|
268
272
|
// As in Pattern.getAstPattern: a parameter's type reaches the parse as a declaration
|
|
269
273
|
[...contextStatements, ...TemplateEngine.parameterPreamble(this.parameters)],
|
|
270
|
-
this.options.dependencies || {}
|
|
274
|
+
this.options.dependencies || {},
|
|
275
|
+
this.options.types
|
|
271
276
|
);
|
|
272
277
|
|
|
273
278
|
// Level 2: Global cache (fast path - shared with Pattern)
|
|
274
279
|
const cached = globalAstCache.get(cacheKey);
|
|
275
280
|
if (cached) {
|
|
276
|
-
this._cachedTemplate = cached
|
|
277
|
-
return cached
|
|
281
|
+
this._cachedTemplate = cached;
|
|
282
|
+
return cached;
|
|
278
283
|
}
|
|
279
284
|
|
|
280
285
|
// Level 3: Compute via TemplateEngine (slow path)
|
|
@@ -282,8 +287,9 @@ export class Template {
|
|
|
282
287
|
this.templateParts,
|
|
283
288
|
this.parameters,
|
|
284
289
|
contextStatements,
|
|
285
|
-
this.options.dependencies || {}
|
|
286
|
-
|
|
290
|
+
this.options.dependencies || {},
|
|
291
|
+
this.options.types
|
|
292
|
+
);
|
|
287
293
|
|
|
288
294
|
// Cache in both levels
|
|
289
295
|
globalAstCache.set(cacheKey, result);
|
|
@@ -298,18 +304,47 @@ export class Template {
|
|
|
298
304
|
* cannot resolve is bound whether or not the template goes on to reference it, so call this
|
|
299
305
|
* where the template is known to apply — {@link RewriteRule.tryOn} does, once a pattern matched.
|
|
300
306
|
*/
|
|
301
|
-
resolveBindings(visitor: JavaScriptVisitor<any>): Record<string, string
|
|
307
|
+
async resolveBindings(visitor: JavaScriptVisitor<any>): Promise<Record<string, string>> {
|
|
302
308
|
const resolved: Record<string, string> = {};
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
+
for (const binding of await this.contextBindings()) {
|
|
310
|
+
// Recognising the reference the template splices in takes attribution, so a module the
|
|
311
|
+
// workspace could not resolve is bound whether or not the template turns out to use it.
|
|
312
|
+
const onlyIfReferenced = binding.attributed;
|
|
313
|
+
const bound = maybeBind(visitor, {
|
|
314
|
+
module: binding.module!,
|
|
315
|
+
member: binding.member,
|
|
316
|
+
typeOnly: binding.typeOnly,
|
|
317
|
+
preferredName: binding.name,
|
|
318
|
+
onlyIfReferenced
|
|
319
|
+
});
|
|
320
|
+
// An unresolved binding is left out rather than recorded as `undefined`, so `apply()`'s
|
|
321
|
+
// own "applied without a local name" check catches it, same as a caller-omitted one.
|
|
322
|
+
if (bound !== undefined) {
|
|
323
|
+
resolved[binding.name] = bound;
|
|
324
|
+
}
|
|
309
325
|
}
|
|
310
326
|
return resolved;
|
|
311
327
|
}
|
|
312
328
|
|
|
329
|
+
/** Whether the context binds a module, which applying this template therefore has to resolve. */
|
|
330
|
+
async bindsModules(): Promise<boolean> {
|
|
331
|
+
return (await this.contextBindings()).length > 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** What this template's context statements bind, which is what it needs bound in the target file. */
|
|
335
|
+
private contextBindings(): Promise<ContextBinding[]> {
|
|
336
|
+
return this._contextBindings ??= this.deriveContextBindings();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private deriveContextBindings(): Promise<ContextBinding[]> {
|
|
340
|
+
return TemplateEngine.getContextBindings(
|
|
341
|
+
this.templateParts, this.parameters,
|
|
342
|
+
this.options.context || this.options.imports || [],
|
|
343
|
+
this.options.dependencies || {},
|
|
344
|
+
this.options.types
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
313
348
|
/**
|
|
314
349
|
* Applies this template and returns the resulting tree.
|
|
315
350
|
*
|
|
@@ -372,17 +407,21 @@ export class Template {
|
|
|
372
407
|
}
|
|
373
408
|
}
|
|
374
409
|
|
|
375
|
-
const declared = this.options.bindings ?? {};
|
|
376
410
|
const renames: Record<string, string> = {};
|
|
377
411
|
const modules: Record<string, string> = {};
|
|
378
|
-
for
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
412
|
+
// Supplying names is what asks for the context's modules to be bound in the file being
|
|
413
|
+
// edited; without them a context import only types the template.
|
|
414
|
+
if (options?.bindings !== undefined) {
|
|
415
|
+
for (const binding of await this.contextBindings()) {
|
|
416
|
+
const bound = options.bindings[binding.name];
|
|
417
|
+
if (bound === undefined) {
|
|
418
|
+
throw new Error(`Template binds '${binding.module}' in its context, but no local name was ` +
|
|
419
|
+
`given for '${binding.name}'. Either pass bindings from resolveBindings(visitor), or — if it ` +
|
|
420
|
+
`already did — binding was refused, which an AMD block or a file requiring its modules can do.`);
|
|
421
|
+
}
|
|
422
|
+
renames[binding.name] = bound;
|
|
423
|
+
modules[binding.name] = binding.module!;
|
|
383
424
|
}
|
|
384
|
-
renames[name] = bound;
|
|
385
|
-
modules[name] = binding.module;
|
|
386
425
|
}
|
|
387
426
|
|
|
388
427
|
// Use instance-level cache to get the template tree
|
|
@@ -18,7 +18,6 @@ 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
21
|
import type {JavaScriptVisitor} from "../visitor";
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -350,6 +349,12 @@ export interface PatternOptions {
|
|
|
350
349
|
*/
|
|
351
350
|
dependencies?: Record<string, string>;
|
|
352
351
|
|
|
352
|
+
/**
|
|
353
|
+
* Type packages to load whose declarations nothing imports, as {@link TemplateOptions.types}.
|
|
354
|
+
* A pattern matches on attribution, so a module typed only ambiently needs this to match.
|
|
355
|
+
*/
|
|
356
|
+
types?: string[];
|
|
357
|
+
|
|
353
358
|
/**
|
|
354
359
|
* When true, allows patterns without type annotations to match code with type annotations.
|
|
355
360
|
* This enables more flexible pattern matching during development or when full type attribution
|
|
@@ -485,26 +490,20 @@ export interface TemplateOptions {
|
|
|
485
490
|
dependencies?: Record<string, string>;
|
|
486
491
|
|
|
487
492
|
/**
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
493
|
+
* Type packages to load whose declarations nothing imports. TypeScript reads `@types/*` on its
|
|
494
|
+
* own and everything else only when named here, so a package declaring its modules ambiently —
|
|
495
|
+
* rather than at a path matching the specifier — resolves only with this set.
|
|
491
496
|
*
|
|
492
|
-
* @example
|
|
493
|
-
* ```typescript
|
|
494
|
-
* template`Theming.setTheme(${capture('theme')})`
|
|
495
|
-
* .configure({bindings: {Theming: {module: 'sap/ui/core/Theming', member: 'default'}}})
|
|
496
|
-
* ```
|
|
497
|
+
* @example `{dependencies: {'@sapui5/types': '^1.120.0'}, types: ['@sapui5/types']}`
|
|
497
498
|
*/
|
|
498
|
-
|
|
499
|
-
}
|
|
499
|
+
types?: string[];
|
|
500
500
|
|
|
501
|
-
/**
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
'module' | 'alias' | 'preferredName' | 'onlyIfReferenced' | 'sideEffectOnly'> & { module: string };
|
|
501
|
+
/**
|
|
502
|
+
* @deprecated Name the module in {@link TemplateOptions.context} as the import it stands for:
|
|
503
|
+
* `bindings: {vi: {module: 'vitest', member: 'vi'}}` is `context: ["import {vi} from 'vitest';"]`.
|
|
504
|
+
*/
|
|
505
|
+
bindings?: never;
|
|
506
|
+
}
|
|
508
507
|
|
|
509
508
|
/**
|
|
510
509
|
* Options for template application.
|
|
@@ -107,13 +107,15 @@ export function generateCacheKey(
|
|
|
107
107
|
templateParts: string[] | TemplateStringsArray,
|
|
108
108
|
itemsKey: string,
|
|
109
109
|
contextStatements: string[],
|
|
110
|
-
dependencies: Record<string, string
|
|
110
|
+
dependencies: Record<string, string>,
|
|
111
|
+
types?: string[]
|
|
111
112
|
): string {
|
|
112
113
|
return [
|
|
113
114
|
Array.from(templateParts).join('|'),
|
|
114
115
|
itemsKey,
|
|
115
116
|
contextStatements.join(';'),
|
|
116
|
-
JSON.stringify(dependencies)
|
|
117
|
+
JSON.stringify(dependencies),
|
|
118
|
+
JSON.stringify(types ?? null)
|
|
117
119
|
].join('::');
|
|
118
120
|
}
|
|
119
121
|
|
package/src/javascript/tree.ts
CHANGED
|
@@ -417,6 +417,7 @@ export namespace JS {
|
|
|
417
417
|
*/
|
|
418
418
|
export interface PropertyAssignment extends JS, Statement, TypedTree {
|
|
419
419
|
readonly kind: typeof Kind.PropertyAssignment;
|
|
420
|
+
readonly modifiers: J.Modifier[];
|
|
420
421
|
readonly name: J.RightPadded<Expression>;
|
|
421
422
|
readonly assigmentToken: PropertyAssignment.Token;
|
|
422
423
|
readonly initializer?: Expression;
|
|
@@ -19,6 +19,20 @@ import {Type} from "../java";
|
|
|
19
19
|
import FUNCTION_TYPE_NAME = Type.FUNCTION_TYPE_NAME;
|
|
20
20
|
import OBJECT_TYPE_NAME = Type.OBJECT_TYPE_NAME;
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* TypeScript orders union constituents by internal type id, which varies with what the checker resolved
|
|
24
|
+
* earlier in the run; ordering by signature keeps a union's signature the same across parses. Intersections
|
|
25
|
+
* keep the order they were written in and are left alone. Call once `bounds` is attached to its owner, so
|
|
26
|
+
* a constituent that reaches back into it reads the whole constituent list rather than an empty one.
|
|
27
|
+
*/
|
|
28
|
+
function sortBySignature(bounds: Type[]): void {
|
|
29
|
+
const keys = new Map<Type, string>(bounds.map(b => [b, Type.signature(b)]));
|
|
30
|
+
bounds.sort((a, b) => {
|
|
31
|
+
const ka = keys.get(a)!, kb = keys.get(b)!;
|
|
32
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
export class JavaScriptTypeMapping {
|
|
23
37
|
// Primary cache: Use type signatures (preferring type.id) as cache keys
|
|
24
38
|
// TypeScript assigns stable IDs to all types, so we don't need secondary caches
|
|
@@ -1610,6 +1624,7 @@ export class JavaScriptTypeMapping {
|
|
|
1610
1624
|
|
|
1611
1625
|
// Update the bounds in the union we created
|
|
1612
1626
|
(union as any).bounds = bounds;
|
|
1627
|
+
sortBySignature(bounds);
|
|
1613
1628
|
|
|
1614
1629
|
return union;
|
|
1615
1630
|
}
|
|
@@ -551,6 +551,7 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
|
|
|
551
551
|
const updates: any = {
|
|
552
552
|
prefix: await this.visitSpace(propertyAssignment.prefix, p),
|
|
553
553
|
markers: await this.visitMarkers(propertyAssignment.markers, p),
|
|
554
|
+
modifiers: await mapAsync(propertyAssignment.modifiers, m => this.visitDefined<J.Modifier>(m, p)),
|
|
554
555
|
name: await this.visitRightPadded(propertyAssignment.name, p),
|
|
555
556
|
initializer: propertyAssignment.initializer && await this.visitDefined<Expression>(propertyAssignment.initializer, p)
|
|
556
557
|
};
|
package/src/parser.ts
CHANGED
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
+
import {createTwoFilesPatch} from "diff";
|
|
16
17
|
import {ExecutionContext} from "./execution";
|
|
17
18
|
import {SourceFile} from "./tree";
|
|
19
|
+
import {TreePrinters} from "./print";
|
|
18
20
|
import fs, {readFileSync} from "node:fs";
|
|
19
21
|
import {isAbsolute, relative} from "path";
|
|
20
22
|
import {ParseError, ParseErrorKind} from "./parse-error";
|
|
@@ -79,6 +81,27 @@ export abstract class Parser {
|
|
|
79
81
|
return isAbsolute(path) && this.relativeTo ? relative(this.relativeTo, path) : path;
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
/**
|
|
85
|
+
* A parse result that does not print back to its input has silently lost source, so it is reported as
|
|
86
|
+
* a `ParseError` carrying the tree that lost it. Mirrors `org.openrewrite.Parser`, down to the message.
|
|
87
|
+
* A parser whose LST cannot hold some byte of its input passes the form it can hold as `source`.
|
|
88
|
+
*/
|
|
89
|
+
protected async requirePrintEqualsInput(sourceFile: SourceFile, input: ParserInput,
|
|
90
|
+
source: string = parserInputRead(input)): Promise<SourceFile> {
|
|
91
|
+
if (!this.ctx.getBoolean(ExecutionContext.REQUIRE_PRINT_EQUALS_INPUT, true)) {
|
|
92
|
+
return sourceFile;
|
|
93
|
+
}
|
|
94
|
+
const printed = await TreePrinters.print(sourceFile);
|
|
95
|
+
if (printed === source) {
|
|
96
|
+
return sourceFile;
|
|
97
|
+
}
|
|
98
|
+
const path = this.relativePath(input);
|
|
99
|
+
const diff = createTwoFilesPatch(path, path, source, printed, "", "", {context: 3});
|
|
100
|
+
const error = this.error(input, new Error(`${sourceFile.sourcePath} is not print idempotent. \n${diff}`));
|
|
101
|
+
const withErroneous: ParseError = {...error, erroneous: sourceFile};
|
|
102
|
+
return withErroneous;
|
|
103
|
+
}
|
|
104
|
+
|
|
82
105
|
protected error(input: ParserInput, e: Error): ParseError {
|
|
83
106
|
return {
|
|
84
107
|
kind: ParseErrorKind,
|
|
@@ -88,7 +111,7 @@ export abstract class Parser {
|
|
|
88
111
|
id: randomId(),
|
|
89
112
|
parserType: this.constructor.name,
|
|
90
113
|
exceptionType: e.name,
|
|
91
|
-
message: e.
|
|
114
|
+
message: e.stack ?? e.message,
|
|
92
115
|
} satisfies ParseExceptionResult as ParseExceptionResult),
|
|
93
116
|
text: parserInputRead(input),
|
|
94
117
|
sourcePath: this.relativePath(input),
|
package/src/print.ts
CHANGED
|
@@ -162,8 +162,11 @@ export class TreePrinters {
|
|
|
162
162
|
return this._registry.get(sourceFileKind)!;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
static print(sourceFile: SourceFile): Promise<string> {
|
|
166
|
-
|
|
165
|
+
static async print(sourceFile: SourceFile): Promise<string> {
|
|
166
|
+
const printed = await this.printer(sourceFile).print(sourceFile);
|
|
167
|
+
// The BOM is a property of the file's encoding rather than of its syntax, so it is held on the
|
|
168
|
+
// source file and put back here. Mirrors `org.openrewrite.Tree.print`.
|
|
169
|
+
return sourceFile.charsetBomMarked && printed.charAt(0) !== "\uFEFF" ? "\uFEFF" + printed : printed;
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
static async printTrimmed(sourceFile: SourceFile): Promise<string> {
|
|
@@ -55,7 +55,11 @@ export class ParseProject {
|
|
|
55
55
|
* If not specified, paths are relative to projectPath.
|
|
56
56
|
* Use this when parsing a subdirectory but wanting paths relative to the repository root.
|
|
57
57
|
*/
|
|
58
|
-
private readonly relativeTo?: string
|
|
58
|
+
private readonly relativeTo?: string,
|
|
59
|
+
/**
|
|
60
|
+
* Parser options, as on the `Parse` request.
|
|
61
|
+
*/
|
|
62
|
+
private readonly options?: { [key: string]: string }
|
|
59
63
|
) {}
|
|
60
64
|
|
|
61
65
|
static handle(
|
|
@@ -85,7 +89,7 @@ export class ParseProject {
|
|
|
85
89
|
const prettierLoader = await projectParser.createPrettierLoader();
|
|
86
90
|
|
|
87
91
|
const resultItems: ParseProjectResponseItem[] = [];
|
|
88
|
-
const ctx = new ExecutionContext();
|
|
92
|
+
const ctx = new ExecutionContext({...request.options});
|
|
89
93
|
|
|
90
94
|
// Parse package.json files (these get NodeResolutionResult markers)
|
|
91
95
|
if (discovered.packageJsonFiles.length > 0) {
|
package/src/rpc/request/parse.ts
CHANGED
|
@@ -23,7 +23,12 @@ import * as path from "path";
|
|
|
23
23
|
|
|
24
24
|
export class Parse {
|
|
25
25
|
constructor(private readonly inputs: ParserInput[],
|
|
26
|
-
private readonly relativeTo?: string
|
|
26
|
+
private readonly relativeTo?: string,
|
|
27
|
+
/**
|
|
28
|
+
* Parser options from the peer. Keys this handler does not recognize are ignored, and a
|
|
29
|
+
* peer that sends none gets the parser's own defaults.
|
|
30
|
+
*/
|
|
31
|
+
private readonly options?: { [key: string]: string }) {
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
/**
|
|
@@ -58,7 +63,7 @@ export class Parse {
|
|
|
58
63
|
: "javascript";
|
|
59
64
|
|
|
60
65
|
const parser = Parsers.createParser(parserType, {
|
|
61
|
-
ctx: new ExecutionContext(),
|
|
66
|
+
ctx: new ExecutionContext({...request.options}),
|
|
62
67
|
relativeTo: request.relativeTo
|
|
63
68
|
})!;
|
|
64
69
|
|