@openrewrite/rewrite 8.69.0-20251212-132620 → 8.69.0-20251212-150738
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/index.d.ts +1 -0
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +1 -0
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/tree-debug.d.ts +140 -0
- package/dist/javascript/tree-debug.d.ts.map +1 -0
- package/dist/javascript/tree-debug.js +892 -0
- package/dist/javascript/tree-debug.js.map +1 -0
- package/dist/version.txt +1 -1
- package/package.json +1 -1
- package/src/javascript/index.ts +1 -0
- package/src/javascript/tree-debug.ts +937 -0
|
@@ -0,0 +1,937 @@
|
|
|
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
|
+
|
|
17
|
+
import {Cursor, Tree} from "../tree";
|
|
18
|
+
import {Comment, isIdentifier, isJava, isLiteral, J, TextComment} from "../java";
|
|
19
|
+
import {JS} from "./tree";
|
|
20
|
+
import {JavaScriptVisitor} from "./visitor";
|
|
21
|
+
import * as fs from "fs";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Options for controlling LST debug output.
|
|
25
|
+
*/
|
|
26
|
+
export interface LstDebugOptions {
|
|
27
|
+
/** Include cursor messages in output. Default: true */
|
|
28
|
+
includeCursorMessages?: boolean;
|
|
29
|
+
/** Include markers in output. Default: false */
|
|
30
|
+
includeMarkers?: boolean;
|
|
31
|
+
/** Include IDs in output. Default: false */
|
|
32
|
+
includeIds?: boolean;
|
|
33
|
+
/** Maximum depth to traverse. Default: unlimited (-1) */
|
|
34
|
+
maxDepth?: number;
|
|
35
|
+
/** Properties to always exclude (in addition to defaults). */
|
|
36
|
+
excludeProperties?: string[];
|
|
37
|
+
/** Output destination: 'console' or a file path. Default: 'console' */
|
|
38
|
+
output?: 'console' | string;
|
|
39
|
+
/** Indent string for nested output. Default: ' ' */
|
|
40
|
+
indent?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const DEFAULT_OPTIONS: Required<LstDebugOptions> = {
|
|
44
|
+
includeCursorMessages: true,
|
|
45
|
+
includeMarkers: false,
|
|
46
|
+
includeIds: false,
|
|
47
|
+
maxDepth: -1,
|
|
48
|
+
excludeProperties: [],
|
|
49
|
+
output: 'console',
|
|
50
|
+
indent: ' ',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Properties to always exclude from debug output (noisy/large).
|
|
55
|
+
*/
|
|
56
|
+
const EXCLUDED_PROPERTIES = new Set([
|
|
57
|
+
'type', // JavaType - very verbose
|
|
58
|
+
'methodType', // JavaType.Method
|
|
59
|
+
'variableType', // JavaType.Variable
|
|
60
|
+
'fieldType', // JavaType.Variable
|
|
61
|
+
'constructorType', // JavaType.Method
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Subscript digits for counts.
|
|
66
|
+
*/
|
|
67
|
+
const SUBSCRIPTS = ['', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Format a count as subscript. Count of 1 is implicit (empty string).
|
|
71
|
+
*/
|
|
72
|
+
function subscript(count: number): string {
|
|
73
|
+
if (count <= 1) return '';
|
|
74
|
+
if (count < 10) return SUBSCRIPTS[count];
|
|
75
|
+
// For counts >= 10, build from individual digits
|
|
76
|
+
return String(count).split('').map(d => SUBSCRIPTS[parseInt(d)]).join('');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Format whitespace string for readable debug output.
|
|
81
|
+
* Uses compact notation with subscript counts:
|
|
82
|
+
* - '\n' = 1 newline (implicit ₁)
|
|
83
|
+
* - '\n₂' = 2 newlines
|
|
84
|
+
* - '·₄' = 4 spaces
|
|
85
|
+
* - '-₂' = 2 tabs
|
|
86
|
+
* - '\n·₄' = newline + 4 spaces
|
|
87
|
+
* - '\n·₄-₂' = newline + 4 spaces + 2 tabs
|
|
88
|
+
*/
|
|
89
|
+
export function formatWhitespace(whitespace: string | undefined): string {
|
|
90
|
+
if (whitespace === undefined || whitespace === '') {
|
|
91
|
+
return "''";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let result = '';
|
|
95
|
+
let newlineCount = 0;
|
|
96
|
+
let spaceCount = 0;
|
|
97
|
+
let tabCount = 0;
|
|
98
|
+
|
|
99
|
+
const flushCounts = () => {
|
|
100
|
+
if (newlineCount > 0) {
|
|
101
|
+
result += '\\n' + subscript(newlineCount);
|
|
102
|
+
newlineCount = 0;
|
|
103
|
+
}
|
|
104
|
+
if (spaceCount > 0) {
|
|
105
|
+
result += '·' + subscript(spaceCount);
|
|
106
|
+
spaceCount = 0;
|
|
107
|
+
}
|
|
108
|
+
if (tabCount > 0) {
|
|
109
|
+
result += '-' + subscript(tabCount);
|
|
110
|
+
tabCount = 0;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < whitespace.length; i++) {
|
|
115
|
+
const c = whitespace[i];
|
|
116
|
+
if (c === '\n') {
|
|
117
|
+
// Flush spaces/tabs before starting newline count
|
|
118
|
+
if (spaceCount > 0 || tabCount > 0) {
|
|
119
|
+
flushCounts();
|
|
120
|
+
}
|
|
121
|
+
newlineCount++;
|
|
122
|
+
} else if (c === '\r') {
|
|
123
|
+
flushCounts();
|
|
124
|
+
result += '\\r';
|
|
125
|
+
} else if (c === ' ') {
|
|
126
|
+
// Flush newlines and tabs before counting spaces
|
|
127
|
+
if (newlineCount > 0) {
|
|
128
|
+
result += '\\n' + subscript(newlineCount);
|
|
129
|
+
newlineCount = 0;
|
|
130
|
+
}
|
|
131
|
+
if (tabCount > 0) {
|
|
132
|
+
result += '-' + subscript(tabCount);
|
|
133
|
+
tabCount = 0;
|
|
134
|
+
}
|
|
135
|
+
spaceCount++;
|
|
136
|
+
} else if (c === '\t') {
|
|
137
|
+
// Flush newlines and spaces before counting tabs
|
|
138
|
+
if (newlineCount > 0) {
|
|
139
|
+
result += '\\n' + subscript(newlineCount);
|
|
140
|
+
newlineCount = 0;
|
|
141
|
+
}
|
|
142
|
+
if (spaceCount > 0) {
|
|
143
|
+
result += '·' + subscript(spaceCount);
|
|
144
|
+
spaceCount = 0;
|
|
145
|
+
}
|
|
146
|
+
tabCount++;
|
|
147
|
+
} else {
|
|
148
|
+
flushCounts();
|
|
149
|
+
// Unexpected character (probably a bug in the parser)
|
|
150
|
+
result += c;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
flushCounts();
|
|
155
|
+
|
|
156
|
+
return `'${result}'`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Format a single comment for debug output.
|
|
161
|
+
*/
|
|
162
|
+
function formatComment(comment: Comment): string {
|
|
163
|
+
const textComment = comment as TextComment;
|
|
164
|
+
const text = textComment.text ?? '';
|
|
165
|
+
// Truncate long comments
|
|
166
|
+
const truncated = text.length > 20 ? text.substring(0, 17) + '...' : text;
|
|
167
|
+
|
|
168
|
+
if (textComment.multiline) {
|
|
169
|
+
return `/*${truncated}*/`;
|
|
170
|
+
} else {
|
|
171
|
+
return `//${truncated}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Format a J.Space for debug output.
|
|
177
|
+
*
|
|
178
|
+
* Compact format:
|
|
179
|
+
* - Empty space: `''`
|
|
180
|
+
* - Whitespace only: `'\n·₄'`
|
|
181
|
+
* - Comment only: `//comment`
|
|
182
|
+
* - Comment with suffix: `//comment'\n'`
|
|
183
|
+
* - Multiple comments: `//c1'\n' + //c2'\n·₄'`
|
|
184
|
+
*/
|
|
185
|
+
export function formatSpace(space: J.Space | undefined): string {
|
|
186
|
+
if (space === undefined) {
|
|
187
|
+
return '<undefined>';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const hasComments = space.comments.length > 0;
|
|
191
|
+
const hasWhitespace = space.whitespace !== undefined && space.whitespace !== '';
|
|
192
|
+
|
|
193
|
+
// Completely empty
|
|
194
|
+
if (!hasComments && !hasWhitespace) {
|
|
195
|
+
return "''";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Whitespace only (common case) - just show the formatted whitespace
|
|
199
|
+
if (!hasComments) {
|
|
200
|
+
return formatWhitespace(space.whitespace);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Format comments with their suffixes
|
|
204
|
+
const parts: string[] = [];
|
|
205
|
+
for (const comment of space.comments) {
|
|
206
|
+
let part = formatComment(comment);
|
|
207
|
+
// Add suffix if present
|
|
208
|
+
if (comment.suffix && comment.suffix !== '') {
|
|
209
|
+
part += formatWhitespace(comment.suffix);
|
|
210
|
+
}
|
|
211
|
+
parts.push(part);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Add trailing whitespace if present
|
|
215
|
+
if (hasWhitespace) {
|
|
216
|
+
parts.push(formatWhitespace(space.whitespace));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return parts.join(' + ');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Format cursor messages for debug output.
|
|
224
|
+
* Returns '<no messages>' if no messages, otherwise returns '⟨key=value, ...⟩'
|
|
225
|
+
*/
|
|
226
|
+
export function formatCursorMessages(cursor: Cursor | undefined): string {
|
|
227
|
+
if (!cursor || cursor.messages.size === 0) {
|
|
228
|
+
return '<no messages>';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const entries: string[] = [];
|
|
232
|
+
cursor.messages.forEach((value, key) => {
|
|
233
|
+
let valueStr: string;
|
|
234
|
+
if (Array.isArray(value)) {
|
|
235
|
+
valueStr = `[${value.map(v => JSON.stringify(v)).join(', ')}]`;
|
|
236
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
237
|
+
valueStr = JSON.stringify(value);
|
|
238
|
+
} else {
|
|
239
|
+
valueStr = String(value);
|
|
240
|
+
}
|
|
241
|
+
const keyStr = typeof key === 'symbol' ? key.toString() : String(key);
|
|
242
|
+
entries.push(`${keyStr}=${valueStr}`);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return `⟨${entries.join(', ')}⟩`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get a short type name from a kind string.
|
|
250
|
+
*/
|
|
251
|
+
function shortTypeName(kind: string | undefined): string {
|
|
252
|
+
if (!kind) return 'Unknown';
|
|
253
|
+
// Extract the last part after the last dot
|
|
254
|
+
const lastDot = kind.lastIndexOf('.');
|
|
255
|
+
return lastDot >= 0 ? kind.substring(lastDot + 1) : kind;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Find which property of the parent contains the given child element.
|
|
260
|
+
* Returns the property name, or property name with array index if in an array.
|
|
261
|
+
* Returns undefined if the relationship cannot be determined.
|
|
262
|
+
*
|
|
263
|
+
* @param cursor - The cursor at the current position
|
|
264
|
+
* @param child - Optional: the actual child node being visited (for RightPadded/LeftPadded/Container visits where cursor.value is the parent)
|
|
265
|
+
*/
|
|
266
|
+
export function findPropertyPath(cursor: Cursor | undefined, child?: any): string | undefined {
|
|
267
|
+
if (!cursor) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// If child is provided, use cursor.value as parent; otherwise use cursor.parent.value
|
|
272
|
+
const actualChild = child ?? cursor.value;
|
|
273
|
+
const parent = child ? cursor.value : cursor.parent?.value;
|
|
274
|
+
|
|
275
|
+
if (!parent || typeof parent !== 'object') {
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Properties to skip when searching
|
|
280
|
+
const skipProps = new Set(['kind', 'id', 'prefix', 'markers', 'type', 'methodType', 'variableType', 'fieldType', 'constructorType']);
|
|
281
|
+
|
|
282
|
+
// Special case: if parent is a Container, we need to look at grandparent to find the property name
|
|
283
|
+
if ((parent as any).kind === J.Kind.Container) {
|
|
284
|
+
const container = parent as J.Container<any>;
|
|
285
|
+
const grandparent = child ? cursor.parent?.value : cursor.parent?.parent?.value;
|
|
286
|
+
|
|
287
|
+
// Find the index of actualChild in container.elements
|
|
288
|
+
let childIndex = -1;
|
|
289
|
+
if (container.elements) {
|
|
290
|
+
for (let i = 0; i < container.elements.length; i++) {
|
|
291
|
+
if (container.elements[i] === actualChild) {
|
|
292
|
+
childIndex = i;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Find which property of grandparent holds this container
|
|
299
|
+
if (grandparent && typeof grandparent === 'object') {
|
|
300
|
+
for (const [key, value] of Object.entries(grandparent)) {
|
|
301
|
+
if (skipProps.has(key)) continue;
|
|
302
|
+
if (value === parent) {
|
|
303
|
+
if (childIndex >= 0) {
|
|
304
|
+
return `${key}[${childIndex}]`;
|
|
305
|
+
}
|
|
306
|
+
return key;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Fallback: just show the index
|
|
312
|
+
if (childIndex >= 0) {
|
|
313
|
+
return `[${childIndex}]`;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
for (const [key, value] of Object.entries(parent)) {
|
|
318
|
+
if (skipProps.has(key)) continue;
|
|
319
|
+
|
|
320
|
+
// Direct match
|
|
321
|
+
if (value === actualChild) {
|
|
322
|
+
return key;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Check if child is in an array
|
|
326
|
+
if (Array.isArray(value)) {
|
|
327
|
+
for (let i = 0; i < value.length; i++) {
|
|
328
|
+
if (value[i] === actualChild) {
|
|
329
|
+
return `${key}[${i}]`;
|
|
330
|
+
}
|
|
331
|
+
// Check inside RightPadded/LeftPadded wrappers
|
|
332
|
+
if (value[i] && typeof value[i] === 'object') {
|
|
333
|
+
if (value[i].element === actualChild) {
|
|
334
|
+
return `${key}[${i}].element`;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Check inside Container
|
|
341
|
+
if (value && typeof value === 'object' && (value as any).kind === J.Kind.Container) {
|
|
342
|
+
const container = value as J.Container<any>;
|
|
343
|
+
if (container.elements) {
|
|
344
|
+
for (let i = 0; i < container.elements.length; i++) {
|
|
345
|
+
const rp = container.elements[i];
|
|
346
|
+
if (rp === actualChild) {
|
|
347
|
+
return `${key}[${i}]`;
|
|
348
|
+
}
|
|
349
|
+
if (rp.element === actualChild) {
|
|
350
|
+
return `${key}[${i}].element`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check inside LeftPadded/RightPadded
|
|
357
|
+
if (value && typeof value === 'object') {
|
|
358
|
+
if ((value as any).kind === J.Kind.LeftPadded || (value as any).kind === J.Kind.RightPadded) {
|
|
359
|
+
if ((value as any).element === actualChild) {
|
|
360
|
+
return `${key}.element`;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Format a literal value for inline display.
|
|
371
|
+
*/
|
|
372
|
+
function formatLiteralValue(lit: J.Literal): string {
|
|
373
|
+
if (lit.valueSource !== undefined) {
|
|
374
|
+
// Truncate long literals
|
|
375
|
+
return lit.valueSource.length > 20
|
|
376
|
+
? lit.valueSource.substring(0, 17) + '...'
|
|
377
|
+
: lit.valueSource;
|
|
378
|
+
}
|
|
379
|
+
return String(lit.value);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Get a compact inline summary for certain node types.
|
|
384
|
+
* - For Identifier: shows "simpleName"
|
|
385
|
+
* - For Literal: shows the value
|
|
386
|
+
* - For other nodes: shows all Identifier/Literal properties inline
|
|
387
|
+
*/
|
|
388
|
+
function getNodeSummary(node: J): string | undefined {
|
|
389
|
+
if (isIdentifier(node)) {
|
|
390
|
+
return `"${node.simpleName}"`;
|
|
391
|
+
}
|
|
392
|
+
if (isLiteral(node)) {
|
|
393
|
+
return formatLiteralValue(node);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// For other nodes, find all Identifier and Literal properties
|
|
397
|
+
const parts: string[] = [];
|
|
398
|
+
const skipProps = new Set(['kind', 'id', 'prefix', 'markers', 'type', 'methodType', 'variableType', 'fieldType', 'constructorType']);
|
|
399
|
+
|
|
400
|
+
for (const [key, value] of Object.entries(node)) {
|
|
401
|
+
if (skipProps.has(key)) continue;
|
|
402
|
+
if (value === null || value === undefined) continue;
|
|
403
|
+
|
|
404
|
+
if (isIdentifier(value)) {
|
|
405
|
+
parts.push(`${key}='${value.simpleName}'`);
|
|
406
|
+
} else if (isLiteral(value)) {
|
|
407
|
+
parts.push(`${key}=${formatLiteralValue(value)}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return parts.length > 0 ? parts.join(' ') : undefined;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* LST Debug Printer - prints LST nodes in a readable format.
|
|
416
|
+
*
|
|
417
|
+
* Usage from within a visitor:
|
|
418
|
+
* ```typescript
|
|
419
|
+
* class MyVisitor extends JavaScriptVisitor<P> {
|
|
420
|
+
* private debug = new LstDebugPrinter({ includeCursorMessages: true });
|
|
421
|
+
*
|
|
422
|
+
* async visitMethodInvocation(mi: J.MethodInvocation, p: P) {
|
|
423
|
+
* this.debug.print(mi, this.cursor);
|
|
424
|
+
* return super.visitMethodInvocation(mi, p);
|
|
425
|
+
* }
|
|
426
|
+
* }
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
export class LstDebugPrinter {
|
|
430
|
+
private readonly options: Required<LstDebugOptions>;
|
|
431
|
+
private outputLines: string[] = [];
|
|
432
|
+
|
|
433
|
+
constructor(options: LstDebugOptions = {}) {
|
|
434
|
+
this.options = {...DEFAULT_OPTIONS, ...options};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Print a tree node with optional cursor context.
|
|
439
|
+
* @param tree The tree node to print
|
|
440
|
+
* @param cursor Optional cursor for context
|
|
441
|
+
* @param label Optional label to identify this debug output (shown as comment before output)
|
|
442
|
+
*/
|
|
443
|
+
print(tree: Tree | J.Container<any> | J.LeftPadded<any> | J.RightPadded<any>, cursor?: Cursor, label?: string): void {
|
|
444
|
+
this.outputLines = [];
|
|
445
|
+
if (label) {
|
|
446
|
+
this.outputLines.push(`// ${label}`);
|
|
447
|
+
}
|
|
448
|
+
this.printNode(tree, cursor, 0);
|
|
449
|
+
this.flush();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Print the cursor path from root to current position.
|
|
454
|
+
*/
|
|
455
|
+
printCursorPath(cursor: Cursor): void {
|
|
456
|
+
this.outputLines = [];
|
|
457
|
+
this.outputLines.push('=== Cursor Path ===');
|
|
458
|
+
|
|
459
|
+
const path: Cursor[] = [];
|
|
460
|
+
for (let c: Cursor | undefined = cursor; c; c = c.parent) {
|
|
461
|
+
path.unshift(c);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
for (let i = 0; i < path.length; i++) {
|
|
465
|
+
const c = path[i];
|
|
466
|
+
const indent = this.options.indent.repeat(i);
|
|
467
|
+
const kind = (c.value as any)?.kind;
|
|
468
|
+
const typeName = shortTypeName(kind);
|
|
469
|
+
|
|
470
|
+
let line = `${indent}${typeName}`;
|
|
471
|
+
if (this.options.includeCursorMessages && c.messages.size > 0) {
|
|
472
|
+
line += ` [${formatCursorMessages(c)}]`;
|
|
473
|
+
}
|
|
474
|
+
this.outputLines.push(line);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
this.flush();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private printNode(
|
|
481
|
+
node: any,
|
|
482
|
+
cursor: Cursor | undefined,
|
|
483
|
+
depth: number
|
|
484
|
+
): void {
|
|
485
|
+
if (this.options.maxDepth >= 0 && depth > this.options.maxDepth) {
|
|
486
|
+
this.outputLines.push(`${this.indent(depth)}...`);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (node === null || node === undefined) {
|
|
491
|
+
this.outputLines.push(`${this.indent(depth)}null`);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Handle special types
|
|
496
|
+
if (this.isSpace(node)) {
|
|
497
|
+
this.outputLines.push(`${this.indent(depth)}${formatSpace(node)}`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (this.isContainer(node)) {
|
|
502
|
+
this.printContainer(node, cursor, depth);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (this.isLeftPadded(node)) {
|
|
507
|
+
this.printLeftPadded(node, cursor, depth);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (this.isRightPadded(node)) {
|
|
512
|
+
this.printRightPadded(node, cursor, depth);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (isJava(node)) {
|
|
517
|
+
this.printJavaNode(node, cursor, depth);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Primitive or unknown type
|
|
522
|
+
if (typeof node !== 'object') {
|
|
523
|
+
this.outputLines.push(`${this.indent(depth)}${JSON.stringify(node)}`);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Generic object - print as JSON-like
|
|
528
|
+
this.printGenericObject(node, depth);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private printJavaNode(node: J, cursor: Cursor | undefined, depth: number): void {
|
|
532
|
+
const typeName = shortTypeName(node.kind);
|
|
533
|
+
let header = `${this.indent(depth)}${typeName}`;
|
|
534
|
+
|
|
535
|
+
// Add inline summary for certain types (Identifier, Literal)
|
|
536
|
+
const summary = getNodeSummary(node);
|
|
537
|
+
if (summary) {
|
|
538
|
+
header += ` ${summary}`;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Add cursor messages if available
|
|
542
|
+
if (this.options.includeCursorMessages && cursor) {
|
|
543
|
+
const messages = formatCursorMessages(cursor);
|
|
544
|
+
if (messages !== '<no messages>') {
|
|
545
|
+
header += ` [${messages}]`;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Add ID if requested
|
|
550
|
+
if (this.options.includeIds && node.id) {
|
|
551
|
+
header += ` (id=${node.id.substring(0, 8)}...)`;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
this.outputLines.push(header);
|
|
555
|
+
|
|
556
|
+
// Print prefix
|
|
557
|
+
if (node.prefix) {
|
|
558
|
+
this.outputLines.push(`${this.indent(depth + 1)}prefix: ${formatSpace(node.prefix)}`);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Print markers if requested
|
|
562
|
+
if (this.options.includeMarkers && node.markers?.markers?.length > 0) {
|
|
563
|
+
this.outputLines.push(`${this.indent(depth + 1)}markers: [${node.markers.markers.map((m: any) => shortTypeName(m.kind)).join(', ')}]`);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Print other properties
|
|
567
|
+
this.printNodeProperties(node, cursor, depth + 1);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
private printNodeProperties(node: any, cursor: Cursor | undefined, depth: number): void {
|
|
571
|
+
const excludedProps = new Set([
|
|
572
|
+
...EXCLUDED_PROPERTIES,
|
|
573
|
+
...this.options.excludeProperties,
|
|
574
|
+
'kind',
|
|
575
|
+
'id',
|
|
576
|
+
'prefix',
|
|
577
|
+
'markers',
|
|
578
|
+
]);
|
|
579
|
+
|
|
580
|
+
for (const [key, value] of Object.entries(node)) {
|
|
581
|
+
if (excludedProps.has(key)) continue;
|
|
582
|
+
if (value === undefined || value === null) continue;
|
|
583
|
+
|
|
584
|
+
if (this.isSpace(value)) {
|
|
585
|
+
this.outputLines.push(`${this.indent(depth)}${key}: ${formatSpace(value)}`);
|
|
586
|
+
} else if (this.isContainer(value)) {
|
|
587
|
+
this.outputLines.push(`${this.indent(depth)}${key}:`);
|
|
588
|
+
this.printContainer(value, undefined, depth + 1);
|
|
589
|
+
} else if (this.isLeftPadded(value)) {
|
|
590
|
+
this.outputLines.push(`${this.indent(depth)}${key}:`);
|
|
591
|
+
this.printLeftPadded(value, undefined, depth + 1);
|
|
592
|
+
} else if (this.isRightPadded(value)) {
|
|
593
|
+
this.outputLines.push(`${this.indent(depth)}${key}:`);
|
|
594
|
+
this.printRightPadded(value, undefined, depth + 1);
|
|
595
|
+
} else if (Array.isArray(value)) {
|
|
596
|
+
if (value.length === 0) {
|
|
597
|
+
this.outputLines.push(`${this.indent(depth)}${key}: []`);
|
|
598
|
+
} else if (value.every(v => this.isRightPadded(v))) {
|
|
599
|
+
this.outputLines.push(`${this.indent(depth)}${key}: [${value.length} RightPadded elements]`);
|
|
600
|
+
for (let i = 0; i < value.length; i++) {
|
|
601
|
+
this.outputLines.push(`${this.indent(depth + 1)}[${i}]:`);
|
|
602
|
+
this.printRightPadded(value[i], undefined, depth + 2);
|
|
603
|
+
}
|
|
604
|
+
} else if (value.every(v => isJava(v))) {
|
|
605
|
+
this.outputLines.push(`${this.indent(depth)}${key}: [${value.length} elements]`);
|
|
606
|
+
for (let i = 0; i < value.length; i++) {
|
|
607
|
+
this.outputLines.push(`${this.indent(depth + 1)}[${i}]:`);
|
|
608
|
+
this.printNode(value[i], undefined, depth + 2);
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
this.outputLines.push(`${this.indent(depth)}${key}: [${value.length} items]`);
|
|
612
|
+
}
|
|
613
|
+
} else if (isJava(value)) {
|
|
614
|
+
this.outputLines.push(`${this.indent(depth)}${key}:`);
|
|
615
|
+
this.printNode(value, undefined, depth + 1);
|
|
616
|
+
} else if (typeof value === 'object') {
|
|
617
|
+
// Skip complex objects that aren't J nodes
|
|
618
|
+
this.outputLines.push(`${this.indent(depth)}${key}: <object>`);
|
|
619
|
+
} else {
|
|
620
|
+
this.outputLines.push(`${this.indent(depth)}${key}: ${JSON.stringify(value)}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
private printContainer(container: J.Container<any>, cursor: Cursor | undefined, depth: number): void {
|
|
626
|
+
const elemCount = container.elements?.length ?? 0;
|
|
627
|
+
let header = `${this.indent(depth)}Container<${elemCount} elements>`;
|
|
628
|
+
|
|
629
|
+
if (this.options.includeCursorMessages && cursor) {
|
|
630
|
+
const messages = formatCursorMessages(cursor);
|
|
631
|
+
if (messages !== '<no messages>') {
|
|
632
|
+
header += ` [${messages}]`;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
this.outputLines.push(header);
|
|
637
|
+
this.outputLines.push(`${this.indent(depth + 1)}before: ${formatSpace(container.before)}`);
|
|
638
|
+
|
|
639
|
+
if (container.elements && container.elements.length > 0) {
|
|
640
|
+
this.outputLines.push(`${this.indent(depth + 1)}elements:`);
|
|
641
|
+
for (let i = 0; i < container.elements.length; i++) {
|
|
642
|
+
this.outputLines.push(`${this.indent(depth + 2)}[${i}]:`);
|
|
643
|
+
this.printRightPadded(container.elements[i], undefined, depth + 3);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private printLeftPadded(lp: J.LeftPadded<any>, cursor: Cursor | undefined, depth: number): void {
|
|
649
|
+
let header = `${this.indent(depth)}LeftPadded`;
|
|
650
|
+
|
|
651
|
+
if (this.options.includeCursorMessages && cursor) {
|
|
652
|
+
const messages = formatCursorMessages(cursor);
|
|
653
|
+
if (messages !== '<no messages>') {
|
|
654
|
+
header += ` [${messages}]`;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
this.outputLines.push(header);
|
|
659
|
+
this.outputLines.push(`${this.indent(depth + 1)}before: ${formatSpace(lp.before)}`);
|
|
660
|
+
|
|
661
|
+
if (lp.element !== undefined) {
|
|
662
|
+
if (isJava(lp.element)) {
|
|
663
|
+
this.outputLines.push(`${this.indent(depth + 1)}element:`);
|
|
664
|
+
this.printNode(lp.element, undefined, depth + 2);
|
|
665
|
+
} else if (this.isSpace(lp.element)) {
|
|
666
|
+
this.outputLines.push(`${this.indent(depth + 1)}element: ${formatSpace(lp.element)}`);
|
|
667
|
+
} else {
|
|
668
|
+
this.outputLines.push(`${this.indent(depth + 1)}element: ${JSON.stringify(lp.element)}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
private printRightPadded(rp: J.RightPadded<any>, cursor: Cursor | undefined, depth: number): void {
|
|
674
|
+
let header = `${this.indent(depth)}RightPadded`;
|
|
675
|
+
|
|
676
|
+
if (this.options.includeCursorMessages && cursor) {
|
|
677
|
+
const messages = formatCursorMessages(cursor);
|
|
678
|
+
if (messages !== '<no messages>') {
|
|
679
|
+
header += ` [${messages}]`;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
this.outputLines.push(header);
|
|
684
|
+
|
|
685
|
+
if (rp.element !== undefined) {
|
|
686
|
+
if (isJava(rp.element)) {
|
|
687
|
+
this.outputLines.push(`${this.indent(depth + 1)}element:`);
|
|
688
|
+
this.printNode(rp.element, undefined, depth + 2);
|
|
689
|
+
} else {
|
|
690
|
+
this.outputLines.push(`${this.indent(depth + 1)}element: ${JSON.stringify(rp.element)}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
this.outputLines.push(`${this.indent(depth + 1)}after: ${formatSpace(rp.after)}`);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
private printGenericObject(obj: any, depth: number): void {
|
|
698
|
+
this.outputLines.push(`${this.indent(depth)}{`);
|
|
699
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
700
|
+
if (typeof value === 'object' && value !== null) {
|
|
701
|
+
this.outputLines.push(`${this.indent(depth + 1)}${key}: <object>`);
|
|
702
|
+
} else {
|
|
703
|
+
this.outputLines.push(`${this.indent(depth + 1)}${key}: ${JSON.stringify(value)}`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
this.outputLines.push(`${this.indent(depth)}}`);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
private isSpace(value: any): value is J.Space {
|
|
710
|
+
return value !== null &&
|
|
711
|
+
typeof value === 'object' &&
|
|
712
|
+
'whitespace' in value &&
|
|
713
|
+
'comments' in value &&
|
|
714
|
+
!('kind' in value);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
private isContainer(value: any): value is J.Container<any> {
|
|
718
|
+
return value !== null &&
|
|
719
|
+
typeof value === 'object' &&
|
|
720
|
+
value.kind === J.Kind.Container;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
private isLeftPadded(value: any): value is J.LeftPadded<any> {
|
|
724
|
+
return value !== null &&
|
|
725
|
+
typeof value === 'object' &&
|
|
726
|
+
value.kind === J.Kind.LeftPadded;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
private isRightPadded(value: any): value is J.RightPadded<any> {
|
|
730
|
+
return value !== null &&
|
|
731
|
+
typeof value === 'object' &&
|
|
732
|
+
value.kind === J.Kind.RightPadded;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private indent(depth: number): string {
|
|
736
|
+
return this.options.indent.repeat(depth);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
private flush(): void {
|
|
740
|
+
const output = this.outputLines.join('\n');
|
|
741
|
+
|
|
742
|
+
if (this.options.output === 'console') {
|
|
743
|
+
console.info(output);
|
|
744
|
+
} else {
|
|
745
|
+
fs.appendFileSync(this.options.output, output + '\n\n');
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
this.outputLines = [];
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* A visitor that prints the LST structure as it traverses.
|
|
754
|
+
* Useful for debugging the entire tree or a subtree.
|
|
755
|
+
*
|
|
756
|
+
* Usage:
|
|
757
|
+
* ```typescript
|
|
758
|
+
* const debugVisitor = new LstDebugVisitor({ maxDepth: 3 });
|
|
759
|
+
* await debugVisitor.visit(tree, ctx);
|
|
760
|
+
* ```
|
|
761
|
+
*/
|
|
762
|
+
export class LstDebugVisitor<P> extends JavaScriptVisitor<P> {
|
|
763
|
+
private readonly printer: LstDebugPrinter;
|
|
764
|
+
private readonly printPreVisit: boolean;
|
|
765
|
+
private readonly printPostVisit: boolean;
|
|
766
|
+
private depth = 0;
|
|
767
|
+
|
|
768
|
+
constructor(
|
|
769
|
+
options: LstDebugOptions = {},
|
|
770
|
+
config: { printPreVisit?: boolean; printPostVisit?: boolean } = {}
|
|
771
|
+
) {
|
|
772
|
+
super();
|
|
773
|
+
this.printer = new LstDebugPrinter(options);
|
|
774
|
+
this.printPreVisit = config.printPreVisit ?? true;
|
|
775
|
+
this.printPostVisit = config.printPostVisit ?? false;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
protected async preVisit(tree: J, _p: P): Promise<J | undefined> {
|
|
779
|
+
if (this.printPreVisit) {
|
|
780
|
+
const typeName = shortTypeName(tree.kind);
|
|
781
|
+
const indent = ' '.repeat(this.depth);
|
|
782
|
+
const messages = formatCursorMessages(this.cursor);
|
|
783
|
+
const prefix = formatSpace(tree.prefix);
|
|
784
|
+
const summary = getNodeSummary(tree);
|
|
785
|
+
const propPath = findPropertyPath(this.cursor);
|
|
786
|
+
|
|
787
|
+
let line = indent;
|
|
788
|
+
if (propPath) {
|
|
789
|
+
line += `${propPath}: `;
|
|
790
|
+
}
|
|
791
|
+
line += `${typeName}{`;
|
|
792
|
+
if (summary) {
|
|
793
|
+
line += `${summary} `;
|
|
794
|
+
}
|
|
795
|
+
line += `prefix=${prefix}}`;
|
|
796
|
+
|
|
797
|
+
console.info(line);
|
|
798
|
+
|
|
799
|
+
// Show cursor messages on a separate indented line
|
|
800
|
+
if (messages !== '<no messages>') {
|
|
801
|
+
console.info(`${indent} ⤷ ${messages}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
this.depth++;
|
|
805
|
+
return tree;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
protected async postVisit(tree: J, _p: P): Promise<J | undefined> {
|
|
809
|
+
this.depth--;
|
|
810
|
+
if (this.printPostVisit) {
|
|
811
|
+
const typeName = shortTypeName(tree.kind);
|
|
812
|
+
const indent = ' '.repeat(this.depth);
|
|
813
|
+
console.info(`${indent}← ${typeName}`);
|
|
814
|
+
}
|
|
815
|
+
return tree;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
public async visitContainer<T extends J>(container: J.Container<T>, p: P): Promise<J.Container<T>> {
|
|
819
|
+
if (this.printPreVisit) {
|
|
820
|
+
const indent = ' '.repeat(this.depth);
|
|
821
|
+
const messages = formatCursorMessages(this.cursor);
|
|
822
|
+
const before = formatSpace(container.before);
|
|
823
|
+
// Pass container as the child since cursor.value is the parent node
|
|
824
|
+
const propPath = findPropertyPath(this.cursor, container);
|
|
825
|
+
|
|
826
|
+
let line = indent;
|
|
827
|
+
if (propPath) {
|
|
828
|
+
line += `${propPath}: `;
|
|
829
|
+
}
|
|
830
|
+
line += `Container<${container.elements.length}>{before=${before}}`;
|
|
831
|
+
|
|
832
|
+
console.info(line);
|
|
833
|
+
|
|
834
|
+
if (messages !== '<no messages>') {
|
|
835
|
+
console.info(`${indent} ⤷ ${messages}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
this.depth++;
|
|
839
|
+
const result = await super.visitContainer(container, p);
|
|
840
|
+
this.depth--;
|
|
841
|
+
return result;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
public async visitLeftPadded<T extends J | J.Space | number | string | boolean>(
|
|
845
|
+
left: J.LeftPadded<T>,
|
|
846
|
+
p: P
|
|
847
|
+
): Promise<J.LeftPadded<T> | undefined> {
|
|
848
|
+
if (this.printPreVisit) {
|
|
849
|
+
const indent = ' '.repeat(this.depth);
|
|
850
|
+
const messages = formatCursorMessages(this.cursor);
|
|
851
|
+
const before = formatSpace(left.before);
|
|
852
|
+
// Pass left as the child since cursor.value is the parent node
|
|
853
|
+
const propPath = findPropertyPath(this.cursor, left);
|
|
854
|
+
|
|
855
|
+
let line = indent;
|
|
856
|
+
if (propPath) {
|
|
857
|
+
line += `${propPath}: `;
|
|
858
|
+
}
|
|
859
|
+
line += `LeftPadded{before=${before}`;
|
|
860
|
+
|
|
861
|
+
// Show element value if it's a primitive (string, number, boolean)
|
|
862
|
+
if (left.element !== null && left.element !== undefined) {
|
|
863
|
+
const elemType = typeof left.element;
|
|
864
|
+
if (elemType === 'string' || elemType === 'number' || elemType === 'boolean') {
|
|
865
|
+
line += ` element=${JSON.stringify(left.element)}`;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
line += '}';
|
|
869
|
+
|
|
870
|
+
console.info(line);
|
|
871
|
+
|
|
872
|
+
if (messages !== '<no messages>') {
|
|
873
|
+
console.info(`${indent} ⤷ ${messages}`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
this.depth++;
|
|
877
|
+
const result = await super.visitLeftPadded(left, p);
|
|
878
|
+
this.depth--;
|
|
879
|
+
return result;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
public async visitRightPadded<T extends J | boolean>(
|
|
883
|
+
right: J.RightPadded<T>,
|
|
884
|
+
p: P
|
|
885
|
+
): Promise<J.RightPadded<T> | undefined> {
|
|
886
|
+
if (this.printPreVisit) {
|
|
887
|
+
const indent = ' '.repeat(this.depth);
|
|
888
|
+
const messages = formatCursorMessages(this.cursor);
|
|
889
|
+
const after = formatSpace(right.after);
|
|
890
|
+
// Pass right as the child since cursor.value is the parent node
|
|
891
|
+
const propPath = findPropertyPath(this.cursor, right);
|
|
892
|
+
|
|
893
|
+
let line = indent;
|
|
894
|
+
if (propPath) {
|
|
895
|
+
line += `${propPath}: `;
|
|
896
|
+
}
|
|
897
|
+
line += `RightPadded{after=${after}`;
|
|
898
|
+
|
|
899
|
+
// Show element value if it's a primitive (string, number, boolean)
|
|
900
|
+
if (right.element !== null && right.element !== undefined) {
|
|
901
|
+
const elemType = typeof right.element;
|
|
902
|
+
if (elemType === 'string' || elemType === 'number' || elemType === 'boolean') {
|
|
903
|
+
line += ` element=${JSON.stringify(right.element)}`;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
line += '}';
|
|
907
|
+
|
|
908
|
+
console.info(line);
|
|
909
|
+
|
|
910
|
+
if (messages !== '<no messages>') {
|
|
911
|
+
console.info(`${indent} ⤷ ${messages}`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
this.depth++;
|
|
915
|
+
const result = await super.visitRightPadded(right, p);
|
|
916
|
+
this.depth--;
|
|
917
|
+
return result;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Convenience function to print a tree node.
|
|
923
|
+
* @param tree The tree node to print
|
|
924
|
+
* @param cursor Optional cursor for context
|
|
925
|
+
* @param label Optional label to identify this debug output
|
|
926
|
+
* @param options Optional debug options
|
|
927
|
+
*/
|
|
928
|
+
export function debugPrint(tree: Tree, cursor?: Cursor, label?: string, options?: LstDebugOptions): void {
|
|
929
|
+
new LstDebugPrinter(options).print(tree, cursor, label);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Convenience function to print cursor path.
|
|
934
|
+
*/
|
|
935
|
+
export function debugPrintCursorPath(cursor: Cursor, options?: LstDebugOptions): void {
|
|
936
|
+
new LstDebugPrinter(options).printCursorPath(cursor);
|
|
937
|
+
}
|