@openrewrite/rewrite 8.81.9 → 8.81.11
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/javascript/preconditions.d.ts +61 -6
- package/dist/javascript/preconditions.d.ts.map +1 -1
- package/dist/javascript/preconditions.js +70 -15
- package/dist/javascript/preconditions.js.map +1 -1
- package/dist/javascript/print.d.ts +1 -1
- 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/preconditions.d.ts +73 -3
- package/dist/preconditions.d.ts.map +1 -1
- package/dist/preconditions.js +172 -6
- package/dist/preconditions.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/request/prepare-recipe.d.ts +25 -1
- package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
- package/dist/rpc/request/prepare-recipe.js +38 -2
- package/dist/rpc/request/prepare-recipe.js.map +1 -1
- package/package.json +1 -1
- package/src/javascript/preconditions.ts +91 -19
- package/src/javascript/print.ts +10 -1
- package/src/preconditions.ts +190 -8
- package/src/rpc/request/prepare-recipe.ts +54 -4
package/src/preconditions.ts
CHANGED
|
@@ -18,8 +18,66 @@ import {Cursor, isSourceFile, SourceFile, Tree} from './tree';
|
|
|
18
18
|
import {Recipe} from "./recipe";
|
|
19
19
|
import {ExecutionContext} from "./execution";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Recipe-identity placeholder for use as a precondition.
|
|
23
|
+
*
|
|
24
|
+
* Captures a Java recipe class name + options without instantiating the
|
|
25
|
+
* recipe or firing an RPC. The framework introspects a
|
|
26
|
+
* ``check(RecipeRef, editor)`` wrapper at PrepareRecipe time and emits
|
|
27
|
+
* the recipe identity directly in
|
|
28
|
+
* ``PrepareRecipeResponse.editPreconditions``. The Java host's
|
|
29
|
+
* ``PreparedRecipeCache.instantiateVisitor`` constructs the recipe via
|
|
30
|
+
* Jackson and uses its visitor.
|
|
31
|
+
*
|
|
32
|
+
* This avoids requiring the recipe author to do an RPC at ``editor()``
|
|
33
|
+
* construction time, which would otherwise block in-process unit tests
|
|
34
|
+
* that don't have an active RPC connection.
|
|
35
|
+
*
|
|
36
|
+
* If ``localVisitor`` is provided, in-process callers (without an active
|
|
37
|
+
* RPC connection) evaluate the gate against that visitor instead of
|
|
38
|
+
* short-circuiting to "always matches". This preserves real filtering
|
|
39
|
+
* behavior in unit tests while still letting the host evaluate the gate
|
|
40
|
+
* over the wire when an RPC connection is present.
|
|
41
|
+
*
|
|
42
|
+
* Helpers in ``@openrewrite/rewrite/javascript/preconditions``
|
|
43
|
+
* (``usesMethod``, ``usesType``, ``hasSourcePath``, ``findMethods``,
|
|
44
|
+
* ``findTypes``) return ``RecipeRef`` instances populated with the
|
|
45
|
+
* matching native TS visitor where one exists.
|
|
46
|
+
*/
|
|
47
|
+
export class RecipeRef {
|
|
48
|
+
constructor(
|
|
49
|
+
readonly recipeName: string,
|
|
50
|
+
readonly options: Readonly<Record<string, any>> = {},
|
|
51
|
+
readonly localVisitor?: TreeVisitor<any, ExecutionContext>
|
|
52
|
+
) {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Composite of nested precondition operands joined by an operator.
|
|
58
|
+
*
|
|
59
|
+
* Mirrors Java's ``Preconditions.or``/``and``/``not``: a gate that
|
|
60
|
+
* short-circuits over its operands. ``op`` is one of ``"or"`` / ``"and"`` /
|
|
61
|
+
* ``"not"``. Operands may be ``TreeVisitor``, ``Recipe``, ``RecipeRef``,
|
|
62
|
+
* or another ``CompositePrecondition``.
|
|
63
|
+
*
|
|
64
|
+
* The framework promotes the composite to a structured wire entry at
|
|
65
|
+
* PrepareRecipe time; the Java host's ``RewriteRpc.matchAll`` rebuilds
|
|
66
|
+
* the visitor via the matching ``Preconditions`` factory so the gate runs
|
|
67
|
+
* locally and the visit RPC is skipped for files the gate rejects.
|
|
68
|
+
*/
|
|
69
|
+
export class CompositePrecondition {
|
|
70
|
+
constructor(
|
|
71
|
+
readonly op: "or" | "and" | "not",
|
|
72
|
+
readonly operands: ReadonlyArray<CheckArg>
|
|
73
|
+
) {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type CheckArg = Recipe | TreeVisitor<any, ExecutionContext> | RecipeRef | CompositePrecondition;
|
|
78
|
+
|
|
21
79
|
export async function check<T extends Tree>(
|
|
22
|
-
checkCondition:
|
|
80
|
+
checkCondition: CheckArg | Promise<CheckArg> | boolean,
|
|
23
81
|
v: TreeVisitor<T, ExecutionContext> | Promise<TreeVisitor<T, ExecutionContext>>
|
|
24
82
|
): Promise<TreeVisitor<T, ExecutionContext>> {
|
|
25
83
|
const resolvedCheck = await checkCondition;
|
|
@@ -31,15 +89,52 @@ export async function check<T extends Tree>(
|
|
|
31
89
|
return new Check(resolvedCheck, resolvedV);
|
|
32
90
|
}
|
|
33
91
|
|
|
92
|
+
/**
|
|
93
|
+
* OR-compose precondition checks. Mirrors Java's
|
|
94
|
+
* ``Preconditions.or(visitor...)``: the gate matches if any operand
|
|
95
|
+
* matches. Requires at least two operands; a single-operand OR has no
|
|
96
|
+
* value over a bare ``check``.
|
|
97
|
+
*/
|
|
98
|
+
export function or(...operands: CheckArg[]): CompositePrecondition {
|
|
99
|
+
if (operands.length < 2) {
|
|
100
|
+
throw new Error("Preconditions.or requires at least two operands");
|
|
101
|
+
}
|
|
102
|
+
return new CompositePrecondition("or", operands);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* AND-compose precondition checks. The outer ``editPreconditions`` list
|
|
107
|
+
* is already AND-composed by the host, so this is mainly useful as an
|
|
108
|
+
* operand of ``or``/``not``.
|
|
109
|
+
*/
|
|
110
|
+
export function and(...operands: CheckArg[]): CompositePrecondition {
|
|
111
|
+
if (operands.length < 2) {
|
|
112
|
+
throw new Error("Preconditions.and requires at least two operands");
|
|
113
|
+
}
|
|
114
|
+
return new CompositePrecondition("and", operands);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Negate a precondition check. Mirrors Java's ``Preconditions.not(visitor)``:
|
|
119
|
+
* the gate matches iff the operand does not.
|
|
120
|
+
*/
|
|
121
|
+
export function not(operand: CheckArg): CompositePrecondition {
|
|
122
|
+
return new CompositePrecondition("not", [operand]);
|
|
123
|
+
}
|
|
124
|
+
|
|
34
125
|
export class Check<T extends Tree> extends TreeVisitor<T, ExecutionContext> {
|
|
35
126
|
constructor(
|
|
36
|
-
readonly check:
|
|
127
|
+
readonly check: CheckArg,
|
|
37
128
|
readonly v: TreeVisitor<T, ExecutionContext>
|
|
38
129
|
) {
|
|
39
130
|
super();
|
|
40
131
|
}
|
|
41
132
|
|
|
42
133
|
async isAcceptable(sourceFile: SourceFile, ctx: ExecutionContext): Promise<boolean> {
|
|
134
|
+
if (this.check instanceof RecipeRef || this.check instanceof CompositePrecondition) {
|
|
135
|
+
// RecipeRef / Composite have no in-process is_acceptable — defer to the wrapped editor.
|
|
136
|
+
return this.v.isAcceptable(sourceFile, ctx);
|
|
137
|
+
}
|
|
43
138
|
return await (await this.checkVisitor()).isAcceptable(sourceFile, ctx) &&
|
|
44
139
|
await this.v.isAcceptable(sourceFile, ctx);
|
|
45
140
|
}
|
|
@@ -53,12 +148,31 @@ export class Check<T extends Tree> extends TreeVisitor<T, ExecutionContext> {
|
|
|
53
148
|
: this.v.visit<R>(tree, ctx);
|
|
54
149
|
}
|
|
55
150
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
151
|
+
// In-process fallback for a RecipeRef: if a localVisitor was
|
|
152
|
+
// provided, evaluate the gate against it (preserves real filtering
|
|
153
|
+
// for unit tests); otherwise treat as "always matches" so the
|
|
154
|
+
// wrapped editor still runs. Wire-side optimization (skip the
|
|
155
|
+
// visit RPC when the precondition rejects the file) lives in
|
|
156
|
+
// optimizePreconditions and is independent of localVisitor.
|
|
157
|
+
if (this.check instanceof RecipeRef) {
|
|
158
|
+
if (this.check.localVisitor !== undefined) {
|
|
159
|
+
const localResult = parent !== undefined
|
|
160
|
+
? await this.check.localVisitor.visit(tree, ctx, parent)
|
|
161
|
+
: await this.check.localVisitor.visit(tree, ctx);
|
|
162
|
+
if (localResult === (tree as unknown as T)) {
|
|
163
|
+
return tree as unknown as R;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return parent !== undefined
|
|
167
|
+
? this.v.visit<R>(tree, ctx, parent)
|
|
168
|
+
: this.v.visit<R>(tree, ctx);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const matched = this.check instanceof CompositePrecondition
|
|
172
|
+
? await evaluateComposite(this.check, tree, ctx, parent)
|
|
173
|
+
: await this.runLeafCheck(tree, ctx, parent);
|
|
59
174
|
|
|
60
|
-
|
|
61
|
-
if (checkResult !== (tree as unknown as T)) {
|
|
175
|
+
if (matched) {
|
|
62
176
|
return parent !== undefined
|
|
63
177
|
? this.v.visit<R>(tree, ctx, parent)
|
|
64
178
|
: this.v.visit<R>(tree, ctx);
|
|
@@ -67,7 +181,75 @@ export class Check<T extends Tree> extends TreeVisitor<T, ExecutionContext> {
|
|
|
67
181
|
return tree as unknown as R;
|
|
68
182
|
}
|
|
69
183
|
|
|
184
|
+
private async runLeafCheck(tree: Tree, ctx: ExecutionContext, parent?: Cursor): Promise<boolean> {
|
|
185
|
+
const checkResult = parent !== undefined
|
|
186
|
+
? await (await this.checkVisitor()).visit(tree, ctx, parent)
|
|
187
|
+
: await (await this.checkVisitor()).visit(tree, ctx);
|
|
188
|
+
return checkResult !== (tree as unknown as T);
|
|
189
|
+
}
|
|
190
|
+
|
|
70
191
|
private async checkVisitor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
71
|
-
return this.check instanceof Recipe ? this.check.editor() : this.check;
|
|
192
|
+
return this.check instanceof Recipe ? this.check.editor() : (this.check as TreeVisitor<any, ExecutionContext>);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Evaluate a {@link CompositePrecondition} in-process for unit tests and
|
|
198
|
+
* direct callers that don't have a live RPC. Mirrors Java's
|
|
199
|
+
* ``Preconditions.or``/``and``/``not`` semantics. Returns ``true`` iff the
|
|
200
|
+
* gate would let the wrapped visitor run.
|
|
201
|
+
*/
|
|
202
|
+
async function evaluateComposite(
|
|
203
|
+
composite: CompositePrecondition,
|
|
204
|
+
tree: Tree,
|
|
205
|
+
ctx: ExecutionContext,
|
|
206
|
+
parent?: Cursor
|
|
207
|
+
): Promise<boolean> {
|
|
208
|
+
const operands = composite.operands;
|
|
209
|
+
switch (composite.op) {
|
|
210
|
+
case "or":
|
|
211
|
+
for (const operand of operands) {
|
|
212
|
+
if (await operandMatches(operand, tree, ctx, parent)) return true;
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
case "and":
|
|
216
|
+
for (const operand of operands) {
|
|
217
|
+
if (!(await operandMatches(operand, tree, ctx, parent))) return false;
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
case "not":
|
|
221
|
+
if (operands.length !== 1) {
|
|
222
|
+
throw new Error("CompositePrecondition op=not requires exactly one operand");
|
|
223
|
+
}
|
|
224
|
+
return !(await operandMatches(operands[0], tree, ctx, parent));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function operandMatches(
|
|
229
|
+
operand: CheckArg,
|
|
230
|
+
tree: Tree,
|
|
231
|
+
ctx: ExecutionContext,
|
|
232
|
+
parent?: Cursor
|
|
233
|
+
): Promise<boolean> {
|
|
234
|
+
if (operand instanceof RecipeRef) {
|
|
235
|
+
// If a localVisitor was provided, evaluate against it for real;
|
|
236
|
+
// otherwise short-circuit to "always matches" so the wrapped
|
|
237
|
+
// editor still runs in unit tests. The host evaluates the gate
|
|
238
|
+
// for real once the response goes over the wire.
|
|
239
|
+
if (operand.localVisitor === undefined) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
const result = parent !== undefined
|
|
243
|
+
? await operand.localVisitor.visit(tree, ctx, parent)
|
|
244
|
+
: await operand.localVisitor.visit(tree, ctx);
|
|
245
|
+
return result !== tree;
|
|
246
|
+
}
|
|
247
|
+
if (operand instanceof CompositePrecondition) {
|
|
248
|
+
return evaluateComposite(operand, tree, ctx, parent);
|
|
72
249
|
}
|
|
250
|
+
const visitor = operand instanceof Recipe ? await operand.editor() : operand;
|
|
251
|
+
const result = parent !== undefined
|
|
252
|
+
? await visitor.visit(tree, ctx, parent)
|
|
253
|
+
: await visitor.visit(tree, ctx);
|
|
254
|
+
return result !== tree;
|
|
73
255
|
}
|
|
@@ -17,7 +17,7 @@ import * as rpc from "vscode-jsonrpc/node";
|
|
|
17
17
|
import {MessageConnection} from "vscode-jsonrpc/node";
|
|
18
18
|
import {Recipe, RecipeDescriptor, ScanningRecipe} from "../../recipe";
|
|
19
19
|
import {SnowflakeId} from "@akashrajpurohit/snowflake-id";
|
|
20
|
-
import {Check} from "../../preconditions";
|
|
20
|
+
import {Check, CheckArg, CompositePrecondition, RecipeRef} from "../../preconditions";
|
|
21
21
|
import {RpcRecipe} from "../recipe";
|
|
22
22
|
import {TreeVisitor} from "../../visitor";
|
|
23
23
|
import {ExecutionContext} from "../../execution";
|
|
@@ -109,8 +109,9 @@ export class PrepareRecipe {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
if (visitor! instanceof Check) {
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
const wireEntry = this.conditionWireEntry(visitor.check, phase);
|
|
113
|
+
if (wireEntry) {
|
|
114
|
+
preconditions.push(wireEntry);
|
|
114
115
|
recipe = Object.assign(
|
|
115
116
|
Object.create(Object.getPrototypeOf(recipe)),
|
|
116
117
|
recipe,
|
|
@@ -135,6 +136,42 @@ export class PrepareRecipe {
|
|
|
135
136
|
return recipe;
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Translate a precondition condition (operand) to a wire entry.
|
|
141
|
+
*
|
|
142
|
+
* Mirrors the Java {@code PrepareRecipeResponse.Precondition} schema:
|
|
143
|
+
* leaves carry {@code visitorName} (+ optional options); composites
|
|
144
|
+
* carry {@code op} ({@code "or"}/{@code "and"}/{@code "not"}) and a
|
|
145
|
+
* nested {@code operands} list. Returns {@code undefined} when the
|
|
146
|
+
* condition can't be serialized — the caller leaves the wrapper
|
|
147
|
+
* intact so the gate runs in-process as a fallback.
|
|
148
|
+
*/
|
|
149
|
+
private static conditionWireEntry(condition: CheckArg, phase: "edit" | "scan"): Precondition | undefined {
|
|
150
|
+
if (condition instanceof CompositePrecondition) {
|
|
151
|
+
const operands: Precondition[] = [];
|
|
152
|
+
for (const operand of condition.operands) {
|
|
153
|
+
const entry = this.conditionWireEntry(operand, phase);
|
|
154
|
+
if (entry === undefined) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
operands.push(entry);
|
|
158
|
+
}
|
|
159
|
+
return {op: condition.op, operands};
|
|
160
|
+
}
|
|
161
|
+
// Common case: helpers like usesMethod / usesType return a lightweight
|
|
162
|
+
// RecipeRef so the recipe author can declare a precondition without
|
|
163
|
+
// firing an RPC at editor() time. The Java host's
|
|
164
|
+
// PreparedRecipeCache.instantiateVisitor constructs the named recipe
|
|
165
|
+
// via Jackson and uses its visitor.
|
|
166
|
+
if (condition instanceof RecipeRef) {
|
|
167
|
+
return {visitorName: condition.recipeName, visitorOptions: {...condition.options}};
|
|
168
|
+
}
|
|
169
|
+
if (condition instanceof RpcRecipe) {
|
|
170
|
+
return {visitorName: phase === "edit" ? condition.editVisitor : condition.scanVisitor!};
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
138
175
|
private static async visitorTypePrecondition(preconditions: Precondition[], v: TreeVisitor<any, ExecutionContext>): Promise<Precondition[]> {
|
|
139
176
|
let treeType: string | undefined;
|
|
140
177
|
|
|
@@ -183,7 +220,20 @@ export interface PrepareRecipeResponse {
|
|
|
183
220
|
delegatesTo?: DelegatesTo
|
|
184
221
|
}
|
|
185
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Either a leaf (a single visitor identified by {@code visitorName} +
|
|
225
|
+
* optional {@code visitorOptions}) or a composite of nested preconditions
|
|
226
|
+
* joined by {@code op} ({@code "or"} / {@code "and"} / {@code "not"}).
|
|
227
|
+
*
|
|
228
|
+
* When {@code op} is undefined the entry is a leaf and {@code visitorName}
|
|
229
|
+
* is required; when {@code op} is set, {@code operands} carries the
|
|
230
|
+
* children and the visitor fields are ignored. The composite form mirrors
|
|
231
|
+
* Java's {@code Preconditions.or}/{@code and}/{@code not} so remote
|
|
232
|
+
* languages can express the same gate shapes the Java side does.
|
|
233
|
+
*/
|
|
186
234
|
export interface Precondition {
|
|
187
|
-
visitorName
|
|
235
|
+
visitorName?: string
|
|
188
236
|
visitorOptions?: {}
|
|
237
|
+
op?: "or" | "and" | "not"
|
|
238
|
+
operands?: Precondition[]
|
|
189
239
|
}
|