@openrewrite/rewrite 8.91.0 → 8.91.2
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 +96 -43
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +668 -236
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts +138 -0
- package/dist/javascript/amd.d.ts.map +1 -0
- package/dist/javascript/amd.js +768 -0
- package/dist/javascript/amd.js.map +1 -0
- 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/binding.d.ts +94 -0
- package/dist/javascript/binding.d.ts.map +1 -0
- package/dist/javascript/binding.js +335 -0
- package/dist/javascript/binding.js.map +1 -0
- 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/index.d.ts +8 -1
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +26 -1
- package/dist/javascript/index.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-utils.d.ts.map +1 -1
- package/dist/javascript/parser-utils.js +2 -0
- 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 +17 -4
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/recipes/change-import.d.ts +2 -4
- package/dist/javascript/recipes/change-import.d.ts.map +1 -1
- package/dist/javascript/recipes/change-import.js +8 -244
- 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/scope.d.ts +49 -0
- package/dist/javascript/scope.d.ts.map +1 -0
- package/dist/javascript/scope.js +324 -0
- package/dist/javascript/scope.js.map +1 -0
- 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 +8 -0
- package/dist/javascript/templating/bindings.d.ts.map +1 -0
- package/dist/javascript/templating/bindings.js +92 -0
- package/dist/javascript/templating/bindings.js.map +1 -0
- package/dist/javascript/templating/comparator.d.ts +10 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +48 -20
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +34 -10
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +109 -55
- 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 +4 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +24 -10
- 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 +19 -17
- package/dist/javascript/templating/rewrite.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +14 -0
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +63 -3
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +39 -1
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +6 -1
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +20 -2
- 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 +2 -2
- package/src/java/formatting-utils.ts +14 -10
- package/src/java/type.ts +5 -0
- package/src/javascript/add-import.ts +751 -247
- package/src/javascript/amd.ts +915 -0
- package/src/javascript/assertions.ts +33 -5
- package/src/javascript/binding.ts +449 -0
- 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/index.ts +12 -1
- package/src/javascript/node-resolution-result.ts +59 -4
- package/src/javascript/package-exported-types.ts +60 -74
- package/src/javascript/parser-utils.ts +2 -0
- package/src/javascript/parser.ts +23 -3
- package/src/javascript/recipes/change-import.ts +10 -263
- package/src/javascript/remove-import.ts +20 -56
- package/src/javascript/scope.ts +338 -0
- package/src/javascript/style.ts +3 -11
- package/src/javascript/templating/bindings.ts +99 -0
- package/src/javascript/templating/comparator.ts +57 -20
- package/src/javascript/templating/engine.ts +148 -68
- package/src/javascript/templating/index.ts +1 -0
- package/src/javascript/templating/pattern.ts +19 -16
- package/src/javascript/templating/placeholder-replacement.ts +32 -11
- package/src/javascript/templating/precedence.ts +21 -13
- package/src/javascript/templating/rewrite.ts +23 -17
- package/src/javascript/templating/template.ts +84 -9
- package/src/javascript/templating/types.ts +46 -1
- package/src/javascript/templating/utils.ts +21 -2
- package/src/javascript/type-mapping.ts +54 -2
- package/src/style.ts +5 -0
- package/src/test/rewrite-test.ts +61 -3
|
@@ -17,16 +17,25 @@ import {Cursor, isTree, produceAsync, Tree, updateIfChanged} from '../..';
|
|
|
17
17
|
import {emptySpace, J, Statement, Type} from '../../java';
|
|
18
18
|
import {Any, Capture, JavaScriptParser, JavaScriptVisitor, JS} from '..';
|
|
19
19
|
import {create as produce} from 'mutative';
|
|
20
|
-
import {CaptureMarker, PlaceholderUtils, randomizeIds, retainIds, treeIds, WRAPPER_FUNCTION_NAME} from './utils';
|
|
20
|
+
import {CaptureMarker, dedentTemplate, PlaceholderUtils, randomizeIds, retainIds, treeIds, WRAPPER_FUNCTION_NAME} from './utils';
|
|
21
21
|
import {CAPTURE_NAME_SYMBOL, CAPTURE_TYPE_SYMBOL, CaptureImpl, CaptureValue, RAW_CODE_SYMBOL, RawCode} from './capture';
|
|
22
22
|
import {PlaceholderReplacementVisitor} from './placeholder-replacement';
|
|
23
23
|
import {maybeParenthesize, parenthesize, requiredPrecedence, startsWithDeclarationToken} from './precedence';
|
|
24
24
|
import {JavaCoordinates} from './template';
|
|
25
25
|
import {maybeAutoFormat} from '../format';
|
|
26
|
+
import {renameBindings} from './bindings';
|
|
26
27
|
import {isExpression, isStatement} from '../parser-utils';
|
|
27
28
|
import {randomId} from '../../uuid';
|
|
28
29
|
import ts from "typescript";
|
|
29
30
|
import {DependencyWorkspace} from "../dependency-workspace";
|
|
31
|
+
import {ModuleScopeBinding, moduleScopeBindings} from '../add-import';
|
|
32
|
+
import {walk} from '../scope';
|
|
33
|
+
import {isIdentifier} from '../../java';
|
|
34
|
+
|
|
35
|
+
/** A module a template's context binds, and whether the parse resolved it well enough to attribute. */
|
|
36
|
+
export interface ContextBinding extends ModuleScopeBinding {
|
|
37
|
+
attributed: boolean;
|
|
38
|
+
}
|
|
30
39
|
import {Parameter} from "./types";
|
|
31
40
|
|
|
32
41
|
/**
|
|
@@ -82,6 +91,23 @@ let templateSourceFileCache: Map<string, ts.SourceFile> | undefined;
|
|
|
82
91
|
*/
|
|
83
92
|
export function setTemplateSourceFileCache(cache?: Map<string, ts.SourceFile>): void {
|
|
84
93
|
templateSourceFileCache = cache;
|
|
94
|
+
templateParsers.clear();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Every template parses under `template.tsx`, so one parser per workspace carries its program
|
|
98
|
+
// from one compile to the next; see `JavaScriptParser.parse`.
|
|
99
|
+
const templateParsers: Map<string, JavaScriptParser> = new Map();
|
|
100
|
+
|
|
101
|
+
function templateParser(workspaceDir?: string, types?: string[]): JavaScriptParser {
|
|
102
|
+
// `types` changes what the compiler loads, so parsers cannot be shared across differing sets.
|
|
103
|
+
// An empty list loads nothing where absence loads the defaults, so the two encode apart.
|
|
104
|
+
const key = `${workspaceDir ?? ""}::${JSON.stringify(types ?? null)}`;
|
|
105
|
+
let parser = templateParsers.get(key);
|
|
106
|
+
if (!parser) {
|
|
107
|
+
parser = new JavaScriptParser({relativeTo: workspaceDir, sourceFileCache: templateSourceFileCache, types});
|
|
108
|
+
templateParsers.set(key, parser);
|
|
109
|
+
}
|
|
110
|
+
return parser;
|
|
85
111
|
}
|
|
86
112
|
|
|
87
113
|
/**
|
|
@@ -99,7 +125,8 @@ class TemplateCache {
|
|
|
99
125
|
templateString: string,
|
|
100
126
|
captures: (Capture | Any)[],
|
|
101
127
|
contextStatements: string[],
|
|
102
|
-
dependencies: Record<string, string
|
|
128
|
+
dependencies: Record<string, string>,
|
|
129
|
+
types: string[] | undefined
|
|
103
130
|
): string {
|
|
104
131
|
// Use the actual template string (with placeholders) as the primary key
|
|
105
132
|
const templateKey = templateString;
|
|
@@ -113,7 +140,7 @@ class TemplateCache {
|
|
|
113
140
|
// Dependencies
|
|
114
141
|
const depsKey = JSON.stringify(dependencies || {});
|
|
115
142
|
|
|
116
|
-
return `${templateKey}::${capturesKey}::${contextKey}::${depsKey}`;
|
|
143
|
+
return `${templateKey}::${capturesKey}::${contextKey}::${depsKey}::${JSON.stringify(types ?? null)}`;
|
|
117
144
|
}
|
|
118
145
|
|
|
119
146
|
/**
|
|
@@ -123,9 +150,10 @@ class TemplateCache {
|
|
|
123
150
|
templateString: string,
|
|
124
151
|
captures: (Capture | Any)[],
|
|
125
152
|
contextStatements: string[],
|
|
126
|
-
dependencies: Record<string, string
|
|
153
|
+
dependencies: Record<string, string>,
|
|
154
|
+
types?: string[]
|
|
127
155
|
): Promise<JS.CompilationUnit> {
|
|
128
|
-
const key = this.generateKey(templateString, captures, contextStatements, dependencies);
|
|
156
|
+
const key = this.generateKey(templateString, captures, contextStatements, dependencies, types);
|
|
129
157
|
|
|
130
158
|
let cu = this.cache.get(key);
|
|
131
159
|
if (cu) {
|
|
@@ -147,10 +175,7 @@ class TemplateCache {
|
|
|
147
175
|
|
|
148
176
|
// Parse and cache (workspace only needed during parsing)
|
|
149
177
|
// Use templateSourceFileCache if configured for ~3.2x speedup on dependency file parsing
|
|
150
|
-
const parser =
|
|
151
|
-
relativeTo: workspaceDir,
|
|
152
|
-
sourceFileCache: templateSourceFileCache
|
|
153
|
-
});
|
|
178
|
+
const parser = templateParser(workspaceDir, types);
|
|
154
179
|
const parseGenerator = parser.parse({text: fullTemplateString, sourcePath: 'template.tsx'});
|
|
155
180
|
cu = (await parseGenerator.next()).value as JS.CompilationUnit;
|
|
156
181
|
|
|
@@ -195,34 +220,62 @@ export class TemplateEngine {
|
|
|
195
220
|
* @param dependencies NPM dependencies for type attribution
|
|
196
221
|
* @returns A Promise resolving to the extracted template AST
|
|
197
222
|
*/
|
|
198
|
-
|
|
223
|
+
/** The template parsed with its context, which is what gives its code types to attribute against. */
|
|
224
|
+
private static async parseWithContext(
|
|
199
225
|
templateParts: TemplateStringsArray,
|
|
200
226
|
parameters: Parameter[],
|
|
201
|
-
contextStatements: string[]
|
|
202
|
-
dependencies: Record<string, string
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// Build the template string with parameter placeholders
|
|
227
|
+
contextStatements: string[],
|
|
228
|
+
dependencies: Record<string, string>,
|
|
229
|
+
types: string[] | undefined
|
|
230
|
+
): Promise<JS.CompilationUnit> {
|
|
231
|
+
// A capture's declared type reaches the parse as a declaration, so it belongs with context.
|
|
232
|
+
const preamble = TemplateEngine.parameterPreamble(parameters);
|
|
208
233
|
const templateString = TemplateEngine.buildTemplateString(templateParts, parameters);
|
|
209
|
-
|
|
210
|
-
// Add preamble to context statements (so they're skipped during extraction)
|
|
211
234
|
const contextWithPreamble = preamble.length > 0
|
|
212
235
|
? [...contextStatements, ...preamble]
|
|
213
236
|
: contextStatements;
|
|
237
|
+
return templateCache.getOrParse(templateString, [], contextWithPreamble, dependencies, types);
|
|
238
|
+
}
|
|
214
239
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
240
|
+
/**
|
|
241
|
+
* The modules the template's context binds, for the caller to bind in the file being edited.
|
|
242
|
+
* An `import` or `require` states one; anything else — a `declare`, a helper signature — types
|
|
243
|
+
* the template without asking for a binding.
|
|
244
|
+
*/
|
|
245
|
+
static async getContextBindings(
|
|
246
|
+
templateParts: TemplateStringsArray,
|
|
247
|
+
parameters: Parameter[],
|
|
248
|
+
contextStatements: string[] = [],
|
|
249
|
+
dependencies: Record<string, string> = {},
|
|
250
|
+
types?: string[]
|
|
251
|
+
): Promise<ContextBinding[]> {
|
|
252
|
+
const cu = await TemplateEngine.parseWithContext(templateParts, parameters, contextStatements, dependencies, types);
|
|
253
|
+
// The template's own code is the last statement, so everything ahead of it is context.
|
|
254
|
+
const context = {...cu, statements: cu.statements.slice(0, -1)};
|
|
255
|
+
const attributed = new Set<string>();
|
|
256
|
+
walk(context.statements, node => {
|
|
257
|
+
if (isIdentifier(node) && (node.type !== undefined || node.fieldType !== undefined)) {
|
|
258
|
+
attributed.add(node.simpleName);
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
});
|
|
262
|
+
return moduleScopeBindings(context)
|
|
263
|
+
.filter(b => b.module !== undefined)
|
|
264
|
+
.map(b => ({...b, attributed: attributed.has(b.name)}));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
static async getTemplateTree(
|
|
268
|
+
templateParts: TemplateStringsArray,
|
|
269
|
+
parameters: Parameter[],
|
|
270
|
+
contextStatements: string[] = [],
|
|
271
|
+
dependencies: Record<string, string> = {},
|
|
272
|
+
types?: string[]
|
|
273
|
+
): Promise<J> {
|
|
274
|
+
const cu = await TemplateEngine.parseWithContext(templateParts, parameters, contextStatements, dependencies, types);
|
|
222
275
|
|
|
223
276
|
// Check if there are any statements
|
|
224
277
|
if (!cu.statements || cu.statements.length === 0) {
|
|
225
|
-
throw new Error(`Failed to parse template code (no statements):\n${
|
|
278
|
+
throw new Error(`Failed to parse template code (no statements):\n${TemplateEngine.buildTemplateString(templateParts, parameters)}`);
|
|
226
279
|
}
|
|
227
280
|
|
|
228
281
|
// The template code is always the last statement (after context + preamble)
|
|
@@ -244,15 +297,21 @@ export class TemplateEngine {
|
|
|
244
297
|
* @param coordinates The coordinates specifying where and how to insert the generated AST
|
|
245
298
|
* @param values Map of capture names to values to replace the parameters with
|
|
246
299
|
* @param wrappersMap Map of capture names to J.RightPadded wrappers (for preserving markers)
|
|
300
|
+
* @param format Whether to fit the result to where it lands
|
|
301
|
+
* @param renames Local names for the template's declared bindings, keyed as declared
|
|
302
|
+
* @param modules The module each declared binding names, keyed as declared
|
|
247
303
|
* @returns A Promise resolving to the generated AST node
|
|
248
304
|
*/
|
|
249
305
|
static async applyTemplateFromAst(
|
|
250
|
-
ast:
|
|
306
|
+
ast: J,
|
|
251
307
|
parameters: Parameter[],
|
|
252
308
|
cursor: Cursor,
|
|
253
309
|
coordinates: JavaCoordinates,
|
|
254
310
|
values: Pick<Map<string, J>, 'get'> = new Map(),
|
|
255
|
-
wrappersMap: Pick<Map<string, J.RightPadded<J> | J.RightPadded<J>[]>, 'get'> = new Map()
|
|
311
|
+
wrappersMap: Pick<Map<string, J.RightPadded<J> | J.RightPadded<J>[]>, 'get'> = new Map(),
|
|
312
|
+
format: boolean = true,
|
|
313
|
+
renames: Record<string, string> = {},
|
|
314
|
+
modules: Record<string, string> = {}
|
|
256
315
|
): Promise<J | undefined> {
|
|
257
316
|
// Create substitutions map for placeholders
|
|
258
317
|
const substitutions = new Map<string, Parameter>();
|
|
@@ -264,9 +323,13 @@ export class TemplateEngine {
|
|
|
264
323
|
// Before substitution, so that ids carried over from the source tree survive this pass
|
|
265
324
|
const fresh = await randomizeIds(ast);
|
|
266
325
|
|
|
326
|
+
const bound = Object.keys(renames).length > 0
|
|
327
|
+
? await renameBindings(fresh.tree as J, renames, modules)
|
|
328
|
+
: fresh.tree;
|
|
329
|
+
|
|
267
330
|
// Unsubstitute placeholders with actual parameter values and match results
|
|
268
331
|
const visitor = new PlaceholderReplacementVisitor(substitutions, values, wrappersMap);
|
|
269
|
-
const unsubstitutedAst = (await visitor.visit(
|
|
332
|
+
const unsubstitutedAst = (await visitor.visit(bound, null))!;
|
|
270
333
|
|
|
271
334
|
// An id may only be kept where the node answering to it is leaving the tree, which is the
|
|
272
335
|
// subtree this application replaces. A parameter named twice, or spliced in from somewhere
|
|
@@ -280,7 +343,7 @@ export class TemplateEngine {
|
|
|
280
343
|
const uniqueAst = await retainIds(unsubstitutedAst, retainable);
|
|
281
344
|
|
|
282
345
|
// Apply the template to the current AST
|
|
283
|
-
return new TemplateApplier(cursor, coordinates, uniqueAst).apply();
|
|
346
|
+
return new TemplateApplier(cursor, coordinates, uniqueAst, format).apply();
|
|
284
347
|
}
|
|
285
348
|
|
|
286
349
|
/**
|
|
@@ -289,7 +352,33 @@ export class TemplateEngine {
|
|
|
289
352
|
* @param parameters The parameters
|
|
290
353
|
* @returns Array of preamble statements
|
|
291
354
|
*/
|
|
292
|
-
|
|
355
|
+
/** The declarations that give a capture's placeholder its type while the pattern is parsed. */
|
|
356
|
+
static capturePreamble(captures: (Capture | Any | RawCode)[]): string[] {
|
|
357
|
+
const preamble: string[] = [];
|
|
358
|
+
for (const capture of captures) {
|
|
359
|
+
// Raw code is spliced in as source, so it declares nothing
|
|
360
|
+
if (capture instanceof RawCode || (capture && typeof capture === 'object' && (capture as any)[RAW_CODE_SYMBOL])) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const captureName = (capture as any)[CAPTURE_NAME_SYMBOL] || capture.getName();
|
|
365
|
+
const captureType = (capture as any)[CAPTURE_TYPE_SYMBOL];
|
|
366
|
+
if (captureType) {
|
|
367
|
+
const typeString = typeof captureType === 'string'
|
|
368
|
+
? captureType
|
|
369
|
+
: this.typeToString(captureType);
|
|
370
|
+
// `any` attributes nothing, so a declaration for it would only cost a parse
|
|
371
|
+
if (typeString !== 'any') {
|
|
372
|
+
const placeholder = PlaceholderUtils.createCapture(captureName, undefined);
|
|
373
|
+
preamble.push(`let ${placeholder}: ${typeString};`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return preamble;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** The parameter counterpart of {@link capturePreamble}. */
|
|
381
|
+
static parameterPreamble(parameters: Parameter[]): string[] {
|
|
293
382
|
const preamble: string[] = [];
|
|
294
383
|
|
|
295
384
|
for (let i = 0; i < parameters.length; i++) {
|
|
@@ -377,7 +466,7 @@ export class TemplateEngine {
|
|
|
377
466
|
|
|
378
467
|
// Always wrap in function body - let the parser decide what it is,
|
|
379
468
|
// then we'll extract intelligently based on what was parsed
|
|
380
|
-
return `function ${WRAPPER_FUNCTION_NAME}() { ${result} }`;
|
|
469
|
+
return `function ${WRAPPER_FUNCTION_NAME}() { ${dedentTemplate(result)} }`;
|
|
381
470
|
}
|
|
382
471
|
|
|
383
472
|
/**
|
|
@@ -428,45 +517,23 @@ export class TemplateEngine {
|
|
|
428
517
|
}
|
|
429
518
|
|
|
430
519
|
/**
|
|
431
|
-
* Gets the parsed and extracted pattern tree with
|
|
432
|
-
*
|
|
433
|
-
* functionality on top of the shared template tree generation.
|
|
520
|
+
* Gets the parsed and extracted pattern tree, with placeholder identifiers left bare;
|
|
521
|
+
* `attachCaptureMarkers` binds it to a particular set of captures.
|
|
434
522
|
*
|
|
435
523
|
* @param templateParts The string parts of the template
|
|
436
524
|
* @param captures The captures between the string parts (can include RawCode)
|
|
437
525
|
* @param contextStatements Context declarations (imports, types, etc.) to prepend for type attribution
|
|
438
526
|
* @param dependencies NPM dependencies for type attribution
|
|
439
|
-
* @returns A Promise resolving to the extracted pattern AST
|
|
527
|
+
* @returns A Promise resolving to the extracted pattern AST
|
|
440
528
|
*/
|
|
441
529
|
static async getPatternTree(
|
|
442
530
|
templateParts: TemplateStringsArray,
|
|
443
531
|
captures: (Capture | Any | RawCode)[],
|
|
444
532
|
contextStatements: string[] = [],
|
|
445
|
-
dependencies: Record<string, string> = {}
|
|
533
|
+
dependencies: Record<string, string> = {},
|
|
534
|
+
types?: string[]
|
|
446
535
|
): Promise<J> {
|
|
447
|
-
|
|
448
|
-
const preamble: string[] = [];
|
|
449
|
-
for (const capture of captures) {
|
|
450
|
-
// Skip raw code - it's not a capture
|
|
451
|
-
if (capture instanceof RawCode || (capture && typeof capture === 'object' && (capture as any)[RAW_CODE_SYMBOL])) {
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const captureName = (capture as any)[CAPTURE_NAME_SYMBOL] || capture.getName();
|
|
456
|
-
const captureType = (capture as any)[CAPTURE_TYPE_SYMBOL];
|
|
457
|
-
if (captureType) {
|
|
458
|
-
// Convert Type to string if needed
|
|
459
|
-
const typeString = typeof captureType === 'string'
|
|
460
|
-
? captureType
|
|
461
|
-
: this.typeToString(captureType);
|
|
462
|
-
// Only add preamble if we have a concrete type (not 'any')
|
|
463
|
-
if (typeString !== 'any') {
|
|
464
|
-
const placeholder = PlaceholderUtils.createCapture(captureName, undefined);
|
|
465
|
-
preamble.push(`let ${placeholder}: ${typeString};`);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
// Don't add preamble declarations without types - they don't provide type attribution
|
|
469
|
-
}
|
|
536
|
+
const preamble = TemplateEngine.capturePreamble(captures);
|
|
470
537
|
|
|
471
538
|
// Build the template string with placeholders for captures and raw code
|
|
472
539
|
let result = '';
|
|
@@ -505,7 +572,8 @@ export class TemplateEngine {
|
|
|
505
572
|
templateString,
|
|
506
573
|
actualCaptures,
|
|
507
574
|
contextWithPreamble,
|
|
508
|
-
dependencies
|
|
575
|
+
dependencies,
|
|
576
|
+
types
|
|
509
577
|
);
|
|
510
578
|
|
|
511
579
|
// Check if there are any statements
|
|
@@ -517,18 +585,25 @@ export class TemplateEngine {
|
|
|
517
585
|
const lastStatement = cu.statements[cu.statements.length - 1].element;
|
|
518
586
|
|
|
519
587
|
// Extract from wrapper using shared utility
|
|
520
|
-
|
|
588
|
+
return PlaceholderUtils.extractFromWrapper(lastStatement, 'Pattern');
|
|
589
|
+
}
|
|
521
590
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
591
|
+
/**
|
|
592
|
+
* Binds a pattern tree to a set of captures by attaching a CaptureMarker, carrying that
|
|
593
|
+
* capture's constraint and variadic options, to each placeholder identifier.
|
|
594
|
+
*/
|
|
595
|
+
static async attachCaptureMarkers(tree: J, captures: (Capture | Any | RawCode)[]): Promise<J> {
|
|
596
|
+
const actualCaptures = captures.filter(c =>
|
|
597
|
+
!(c instanceof RawCode || (c && typeof c === 'object' && (c as any)[RAW_CODE_SYMBOL]))
|
|
598
|
+
) as (Capture | Any)[];
|
|
599
|
+
return (await new MarkerAttachmentVisitor(actualCaptures).visit(tree, undefined))!;
|
|
525
600
|
}
|
|
526
601
|
}
|
|
527
602
|
|
|
528
603
|
/**
|
|
529
604
|
* Visitor that attaches CaptureMarkers to capture identifiers in pattern ASTs.
|
|
530
605
|
* This allows efficient capture detection without string parsing during matching.
|
|
531
|
-
*
|
|
606
|
+
* Reached through TemplateEngine.attachCaptureMarkers().
|
|
532
607
|
*/
|
|
533
608
|
class MarkerAttachmentVisitor extends JavaScriptVisitor<undefined> {
|
|
534
609
|
constructor(private readonly captures: (Capture | Any)[]) {
|
|
@@ -656,7 +731,8 @@ export class TemplateApplier {
|
|
|
656
731
|
constructor(
|
|
657
732
|
private readonly cursor: Cursor,
|
|
658
733
|
private readonly coordinates: JavaCoordinates,
|
|
659
|
-
private readonly ast: J
|
|
734
|
+
private readonly ast: J,
|
|
735
|
+
private readonly shouldFormat: boolean = true
|
|
660
736
|
) {
|
|
661
737
|
}
|
|
662
738
|
|
|
@@ -738,6 +814,10 @@ export class TemplateApplier {
|
|
|
738
814
|
prefix: originalTree.prefix
|
|
739
815
|
};
|
|
740
816
|
|
|
817
|
+
if (!this.shouldFormat) {
|
|
818
|
+
return {...result, id: resultToUse.id};
|
|
819
|
+
}
|
|
820
|
+
|
|
741
821
|
// Apply auto-formatting to the result
|
|
742
822
|
const formatted =
|
|
743
823
|
await maybeAutoFormat(originalTree, result, null, undefined, this.cursor?.parent);
|
|
@@ -233,27 +233,30 @@ export class Pattern {
|
|
|
233
233
|
const cacheKey = generateCacheKey(
|
|
234
234
|
this.templateParts,
|
|
235
235
|
capturesKey,
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
// A capture's type reaches the parse as a declaration, so it shapes the tree the same
|
|
237
|
+
// way an explicit context statement does and belongs in the key alongside one.
|
|
238
|
+
[...contextStatements, ...TemplateEngine.capturePreamble(this.captures)],
|
|
239
|
+
this._options.dependencies || {},
|
|
240
|
+
this._options.types
|
|
238
241
|
);
|
|
239
242
|
|
|
240
243
|
// Level 2: Global cache (fast path - shared with Template)
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
let tree = globalAstCache.get(cacheKey);
|
|
245
|
+
if (!tree) {
|
|
246
|
+
// Level 3: Compute via TemplateEngine (slow path)
|
|
247
|
+
tree = await TemplateEngine.getPatternTree(
|
|
248
|
+
this.templateParts,
|
|
249
|
+
this.captures,
|
|
250
|
+
contextStatements,
|
|
251
|
+
this._options.dependencies || {},
|
|
252
|
+
this._options.types
|
|
253
|
+
);
|
|
254
|
+
globalAstCache.set(cacheKey, tree);
|
|
245
255
|
}
|
|
246
256
|
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
this.captures,
|
|
251
|
-
contextStatements,
|
|
252
|
-
this._options.dependencies || {}
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
// Cache in both levels
|
|
256
|
-
globalAstCache.set(cacheKey, result);
|
|
257
|
+
// The key names captures but says nothing about their constraints, so two patterns of the
|
|
258
|
+
// same shape share an entry; markers are attached per instance to keep them apart.
|
|
259
|
+
const result = await TemplateEngine.attachCaptureMarkers(tree, this.captures);
|
|
257
260
|
this._cachedAstPattern = result;
|
|
258
261
|
|
|
259
262
|
return result;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {Cursor, isTree} from '../..';
|
|
16
|
+
import {Cursor, isTree, Markers} from '../..';
|
|
17
17
|
import {J} from '../../java';
|
|
18
18
|
import {JS} from '..';
|
|
19
19
|
import {JavaScriptVisitor} from '../visitor';
|
|
@@ -42,7 +42,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
42
42
|
const replacement = this.replacePlaceholder(tree);
|
|
43
43
|
if (replacement !== tree) {
|
|
44
44
|
// `this.cursor` is still the enclosing template node: `super.visit()` has not pushed this one
|
|
45
|
-
return maybeParenthesize(enclosingTree(parent ?? this.cursor), tree.id, replacement,
|
|
45
|
+
return maybeParenthesize(enclosingTree(parent ?? this.cursor), tree.id, replacement, tree.markers) as R;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -82,8 +82,8 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
82
82
|
return super.visitContainer(container, p);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
//
|
|
86
|
-
const newElements = await this.expandVariadicElements(container.elements, undefined, p);
|
|
85
|
+
// A container's element layout is the author's; a block lays its statements out by indentation
|
|
86
|
+
const newElements = await this.expandVariadicElements(container.elements, undefined, p, true);
|
|
87
87
|
|
|
88
88
|
return produce(container, draft => {
|
|
89
89
|
draft.elements = newElements as any;
|
|
@@ -175,6 +175,22 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/** As `mergePrefix`, but a line break the source wrote and the template did not is the source's layout. */
|
|
179
|
+
private mergeElementPrefix(sourcePrefix: J.Space, templatePrefix: J.Space): J.Space {
|
|
180
|
+
return sourcePrefix.whitespace.includes('\n') && !templatePrefix.whitespace.includes('\n') ?
|
|
181
|
+
sourcePrefix : this.mergePrefix(sourcePrefix, templatePrefix);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** As `mergePrefix`, for markers: everything the value was matched with, plus the kinds only the slot wrote. */
|
|
185
|
+
private mergeMarkers(sourceMarkers: Markers, templateMarkers: Markers): Markers {
|
|
186
|
+
const added = templateMarkers.markers.filter(
|
|
187
|
+
template => !sourceMarkers.markers.some(source => source.kind === template.kind));
|
|
188
|
+
return added.length === 0 ? sourceMarkers : {
|
|
189
|
+
...sourceMarkers,
|
|
190
|
+
markers: [...sourceMarkers.markers, ...added]
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
178
194
|
/**
|
|
179
195
|
* Expands variadic placeholders in a list of elements.
|
|
180
196
|
*
|
|
@@ -186,7 +202,8 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
186
202
|
private async expandVariadicElements(
|
|
187
203
|
elements: J.RightPadded<J>[],
|
|
188
204
|
unwrapElement: (element: J) => J = (e) => e,
|
|
189
|
-
p: any
|
|
205
|
+
p: any,
|
|
206
|
+
ownLayout: boolean = false
|
|
190
207
|
): Promise<J.RightPadded<J>[]> {
|
|
191
208
|
const newElements: J.RightPadded<J>[] = [];
|
|
192
209
|
|
|
@@ -263,7 +280,9 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
263
280
|
if (i === 0 && draft.element) {
|
|
264
281
|
// Merge the placeholder's prefix with the first item's prefix
|
|
265
282
|
// Modify prefix directly within the draft
|
|
266
|
-
draft.element.prefix =
|
|
283
|
+
draft.element.prefix = ownLayout ?
|
|
284
|
+
this.mergeElementPrefix(draft.element.prefix, element.prefix) :
|
|
285
|
+
this.mergePrefix(draft.element.prefix, element.prefix);
|
|
267
286
|
}
|
|
268
287
|
// Keep all other wrapper properties (including markers with Semicolon)
|
|
269
288
|
}));
|
|
@@ -273,7 +292,9 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
273
292
|
newElements.push(produce(wrapped, draft => {
|
|
274
293
|
draft.element = produce(elem, itemDraft => {
|
|
275
294
|
if (i === 0) {
|
|
276
|
-
itemDraft.prefix =
|
|
295
|
+
itemDraft.prefix = ownLayout ?
|
|
296
|
+
this.mergeElementPrefix(elem.prefix, element.prefix) :
|
|
297
|
+
this.mergePrefix(elem.prefix, element.prefix);
|
|
277
298
|
}
|
|
278
299
|
// For i > 0, prefix is already correct, no changes needed
|
|
279
300
|
});
|
|
@@ -375,7 +396,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
375
396
|
if (isTree(propertyValue)) {
|
|
376
397
|
const propValueAsJ = propertyValue as J;
|
|
377
398
|
return produce(propValueAsJ, draft => {
|
|
378
|
-
draft.markers = placeholder.markers;
|
|
399
|
+
draft.markers = this.mergeMarkers(propValueAsJ.markers, placeholder.markers);
|
|
379
400
|
draft.prefix = this.mergePrefix(propValueAsJ.prefix, placeholder.prefix);
|
|
380
401
|
});
|
|
381
402
|
}
|
|
@@ -410,7 +431,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
410
431
|
const matchedNode = this.values.get(name);
|
|
411
432
|
if (matchedNode && !Array.isArray(matchedNode)) {
|
|
412
433
|
return produce(matchedNode, draft => {
|
|
413
|
-
draft.markers = placeholder.markers;
|
|
434
|
+
draft.markers = this.mergeMarkers(matchedNode.markers, placeholder.markers);
|
|
414
435
|
draft.prefix = this.mergePrefix(matchedNode.prefix, placeholder.prefix);
|
|
415
436
|
});
|
|
416
437
|
}
|
|
@@ -427,7 +448,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
427
448
|
// Extract the element from the J.RightPadded wrapper
|
|
428
449
|
const element = param.value.element as J;
|
|
429
450
|
return produce(element, draft => {
|
|
430
|
-
draft.markers = placeholder.markers;
|
|
451
|
+
draft.markers = this.mergeMarkers(element.markers, placeholder.markers);
|
|
431
452
|
draft.prefix = this.mergePrefix(element.prefix, placeholder.prefix);
|
|
432
453
|
});
|
|
433
454
|
}
|
|
@@ -447,7 +468,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
447
468
|
if (isTree(param.value)) {
|
|
448
469
|
// Return the AST node, preserving comments from the source
|
|
449
470
|
return produce(param.value as J, draft => {
|
|
450
|
-
draft.markers = placeholder.markers;
|
|
471
|
+
draft.markers = this.mergeMarkers(param.value.markers, placeholder.markers);
|
|
451
472
|
draft.prefix = this.mergePrefix(param.value.prefix, placeholder.prefix);
|
|
452
473
|
});
|
|
453
474
|
}
|
|
@@ -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
|
}
|