@openrewrite/rewrite 8.66.0 → 8.66.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/java/tree.d.ts +10 -1
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +21 -5
- package/dist/java/tree.js.map +1 -1
- package/dist/java/type-visitor.d.ts +1 -1
- package/dist/java/type-visitor.d.ts.map +1 -1
- package/dist/java/visitor.d.ts +2 -2
- package/dist/java/visitor.d.ts.map +1 -1
- package/dist/java/visitor.js +8 -2
- package/dist/java/visitor.js.map +1 -1
- package/dist/javascript/assertions.d.ts +6 -0
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +14 -6
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/comparator.d.ts +217 -7
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +1020 -2848
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format.d.ts +5 -3
- package/dist/javascript/format.d.ts.map +1 -1
- package/dist/javascript/format.js +87 -44
- package/dist/javascript/format.js.map +1 -1
- package/dist/javascript/index.d.ts +2 -1
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +2 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/parser.d.ts +2 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +54 -43
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/templating/capture.d.ts +293 -0
- package/dist/javascript/templating/capture.d.ts.map +1 -0
- package/dist/javascript/templating/capture.js +461 -0
- package/dist/javascript/templating/capture.js.map +1 -0
- package/dist/javascript/templating/comparator.d.ts +171 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -0
- package/dist/javascript/templating/comparator.js +1221 -0
- package/dist/javascript/templating/comparator.js.map +1 -0
- package/dist/javascript/templating/engine.d.ts +108 -0
- package/dist/javascript/templating/engine.d.ts.map +1 -0
- package/dist/javascript/templating/engine.js +661 -0
- package/dist/javascript/templating/engine.js.map +1 -0
- package/dist/javascript/templating/index.d.ts +6 -0
- package/dist/javascript/templating/index.d.ts.map +1 -0
- package/dist/javascript/templating/index.js +44 -0
- package/dist/javascript/templating/index.js.map +1 -0
- package/dist/javascript/templating/pattern.d.ts +276 -0
- package/dist/javascript/templating/pattern.d.ts.map +1 -0
- package/dist/javascript/templating/pattern.js +952 -0
- package/dist/javascript/templating/pattern.js.map +1 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts +83 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -0
- package/dist/javascript/templating/placeholder-replacement.js +467 -0
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -0
- package/dist/javascript/templating/rewrite.d.ts +84 -0
- package/dist/javascript/templating/rewrite.d.ts.map +1 -0
- package/dist/javascript/templating/rewrite.js +208 -0
- package/dist/javascript/templating/rewrite.js.map +1 -0
- package/dist/javascript/templating/template.d.ts +230 -0
- package/dist/javascript/templating/template.d.ts.map +1 -0
- package/dist/javascript/templating/template.js +367 -0
- package/dist/javascript/templating/template.js.map +1 -0
- package/dist/javascript/templating/types.d.ts +610 -0
- package/dist/javascript/templating/types.d.ts.map +1 -0
- package/dist/javascript/templating/types.js +3 -0
- package/dist/javascript/templating/types.js.map +1 -0
- package/dist/javascript/templating/utils.d.ts +135 -0
- package/dist/javascript/templating/utils.d.ts.map +1 -0
- package/dist/javascript/templating/utils.js +251 -0
- package/dist/javascript/templating/utils.js.map +1 -0
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +21 -11
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/json/rpc.js +2 -2
- package/dist/json/rpc.js.map +1 -1
- package/dist/recipe/order-imports.js.map +1 -1
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +10 -6
- package/dist/test/rewrite-test.js.map +1 -1
- package/dist/version.txt +1 -1
- package/dist/visitor.d.ts +4 -4
- package/dist/visitor.d.ts.map +1 -1
- package/dist/visitor.js +8 -3
- package/dist/visitor.js.map +1 -1
- package/package.json +5 -2
- package/src/java/tree.ts +10 -3
- package/src/java/type-visitor.ts +1 -1
- package/src/java/visitor.ts +11 -5
- package/src/javascript/assertions.ts +9 -3
- package/src/javascript/comparator.ts +1095 -3373
- package/src/javascript/format.ts +72 -33
- package/src/javascript/index.ts +2 -1
- package/src/javascript/parser.ts +67 -45
- package/src/javascript/templating/capture.ts +595 -0
- package/src/javascript/templating/comparator.ts +1383 -0
- package/src/javascript/templating/engine.ts +750 -0
- package/src/javascript/templating/index.ts +67 -0
- package/src/javascript/templating/pattern.ts +1101 -0
- package/src/javascript/templating/placeholder-replacement.ts +475 -0
- package/src/javascript/templating/rewrite.ts +229 -0
- package/src/javascript/templating/template.ts +414 -0
- package/src/javascript/templating/types.ts +674 -0
- package/src/javascript/templating/utils.ts +298 -0
- package/src/javascript/type-mapping.ts +20 -11
- package/src/json/rpc.ts +2 -2
- package/src/recipe/order-imports.ts +1 -1
- package/src/test/rewrite-test.ts +12 -7
- package/src/visitor.ts +14 -6
- package/dist/javascript/templating.d.ts +0 -265
- package/dist/javascript/templating.d.ts.map +0 -1
- package/dist/javascript/templating.js +0 -1027
- package/dist/javascript/templating.js.map +0 -1
- package/src/javascript/templating.ts +0 -1226
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 the original author or authors.
|
|
3
|
+
* <p>
|
|
4
|
+
* Licensed under the Moderne Source Available License (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
* <p>
|
|
8
|
+
* https://docs.moderne.io/licensing/moderne-source-available-license
|
|
9
|
+
* <p>
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import {Cursor} from '../..';
|
|
17
|
+
import {J} from '../../java';
|
|
18
|
+
import {JS} from '../index';
|
|
19
|
+
import {Marker, Markers} from '../../markers';
|
|
20
|
+
import {randomId} from '../../uuid';
|
|
21
|
+
import {ConstraintFunction, VariadicOptions} from './types';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Internal storage value type for pattern match captures.
|
|
25
|
+
* - J: Scalar captures without wrapper (fallback)
|
|
26
|
+
* - J.RightPadded<J>: Scalar captures with wrapper (preserves trailing markers like semicolons)
|
|
27
|
+
* - J[]: Variadic captures without wrapper metadata
|
|
28
|
+
* - J.RightPadded<J>[]: Variadic captures with wrapper metadata (preserves markers like commas)
|
|
29
|
+
*/
|
|
30
|
+
export type CaptureStorageValue = J | J.RightPadded<J> | J[] | J.RightPadded<J>[];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Symbol to access wrappersMap without exposing it as public API
|
|
34
|
+
*/
|
|
35
|
+
export const WRAPPERS_MAP_SYMBOL = Symbol('wrappersMap');
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Shared wrapper function name used by both patterns and templates.
|
|
39
|
+
* Using the same name allows cache sharing when pattern and template code is identical.
|
|
40
|
+
*/
|
|
41
|
+
export const WRAPPER_FUNCTION_NAME = '__WRAPPER__';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Simple LRU (Least Recently Used) cache implementation using Map's insertion order.
|
|
45
|
+
* JavaScript Map maintains insertion order, so the first entry is the oldest.
|
|
46
|
+
*
|
|
47
|
+
* Used by both Pattern and Template caching to provide bounded memory usage.
|
|
48
|
+
*/
|
|
49
|
+
export class LRUCache<K, V> {
|
|
50
|
+
private cache = new Map<K, V>();
|
|
51
|
+
|
|
52
|
+
constructor(private maxSize: number) {
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get(key: K): V | undefined {
|
|
56
|
+
const value = this.cache.get(key);
|
|
57
|
+
if (value !== undefined) {
|
|
58
|
+
// Move to end (most recently used)
|
|
59
|
+
this.cache.delete(key);
|
|
60
|
+
this.cache.set(key, value);
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
set(key: K, value: V): void {
|
|
66
|
+
// Remove if exists (to update position)
|
|
67
|
+
this.cache.delete(key);
|
|
68
|
+
|
|
69
|
+
// Add to end
|
|
70
|
+
this.cache.set(key, value);
|
|
71
|
+
|
|
72
|
+
// Evict oldest if over capacity
|
|
73
|
+
if (this.cache.size > this.maxSize) {
|
|
74
|
+
const iterator = this.cache.keys();
|
|
75
|
+
const firstEntry = iterator.next();
|
|
76
|
+
if (!firstEntry.done) {
|
|
77
|
+
this.cache.delete(firstEntry.value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
clear(): void {
|
|
83
|
+
this.cache.clear();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Shared global LRU cache for both pattern and template ASTs.
|
|
89
|
+
* When pattern and template code is identical, they share the same cached AST.
|
|
90
|
+
* This mirrors JavaTemplate's unified approach in the Java implementation.
|
|
91
|
+
* Bounded to 100 entries using LRU eviction.
|
|
92
|
+
*/
|
|
93
|
+
export const globalAstCache = new LRUCache<string, J>(100);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generates a cache key for template/pattern processing.
|
|
97
|
+
* Used by both Pattern and Template for consistent cache key generation.
|
|
98
|
+
*
|
|
99
|
+
* @param templateParts The template string parts
|
|
100
|
+
* @param itemsKey String representing the captures/parameters (comma-separated)
|
|
101
|
+
* @param contextStatements Context declarations
|
|
102
|
+
* @param dependencies NPM dependencies
|
|
103
|
+
* @returns A cache key string
|
|
104
|
+
*/
|
|
105
|
+
export function generateCacheKey(
|
|
106
|
+
templateParts: string[] | TemplateStringsArray,
|
|
107
|
+
itemsKey: string,
|
|
108
|
+
contextStatements: string[],
|
|
109
|
+
dependencies: Record<string, string>
|
|
110
|
+
): string {
|
|
111
|
+
return [
|
|
112
|
+
Array.from(templateParts).join('|'),
|
|
113
|
+
itemsKey,
|
|
114
|
+
contextStatements.join(';'),
|
|
115
|
+
JSON.stringify(dependencies)
|
|
116
|
+
].join('::');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Marker that stores capture metadata on pattern AST nodes.
|
|
121
|
+
* This avoids the need to parse capture names from identifiers during matching.
|
|
122
|
+
*/
|
|
123
|
+
export class CaptureMarker implements Marker {
|
|
124
|
+
readonly kind = 'org.openrewrite.javascript.CaptureMarker';
|
|
125
|
+
readonly id = randomId();
|
|
126
|
+
|
|
127
|
+
constructor(
|
|
128
|
+
public readonly captureName: string,
|
|
129
|
+
public readonly variadicOptions?: VariadicOptions,
|
|
130
|
+
public readonly constraint?: ConstraintFunction<any>
|
|
131
|
+
) {
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Utility class for managing placeholder naming and parsing.
|
|
137
|
+
* Centralizes all logic related to capture placeholders.
|
|
138
|
+
*/
|
|
139
|
+
export class PlaceholderUtils {
|
|
140
|
+
static readonly CAPTURE_PREFIX = '__capt_';
|
|
141
|
+
static readonly PLACEHOLDER_PREFIX = '__PLACEHOLDER_';
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Checks if a node is a capture placeholder.
|
|
145
|
+
*
|
|
146
|
+
* @param node The node to check
|
|
147
|
+
* @returns true if the node is a capture placeholder, false otherwise
|
|
148
|
+
*/
|
|
149
|
+
static isCapture(node: J): boolean {
|
|
150
|
+
// Check for CaptureMarker first (efficient)
|
|
151
|
+
for (const marker of node.markers.markers) {
|
|
152
|
+
if (marker instanceof CaptureMarker) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Gets the CaptureMarker from a node, if present.
|
|
161
|
+
*
|
|
162
|
+
* @param node The node to check
|
|
163
|
+
* @returns The CaptureMarker or undefined
|
|
164
|
+
*/
|
|
165
|
+
static getCaptureMarker(node: { markers: Markers }): CaptureMarker | undefined {
|
|
166
|
+
for (const marker of node.markers.markers) {
|
|
167
|
+
if (marker instanceof CaptureMarker) {
|
|
168
|
+
return marker;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Parses a capture placeholder to extract name and type constraint.
|
|
176
|
+
*
|
|
177
|
+
* @param identifier The identifier string to parse
|
|
178
|
+
* @returns Object with name and optional type constraint, or null if not a valid capture
|
|
179
|
+
*/
|
|
180
|
+
static parseCapture(identifier: string): { name: string; typeConstraint?: string } | null {
|
|
181
|
+
if (!identifier.startsWith(this.CAPTURE_PREFIX)) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Handle unnamed captures: "__capt_unnamed_N__"
|
|
186
|
+
if (identifier.startsWith(`${this.CAPTURE_PREFIX}unnamed_`)) {
|
|
187
|
+
const match = identifier.match(/__capt_(unnamed_\d+)__/);
|
|
188
|
+
return match ? {name: match[1]} : null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Handle all other captures (including any()): "__capt_name__" or "__capt_name_type__"
|
|
192
|
+
const match = identifier.match(/__capt_([^_]+(?:_\d+)?)(?:_([^_]+))?__/);
|
|
193
|
+
if (!match) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
name: match[1],
|
|
199
|
+
typeConstraint: match[2]
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Creates a capture placeholder string.
|
|
205
|
+
*
|
|
206
|
+
* @param name The capture name
|
|
207
|
+
* @param typeConstraint Optional type constraint
|
|
208
|
+
* @returns The formatted placeholder string
|
|
209
|
+
*/
|
|
210
|
+
static createCapture(name: string, typeConstraint?: string): string {
|
|
211
|
+
// Always use CAPTURE_PREFIX - the capturing flag is used internally for binding behavior
|
|
212
|
+
return typeConstraint
|
|
213
|
+
? `${this.CAPTURE_PREFIX}${name}_${typeConstraint}__`
|
|
214
|
+
: `${this.CAPTURE_PREFIX}${name}__`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Checks if a capture marker indicates a variadic capture.
|
|
219
|
+
*
|
|
220
|
+
* @param node The node to check
|
|
221
|
+
* @returns true if the node has a variadic CaptureMarker, false otherwise
|
|
222
|
+
*/
|
|
223
|
+
static isVariadicCapture(node: { markers: Markers }): boolean {
|
|
224
|
+
for (const marker of node.markers.markers) {
|
|
225
|
+
if (marker instanceof CaptureMarker && marker.variadicOptions) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Gets the variadic options from a capture marker.
|
|
234
|
+
*
|
|
235
|
+
* @param node The node to extract variadic options from
|
|
236
|
+
* @returns The VariadicOptions, or undefined if not a variadic capture
|
|
237
|
+
*/
|
|
238
|
+
static getVariadicOptions(node: { markers: Markers }): VariadicOptions | undefined {
|
|
239
|
+
for (const marker of node.markers.markers) {
|
|
240
|
+
if (marker instanceof CaptureMarker) {
|
|
241
|
+
return marker.variadicOptions;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Extracts the relevant AST node from a wrapper function.
|
|
249
|
+
* Used by both pattern and template processors to intelligently extract
|
|
250
|
+
* code from `function __WRAPPER__() { code }` wrappers.
|
|
251
|
+
*
|
|
252
|
+
* @param lastStatement The last statement from the compilation unit
|
|
253
|
+
* @param contextName Context name for error messages (e.g., 'Pattern', 'Template')
|
|
254
|
+
* @returns The extracted AST node
|
|
255
|
+
*/
|
|
256
|
+
static extractFromWrapper(lastStatement: J, contextName: string): J {
|
|
257
|
+
let extracted: J;
|
|
258
|
+
|
|
259
|
+
// Since we always wrap in function __WRAPPER__() { code }, look for it
|
|
260
|
+
if (lastStatement.kind === J.Kind.MethodDeclaration) {
|
|
261
|
+
const method = lastStatement as J.MethodDeclaration;
|
|
262
|
+
if (method.name?.simpleName === WRAPPER_FUNCTION_NAME && method.body) {
|
|
263
|
+
const body = method.body;
|
|
264
|
+
|
|
265
|
+
// Intelligently extract based on what's in the function body
|
|
266
|
+
if (body.statements.length === 0) {
|
|
267
|
+
throw new Error(`${contextName} function body is empty`);
|
|
268
|
+
} else if (body.statements.length === 1) {
|
|
269
|
+
const stmt = body.statements[0].element;
|
|
270
|
+
|
|
271
|
+
// Single expression statement → extract the expression
|
|
272
|
+
if (stmt.kind === JS.Kind.ExpressionStatement) {
|
|
273
|
+
extracted = (stmt as JS.ExpressionStatement).expression;
|
|
274
|
+
}
|
|
275
|
+
// Single block statement → keep the block
|
|
276
|
+
else if (stmt.kind === J.Kind.Block) {
|
|
277
|
+
extracted = stmt;
|
|
278
|
+
}
|
|
279
|
+
// Other single statement → keep it
|
|
280
|
+
else {
|
|
281
|
+
extracted = stmt;
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
// Multiple statements → keep the block
|
|
285
|
+
extracted = body;
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
// Not our wrapper function
|
|
289
|
+
extracted = lastStatement;
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
// Shouldn't happen with our wrapping strategy, but handle it
|
|
293
|
+
extracted = lastStatement;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return extracted;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -402,19 +402,28 @@ export class JavaScriptTypeMapping {
|
|
|
402
402
|
// If getAliasedSymbol returns something different, it's an import
|
|
403
403
|
if (aliasedSymbol && aliasedSymbol !== symbol) {
|
|
404
404
|
// This is definitely an imported symbol
|
|
405
|
-
|
|
406
|
-
if (symbol.declarations && symbol.declarations.length > 0) {
|
|
407
|
-
let importNode: ts.Node = symbol.declarations[0];
|
|
405
|
+
const aliasedParentSymbol = (aliasedSymbol as any).parent as ts.Symbol | undefined;
|
|
408
406
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
407
|
+
if (aliasedParentSymbol && aliasedParentSymbol.declarations?.[0] &&
|
|
408
|
+
ts.isModuleDeclaration(aliasedParentSymbol.declarations[0]) &&
|
|
409
|
+
ts.isIdentifier(aliasedParentSymbol.declarations[0].name)) {
|
|
410
|
+
// For namespace imports, use the namespace symbol's `name` as the module specifier (e.g. `React` instead of `react`)
|
|
411
|
+
moduleSpecifier = aliasedParentSymbol.name;
|
|
412
|
+
} else {
|
|
413
|
+
// Now find the import declaration to get the module specifier
|
|
414
|
+
if (symbol.declarations && symbol.declarations.length > 0) {
|
|
415
|
+
let importNode: ts.Node = symbol.declarations[0];
|
|
413
416
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
417
|
+
// Traverse up to find the ImportDeclaration
|
|
418
|
+
while (importNode && !ts.isImportDeclaration(importNode)) {
|
|
419
|
+
importNode = importNode.parent;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (importNode && ts.isImportDeclaration(importNode)) {
|
|
423
|
+
const importDeclNode = importNode as ts.ImportDeclaration;
|
|
424
|
+
if (ts.isStringLiteral(importDeclNode.moduleSpecifier)) {
|
|
425
|
+
moduleSpecifier = importDeclNode.moduleSpecifier.text;
|
|
426
|
+
}
|
|
418
427
|
}
|
|
419
428
|
}
|
|
420
429
|
}
|
package/src/json/rpc.ts
CHANGED
|
@@ -158,7 +158,7 @@ class JsonReceiver extends JsonVisitor<RpcReceiveQueue> {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
public async visitSpace(space: Json.Space, q: RpcReceiveQueue): Promise<Json.Space> {
|
|
161
|
-
return produceAsync<Json.Space>(space, async draft => {
|
|
161
|
+
return (await produceAsync<Json.Space>(space, async draft => {
|
|
162
162
|
draft.comments = await q.receiveListDefined(space.comments, async c => {
|
|
163
163
|
return await produceAsync(c, async draft => {
|
|
164
164
|
draft.multiline = await q.receive(c.multiline);
|
|
@@ -168,7 +168,7 @@ class JsonReceiver extends JsonVisitor<RpcReceiveQueue> {
|
|
|
168
168
|
})
|
|
169
169
|
});
|
|
170
170
|
draft.whitespace = await q.receive(space.whitespace);
|
|
171
|
-
})
|
|
171
|
+
}))!;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
public async visitRightPadded<T extends Json>(right: Json.RightPadded<T>, p: RpcReceiveQueue): Promise<Json.RightPadded<T> | undefined> {
|
|
@@ -63,7 +63,7 @@ export class OrderImports extends Recipe {
|
|
|
63
63
|
const cuWithImportsSorted = await produceAsync(cu, async draft => {
|
|
64
64
|
draft.statements = [...sortedImports, ...restStatements];
|
|
65
65
|
});
|
|
66
|
-
return produce(cuWithImportsSorted
|
|
66
|
+
return produce(cuWithImportsSorted!, draft => {
|
|
67
67
|
for (let i = 0; i < importCount; i++) {
|
|
68
68
|
draft.statements[i].element.prefix.whitespace = i > 0 ? "\n" : "";
|
|
69
69
|
}
|
package/src/test/rewrite-test.ts
CHANGED
|
@@ -175,7 +175,7 @@ export class RecipeSpec {
|
|
|
175
175
|
(spec.after as (actual: string) => string)(actualAfter) : spec.after as string;
|
|
176
176
|
expect(actualAfter).toEqual(afterSource);
|
|
177
177
|
if (spec.afterRecipe) {
|
|
178
|
-
await spec.afterRecipe(
|
|
178
|
+
await spec.afterRecipe(after);
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -279,9 +279,14 @@ function dedent(s: string): string {
|
|
|
279
279
|
const str = start > 0 || end < s.length ? s.slice(start, end) : s;
|
|
280
280
|
const lines = str.split('\n');
|
|
281
281
|
|
|
282
|
-
//
|
|
282
|
+
// If we removed a leading newline, consider all lines for minIndent
|
|
283
|
+
// Otherwise, skip the first line (it's on the same line as the opening quote)
|
|
284
|
+
const startLine = start > 0 ? 0 : 1;
|
|
285
|
+
|
|
286
|
+
// Find minimum indentation
|
|
283
287
|
let minIndent = Infinity;
|
|
284
|
-
for (
|
|
288
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
289
|
+
const line = lines[i];
|
|
285
290
|
let indent = 0;
|
|
286
291
|
for (let j = 0; j < line.length; j++) {
|
|
287
292
|
const ch = line.charCodeAt(j);
|
|
@@ -297,12 +302,12 @@ function dedent(s: string): string {
|
|
|
297
302
|
|
|
298
303
|
// If all lines are empty or no indentation
|
|
299
304
|
if (minIndent === Infinity || minIndent === 0) {
|
|
300
|
-
return lines.
|
|
305
|
+
return lines.join('\n');
|
|
301
306
|
}
|
|
302
307
|
|
|
303
|
-
// Remove common indentation from
|
|
304
|
-
return lines.map(line =>
|
|
305
|
-
line.length >= minIndent ? line.slice(minIndent) : ''
|
|
308
|
+
// Remove common indentation from lines (skip first line only if we didn't remove leading newline)
|
|
309
|
+
return lines.map((line, i) =>
|
|
310
|
+
(i === 0 && startLine === 1) ? line : (line.length >= minIndent ? line.slice(minIndent) : '')
|
|
306
311
|
).join('\n');
|
|
307
312
|
}
|
|
308
313
|
|
package/src/visitor.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import {emptyMarkers, Marker, Markers} from "./markers";
|
|
17
17
|
import {Cursor, isSourceFile, rootCursor, SourceFile, Tree} from "./tree";
|
|
18
|
-
import {createDraft, Draft, finishDraft, Objectish} from "immer";
|
|
18
|
+
import {createDraft, Draft, finishDraft, nothing, Objectish} from "immer";
|
|
19
19
|
import {mapAsync} from "./util";
|
|
20
20
|
|
|
21
21
|
/* Not exported beyond the internal immer module */
|
|
@@ -23,15 +23,23 @@ export type ValidImmerRecipeReturnType<State> =
|
|
|
23
23
|
| State
|
|
24
24
|
| void
|
|
25
25
|
| undefined
|
|
26
|
+
| typeof nothing
|
|
26
27
|
|
|
27
28
|
export async function produceAsync<Base extends Objectish>(
|
|
28
29
|
before: Promise<Base> | Base,
|
|
29
30
|
recipe: (draft: Draft<Base>) => ValidImmerRecipeReturnType<Draft<Base>> |
|
|
30
31
|
PromiseLike<ValidImmerRecipeReturnType<Draft<Base>>>
|
|
31
|
-
): Promise<Base> {
|
|
32
|
+
): Promise<Base | undefined> {
|
|
32
33
|
const b: Base = await before;
|
|
33
34
|
const draft = createDraft(b);
|
|
34
|
-
await recipe(draft);
|
|
35
|
+
const result = await recipe(draft);
|
|
36
|
+
|
|
37
|
+
// If recipe explicitly returned Immer's nothing, return undefined
|
|
38
|
+
if (result === nothing) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Otherwise, return the finished draft (void/undefined means use draft)
|
|
35
43
|
return finishDraft(draft) as Base;
|
|
36
44
|
}
|
|
37
45
|
|
|
@@ -128,9 +136,9 @@ export abstract class TreeVisitor<T extends Tree, P> {
|
|
|
128
136
|
} else if ((markers.markers?.length || 0) === 0) {
|
|
129
137
|
return markers;
|
|
130
138
|
}
|
|
131
|
-
return produceAsync<Markers>(markers, async (draft) => {
|
|
139
|
+
return (await produceAsync<Markers>(markers, async (draft) => {
|
|
132
140
|
draft.markers = await mapAsync(markers.markers, m => this.visitMarker(m, p))
|
|
133
|
-
})
|
|
141
|
+
}))!;
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
protected async visitMarker<M extends Marker>(marker: M, p: P): Promise<M> {
|
|
@@ -143,7 +151,7 @@ export abstract class TreeVisitor<T extends Tree, P> {
|
|
|
143
151
|
recipe?:
|
|
144
152
|
((draft: Draft<T>) => ValidImmerRecipeReturnType<Draft<T>>) |
|
|
145
153
|
((draft: Draft<T>) => Promise<ValidImmerRecipeReturnType<Draft<T>>>)
|
|
146
|
-
): Promise<T> {
|
|
154
|
+
): Promise<T | undefined> {
|
|
147
155
|
return produceAsync(before, async draft => {
|
|
148
156
|
draft.markers = await this.visitMarkers(before.markers, p);
|
|
149
157
|
if (recipe) {
|