@openrewrite/rewrite 8.92.17 → 8.92.18
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/README.md +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/java/formatting-utils.d.ts +2 -1
- package/dist/java/formatting-utils.d.ts.map +1 -1
- package/dist/java/formatting-utils.js +4 -3
- package/dist/java/formatting-utils.js.map +1 -1
- package/dist/java/rpc.d.ts.map +1 -1
- package/dist/java/rpc.js +2 -0
- package/dist/java/rpc.js.map +1 -1
- package/dist/java/tree.d.ts +8 -0
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +11 -1
- package/dist/java/tree.js.map +1 -1
- package/dist/java/type.d.ts +7 -1
- package/dist/java/type.d.ts.map +1 -1
- package/dist/java/type.js +16 -1
- package/dist/java/type.js.map +1 -1
- package/dist/javascript/add-import.d.ts +27 -21
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +303 -143
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts.map +1 -1
- package/dist/javascript/amd.js +25 -10
- package/dist/javascript/amd.js.map +1 -1
- package/dist/javascript/autodetect.js +36 -45
- package/dist/javascript/autodetect.js.map +1 -1
- package/dist/javascript/binding.d.ts +6 -4
- package/dist/javascript/binding.d.ts.map +1 -1
- package/dist/javascript/binding.js +54 -18
- package/dist/javascript/binding.js.map +1 -1
- package/dist/javascript/comparator.d.ts +22 -5
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +116 -38
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts +2 -0
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +11 -1
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/index.d.ts +4 -2
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +10 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/markers.d.ts.map +1 -1
- package/dist/javascript/markers.js +64 -0
- package/dist/javascript/markers.js.map +1 -1
- package/dist/javascript/method-matcher.d.ts +13 -2
- package/dist/javascript/method-matcher.d.ts.map +1 -1
- package/dist/javascript/method-matcher.js +32 -19
- package/dist/javascript/method-matcher.js.map +1 -1
- package/dist/javascript/package-name.d.ts +6 -0
- package/dist/javascript/package-name.d.ts.map +1 -0
- package/dist/javascript/package-name.js +47 -0
- package/dist/javascript/package-name.js.map +1 -0
- package/dist/javascript/preconditions.d.ts +4 -1
- package/dist/javascript/preconditions.d.ts.map +1 -1
- package/dist/javascript/preconditions.js +7 -4
- package/dist/javascript/preconditions.js.map +1 -1
- package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
- package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
- package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
- package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
- package/dist/javascript/recipes/index.d.ts +1 -0
- package/dist/javascript/recipes/index.d.ts.map +1 -1
- package/dist/javascript/recipes/index.js +1 -0
- package/dist/javascript/recipes/index.js.map +1 -1
- package/dist/javascript/scope.d.ts +60 -16
- package/dist/javascript/scope.d.ts.map +1 -1
- package/dist/javascript/scope.js +596 -131
- package/dist/javascript/scope.js.map +1 -1
- package/dist/javascript/search/uses-method.d.ts +1 -1
- package/dist/javascript/search/uses-method.d.ts.map +1 -1
- package/dist/javascript/search/uses-method.js +2 -2
- package/dist/javascript/search/uses-method.js.map +1 -1
- package/dist/javascript/search/uses-type.d.ts +14 -1
- package/dist/javascript/search/uses-type.d.ts.map +1 -1
- package/dist/javascript/search/uses-type.js +24 -9
- package/dist/javascript/search/uses-type.js.map +1 -1
- package/dist/javascript/templating/bindings.d.ts +1 -1
- package/dist/javascript/templating/bindings.d.ts.map +1 -1
- package/dist/javascript/templating/bindings.js +10 -50
- package/dist/javascript/templating/bindings.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +8 -11
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +12 -110
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +34 -24
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +200 -112
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +11 -5
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +86 -20
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +126 -60
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/precedence.d.ts +3 -1
- package/dist/javascript/templating/precedence.d.ts.map +1 -1
- package/dist/javascript/templating/precedence.js +28 -8
- package/dist/javascript/templating/precedence.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +7 -6
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +31 -28
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +6 -3
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +10 -1
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +22 -3
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/tree-debug.d.ts +2 -0
- package/dist/javascript/tree-debug.d.ts.map +1 -1
- package/dist/javascript/tree-debug.js +6 -0
- package/dist/javascript/tree-debug.js.map +1 -1
- package/dist/javascript/tsconfig.d.ts.map +1 -1
- package/dist/javascript/tsconfig.js +7 -17
- package/dist/javascript/tsconfig.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +34 -16
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +253 -249
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/javascript/type-report-cli.d.ts +7 -0
- package/dist/javascript/type-report-cli.d.ts.map +1 -0
- package/dist/javascript/type-report-cli.js +129 -0
- package/dist/javascript/type-report-cli.js.map +1 -0
- package/dist/javascript/type-report.d.ts +61 -0
- package/dist/javascript/type-report.d.ts.map +1 -0
- package/dist/javascript/type-report.js +488 -0
- package/dist/javascript/type-report.js.map +1 -0
- package/dist/markers.d.ts +4 -0
- package/dist/markers.d.ts.map +1 -1
- package/dist/markers.js +8 -0
- package/dist/markers.js.map +1 -1
- package/dist/recipe.d.ts +5 -0
- package/dist/recipe.d.ts.map +1 -1
- package/dist/recipe.js +5 -0
- package/dist/recipe.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/queue.d.ts +16 -0
- package/dist/rpc/queue.d.ts.map +1 -1
- package/dist/rpc/queue.js +27 -9
- package/dist/rpc/queue.js.map +1 -1
- package/dist/rpc/request/prepare-recipe.d.ts +3 -1
- package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
- package/dist/rpc/request/prepare-recipe.js +13 -6
- package/dist/rpc/request/prepare-recipe.js.map +1 -1
- package/dist/search/is-source-file.d.ts +1 -0
- package/dist/search/is-source-file.d.ts.map +1 -1
- package/dist/search/is-source-file.js +7 -2
- package/dist/search/is-source-file.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +5 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +14 -3
- package/dist/test/rewrite-test.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +3 -1
- package/src/java/formatting-utils.ts +5 -4
- package/src/java/rpc.ts +2 -0
- package/src/java/tree.ts +15 -0
- package/src/java/type.ts +19 -2
- package/src/javascript/add-import.ts +338 -152
- package/src/javascript/amd.ts +28 -12
- package/src/javascript/autodetect.ts +32 -41
- package/src/javascript/binding.ts +64 -22
- package/src/javascript/comparator.ts +119 -43
- package/src/javascript/format/format.ts +9 -0
- package/src/javascript/index.ts +6 -3
- package/src/javascript/markers.ts +43 -1
- package/src/javascript/method-matcher.ts +34 -22
- package/src/javascript/package-name.ts +45 -0
- package/src/javascript/preconditions.ts +7 -4
- package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
- package/src/javascript/recipes/index.ts +1 -0
- package/src/javascript/scope.ts +705 -169
- package/src/javascript/search/uses-method.ts +2 -2
- package/src/javascript/search/uses-type.ts +30 -9
- package/src/javascript/templating/bindings.ts +13 -38
- package/src/javascript/templating/comparator.ts +16 -127
- package/src/javascript/templating/engine.ts +229 -127
- package/src/javascript/templating/pattern.ts +107 -28
- package/src/javascript/templating/placeholder-replacement.ts +154 -62
- package/src/javascript/templating/precedence.ts +33 -11
- package/src/javascript/templating/template.ts +35 -30
- package/src/javascript/templating/types.ts +6 -3
- package/src/javascript/templating/utils.ts +24 -2
- package/src/javascript/tree-debug.ts +9 -0
- package/src/javascript/tsconfig.ts +7 -17
- package/src/javascript/type-mapping.ts +258 -252
- package/src/javascript/type-report-cli.ts +96 -0
- package/src/javascript/type-report.ts +575 -0
- package/src/markers.ts +8 -0
- package/src/recipe.ts +6 -0
- package/src/rpc/queue.ts +44 -5
- package/src/rpc/request/prepare-recipe.ts +16 -6
- package/src/search/is-source-file.ts +8 -3
- package/src/test/rewrite-test.ts +14 -3
|
@@ -13,30 +13,25 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {Cursor, isTree, produceAsync, Tree, updateIfChanged} from '../..';
|
|
17
|
-
import {emptySpace, J, Statement, Type} from '../../java';
|
|
16
|
+
import {Cursor, isTree, Markers, markers, produceAsync, Tree, updateIfChanged} from '../..';
|
|
17
|
+
import {emptySpace, J, Statement, Type, TypedTree} from '../../java';
|
|
18
18
|
import {Any, Capture, JavaScriptParser, JavaScriptVisitor, JS} from '..';
|
|
19
19
|
import {create as produce} from 'mutative';
|
|
20
|
-
import {CaptureMarker, dedentTemplate, PlaceholderUtils, randomizeIds, retainIds, treeIds,
|
|
21
|
-
import {CAPTURE_NAME_SYMBOL, CAPTURE_TYPE_SYMBOL, CaptureImpl, CaptureValue, RAW_CODE_SYMBOL, RawCode} from './capture';
|
|
22
|
-
import {PlaceholderReplacementVisitor} from './placeholder-replacement';
|
|
23
|
-
import {maybeParenthesize, parenthesize, requiredPrecedence, startsWithDeclarationToken} from './precedence';
|
|
20
|
+
import {CaptureMarker, dedentTemplate, PlaceholderUtils, randomizeIds, retainIds, TEMPLATE_MODULE, treeIds, wrapCode} from './utils';
|
|
21
|
+
import {CAPTURE_NAME_SYMBOL, CAPTURE_TYPE_SYMBOL, CaptureImpl, CaptureValue, RAW_CODE_SYMBOL, RawCode, TemplateParamImpl} from './capture';
|
|
22
|
+
import {PlaceholderReplacementVisitor, SubstitutedValue} from './placeholder-replacement';
|
|
23
|
+
import {isTrailingMarker, maybeParenthesize, parenthesize, requiredPrecedence, startsWithDeclarationToken} from './precedence';
|
|
24
24
|
import {JavaCoordinates} from './template';
|
|
25
|
-
import {maybeAutoFormat} from '../format';
|
|
25
|
+
import {autoIndent, maybeAutoFormat} from '../format';
|
|
26
26
|
import {renameBindings} from './bindings';
|
|
27
27
|
import {isExpression, isStatement} from '../parser-utils';
|
|
28
28
|
import {randomId} from '../../uuid';
|
|
29
29
|
import ts from "typescript";
|
|
30
30
|
import {DependencyWorkspace} from "../dependency-workspace";
|
|
31
31
|
import {ModuleScopeBinding, moduleScopeBindings} from '../add-import';
|
|
32
|
-
import {
|
|
33
|
-
import {isIdentifier} from '../../java';
|
|
32
|
+
import {namesReferencedWithin} from '../scope';
|
|
34
33
|
import {findMarker, MarkersKind, ParseExceptionResult} from '../../markers';
|
|
35
34
|
|
|
36
|
-
/** A module a template's context binds, and whether the parse resolved it well enough to attribute. */
|
|
37
|
-
export interface ContextBinding extends ModuleScopeBinding {
|
|
38
|
-
attributed: boolean;
|
|
39
|
-
}
|
|
40
35
|
import {Parameter} from "./types";
|
|
41
36
|
|
|
42
37
|
/**
|
|
@@ -177,7 +172,7 @@ class TemplateCache {
|
|
|
177
172
|
// Parse and cache (workspace only needed during parsing)
|
|
178
173
|
// Use templateSourceFileCache if configured for ~3.2x speedup on dependency file parsing
|
|
179
174
|
const parser = templateParser(workspaceDir, types);
|
|
180
|
-
const parseGenerator = parser.parse({text: fullTemplateString, sourcePath:
|
|
175
|
+
const parseGenerator = parser.parse({text: fullTemplateString, sourcePath: `${TEMPLATE_MODULE}.tsx`});
|
|
181
176
|
cu = (await parseGenerator.next()).value as JS.CompilationUnit;
|
|
182
177
|
|
|
183
178
|
this.cache.set(key, cu);
|
|
@@ -225,27 +220,18 @@ function parseFailureReason(cu: JS.CompilationUnit): string {
|
|
|
225
220
|
* Not exported from index, so only visible within the templating module.
|
|
226
221
|
*/
|
|
227
222
|
export class TemplateEngine {
|
|
228
|
-
/**
|
|
229
|
-
* Gets the parsed and extracted template tree (before value substitution).
|
|
230
|
-
* This is the cacheable part of template processing.
|
|
231
|
-
*
|
|
232
|
-
* @param templateParts The string parts of the template
|
|
233
|
-
* @param parameters The parameters between the string parts
|
|
234
|
-
* @param contextStatements Context declarations (imports, types, etc.) to prepend for type attribution
|
|
235
|
-
* @param dependencies NPM dependencies for type attribution
|
|
236
|
-
* @returns A Promise resolving to the extracted template AST
|
|
237
|
-
*/
|
|
238
223
|
/** The template parsed with its context, which is what gives its code types to attribute against. */
|
|
239
224
|
private static async parseWithContext(
|
|
240
225
|
templateParts: TemplateStringsArray,
|
|
241
226
|
parameters: Parameter[],
|
|
242
227
|
contextStatements: string[],
|
|
243
228
|
dependencies: Record<string, string>,
|
|
244
|
-
types: string[] | undefined
|
|
229
|
+
types: string[] | undefined,
|
|
230
|
+
expression: boolean = false,
|
|
231
|
+
preamble: string[] = TemplateEngine.parameterPreamble(parameters)
|
|
245
232
|
): Promise<JS.CompilationUnit> {
|
|
246
|
-
// A capture's
|
|
247
|
-
const
|
|
248
|
-
const templateString = TemplateEngine.buildTemplateString(templateParts, parameters);
|
|
233
|
+
// A capture's type reaches the parse as a declaration, so it belongs with context.
|
|
234
|
+
const templateString = TemplateEngine.buildTemplateString(templateParts, parameters, expression);
|
|
249
235
|
const contextWithPreamble = preamble.length > 0
|
|
250
236
|
? [...contextStatements, ...preamble]
|
|
251
237
|
: contextStatements;
|
|
@@ -272,9 +258,9 @@ export class TemplateEngine {
|
|
|
272
258
|
}
|
|
273
259
|
|
|
274
260
|
/**
|
|
275
|
-
* The modules the template's context binds, for the caller to bind in the
|
|
276
|
-
* An `import` or `require` states one; anything else — a `declare`, a helper
|
|
277
|
-
* the template without asking for a binding.
|
|
261
|
+
* The modules the template's context binds and its code reads, for the caller to bind in the
|
|
262
|
+
* file being edited. An `import` or `require` states one; anything else — a `declare`, a helper
|
|
263
|
+
* signature — types the template without asking for a binding.
|
|
278
264
|
*/
|
|
279
265
|
static async getContextBindings(
|
|
280
266
|
templateParts: TemplateStringsArray,
|
|
@@ -282,36 +268,47 @@ export class TemplateEngine {
|
|
|
282
268
|
contextStatements: string[] = [],
|
|
283
269
|
dependencies: Record<string, string> = {},
|
|
284
270
|
types?: string[]
|
|
285
|
-
): Promise<
|
|
286
|
-
|
|
287
|
-
|
|
271
|
+
): Promise<ModuleScopeBinding[]> {
|
|
272
|
+
// The bindings are the context's alone, so either reading of code opening with `{` serves
|
|
273
|
+
const cu = await TemplateEngine.parseWithContext(templateParts, parameters, contextStatements, dependencies, types)
|
|
274
|
+
.catch(e => opensWithBrace(templateParts) ?
|
|
275
|
+
TemplateEngine.parseWithContext(templateParts, parameters, contextStatements, dependencies, types, true) :
|
|
276
|
+
Promise.reject(e));
|
|
277
|
+
// The template's own code is the last statement, so everything ahead of it is context, and a
|
|
278
|
+
// name the code reads without binding it itself resolves to the context's module-scope binding.
|
|
288
279
|
const context = {...cu, statements: cu.statements.slice(0, -1)};
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
if (isIdentifier(node) && (node.type !== undefined || node.fieldType !== undefined)) {
|
|
292
|
-
attributed.add(node.simpleName);
|
|
293
|
-
}
|
|
294
|
-
return true;
|
|
295
|
-
});
|
|
296
|
-
return moduleScopeBindings(context)
|
|
297
|
-
.filter(b => b.module !== undefined)
|
|
298
|
-
.map(b => ({...b, attributed: attributed.has(b.name)}));
|
|
280
|
+
const referenced = namesReferencedWithin(cu.statements[cu.statements.length - 1]);
|
|
281
|
+
return moduleScopeBindings(context).filter(b => b.module !== undefined && referenced.has(b.name));
|
|
299
282
|
}
|
|
300
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Gets the parsed and extracted template tree (before value substitution).
|
|
286
|
+
* This is the cacheable part of template processing.
|
|
287
|
+
*
|
|
288
|
+
* @param templateParts The string parts of the template
|
|
289
|
+
* @param parameters The parameters between the string parts
|
|
290
|
+
* @param contextStatements Context declarations (imports, types, etc.) to prepend for type attribution
|
|
291
|
+
* @param dependencies NPM dependencies for type attribution
|
|
292
|
+
* @param expression Whether to parse the code as an expression
|
|
293
|
+
* @param preamble The parameters' declarations, from {@link parameterPreamble}
|
|
294
|
+
* @returns A Promise resolving to the extracted template AST
|
|
295
|
+
*/
|
|
301
296
|
static async getTemplateTree(
|
|
302
297
|
templateParts: TemplateStringsArray,
|
|
303
298
|
parameters: Parameter[],
|
|
304
299
|
contextStatements: string[] = [],
|
|
305
300
|
dependencies: Record<string, string> = {},
|
|
306
|
-
types?: string[]
|
|
301
|
+
types?: string[],
|
|
302
|
+
expression: boolean = false,
|
|
303
|
+
preamble?: string[]
|
|
307
304
|
): Promise<J> {
|
|
308
|
-
const cu = await TemplateEngine.parseWithContext(templateParts, parameters, contextStatements, dependencies, types);
|
|
305
|
+
const cu = await TemplateEngine.parseWithContext(templateParts, parameters, contextStatements, dependencies, types, expression, preamble);
|
|
309
306
|
|
|
310
307
|
// The template code is always the last statement (after context + preamble)
|
|
311
308
|
const lastStatement = cu.statements[cu.statements.length - 1].element;
|
|
312
309
|
|
|
313
310
|
// Extract from wrapper using shared utility
|
|
314
|
-
const extracted = PlaceholderUtils.extractFromWrapper(lastStatement, 'Template');
|
|
311
|
+
const extracted = PlaceholderUtils.extractFromWrapper(lastStatement, 'Template', expression);
|
|
315
312
|
|
|
316
313
|
return produce(extracted, _ => {});
|
|
317
314
|
}
|
|
@@ -328,7 +325,7 @@ export class TemplateEngine {
|
|
|
328
325
|
* @param wrappersMap Map of capture names to J.RightPadded wrappers (for preserving markers)
|
|
329
326
|
* @param format Whether to fit the result to where it lands
|
|
330
327
|
* @param renames Local names for the template's declared bindings, keyed as declared
|
|
331
|
-
* @param
|
|
328
|
+
* @param patternPrefixes The matched pattern's capture prefixes, given only under `format: false`
|
|
332
329
|
* @returns A Promise resolving to the generated AST node
|
|
333
330
|
*/
|
|
334
331
|
static async applyTemplateFromAst(
|
|
@@ -340,7 +337,7 @@ export class TemplateEngine {
|
|
|
340
337
|
wrappersMap: Pick<Map<string, J.RightPadded<J> | J.RightPadded<J>[]>, 'get'> = new Map(),
|
|
341
338
|
format: boolean = true,
|
|
342
339
|
renames: Record<string, string> = {},
|
|
343
|
-
|
|
340
|
+
patternPrefixes?: Pick<Map<string, J.Space>, 'get'>
|
|
344
341
|
): Promise<J | undefined> {
|
|
345
342
|
// Create substitutions map for placeholders
|
|
346
343
|
const substitutions = new Map<string, Parameter>();
|
|
@@ -353,11 +350,11 @@ export class TemplateEngine {
|
|
|
353
350
|
const fresh = await randomizeIds(ast);
|
|
354
351
|
|
|
355
352
|
const bound = Object.keys(renames).length > 0
|
|
356
|
-
? await renameBindings(fresh.tree as J, renames
|
|
353
|
+
? await renameBindings(fresh.tree as J, renames)
|
|
357
354
|
: fresh.tree;
|
|
358
355
|
|
|
359
356
|
// Unsubstitute placeholders with actual parameter values and match results
|
|
360
|
-
const visitor = new PlaceholderReplacementVisitor(substitutions, values, wrappersMap);
|
|
357
|
+
const visitor = new PlaceholderReplacementVisitor(substitutions, values, wrappersMap, patternPrefixes);
|
|
361
358
|
const unsubstitutedAst = (await visitor.visit(bound, null))!;
|
|
362
359
|
|
|
363
360
|
// An id may only be kept where the node answering to it is leaving the tree, which is the
|
|
@@ -372,7 +369,7 @@ export class TemplateEngine {
|
|
|
372
369
|
const uniqueAst = await retainIds(unsubstitutedAst, retainable);
|
|
373
370
|
|
|
374
371
|
// Apply the template to the current AST
|
|
375
|
-
return new TemplateApplier(cursor, coordinates, uniqueAst, format).apply();
|
|
372
|
+
return new TemplateApplier(cursor, coordinates, uniqueAst, format, visitor.substituted).apply();
|
|
376
373
|
}
|
|
377
374
|
|
|
378
375
|
/**
|
|
@@ -406,62 +403,42 @@ export class TemplateEngine {
|
|
|
406
403
|
return preamble;
|
|
407
404
|
}
|
|
408
405
|
|
|
409
|
-
/**
|
|
410
|
-
|
|
406
|
+
/**
|
|
407
|
+
* The parameter counterpart of {@link capturePreamble}. A capture without a declared type takes
|
|
408
|
+
* the type of the expression bound to it, as an expression parameter takes its own. A variadic
|
|
409
|
+
* capture binds a list, which has no one type to declare.
|
|
410
|
+
*/
|
|
411
|
+
static parameterPreamble(parameters: Parameter[], values?: Pick<Map<string, J | J[]>, 'get'>): string[] {
|
|
411
412
|
const preamble: string[] = [];
|
|
412
|
-
|
|
413
413
|
for (let i = 0; i < parameters.length; i++) {
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const isCapture = param instanceof CaptureImpl ||
|
|
419
|
-
(param && typeof param === 'object' && param[CAPTURE_NAME_SYMBOL]);
|
|
420
|
-
const isCaptureValue = param instanceof CaptureValue;
|
|
421
|
-
const isTreeArray = Array.isArray(param) && param.length > 0 && isTree(param[0]);
|
|
422
|
-
|
|
423
|
-
if (isCapture) {
|
|
424
|
-
const captureType = param[CAPTURE_TYPE_SYMBOL];
|
|
425
|
-
if (captureType) {
|
|
426
|
-
const typeString = typeof captureType === 'string'
|
|
427
|
-
? captureType
|
|
428
|
-
: this.typeToString(captureType);
|
|
429
|
-
// Only add preamble if we have a concrete type (not 'any')
|
|
430
|
-
if (typeString !== 'any') {
|
|
431
|
-
preamble.push(`let ${placeholder}: ${typeString};`);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
} else if (isCaptureValue) {
|
|
435
|
-
// For CaptureValue, check if the root capture has a type
|
|
436
|
-
const rootCapture = param.rootCapture;
|
|
437
|
-
if (rootCapture) {
|
|
438
|
-
const captureType = (rootCapture as any)[CAPTURE_TYPE_SYMBOL];
|
|
439
|
-
if (captureType) {
|
|
440
|
-
const typeString = typeof captureType === 'string'
|
|
441
|
-
? captureType
|
|
442
|
-
: this.typeToString(captureType);
|
|
443
|
-
// Only add preamble if we have a concrete type (not 'any')
|
|
444
|
-
if (typeString !== 'any') {
|
|
445
|
-
preamble.push(`let ${placeholder}: ${typeString};`);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
} else if (isTree(param) && !isTreeArray) {
|
|
450
|
-
// For J elements, derive type from the element's type property if it exists
|
|
451
|
-
const jElement = param as J;
|
|
452
|
-
if ((jElement as any).type) {
|
|
453
|
-
const typeString = this.typeToString((jElement as any).type);
|
|
454
|
-
// Only add preamble if we have a concrete type (not 'any')
|
|
455
|
-
if (typeString !== 'any') {
|
|
456
|
-
preamble.push(`let ${placeholder}: ${typeString};`);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
414
|
+
const type = this.parameterType(parameters[i].value, values);
|
|
415
|
+
const typeString = typeof type === 'string' ? type : type && this.typeToString(type);
|
|
416
|
+
if (typeString && typeString !== 'any') {
|
|
417
|
+
preamble.push(`let ${PlaceholderUtils.PLACEHOLDER_PREFIX}${i}__: ${typeString};`);
|
|
459
418
|
}
|
|
460
419
|
}
|
|
461
|
-
|
|
462
420
|
return preamble;
|
|
463
421
|
}
|
|
464
422
|
|
|
423
|
+
private static parameterType(param: any, values?: Pick<Map<string, J | J[]>, 'get'>): Type | string | undefined {
|
|
424
|
+
// A Capture may be a Proxy, so it is recognised by its symbol
|
|
425
|
+
if (param instanceof CaptureImpl || (param && typeof param === 'object' && param[CAPTURE_NAME_SYMBOL])) {
|
|
426
|
+
return param[CAPTURE_TYPE_SYMBOL] ?? this.treeType(values?.get(param[CAPTURE_NAME_SYMBOL] || param.getName()));
|
|
427
|
+
} else if (param instanceof CaptureValue) {
|
|
428
|
+
// The root's declared type is the root's alone, not that of a property or element of it
|
|
429
|
+
return values && this.treeType(param.resolve(values));
|
|
430
|
+
} else if (param instanceof TemplateParamImpl) {
|
|
431
|
+
return this.treeType(values?.get(param.name));
|
|
432
|
+
}
|
|
433
|
+
return this.treeType(param);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private static treeType(value: unknown): Type | undefined {
|
|
437
|
+
// A declaration's type is that of what it declares, which is no value the placeholder holds
|
|
438
|
+
return isTree(value) && (isExpression(value as J) || !isStatement(value as J)) ?
|
|
439
|
+
TypedTree.getType(value as TypedTree) : undefined;
|
|
440
|
+
}
|
|
441
|
+
|
|
465
442
|
/**
|
|
466
443
|
* Builds a template string with parameter placeholders.
|
|
467
444
|
* RawCode parameters are spliced directly into the template at construction time.
|
|
@@ -473,7 +450,8 @@ export class TemplateEngine {
|
|
|
473
450
|
*/
|
|
474
451
|
private static buildTemplateString(
|
|
475
452
|
templateParts: TemplateStringsArray,
|
|
476
|
-
parameters: Parameter[]
|
|
453
|
+
parameters: Parameter[],
|
|
454
|
+
expression: boolean
|
|
477
455
|
): string {
|
|
478
456
|
let result = '';
|
|
479
457
|
for (let i = 0; i < templateParts.length; i++) {
|
|
@@ -495,7 +473,7 @@ export class TemplateEngine {
|
|
|
495
473
|
|
|
496
474
|
// Always wrap in function body - let the parser decide what it is,
|
|
497
475
|
// then we'll extract intelligently based on what was parsed
|
|
498
|
-
return
|
|
476
|
+
return wrapCode(dedentTemplate(result), expression);
|
|
499
477
|
}
|
|
500
478
|
|
|
501
479
|
/**
|
|
@@ -556,6 +534,7 @@ export class TemplateEngine {
|
|
|
556
534
|
* @param captures The captures between the string parts (can include RawCode)
|
|
557
535
|
* @param contextStatements Context declarations (imports, types, etc.) to prepend for type attribution
|
|
558
536
|
* @param dependencies NPM dependencies for type attribution
|
|
537
|
+
* @param expression Whether to parse the code as an expression
|
|
559
538
|
* @returns A Promise resolving to the extracted pattern AST
|
|
560
539
|
*/
|
|
561
540
|
static async getPatternTree(
|
|
@@ -563,7 +542,8 @@ export class TemplateEngine {
|
|
|
563
542
|
captures: (Capture | Any | RawCode)[],
|
|
564
543
|
contextStatements: string[] = [],
|
|
565
544
|
dependencies: Record<string, string> = {},
|
|
566
|
-
types?: string[]
|
|
545
|
+
types?: string[],
|
|
546
|
+
expression: boolean = false
|
|
567
547
|
): Promise<J> {
|
|
568
548
|
const preamble = TemplateEngine.capturePreamble(captures);
|
|
569
549
|
|
|
@@ -587,7 +567,7 @@ export class TemplateEngine {
|
|
|
587
567
|
|
|
588
568
|
// Always wrap in function body - let the parser decide what it is,
|
|
589
569
|
// then we'll extract intelligently based on what was parsed
|
|
590
|
-
const templateString =
|
|
570
|
+
const templateString = wrapCode(result, expression);
|
|
591
571
|
|
|
592
572
|
// Add preamble to context statements (so they're skipped during extraction)
|
|
593
573
|
const contextWithPreamble = preamble.length > 0
|
|
@@ -612,7 +592,7 @@ export class TemplateEngine {
|
|
|
612
592
|
const lastStatement = cu.statements[cu.statements.length - 1].element;
|
|
613
593
|
|
|
614
594
|
// Extract from wrapper using shared utility
|
|
615
|
-
return PlaceholderUtils.extractFromWrapper(lastStatement, 'Pattern');
|
|
595
|
+
return PlaceholderUtils.extractFromWrapper(lastStatement, 'Pattern', expression);
|
|
616
596
|
}
|
|
617
597
|
|
|
618
598
|
/**
|
|
@@ -749,6 +729,59 @@ class MarkerAttachmentVisitor extends JavaScriptVisitor<undefined> {
|
|
|
749
729
|
name: visitedName
|
|
750
730
|
});
|
|
751
731
|
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Promotes a variadic capture's marker from a shorthand property's name to the property, so
|
|
735
|
+
* that `{${props}}` matches the object literal's properties as a sequence. A scalar capture
|
|
736
|
+
* stays on the name, binding the identifier a shorthand property consists of.
|
|
737
|
+
*/
|
|
738
|
+
protected override async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, p: undefined): Promise<J | undefined> {
|
|
739
|
+
const visited = await super.visitPropertyAssignment(propertyAssignment, p) as JS.PropertyAssignment;
|
|
740
|
+
const nameMarker = visited.initializer === undefined ? PlaceholderUtils.getCaptureMarker(visited.name) : undefined;
|
|
741
|
+
if (!nameMarker?.variadicOptions) {
|
|
742
|
+
return visited;
|
|
743
|
+
}
|
|
744
|
+
return updateIfChanged(visited, {
|
|
745
|
+
name: {
|
|
746
|
+
...visited.name,
|
|
747
|
+
markers: {...visited.name.markers, markers: visited.name.markers.markers.filter(m => m !== nameMarker)}
|
|
748
|
+
},
|
|
749
|
+
markers: {
|
|
750
|
+
...visited.markers,
|
|
751
|
+
markers: [...visited.markers.markers, nameMarker]
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// FIXME: This is a heuristic to determine if the parent expects a statement child
|
|
758
|
+
function expectsStatement(parentTree: J): boolean {
|
|
759
|
+
return parentTree.kind === J.Kind.Block ||
|
|
760
|
+
parentTree.kind === J.Kind.Case ||
|
|
761
|
+
parentTree.kind === J.Kind.DoWhileLoop ||
|
|
762
|
+
parentTree.kind === J.Kind.ForEachLoop ||
|
|
763
|
+
parentTree.kind === J.Kind.ForLoop ||
|
|
764
|
+
parentTree.kind === J.Kind.If ||
|
|
765
|
+
parentTree.kind === J.Kind.IfElse ||
|
|
766
|
+
parentTree.kind === J.Kind.WhileLoop ||
|
|
767
|
+
parentTree.kind === JS.Kind.CompilationUnit ||
|
|
768
|
+
parentTree.kind === JS.Kind.ForInLoop;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/** Whether the code opens with `{`, which is a block where a statement goes and an object literal elsewhere. */
|
|
772
|
+
export function opensWithBrace(templateParts: TemplateStringsArray): boolean {
|
|
773
|
+
return templateParts[0].trimStart().startsWith('{');
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/** Whether code opening with `{` that replaces `tree`, which `cursor` points at, is an object literal. */
|
|
777
|
+
export function replacedByObjectLiteral(tree: J, cursor: Cursor): boolean {
|
|
778
|
+
if (tree.kind === J.Kind.Block) {
|
|
779
|
+
return false;
|
|
780
|
+
} else if (!isStatement(tree)) {
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
const parentTree = cursor.parentTree()?.value;
|
|
784
|
+
return parentTree !== undefined && !expectsStatement(parentTree);
|
|
752
785
|
}
|
|
753
786
|
|
|
754
787
|
/**
|
|
@@ -759,7 +792,8 @@ export class TemplateApplier {
|
|
|
759
792
|
private readonly cursor: Cursor,
|
|
760
793
|
private readonly coordinates: JavaCoordinates,
|
|
761
794
|
private readonly ast: J,
|
|
762
|
-
private readonly shouldFormat: boolean = true
|
|
795
|
+
private readonly shouldFormat: boolean = true,
|
|
796
|
+
private readonly substituted: boolean = false
|
|
763
797
|
) {
|
|
764
798
|
}
|
|
765
799
|
|
|
@@ -791,16 +825,21 @@ export class TemplateApplier {
|
|
|
791
825
|
const {tree} = this.coordinates;
|
|
792
826
|
|
|
793
827
|
if (!tree) {
|
|
794
|
-
return this.ast;
|
|
828
|
+
return this.substituted ? new SubstitutedLayoutVisitor().visit(this.ast, undefined) : this.ast;
|
|
795
829
|
}
|
|
796
830
|
|
|
797
831
|
const originalTree = tree as J;
|
|
798
832
|
let resultToUse = this.wrapTree(originalTree, this.ast);
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
833
|
+
// A replaced node spliced back in as a capture carries its own trailing markers
|
|
834
|
+
const trailing = trailingMarkers(originalTree);
|
|
835
|
+
const slotMarkers = trailing && !(await treeIds(this.ast)).has(originalTree.id) ? trailing : undefined;
|
|
836
|
+
if (slotMarkers) {
|
|
837
|
+
resultToUse = {...resultToUse, markers: withMarkers(resultToUse.markers, slotMarkers)};
|
|
803
838
|
}
|
|
839
|
+
const slot = this.replacedSlot(originalTree);
|
|
840
|
+
// `format` substitutes the target's prefix, so decide against the prefix that will print
|
|
841
|
+
resultToUse = maybeParenthesize(slot?.[0], slot?.[1] ?? originalTree.id,
|
|
842
|
+
{...resultToUse, prefix: originalTree.prefix}, slotMarkers);
|
|
804
843
|
return this.format(resultToUse, originalTree);
|
|
805
844
|
}
|
|
806
845
|
|
|
@@ -842,7 +881,8 @@ export class TemplateApplier {
|
|
|
842
881
|
};
|
|
843
882
|
|
|
844
883
|
if (!this.shouldFormat) {
|
|
845
|
-
|
|
884
|
+
const unmarked = this.substituted ? await new SubstitutedLayoutVisitor().visit<J>(result, undefined) : result;
|
|
885
|
+
return {...unmarked!, id: resultToUse.id};
|
|
846
886
|
}
|
|
847
887
|
|
|
848
888
|
// A recipe can apply a template to a child while its cursor still points at the
|
|
@@ -853,9 +893,18 @@ export class TemplateApplier {
|
|
|
853
893
|
(await treeIds(owner as J)).has(originalTree.id) ? this.cursor : this.cursor?.parent;
|
|
854
894
|
const formatted =
|
|
855
895
|
await maybeAutoFormat(originalTree, result, null, undefined, parent);
|
|
896
|
+
// A value Prettier lays out arrives unmarked, since reconciling takes the markers of Prettier's parse
|
|
897
|
+
let laidOut = formatted;
|
|
898
|
+
if (this.substituted) {
|
|
899
|
+
const restore = new SubstitutedLayoutVisitor(await substitutedValues(result));
|
|
900
|
+
laidOut = (await restore.visit<J>(formatted, undefined))!;
|
|
901
|
+
if (restore.restored) {
|
|
902
|
+
laidOut = await autoIndent(laidOut, null, parent);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
856
905
|
|
|
857
906
|
// Restore the original ID
|
|
858
|
-
return {...
|
|
907
|
+
return {...laidOut!, id: resultToUse.id};
|
|
859
908
|
}
|
|
860
909
|
|
|
861
910
|
private wrapTree(originalTree: J, resultToUse: J) {
|
|
@@ -863,17 +912,7 @@ export class TemplateApplier {
|
|
|
863
912
|
|
|
864
913
|
// Only apply wrapping logic if we have parent context
|
|
865
914
|
if (parentTree) {
|
|
866
|
-
|
|
867
|
-
const parentExpectsStatement = parentTree.kind === J.Kind.Block ||
|
|
868
|
-
parentTree.kind === J.Kind.Case ||
|
|
869
|
-
parentTree.kind === J.Kind.DoWhileLoop ||
|
|
870
|
-
parentTree.kind === J.Kind.ForEachLoop ||
|
|
871
|
-
parentTree.kind === J.Kind.ForLoop ||
|
|
872
|
-
parentTree.kind === J.Kind.If ||
|
|
873
|
-
parentTree.kind === J.Kind.IfElse ||
|
|
874
|
-
parentTree.kind === J.Kind.WhileLoop ||
|
|
875
|
-
parentTree.kind === JS.Kind.CompilationUnit ||
|
|
876
|
-
parentTree.kind === JS.Kind.ForInLoop;
|
|
915
|
+
const parentExpectsStatement = expectsStatement(parentTree);
|
|
877
916
|
const originalIsStatement = isStatement(originalTree);
|
|
878
917
|
|
|
879
918
|
const resultIsStatement = isStatement(resultToUse);
|
|
@@ -889,7 +928,7 @@ export class TemplateApplier {
|
|
|
889
928
|
kind: JS.Kind.ExpressionStatement,
|
|
890
929
|
id: randomId(),
|
|
891
930
|
prefix: expression.prefix,
|
|
892
|
-
markers: expression
|
|
931
|
+
markers: wrapperMarkers(expression),
|
|
893
932
|
expression: { ...expression, prefix: emptySpace }
|
|
894
933
|
} as JS.ExpressionStatement;
|
|
895
934
|
}
|
|
@@ -901,7 +940,7 @@ export class TemplateApplier {
|
|
|
901
940
|
kind: JS.Kind.StatementExpression,
|
|
902
941
|
id: randomId(),
|
|
903
942
|
prefix: stmt.prefix,
|
|
904
|
-
markers: stmt
|
|
943
|
+
markers: wrapperMarkers(stmt),
|
|
905
944
|
statement: { ...stmt, prefix: emptySpace }
|
|
906
945
|
} as JS.StatementExpression;
|
|
907
946
|
}
|
|
@@ -910,3 +949,66 @@ export class TemplateApplier {
|
|
|
910
949
|
return resultToUse;
|
|
911
950
|
}
|
|
912
951
|
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* The trailing markers of the node a template replaces. Each prints after whatever node fills the slot,
|
|
955
|
+
* so a pattern does not match on it and the replacement keeps it.
|
|
956
|
+
*/
|
|
957
|
+
function trailingMarkers(replaced: J): Markers | undefined {
|
|
958
|
+
const trailing = replaced.markers.markers.filter(isTrailingMarker);
|
|
959
|
+
return trailing.length === 0 ? undefined : markers(...trailing);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/** `own`, plus each marker of `added` whose kind `own` does not already hold. */
|
|
963
|
+
function withMarkers(own: Markers, added: Markers): Markers {
|
|
964
|
+
const missing = added.markers.filter(m => !own.markers.some(o => o.kind === m.kind));
|
|
965
|
+
return missing.length === 0 ? own : {...own, markers: [...own.markers, ...missing]};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/** The markers of a node wrapping `wrapped`, less the {@link SubstitutedValue} that belongs to `wrapped` alone. */
|
|
969
|
+
function wrapperMarkers(wrapped: J): J['markers'] {
|
|
970
|
+
return {...wrapped.markers, markers: wrapped.markers.markers.filter(m => m.kind !== SubstitutedValue.KIND)};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/** The substituted values in `tree`, by the id of the {@link SubstitutedValue} marking each. */
|
|
974
|
+
async function substitutedValues(tree: J): Promise<Map<string, J>> {
|
|
975
|
+
const values = new Map<string, J>();
|
|
976
|
+
await new class extends JavaScriptVisitor<undefined> {
|
|
977
|
+
protected override async preVisit(t: J): Promise<J | undefined> {
|
|
978
|
+
const marker = findMarker<SubstitutedValue>(t, SubstitutedValue.KIND);
|
|
979
|
+
if (marker) {
|
|
980
|
+
values.set(marker.id, t);
|
|
981
|
+
}
|
|
982
|
+
return t;
|
|
983
|
+
}
|
|
984
|
+
}().visit(tree, undefined);
|
|
985
|
+
return values;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Puts each substituted value back as it was in `unformatted`, under the prefix its marker assigns it,
|
|
990
|
+
* and drops its {@link SubstitutedValue} marker. Re-indenting them is left to the indent pass.
|
|
991
|
+
*/
|
|
992
|
+
class SubstitutedLayoutVisitor extends JavaScriptVisitor<undefined> {
|
|
993
|
+
/** Whether any value was put back. */
|
|
994
|
+
restored = false;
|
|
995
|
+
|
|
996
|
+
constructor(private readonly unformatted: Map<string, J> = new Map()) {
|
|
997
|
+
super();
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
override async visit<R extends J>(tree: Tree, p: undefined, parent?: Cursor): Promise<R | undefined> {
|
|
1001
|
+
const marker = isTree(tree) ? findMarker<SubstitutedValue>(tree as J, SubstitutedValue.KIND) : undefined;
|
|
1002
|
+
if (!marker) {
|
|
1003
|
+
return super.visit(tree, p, parent);
|
|
1004
|
+
}
|
|
1005
|
+
const formatted = tree as J;
|
|
1006
|
+
const before = this.unformatted.get(marker.id);
|
|
1007
|
+
this.restored ||= before !== undefined;
|
|
1008
|
+
const value = before ? {...before, prefix: marker.ownPrefix ? before.prefix : formatted.prefix} : formatted;
|
|
1009
|
+
return {
|
|
1010
|
+
...value,
|
|
1011
|
+
markers: {...value.markers, markers: value.markers.markers.filter(m => m !== marker)}
|
|
1012
|
+
} as J as R;
|
|
1013
|
+
}
|
|
1014
|
+
}
|