@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,37 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import {J, Type} from '../../java';
|
|
17
17
|
import {JavaScriptVisitor} from '../visitor';
|
|
18
|
-
import {
|
|
19
|
-
import {ModuleBinding} from './types';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Whether the template's dependencies bring a workspace that could resolve `module`.
|
|
23
|
-
*/
|
|
24
|
-
export function isResolvable(module: string, dependencies: Record<string, string>): boolean {
|
|
25
|
-
const segments = module.split('/');
|
|
26
|
-
const pkg = module.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0];
|
|
27
|
-
return Object.prototype.hasOwnProperty.call(dependencies, pkg);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* What a declared binding's name is parsed against, ahead of the template and so out of its output.
|
|
32
|
-
* An import is what carries attribution, and costs a module resolution to get it; a declaration
|
|
33
|
-
* names the binding for a module no workspace could have resolved anyway.
|
|
34
|
-
*/
|
|
35
|
-
export function bindingContextStatement(name: string, binding: ModuleBinding, dependencies: Record<string, string>): string {
|
|
36
|
-
if (!isResolvable(binding.module, dependencies)) {
|
|
37
|
-
return binding.typeOnly ? `type ${name} = any;` : `declare const ${name}: any;`;
|
|
38
|
-
}
|
|
39
|
-
const type = binding.typeOnly ? 'type ' : '';
|
|
40
|
-
if (binding.member === '*') {
|
|
41
|
-
return `import ${type}* as ${name} from '${binding.module}';`;
|
|
42
|
-
}
|
|
43
|
-
if (binding.member === undefined || binding.member === 'default') {
|
|
44
|
-
return `import ${type}${name} from '${binding.module}';`;
|
|
45
|
-
}
|
|
46
|
-
const specifier = binding.member === name ? name : `${binding.member} as ${name}`;
|
|
47
|
-
return `import ${type}{${specifier}} from '${binding.module}';`;
|
|
48
|
-
}
|
|
18
|
+
import {isValueReference} from '../scope';
|
|
49
19
|
|
|
50
20
|
/**
|
|
51
21
|
* Renames the identifiers a template uses for its declared bindings to the names the file
|
|
@@ -73,35 +43,12 @@ class RenameBindingsVisitor extends JavaScriptVisitor<undefined> {
|
|
|
73
43
|
const resolved = resolvedModule(identifier);
|
|
74
44
|
const refersToBinding = resolved !== undefined
|
|
75
45
|
? resolved === this.modules[identifier.simpleName]
|
|
76
|
-
:
|
|
46
|
+
: isValueReference(this.cursor, identifier);
|
|
77
47
|
|
|
78
48
|
return refersToBinding ? {...identifier, simpleName: renamed} as J.Identifier : identifier;
|
|
79
49
|
}
|
|
80
50
|
}
|
|
81
51
|
|
|
82
|
-
/** Whether the parent is naming this identifier — as a property, a method, a declaration — rather than referencing it. */
|
|
83
|
-
function namesItsParent(cursor: Cursor, identifier: J.Identifier): boolean {
|
|
84
|
-
let c: Cursor | undefined = cursor.parent;
|
|
85
|
-
while (c && isPadding(c.value)) {
|
|
86
|
-
c = c.parent;
|
|
87
|
-
}
|
|
88
|
-
const parent = c?.value as { kind?: string; name?: unknown; select?: unknown } | undefined;
|
|
89
|
-
|
|
90
|
-
// A call names a member of whatever it selects from. With nothing selected there is no member,
|
|
91
|
-
// and its `name` is a reference to the function being called.
|
|
92
|
-
if (parent?.kind === J.Kind.MethodInvocation && !parent.select) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const name = parent?.name;
|
|
97
|
-
return name === identifier || (name as { element?: unknown } | undefined)?.element === identifier;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function isPadding(value: unknown): boolean {
|
|
101
|
-
const kind = (value as { kind?: string } | undefined)?.kind;
|
|
102
|
-
return kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded || kind === J.Kind.Container;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
52
|
/** The module an identifier's attribution traces back to, following the owning-class chain to its root. */
|
|
106
53
|
function resolvedModule(identifier: J.Identifier): string | undefined {
|
|
107
54
|
const fieldType = identifier.fieldType;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {Cursor, Tree} from '../..';
|
|
17
|
-
import {J} from '../../java';
|
|
16
|
+
import {Cursor, Markers, Tree} from '../..';
|
|
17
|
+
import {J, Type} from '../../java';
|
|
18
18
|
import {JS} from '../index';
|
|
19
19
|
import {JavaScriptSemanticComparatorVisitor} from '../comparator';
|
|
20
20
|
import {CaptureMarker, CaptureStorageValue, PlaceholderUtils} from './utils';
|
|
@@ -85,6 +85,17 @@ class CaptureMapImpl implements CaptureMap {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/** A capture found in a pattern, and the target node it binds to. */
|
|
89
|
+
interface CaptureAt {
|
|
90
|
+
marker: CaptureMarker;
|
|
91
|
+
target: J;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Parentheses that `JavaScriptSemanticComparatorVisitor.unwrap()` sees through. */
|
|
95
|
+
function isParenthesized(j: any): boolean {
|
|
96
|
+
return j?.kind === J.Kind.Parentheses || j?.kind === J.Kind.ControlParentheses;
|
|
97
|
+
}
|
|
98
|
+
|
|
88
99
|
/**
|
|
89
100
|
* A comparator for pattern matching that is lenient about optional properties.
|
|
90
101
|
* Allows patterns without type annotations to match actual code with type annotations.
|
|
@@ -112,33 +123,59 @@ export class PatternMatchingComparator extends JavaScriptSemanticComparatorVisit
|
|
|
112
123
|
};
|
|
113
124
|
}
|
|
114
125
|
|
|
126
|
+
/** The capture this pattern node stands for, paired with the target node it should bind to. */
|
|
127
|
+
protected captureAt(j: Tree, p: J): CaptureAt | undefined {
|
|
128
|
+
const marker = PlaceholderUtils.getCaptureMarker(j as J);
|
|
129
|
+
return marker ? {marker, target: p} : this.captureBehindParentheses(j, p);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The marker `attachCaptureMarkers` moved onto a parenthesized capture's `J.RightPadded`, which `unwrap()` skips past. */
|
|
133
|
+
private captureBehindParentheses(j: Tree, p: J): CaptureAt | undefined {
|
|
134
|
+
let pattern: any = j;
|
|
135
|
+
let target: any = p;
|
|
136
|
+
while (isParenthesized(pattern)) {
|
|
137
|
+
const padded = pattern.tree as J.RightPadded<J> | undefined;
|
|
138
|
+
if (!padded) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
target = isParenthesized(target) ? target.tree.element : target;
|
|
142
|
+
const marker = PlaceholderUtils.getCaptureMarker(padded);
|
|
143
|
+
if (marker) {
|
|
144
|
+
return {marker, target: target as J};
|
|
145
|
+
}
|
|
146
|
+
pattern = padded.element;
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
115
151
|
override async visit<R extends J>(j: Tree, p: J, parent?: Cursor): Promise<R | undefined> {
|
|
116
152
|
// Check if the pattern node is a capture - this handles unwrapped captures
|
|
117
153
|
// (Wrapped captures in J.RightPadded are handled by visitRightPadded override)
|
|
118
154
|
// Note: targetCursor will be pushed by parent's visit() method after this check
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
155
|
+
const capture = this.captureAt(j, p);
|
|
156
|
+
if (capture) {
|
|
157
|
+
const {marker: captureMarker, target} = capture;
|
|
121
158
|
|
|
122
159
|
// Push targetCursor to position it at the captured node for constraint evaluation
|
|
123
160
|
// Only create cursor if targetCursor was initialized (meaning user provided one)
|
|
124
161
|
const savedTargetCursor = this.targetCursor;
|
|
125
162
|
const cursorAtCapturedNode = this.targetCursor !== undefined
|
|
126
|
-
? new Cursor(
|
|
127
|
-
: new Cursor(
|
|
163
|
+
? new Cursor(target, this.targetCursor)
|
|
164
|
+
: new Cursor(target);
|
|
128
165
|
this.targetCursor = cursorAtCapturedNode;
|
|
129
166
|
try {
|
|
130
167
|
// Evaluate constraint with context (cursor + previous captures)
|
|
131
168
|
// Skip constraint for variadic captures - they're evaluated in matchSequence with the full array
|
|
132
169
|
if (captureMarker.constraint && !captureMarker.variadicOptions) {
|
|
133
170
|
const context = this.buildConstraintContext(cursorAtCapturedNode);
|
|
134
|
-
if (!captureMarker.constraint(
|
|
171
|
+
if (!captureMarker.constraint(target, context)) {
|
|
135
172
|
const captureName = captureMarker.captureName || 'unnamed';
|
|
136
|
-
const targetKind = (
|
|
173
|
+
const targetKind = (target as any).kind || 'unknown';
|
|
137
174
|
return this.constraintFailed(captureName, targetKind) as R;
|
|
138
175
|
}
|
|
139
176
|
}
|
|
140
177
|
|
|
141
|
-
const success = this.matcher.handleCapture(captureMarker,
|
|
178
|
+
const success = this.matcher.handleCapture(captureMarker, target, undefined);
|
|
142
179
|
if (!success) {
|
|
143
180
|
const captureName = captureMarker.captureName || 'unnamed';
|
|
144
181
|
return this.captureConflict(captureName) as R;
|
|
@@ -157,6 +194,11 @@ export class PatternMatchingComparator extends JavaScriptSemanticComparatorVisit
|
|
|
157
194
|
return await super.visit(j, p, parent);
|
|
158
195
|
}
|
|
159
196
|
|
|
197
|
+
/** A capture in the receiver has to bind, so a matching `methodType` cannot stand in for it. */
|
|
198
|
+
protected override selectMustBeVisited(method: J.MethodInvocation): boolean {
|
|
199
|
+
return method.select !== undefined && containsCapture(method.select);
|
|
200
|
+
}
|
|
201
|
+
|
|
160
202
|
protected hasSameKind(j: J, other: J): boolean {
|
|
161
203
|
return super.hasSameKind(j, other) ||
|
|
162
204
|
(j.kind == J.Kind.Identifier && PlaceholderUtils.isCapture(j as J.Identifier));
|
|
@@ -896,26 +938,27 @@ export class DebugPatternMatchingComparator extends PatternMatchingComparator {
|
|
|
896
938
|
}
|
|
897
939
|
|
|
898
940
|
override async visit<R extends J>(j: Tree, p: J, parent?: Cursor): Promise<R | undefined> {
|
|
899
|
-
const
|
|
900
|
-
if (
|
|
941
|
+
const capture = this.captureAt(j, p);
|
|
942
|
+
if (capture) {
|
|
943
|
+
const {marker: captureMarker, target} = capture;
|
|
901
944
|
const savedTargetCursor = this.targetCursor;
|
|
902
945
|
const cursorAtCapturedNode = this.targetCursor !== undefined
|
|
903
|
-
? new Cursor(
|
|
904
|
-
: new Cursor(
|
|
946
|
+
? new Cursor(target, this.targetCursor)
|
|
947
|
+
: new Cursor(target);
|
|
905
948
|
this.targetCursor = cursorAtCapturedNode;
|
|
906
949
|
try {
|
|
907
950
|
if (captureMarker.constraint && !captureMarker.variadicOptions) {
|
|
908
951
|
this.debug.log('debug', 'constraint', `Evaluating constraint for capture: ${captureMarker.captureName}`);
|
|
909
|
-
const constraintResult = captureMarker.constraint(
|
|
952
|
+
const constraintResult = captureMarker.constraint(target, this.buildConstraintContext(cursorAtCapturedNode));
|
|
910
953
|
if (!constraintResult) {
|
|
911
954
|
this.debug.log('info', 'constraint', `Constraint failed for capture: ${captureMarker.captureName}`);
|
|
912
|
-
this.debug.setExplanation('constraint-failed', `Capture ${captureMarker.captureName} with valid constraint`, `Constraint failed for ${(
|
|
955
|
+
this.debug.setExplanation('constraint-failed', `Capture ${captureMarker.captureName} with valid constraint`, `Constraint failed for ${(target as any).kind}`, `Constraint evaluation returned false`);
|
|
913
956
|
return this.abort(j) as R;
|
|
914
957
|
}
|
|
915
958
|
this.debug.log('debug', 'constraint', `Constraint passed for capture: ${captureMarker.captureName}`);
|
|
916
959
|
}
|
|
917
960
|
|
|
918
|
-
const success = this.matcher.handleCapture(captureMarker,
|
|
961
|
+
const success = this.matcher.handleCapture(captureMarker, target, undefined);
|
|
919
962
|
if (!success) {
|
|
920
963
|
return this.abort(j) as R;
|
|
921
964
|
}
|
|
@@ -1419,3 +1462,25 @@ export class DebugPatternMatchingComparator extends PatternMatchingComparator {
|
|
|
1419
1462
|
}
|
|
1420
1463
|
}
|
|
1421
1464
|
}
|
|
1465
|
+
|
|
1466
|
+
/** Whether anything under `node`, the padding wrappers a marker moves onto included, is a capture. */
|
|
1467
|
+
function containsCapture(node: unknown): boolean {
|
|
1468
|
+
if (node === null || typeof node !== 'object') {
|
|
1469
|
+
return false;
|
|
1470
|
+
}
|
|
1471
|
+
if (Array.isArray(node)) {
|
|
1472
|
+
return node.some(containsCapture);
|
|
1473
|
+
}
|
|
1474
|
+
const record = node as Record<string, unknown>;
|
|
1475
|
+
if (typeof record.kind !== 'string') {
|
|
1476
|
+
return false;
|
|
1477
|
+
}
|
|
1478
|
+
// Attribution is a cyclic graph, and a capture is never reachable through one
|
|
1479
|
+
if (Type.isType(node)) {
|
|
1480
|
+
return false;
|
|
1481
|
+
}
|
|
1482
|
+
if (record.markers && PlaceholderUtils.getCaptureMarker(node as { markers: Markers })) {
|
|
1483
|
+
return true;
|
|
1484
|
+
}
|
|
1485
|
+
return Object.entries(record).some(([key, value]) => key !== 'markers' && containsCapture(value));
|
|
1486
|
+
}
|
|
@@ -28,6 +28,15 @@ 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
|
+
import {ModuleScopeBinding, moduleScopeBindings} from '../add-import';
|
|
32
|
+
import {walk} from '../scope';
|
|
33
|
+
import {isIdentifier} from '../../java';
|
|
34
|
+
import {findMarker, MarkersKind, ParseExceptionResult} from '../../markers';
|
|
35
|
+
|
|
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
|
+
}
|
|
31
40
|
import {Parameter} from "./types";
|
|
32
41
|
|
|
33
42
|
/**
|
|
@@ -90,11 +99,13 @@ export function setTemplateSourceFileCache(cache?: Map<string, ts.SourceFile>):
|
|
|
90
99
|
// from one compile to the next; see `JavaScriptParser.parse`.
|
|
91
100
|
const templateParsers: Map<string, JavaScriptParser> = new Map();
|
|
92
101
|
|
|
93
|
-
function templateParser(workspaceDir?: string): JavaScriptParser {
|
|
94
|
-
|
|
102
|
+
function templateParser(workspaceDir?: string, types?: string[]): JavaScriptParser {
|
|
103
|
+
// `types` changes what the compiler loads, so parsers cannot be shared across differing sets.
|
|
104
|
+
// An empty list loads nothing where absence loads the defaults, so the two encode apart.
|
|
105
|
+
const key = `${workspaceDir ?? ""}::${JSON.stringify(types ?? null)}`;
|
|
95
106
|
let parser = templateParsers.get(key);
|
|
96
107
|
if (!parser) {
|
|
97
|
-
parser = new JavaScriptParser({relativeTo: workspaceDir, sourceFileCache: templateSourceFileCache});
|
|
108
|
+
parser = new JavaScriptParser({relativeTo: workspaceDir, sourceFileCache: templateSourceFileCache, types});
|
|
98
109
|
templateParsers.set(key, parser);
|
|
99
110
|
}
|
|
100
111
|
return parser;
|
|
@@ -115,7 +126,8 @@ class TemplateCache {
|
|
|
115
126
|
templateString: string,
|
|
116
127
|
captures: (Capture | Any)[],
|
|
117
128
|
contextStatements: string[],
|
|
118
|
-
dependencies: Record<string, string
|
|
129
|
+
dependencies: Record<string, string>,
|
|
130
|
+
types: string[] | undefined
|
|
119
131
|
): string {
|
|
120
132
|
// Use the actual template string (with placeholders) as the primary key
|
|
121
133
|
const templateKey = templateString;
|
|
@@ -129,7 +141,7 @@ class TemplateCache {
|
|
|
129
141
|
// Dependencies
|
|
130
142
|
const depsKey = JSON.stringify(dependencies || {});
|
|
131
143
|
|
|
132
|
-
return `${templateKey}::${capturesKey}::${contextKey}::${depsKey}`;
|
|
144
|
+
return `${templateKey}::${capturesKey}::${contextKey}::${depsKey}::${JSON.stringify(types ?? null)}`;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
147
|
/**
|
|
@@ -139,9 +151,10 @@ class TemplateCache {
|
|
|
139
151
|
templateString: string,
|
|
140
152
|
captures: (Capture | Any)[],
|
|
141
153
|
contextStatements: string[],
|
|
142
|
-
dependencies: Record<string, string
|
|
154
|
+
dependencies: Record<string, string>,
|
|
155
|
+
types?: string[]
|
|
143
156
|
): Promise<JS.CompilationUnit> {
|
|
144
|
-
const key = this.generateKey(templateString, captures, contextStatements, dependencies);
|
|
157
|
+
const key = this.generateKey(templateString, captures, contextStatements, dependencies, types);
|
|
145
158
|
|
|
146
159
|
let cu = this.cache.get(key);
|
|
147
160
|
if (cu) {
|
|
@@ -163,7 +176,7 @@ class TemplateCache {
|
|
|
163
176
|
|
|
164
177
|
// Parse and cache (workspace only needed during parsing)
|
|
165
178
|
// Use templateSourceFileCache if configured for ~3.2x speedup on dependency file parsing
|
|
166
|
-
const parser = templateParser(workspaceDir);
|
|
179
|
+
const parser = templateParser(workspaceDir, types);
|
|
167
180
|
const parseGenerator = parser.parse({text: fullTemplateString, sourcePath: 'template.tsx'});
|
|
168
181
|
cu = (await parseGenerator.next()).value as JS.CompilationUnit;
|
|
169
182
|
|
|
@@ -193,6 +206,20 @@ export function clearTemplateCache(): void {
|
|
|
193
206
|
templateCache.clear();
|
|
194
207
|
}
|
|
195
208
|
|
|
209
|
+
/** Whether a name reads as a TypeScript type reference: a dotted chain of identifiers. */
|
|
210
|
+
function isTypeReference(name: string): boolean {
|
|
211
|
+
return /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(name);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The compiler's diagnostic for code that did not parse, as one line: the marker states it ahead
|
|
216
|
+
* of a stack, and its positions index the whole parsed text, context statements included.
|
|
217
|
+
*/
|
|
218
|
+
function parseFailureReason(cu: JS.CompilationUnit): string {
|
|
219
|
+
const failure = findMarker<ParseExceptionResult>(cu, MarkersKind.ParseExceptionResult);
|
|
220
|
+
return failure ? failure.message.split('\n')[0].replace(/:$/, '') : 'no statements';
|
|
221
|
+
}
|
|
222
|
+
|
|
196
223
|
/**
|
|
197
224
|
* Internal template engine - handles the core templating logic.
|
|
198
225
|
* Not exported from index, so only visible within the templating module.
|
|
@@ -208,35 +235,77 @@ export class TemplateEngine {
|
|
|
208
235
|
* @param dependencies NPM dependencies for type attribution
|
|
209
236
|
* @returns A Promise resolving to the extracted template AST
|
|
210
237
|
*/
|
|
211
|
-
|
|
238
|
+
/** The template parsed with its context, which is what gives its code types to attribute against. */
|
|
239
|
+
private static async parseWithContext(
|
|
212
240
|
templateParts: TemplateStringsArray,
|
|
213
241
|
parameters: Parameter[],
|
|
214
|
-
contextStatements: string[]
|
|
215
|
-
dependencies: Record<string, string
|
|
216
|
-
|
|
217
|
-
|
|
242
|
+
contextStatements: string[],
|
|
243
|
+
dependencies: Record<string, string>,
|
|
244
|
+
types: string[] | undefined
|
|
245
|
+
): Promise<JS.CompilationUnit> {
|
|
246
|
+
// A capture's declared type reaches the parse as a declaration, so it belongs with context.
|
|
218
247
|
const preamble = TemplateEngine.parameterPreamble(parameters);
|
|
219
|
-
|
|
220
|
-
// Build the template string with parameter placeholders
|
|
221
248
|
const templateString = TemplateEngine.buildTemplateString(templateParts, parameters);
|
|
222
|
-
|
|
223
|
-
// Add preamble to context statements (so they're skipped during extraction)
|
|
224
249
|
const contextWithPreamble = preamble.length > 0
|
|
225
250
|
? [...contextStatements, ...preamble]
|
|
226
251
|
: contextStatements;
|
|
252
|
+
return TemplateEngine.parseOrThrow('template', templateString, [], contextWithPreamble, dependencies, types);
|
|
253
|
+
}
|
|
227
254
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
255
|
+
/**
|
|
256
|
+
* The parse behind every template and pattern. Code that fails to parse yields a compilation
|
|
257
|
+
* unit with no statements, which every caller goes on to read, so it fails here naming the code.
|
|
258
|
+
*/
|
|
259
|
+
private static async parseOrThrow(
|
|
260
|
+
kind: 'template' | 'pattern',
|
|
261
|
+
templateString: string,
|
|
262
|
+
captures: (Capture | Any)[],
|
|
263
|
+
contextStatements: string[],
|
|
264
|
+
dependencies: Record<string, string>,
|
|
265
|
+
types: string[] | undefined
|
|
266
|
+
): Promise<JS.CompilationUnit> {
|
|
267
|
+
const cu = await templateCache.getOrParse(templateString, captures, contextStatements, dependencies, types);
|
|
237
268
|
if (!cu.statements || cu.statements.length === 0) {
|
|
238
|
-
throw new Error(`Failed to parse
|
|
269
|
+
throw new Error(`Failed to parse ${kind} code (${parseFailureReason(cu)}):\n${templateString}`);
|
|
239
270
|
}
|
|
271
|
+
return cu;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* The modules the template's context binds, for the caller to bind in the file being edited.
|
|
276
|
+
* An `import` or `require` states one; anything else — a `declare`, a helper signature — types
|
|
277
|
+
* the template without asking for a binding.
|
|
278
|
+
*/
|
|
279
|
+
static async getContextBindings(
|
|
280
|
+
templateParts: TemplateStringsArray,
|
|
281
|
+
parameters: Parameter[],
|
|
282
|
+
contextStatements: string[] = [],
|
|
283
|
+
dependencies: Record<string, string> = {},
|
|
284
|
+
types?: string[]
|
|
285
|
+
): Promise<ContextBinding[]> {
|
|
286
|
+
const cu = await TemplateEngine.parseWithContext(templateParts, parameters, contextStatements, dependencies, types);
|
|
287
|
+
// The template's own code is the last statement, so everything ahead of it is context.
|
|
288
|
+
const context = {...cu, statements: cu.statements.slice(0, -1)};
|
|
289
|
+
const attributed = new Set<string>();
|
|
290
|
+
walk(context.statements, node => {
|
|
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)}));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
static async getTemplateTree(
|
|
302
|
+
templateParts: TemplateStringsArray,
|
|
303
|
+
parameters: Parameter[],
|
|
304
|
+
contextStatements: string[] = [],
|
|
305
|
+
dependencies: Record<string, string> = {},
|
|
306
|
+
types?: string[]
|
|
307
|
+
): Promise<J> {
|
|
308
|
+
const cu = await TemplateEngine.parseWithContext(templateParts, parameters, contextStatements, dependencies, types);
|
|
240
309
|
|
|
241
310
|
// The template code is always the last statement (after context + preamble)
|
|
242
311
|
const lastStatement = cu.statements[cu.statements.length - 1].element;
|
|
@@ -263,7 +332,7 @@ export class TemplateEngine {
|
|
|
263
332
|
* @returns A Promise resolving to the generated AST node
|
|
264
333
|
*/
|
|
265
334
|
static async applyTemplateFromAst(
|
|
266
|
-
ast:
|
|
335
|
+
ast: J,
|
|
267
336
|
parameters: Parameter[],
|
|
268
337
|
cursor: Cursor,
|
|
269
338
|
coordinates: JavaCoordinates,
|
|
@@ -439,7 +508,10 @@ export class TemplateEngine {
|
|
|
439
508
|
// Handle Type.Class and Type.ShallowClass - return their fully qualified names
|
|
440
509
|
if (type.kind === Type.Kind.Class || type.kind === Type.Kind.ShallowClass) {
|
|
441
510
|
const classType = type as Type.Class;
|
|
442
|
-
|
|
511
|
+
// A module qualifies its types by where they come from — `src/a.Foo` — and a path that
|
|
512
|
+
// is no type reference cannot be declared for. Naming a module's type takes a `context`
|
|
513
|
+
// statement of the caller's own, which `type` then names.
|
|
514
|
+
return isTypeReference(classType.fullyQualifiedName) ? classType.fullyQualifiedName : 'any';
|
|
443
515
|
}
|
|
444
516
|
|
|
445
517
|
// Handle Type.Primitive - map to TypeScript primitive types
|
|
@@ -490,7 +562,8 @@ export class TemplateEngine {
|
|
|
490
562
|
templateParts: TemplateStringsArray,
|
|
491
563
|
captures: (Capture | Any | RawCode)[],
|
|
492
564
|
contextStatements: string[] = [],
|
|
493
|
-
dependencies: Record<string, string> = {}
|
|
565
|
+
dependencies: Record<string, string> = {},
|
|
566
|
+
types?: string[]
|
|
494
567
|
): Promise<J> {
|
|
495
568
|
const preamble = TemplateEngine.capturePreamble(captures);
|
|
496
569
|
|
|
@@ -526,19 +599,15 @@ export class TemplateEngine {
|
|
|
526
599
|
!(c instanceof RawCode || (c && typeof c === 'object' && (c as any)[RAW_CODE_SYMBOL]))
|
|
527
600
|
) as (Capture | Any)[];
|
|
528
601
|
|
|
529
|
-
|
|
530
|
-
|
|
602
|
+
const cu = await TemplateEngine.parseOrThrow(
|
|
603
|
+
'pattern',
|
|
531
604
|
templateString,
|
|
532
605
|
actualCaptures,
|
|
533
606
|
contextWithPreamble,
|
|
534
|
-
dependencies
|
|
607
|
+
dependencies,
|
|
608
|
+
types
|
|
535
609
|
);
|
|
536
610
|
|
|
537
|
-
// Check if there are any statements
|
|
538
|
-
if (!cu.statements || cu.statements.length === 0) {
|
|
539
|
-
throw new Error(`Failed to parse pattern code (no statements):\n${templateString}`);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
611
|
// The pattern code is always the last statement (after context + preamble)
|
|
543
612
|
const lastStatement = cu.statements[cu.statements.length - 1].element;
|
|
544
613
|
|
|
@@ -236,7 +236,8 @@ export class Pattern {
|
|
|
236
236
|
// A capture's type reaches the parse as a declaration, so it shapes the tree the same
|
|
237
237
|
// way an explicit context statement does and belongs in the key alongside one.
|
|
238
238
|
[...contextStatements, ...TemplateEngine.capturePreamble(this.captures)],
|
|
239
|
-
this._options.dependencies || {}
|
|
239
|
+
this._options.dependencies || {},
|
|
240
|
+
this._options.types
|
|
240
241
|
);
|
|
241
242
|
|
|
242
243
|
// Level 2: Global cache (fast path - shared with Template)
|
|
@@ -247,7 +248,8 @@ export class Pattern {
|
|
|
247
248
|
this.templateParts,
|
|
248
249
|
this.captures,
|
|
249
250
|
contextStatements,
|
|
250
|
-
this._options.dependencies || {}
|
|
251
|
+
this._options.dependencies || {},
|
|
252
|
+
this._options.types
|
|
251
253
|
);
|
|
252
254
|
globalAstCache.set(cacheKey, tree);
|
|
253
255
|
}
|
|
@@ -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
|
|
|
@@ -181,6 +181,16 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
181
181
|
sourcePrefix : this.mergePrefix(sourcePrefix, templatePrefix);
|
|
182
182
|
}
|
|
183
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
|
+
|
|
184
194
|
/**
|
|
185
195
|
* Expands variadic placeholders in a list of elements.
|
|
186
196
|
*
|
|
@@ -386,7 +396,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
386
396
|
if (isTree(propertyValue)) {
|
|
387
397
|
const propValueAsJ = propertyValue as J;
|
|
388
398
|
return produce(propValueAsJ, draft => {
|
|
389
|
-
draft.markers = placeholder.markers;
|
|
399
|
+
draft.markers = this.mergeMarkers(propValueAsJ.markers, placeholder.markers);
|
|
390
400
|
draft.prefix = this.mergePrefix(propValueAsJ.prefix, placeholder.prefix);
|
|
391
401
|
});
|
|
392
402
|
}
|
|
@@ -421,7 +431,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
421
431
|
const matchedNode = this.values.get(name);
|
|
422
432
|
if (matchedNode && !Array.isArray(matchedNode)) {
|
|
423
433
|
return produce(matchedNode, draft => {
|
|
424
|
-
draft.markers = placeholder.markers;
|
|
434
|
+
draft.markers = this.mergeMarkers(matchedNode.markers, placeholder.markers);
|
|
425
435
|
draft.prefix = this.mergePrefix(matchedNode.prefix, placeholder.prefix);
|
|
426
436
|
});
|
|
427
437
|
}
|
|
@@ -438,7 +448,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
438
448
|
// Extract the element from the J.RightPadded wrapper
|
|
439
449
|
const element = param.value.element as J;
|
|
440
450
|
return produce(element, draft => {
|
|
441
|
-
draft.markers = placeholder.markers;
|
|
451
|
+
draft.markers = this.mergeMarkers(element.markers, placeholder.markers);
|
|
442
452
|
draft.prefix = this.mergePrefix(element.prefix, placeholder.prefix);
|
|
443
453
|
});
|
|
444
454
|
}
|
|
@@ -458,7 +468,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
458
468
|
if (isTree(param.value)) {
|
|
459
469
|
// Return the AST node, preserving comments from the source
|
|
460
470
|
return produce(param.value as J, draft => {
|
|
461
|
-
draft.markers = placeholder.markers;
|
|
471
|
+
draft.markers = this.mergeMarkers(param.value.markers, placeholder.markers);
|
|
462
472
|
draft.prefix = this.mergePrefix(param.value.prefix, placeholder.prefix);
|
|
463
473
|
});
|
|
464
474
|
}
|