@ontrails/warden 1.0.0-beta.24 → 1.0.0-beta.29
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/CHANGELOG.md +102 -0
- package/README.md +35 -1
- package/package.json +10 -9
- package/src/ast.ts +109 -0
- package/src/cli.ts +44 -4
- package/src/command.ts +24 -34
- package/src/drift.ts +24 -8
- package/src/index.ts +9 -0
- package/src/project-rules.ts +290 -0
- package/src/resolve.ts +8 -7
- package/src/rules/ast.ts +837 -6
- package/src/rules/cli-command-route-coherence.ts +177 -0
- package/src/rules/composes-declarations.ts +210 -77
- package/src/rules/context-no-surface-types.ts +15 -12
- package/src/rules/contour-exists.ts +7 -7
- package/src/rules/dead-internal-trail.ts +11 -4
- package/src/rules/dead-public-trail.ts +258 -0
- package/src/rules/duplicate-public-contract.ts +91 -0
- package/src/rules/error-mapping-completeness.ts +5 -3
- package/src/rules/example-valid.ts +6 -12
- package/src/rules/fires-declarations.ts +41 -64
- package/src/rules/implementation-returns-result.ts +208 -76
- package/src/rules/incomplete-crud.ts +20 -19
- package/src/rules/index.ts +15 -0
- package/src/rules/layer-field-name-drift.ts +12 -6
- package/src/rules/library-projection-coherence.ts +100 -0
- package/src/rules/metadata.ts +102 -1
- package/src/rules/no-destructured-compose.ts +16 -14
- package/src/rules/no-native-error-result.ts +15 -8
- package/src/rules/no-redundant-result-error-wrap.ts +82 -29
- package/src/rules/no-sync-result-assumption.ts +70 -68
- package/src/rules/no-top-level-surface.ts +46 -64
- package/src/rules/on-references-exist.ts +1 -1
- package/src/rules/owner-projection-parity.ts +10 -13
- package/src/rules/public-export-example-coverage.ts +29 -20
- package/src/rules/public-internal-deep-imports.ts +2 -2
- package/src/rules/read-intent-fires.ts +8 -8
- package/src/rules/registry-names.ts +10 -0
- package/src/rules/resource-declarations.ts +20 -31
- package/src/rules/resource-exists.ts +2 -2
- package/src/rules/resource-id-grammar.ts +2 -2
- package/src/rules/resource-mock-coverage.ts +2 -2
- package/src/rules/static-resource-accessor-preference.ts +26 -34
- package/src/rules/surface-facet-coherence.ts +21 -29
- package/src/rules/trail-fork-coaching.ts +616 -0
- package/src/rules/trail-versioning-source.ts +56 -78
- package/src/rules/types.ts +2 -0
- package/src/rules/unreachable-detour-shadowing.ts +16 -21
- package/src/rules/valid-describe-refs.ts +14 -12
- package/src/rules/warden-export-symmetry.ts +42 -35
- package/src/rules/warden-rules-use-ast.ts +168 -50
- package/src/trails/cli-command-route-coherence.trail.ts +47 -0
- package/src/trails/dead-public-trail.trail.ts +31 -0
- package/src/trails/duplicate-public-contract.trail.ts +47 -0
- package/src/trails/error-mapping-completeness.trail.ts +1 -0
- package/src/trails/index.ts +5 -0
- package/src/trails/library-projection-coherence.trail.ts +43 -0
- package/src/trails/schema.ts +4 -0
- package/src/trails/trail-fork-coaching.trail.ts +42 -0
- package/src/trails/warden-rules-use-ast.trail.ts +19 -0
- package/src/trails/wrap-rule.ts +1 -0
package/src/rules/ast.ts
CHANGED
|
@@ -12,6 +12,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
12
12
|
import { DRAFT_ID_PREFIX, intentValues } from '@ontrails/core';
|
|
13
13
|
import type { Intent } from '@ontrails/core';
|
|
14
14
|
import { parseSync } from 'oxc-parser';
|
|
15
|
+
import { ScopeTracker, walk as walkWithOxc } from 'oxc-walker';
|
|
16
|
+
import type {
|
|
17
|
+
ScopeTrackerNode,
|
|
18
|
+
WalkerCallbackContext,
|
|
19
|
+
WalkOptions,
|
|
20
|
+
} from 'oxc-walker';
|
|
15
21
|
|
|
16
22
|
// ---------------------------------------------------------------------------
|
|
17
23
|
// Types (minimal, avoiding full @oxc-project/types dep)
|
|
@@ -21,20 +27,640 @@ export interface AstNode {
|
|
|
21
27
|
readonly type: string;
|
|
22
28
|
readonly start: number;
|
|
23
29
|
readonly end: number;
|
|
24
|
-
readonly
|
|
30
|
+
readonly parent?: AstNode | null;
|
|
31
|
+
readonly key?: unknown;
|
|
25
32
|
readonly value?: unknown;
|
|
26
33
|
readonly body?: AstNode | readonly AstNode[];
|
|
27
34
|
readonly [key: string]: unknown;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
interface
|
|
37
|
+
export interface ProgramNode extends AstNode {
|
|
38
|
+
readonly type: 'Program';
|
|
39
|
+
readonly body?: readonly AstNode[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface IdentifierNode extends AstNode {
|
|
43
|
+
readonly type: 'Identifier';
|
|
44
|
+
readonly name?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface StringLiteralNode extends AstNode {
|
|
31
48
|
readonly type: 'Literal' | 'StringLiteral';
|
|
32
49
|
readonly value?: unknown;
|
|
33
50
|
}
|
|
34
51
|
|
|
35
|
-
|
|
52
|
+
export interface CallExpressionNode extends AstNode {
|
|
53
|
+
readonly type: 'CallExpression' | 'NewExpression';
|
|
54
|
+
readonly arguments?: readonly AstNode[];
|
|
55
|
+
readonly callee?: AstNode;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface MemberExpressionNode extends AstNode {
|
|
59
|
+
readonly type: 'MemberExpression' | 'StaticMemberExpression';
|
|
60
|
+
readonly computed?: boolean;
|
|
61
|
+
readonly object?: AstNode;
|
|
62
|
+
readonly property?: AstNode;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ImportDeclarationNode extends AstNode {
|
|
66
|
+
readonly type: 'ImportDeclaration';
|
|
67
|
+
readonly source?: AstNode;
|
|
68
|
+
readonly specifiers?: readonly AstNode[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ImportSpecifierNode extends AstNode {
|
|
72
|
+
readonly type:
|
|
73
|
+
| 'ImportDefaultSpecifier'
|
|
74
|
+
| 'ImportNamespaceSpecifier'
|
|
75
|
+
| 'ImportSpecifier';
|
|
76
|
+
readonly imported?: AstNode;
|
|
77
|
+
readonly local?: AstNode;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ExportDeclarationNode extends AstNode {
|
|
81
|
+
readonly type:
|
|
82
|
+
| 'ExportAllDeclaration'
|
|
83
|
+
| 'ExportDefaultDeclaration'
|
|
84
|
+
| 'ExportNamedDeclaration';
|
|
85
|
+
readonly declaration?: AstNode;
|
|
86
|
+
readonly source?: AstNode;
|
|
87
|
+
readonly specifiers?: readonly AstNode[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface ExportSpecifierNode extends AstNode {
|
|
91
|
+
readonly type: 'ExportSpecifier';
|
|
92
|
+
readonly exportKind?: string;
|
|
93
|
+
readonly exported?: AstNode;
|
|
94
|
+
readonly local?: AstNode;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface VariableDeclarationNode extends AstNode {
|
|
98
|
+
readonly type: 'VariableDeclaration';
|
|
99
|
+
readonly declarations?: readonly AstNode[];
|
|
100
|
+
readonly kind?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface VariableDeclaratorNode extends AstNode {
|
|
104
|
+
readonly type: 'VariableDeclarator';
|
|
105
|
+
readonly id?: AstNode;
|
|
106
|
+
readonly init?: AstNode;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface DeclarationWithIdNode extends AstNode {
|
|
110
|
+
readonly type:
|
|
111
|
+
| 'ClassDeclaration'
|
|
112
|
+
| 'FunctionDeclaration'
|
|
113
|
+
| 'InterfaceDeclaration'
|
|
114
|
+
| 'TSInterfaceDeclaration'
|
|
115
|
+
| 'TSTypeAliasDeclaration';
|
|
116
|
+
readonly id?: AstNode;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface ClassMemberNode extends AstNode {
|
|
120
|
+
readonly type: 'MethodDefinition' | 'PropertyDefinition';
|
|
121
|
+
readonly computed?: boolean;
|
|
122
|
+
readonly key?: AstNode;
|
|
123
|
+
readonly value?: AstNode;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface ArrayExpressionNode extends AstNode {
|
|
127
|
+
readonly type: 'ArrayExpression';
|
|
128
|
+
readonly elements?: readonly (AstNode | null)[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface ObjectExpressionNode extends AstNode {
|
|
132
|
+
readonly type: 'ObjectExpression' | 'ObjectPattern';
|
|
133
|
+
readonly properties?: readonly AstNode[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface PropertyNode extends AstNode {
|
|
137
|
+
readonly type: 'Property';
|
|
138
|
+
readonly computed?: boolean;
|
|
139
|
+
readonly key?: AstNode;
|
|
140
|
+
readonly value?: AstNode;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface RestElementNode extends AstNode {
|
|
144
|
+
readonly type: 'RestElement';
|
|
145
|
+
readonly argument?: AstNode;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface AssignmentPatternNode extends AstNode {
|
|
149
|
+
readonly type: 'AssignmentPattern';
|
|
150
|
+
readonly left?: AstNode;
|
|
151
|
+
readonly right?: AstNode;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface ExpressionStatementNode extends AstNode {
|
|
155
|
+
readonly type: 'ExpressionStatement';
|
|
156
|
+
readonly expression?: AstNode;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface UnaryExpressionNode extends AstNode {
|
|
160
|
+
readonly type: 'AwaitExpression' | 'ChainExpression' | 'UnaryExpression';
|
|
161
|
+
readonly argument?: AstNode;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface BinaryExpressionNode extends AstNode {
|
|
165
|
+
readonly type:
|
|
166
|
+
| 'AssignmentExpression'
|
|
167
|
+
| 'BinaryExpression'
|
|
168
|
+
| 'ConditionalExpression'
|
|
169
|
+
| 'LogicalExpression';
|
|
170
|
+
readonly alternate?: AstNode;
|
|
171
|
+
readonly consequent?: AstNode;
|
|
172
|
+
readonly left?: AstNode;
|
|
173
|
+
readonly operator?: string;
|
|
174
|
+
readonly right?: AstNode;
|
|
175
|
+
readonly test?: AstNode;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface FunctionLikeNode extends AstNode {
|
|
179
|
+
readonly type:
|
|
180
|
+
| 'ArrowFunctionExpression'
|
|
181
|
+
| 'FunctionDeclaration'
|
|
182
|
+
| 'FunctionExpression';
|
|
183
|
+
readonly body?: AstNode;
|
|
184
|
+
readonly params?: readonly AstNode[];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface BlockStatementNode extends AstNode {
|
|
188
|
+
readonly type: 'BlockStatement' | 'StaticBlock';
|
|
189
|
+
readonly body?: readonly AstNode[];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface ReturnStatementNode extends AstNode {
|
|
193
|
+
readonly type: 'ReturnStatement';
|
|
194
|
+
readonly argument?: AstNode;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export type CuratedAstNode =
|
|
198
|
+
| ArrayExpressionNode
|
|
199
|
+
| AssignmentPatternNode
|
|
200
|
+
| BinaryExpressionNode
|
|
201
|
+
| BlockStatementNode
|
|
202
|
+
| CallExpressionNode
|
|
203
|
+
| ClassMemberNode
|
|
204
|
+
| DeclarationWithIdNode
|
|
205
|
+
| ExportDeclarationNode
|
|
206
|
+
| ExportSpecifierNode
|
|
207
|
+
| ExpressionStatementNode
|
|
208
|
+
| FunctionLikeNode
|
|
209
|
+
| IdentifierNode
|
|
210
|
+
| ImportDeclarationNode
|
|
211
|
+
| ImportSpecifierNode
|
|
212
|
+
| MemberExpressionNode
|
|
213
|
+
| ObjectExpressionNode
|
|
214
|
+
| ProgramNode
|
|
215
|
+
| PropertyNode
|
|
216
|
+
| RestElementNode
|
|
217
|
+
| ReturnStatementNode
|
|
218
|
+
| StringLiteralNode
|
|
219
|
+
| UnaryExpressionNode
|
|
220
|
+
| VariableDeclarationNode
|
|
221
|
+
| VariableDeclaratorNode;
|
|
222
|
+
|
|
223
|
+
export interface AstParentContext {
|
|
224
|
+
readonly index: number | null;
|
|
225
|
+
readonly key: string | number | symbol | null | undefined;
|
|
226
|
+
readonly parent: AstNode | null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface AstScopeDeclaration {
|
|
230
|
+
readonly end: number;
|
|
231
|
+
readonly node: AstNode;
|
|
232
|
+
readonly start: number;
|
|
233
|
+
readonly type: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface AstScopeContext extends AstParentContext {
|
|
237
|
+
readonly currentScope: string;
|
|
238
|
+
readonly getDeclaration: (name: string) => AstScopeDeclaration | null;
|
|
239
|
+
readonly isDeclared: (name: string) => boolean;
|
|
240
|
+
readonly isCurrentScopeUnder: (scope: string) => boolean;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface SourceEdit {
|
|
244
|
+
readonly end: number;
|
|
245
|
+
readonly replacement: string;
|
|
246
|
+
readonly start: number;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface SourceLocation {
|
|
250
|
+
readonly column: number;
|
|
251
|
+
readonly line: number;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface AstParseDiagnosticLabel {
|
|
255
|
+
readonly end: number;
|
|
256
|
+
readonly message: string | null;
|
|
257
|
+
readonly start: number;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface AstParseDiagnostic {
|
|
261
|
+
readonly helpMessage: string | null;
|
|
262
|
+
readonly labels: readonly AstParseDiagnosticLabel[];
|
|
263
|
+
readonly message: string;
|
|
264
|
+
readonly severity: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface AstParseResult {
|
|
268
|
+
readonly ast: AstNode | null;
|
|
269
|
+
readonly diagnostics: readonly AstParseDiagnostic[];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface AstFieldProjection {
|
|
273
|
+
readonly alternate?: AstNode;
|
|
274
|
+
readonly argument?: AstNode;
|
|
275
|
+
readonly arguments?: readonly AstNode[];
|
|
276
|
+
readonly body?: AstNode | readonly AstNode[];
|
|
277
|
+
readonly callee?: AstNode;
|
|
278
|
+
readonly cases?: readonly AstNode[];
|
|
279
|
+
readonly computed?: boolean;
|
|
280
|
+
readonly consequent?: AstNode;
|
|
281
|
+
readonly declaration?: AstNode;
|
|
282
|
+
readonly declarations?: readonly AstNode[];
|
|
283
|
+
readonly discriminant?: AstNode;
|
|
284
|
+
readonly elements?: readonly (AstNode | null)[];
|
|
285
|
+
readonly exportKind?: string;
|
|
286
|
+
readonly exported?: AstNode;
|
|
287
|
+
readonly expression?: AstNode;
|
|
288
|
+
readonly id?: AstNode;
|
|
289
|
+
readonly imported?: AstNode;
|
|
290
|
+
readonly init?: AstNode;
|
|
291
|
+
readonly key?: AstNode;
|
|
292
|
+
readonly kind?: string;
|
|
293
|
+
readonly left?: AstNode;
|
|
294
|
+
readonly local?: AstNode;
|
|
295
|
+
readonly name?: string;
|
|
296
|
+
readonly object?: AstNode;
|
|
297
|
+
readonly operator?: string;
|
|
298
|
+
readonly param?: AstNode;
|
|
299
|
+
readonly params?: readonly AstNode[];
|
|
300
|
+
readonly properties?: readonly AstNode[];
|
|
301
|
+
readonly property?: AstNode;
|
|
302
|
+
readonly returnType?: AstNode;
|
|
303
|
+
readonly right?: AstNode;
|
|
304
|
+
readonly source?: AstNode;
|
|
305
|
+
readonly specifiers?: readonly AstNode[];
|
|
306
|
+
readonly superClass?: AstNode;
|
|
307
|
+
readonly test?: AstNode;
|
|
308
|
+
readonly typeAnnotation?: AstNode;
|
|
309
|
+
readonly value?: unknown;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export const isAstNode = (value: unknown): value is AstNode =>
|
|
36
313
|
Boolean(value && typeof value === 'object' && (value as AstNode).type);
|
|
37
314
|
|
|
315
|
+
const isNodeType = <TNode extends CuratedAstNode>(
|
|
316
|
+
node: AstNode | null | undefined,
|
|
317
|
+
types: readonly string[]
|
|
318
|
+
): node is TNode =>
|
|
319
|
+
node !== null && node !== undefined && types.includes(node.type);
|
|
320
|
+
|
|
321
|
+
export const isProgram = (
|
|
322
|
+
node: AstNode | null | undefined
|
|
323
|
+
): node is ProgramNode => isNodeType<ProgramNode>(node, ['Program']);
|
|
324
|
+
|
|
325
|
+
export const isIdentifier = (
|
|
326
|
+
node: AstNode | null | undefined
|
|
327
|
+
): node is IdentifierNode => isNodeType<IdentifierNode>(node, ['Identifier']);
|
|
328
|
+
|
|
329
|
+
export const isCallExpression = (
|
|
330
|
+
node: AstNode | null | undefined
|
|
331
|
+
): node is CallExpressionNode =>
|
|
332
|
+
isNodeType<CallExpressionNode>(node, ['CallExpression', 'NewExpression']);
|
|
333
|
+
|
|
334
|
+
export const isMemberExpression = (
|
|
335
|
+
node: AstNode | null | undefined
|
|
336
|
+
): node is MemberExpressionNode =>
|
|
337
|
+
isNodeType<MemberExpressionNode>(node, [
|
|
338
|
+
'MemberExpression',
|
|
339
|
+
'StaticMemberExpression',
|
|
340
|
+
]);
|
|
341
|
+
|
|
342
|
+
export const isImportDeclaration = (
|
|
343
|
+
node: AstNode | null | undefined
|
|
344
|
+
): node is ImportDeclarationNode =>
|
|
345
|
+
isNodeType<ImportDeclarationNode>(node, ['ImportDeclaration']);
|
|
346
|
+
|
|
347
|
+
export const isImportSpecifier = (
|
|
348
|
+
node: AstNode | null | undefined
|
|
349
|
+
): node is ImportSpecifierNode =>
|
|
350
|
+
isNodeType<ImportSpecifierNode>(node, [
|
|
351
|
+
'ImportDefaultSpecifier',
|
|
352
|
+
'ImportNamespaceSpecifier',
|
|
353
|
+
'ImportSpecifier',
|
|
354
|
+
]);
|
|
355
|
+
|
|
356
|
+
export const isExportDeclaration = (
|
|
357
|
+
node: AstNode | null | undefined
|
|
358
|
+
): node is ExportDeclarationNode =>
|
|
359
|
+
isNodeType<ExportDeclarationNode>(node, [
|
|
360
|
+
'ExportAllDeclaration',
|
|
361
|
+
'ExportDefaultDeclaration',
|
|
362
|
+
'ExportNamedDeclaration',
|
|
363
|
+
]);
|
|
364
|
+
|
|
365
|
+
export const isExportNamedDeclaration = (
|
|
366
|
+
node: AstNode | null | undefined
|
|
367
|
+
): node is ExportDeclarationNode & {
|
|
368
|
+
readonly type: 'ExportNamedDeclaration';
|
|
369
|
+
} =>
|
|
370
|
+
isNodeType<
|
|
371
|
+
ExportDeclarationNode & { readonly type: 'ExportNamedDeclaration' }
|
|
372
|
+
>(node, ['ExportNamedDeclaration']);
|
|
373
|
+
|
|
374
|
+
export const isExportDefaultDeclaration = (
|
|
375
|
+
node: AstNode | null | undefined
|
|
376
|
+
): node is ExportDeclarationNode & {
|
|
377
|
+
readonly type: 'ExportDefaultDeclaration';
|
|
378
|
+
} =>
|
|
379
|
+
isNodeType<
|
|
380
|
+
ExportDeclarationNode & { readonly type: 'ExportDefaultDeclaration' }
|
|
381
|
+
>(node, ['ExportDefaultDeclaration']);
|
|
382
|
+
|
|
383
|
+
export const isExportAllDeclaration = (
|
|
384
|
+
node: AstNode | null | undefined
|
|
385
|
+
): node is ExportDeclarationNode & { readonly type: 'ExportAllDeclaration' } =>
|
|
386
|
+
isNodeType<ExportDeclarationNode & { readonly type: 'ExportAllDeclaration' }>(
|
|
387
|
+
node,
|
|
388
|
+
['ExportAllDeclaration']
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
export const isExportSpecifier = (
|
|
392
|
+
node: AstNode | null | undefined
|
|
393
|
+
): node is ExportSpecifierNode =>
|
|
394
|
+
isNodeType<ExportSpecifierNode>(node, ['ExportSpecifier']);
|
|
395
|
+
|
|
396
|
+
export const isVariableDeclaration = (
|
|
397
|
+
node: AstNode | null | undefined
|
|
398
|
+
): node is VariableDeclarationNode =>
|
|
399
|
+
isNodeType<VariableDeclarationNode>(node, ['VariableDeclaration']);
|
|
400
|
+
|
|
401
|
+
export const isVariableDeclarator = (
|
|
402
|
+
node: AstNode | null | undefined
|
|
403
|
+
): node is VariableDeclaratorNode =>
|
|
404
|
+
isNodeType<VariableDeclaratorNode>(node, ['VariableDeclarator']);
|
|
405
|
+
|
|
406
|
+
export const isDeclarationWithId = (
|
|
407
|
+
node: AstNode | null | undefined
|
|
408
|
+
): node is DeclarationWithIdNode =>
|
|
409
|
+
isNodeType<DeclarationWithIdNode>(node, [
|
|
410
|
+
'ClassDeclaration',
|
|
411
|
+
'FunctionDeclaration',
|
|
412
|
+
'InterfaceDeclaration',
|
|
413
|
+
'TSInterfaceDeclaration',
|
|
414
|
+
'TSTypeAliasDeclaration',
|
|
415
|
+
]);
|
|
416
|
+
|
|
417
|
+
export const isClassMember = (
|
|
418
|
+
node: AstNode | null | undefined
|
|
419
|
+
): node is ClassMemberNode =>
|
|
420
|
+
isNodeType<ClassMemberNode>(node, ['MethodDefinition', 'PropertyDefinition']);
|
|
421
|
+
|
|
422
|
+
export const isArrayExpression = (
|
|
423
|
+
node: AstNode | null | undefined
|
|
424
|
+
): node is ArrayExpressionNode =>
|
|
425
|
+
isNodeType<ArrayExpressionNode>(node, ['ArrayExpression']);
|
|
426
|
+
|
|
427
|
+
export const isObjectExpression = (
|
|
428
|
+
node: AstNode | null | undefined
|
|
429
|
+
): node is ObjectExpressionNode =>
|
|
430
|
+
isNodeType<ObjectExpressionNode>(node, ['ObjectExpression', 'ObjectPattern']);
|
|
431
|
+
|
|
432
|
+
export const isProperty = (
|
|
433
|
+
node: AstNode | null | undefined
|
|
434
|
+
): node is PropertyNode => isNodeType<PropertyNode>(node, ['Property']);
|
|
435
|
+
|
|
436
|
+
export const isRestElement = (
|
|
437
|
+
node: AstNode | null | undefined
|
|
438
|
+
): node is RestElementNode =>
|
|
439
|
+
isNodeType<RestElementNode>(node, ['RestElement']);
|
|
440
|
+
|
|
441
|
+
export const isAssignmentPattern = (
|
|
442
|
+
node: AstNode | null | undefined
|
|
443
|
+
): node is AssignmentPatternNode =>
|
|
444
|
+
isNodeType<AssignmentPatternNode>(node, ['AssignmentPattern']);
|
|
445
|
+
|
|
446
|
+
export const isExpressionStatement = (
|
|
447
|
+
node: AstNode | null | undefined
|
|
448
|
+
): node is ExpressionStatementNode =>
|
|
449
|
+
isNodeType<ExpressionStatementNode>(node, ['ExpressionStatement']);
|
|
450
|
+
|
|
451
|
+
export const isUnaryExpression = (
|
|
452
|
+
node: AstNode | null | undefined
|
|
453
|
+
): node is UnaryExpressionNode =>
|
|
454
|
+
isNodeType<UnaryExpressionNode>(node, [
|
|
455
|
+
'AwaitExpression',
|
|
456
|
+
'ChainExpression',
|
|
457
|
+
'UnaryExpression',
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
export const isBinaryExpression = (
|
|
461
|
+
node: AstNode | null | undefined
|
|
462
|
+
): node is BinaryExpressionNode =>
|
|
463
|
+
isNodeType<BinaryExpressionNode>(node, [
|
|
464
|
+
'AssignmentExpression',
|
|
465
|
+
'BinaryExpression',
|
|
466
|
+
'ConditionalExpression',
|
|
467
|
+
'LogicalExpression',
|
|
468
|
+
]);
|
|
469
|
+
|
|
470
|
+
export const isFunctionLike = (
|
|
471
|
+
node: AstNode | null | undefined
|
|
472
|
+
): node is FunctionLikeNode =>
|
|
473
|
+
isNodeType<FunctionLikeNode>(node, [
|
|
474
|
+
'ArrowFunctionExpression',
|
|
475
|
+
'FunctionDeclaration',
|
|
476
|
+
'FunctionExpression',
|
|
477
|
+
]);
|
|
478
|
+
|
|
479
|
+
export const isBlockStatement = (
|
|
480
|
+
node: AstNode | null | undefined
|
|
481
|
+
): node is BlockStatementNode =>
|
|
482
|
+
isNodeType<BlockStatementNode>(node, ['BlockStatement', 'StaticBlock']);
|
|
483
|
+
|
|
484
|
+
export const isReturnStatement = (
|
|
485
|
+
node: AstNode | null | undefined
|
|
486
|
+
): node is ReturnStatementNode =>
|
|
487
|
+
isNodeType<ReturnStatementNode>(node, ['ReturnStatement']);
|
|
488
|
+
|
|
489
|
+
const projectAstFields = (
|
|
490
|
+
node: AstNode | null | undefined
|
|
491
|
+
): AstFieldProjection | null => (node ? (node as AstFieldProjection) : null);
|
|
492
|
+
|
|
493
|
+
export const getNodeAlternate = (
|
|
494
|
+
node: AstNode | null | undefined
|
|
495
|
+
): AstNode | undefined => projectAstFields(node)?.alternate;
|
|
496
|
+
|
|
497
|
+
export const getNodeArgument = (
|
|
498
|
+
node: AstNode | null | undefined
|
|
499
|
+
): AstNode | undefined => projectAstFields(node)?.argument;
|
|
500
|
+
|
|
501
|
+
export const getNodeArguments = (
|
|
502
|
+
node: AstNode | null | undefined
|
|
503
|
+
): readonly AstNode[] => (isCallExpression(node) ? (node.arguments ?? []) : []);
|
|
504
|
+
|
|
505
|
+
export const getNodeBody = (
|
|
506
|
+
node: AstNode | null | undefined
|
|
507
|
+
): AstNode | readonly AstNode[] | undefined => projectAstFields(node)?.body;
|
|
508
|
+
|
|
509
|
+
export const getNodeBodyNode = (
|
|
510
|
+
node: AstNode | null | undefined
|
|
511
|
+
): AstNode | undefined => {
|
|
512
|
+
const body = getNodeBody(node);
|
|
513
|
+
return isAstNode(body) ? body : undefined;
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
export const getNodeBodyStatements = (
|
|
517
|
+
node: AstNode | null | undefined
|
|
518
|
+
): readonly AstNode[] => {
|
|
519
|
+
const body = getNodeBody(node);
|
|
520
|
+
return Array.isArray(body) ? body : [];
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
export const getNodeCallee = (
|
|
524
|
+
node: AstNode | null | undefined
|
|
525
|
+
): AstNode | undefined => (isCallExpression(node) ? node.callee : undefined);
|
|
526
|
+
|
|
527
|
+
export const getNodeCases = (
|
|
528
|
+
node: AstNode | null | undefined
|
|
529
|
+
): readonly AstNode[] => projectAstFields(node)?.cases ?? [];
|
|
530
|
+
|
|
531
|
+
export const getNodeComputed = (
|
|
532
|
+
node: AstNode | null | undefined
|
|
533
|
+
): boolean | undefined => projectAstFields(node)?.computed;
|
|
534
|
+
|
|
535
|
+
export const getNodeConsequent = (
|
|
536
|
+
node: AstNode | null | undefined
|
|
537
|
+
): AstNode | undefined => projectAstFields(node)?.consequent;
|
|
538
|
+
|
|
539
|
+
export const getNodeDeclaration = (
|
|
540
|
+
node: AstNode | null | undefined
|
|
541
|
+
): AstNode | undefined => projectAstFields(node)?.declaration;
|
|
542
|
+
|
|
543
|
+
export const getNodeDeclarations = (
|
|
544
|
+
node: AstNode | null | undefined
|
|
545
|
+
): readonly AstNode[] =>
|
|
546
|
+
isVariableDeclaration(node) ? (node.declarations ?? []) : [];
|
|
547
|
+
|
|
548
|
+
export const getNodeDiscriminant = (
|
|
549
|
+
node: AstNode | null | undefined
|
|
550
|
+
): AstNode | undefined => projectAstFields(node)?.discriminant;
|
|
551
|
+
|
|
552
|
+
export const getNodeElements = (
|
|
553
|
+
node: AstNode | null | undefined
|
|
554
|
+
): readonly (AstNode | null)[] => projectAstFields(node)?.elements ?? [];
|
|
555
|
+
|
|
556
|
+
export const getNodeExported = (
|
|
557
|
+
node: AstNode | null | undefined
|
|
558
|
+
): AstNode | undefined => projectAstFields(node)?.exported;
|
|
559
|
+
|
|
560
|
+
export const getNodeExportKind = (
|
|
561
|
+
node: AstNode | null | undefined
|
|
562
|
+
): string | undefined => projectAstFields(node)?.exportKind;
|
|
563
|
+
|
|
564
|
+
export const getNodeExpression = (
|
|
565
|
+
node: AstNode | null | undefined
|
|
566
|
+
): AstNode | undefined => projectAstFields(node)?.expression;
|
|
567
|
+
|
|
568
|
+
export const getNodeId = (
|
|
569
|
+
node: AstNode | null | undefined
|
|
570
|
+
): AstNode | undefined => projectAstFields(node)?.id;
|
|
571
|
+
|
|
572
|
+
export const getNodeImported = (
|
|
573
|
+
node: AstNode | null | undefined
|
|
574
|
+
): AstNode | undefined => projectAstFields(node)?.imported;
|
|
575
|
+
|
|
576
|
+
export const getNodeInit = (
|
|
577
|
+
node: AstNode | null | undefined
|
|
578
|
+
): AstNode | undefined => projectAstFields(node)?.init;
|
|
579
|
+
|
|
580
|
+
export const getNodeKey = (
|
|
581
|
+
node: AstNode | null | undefined
|
|
582
|
+
): AstNode | undefined => projectAstFields(node)?.key;
|
|
583
|
+
|
|
584
|
+
export const getNodeKind = (
|
|
585
|
+
node: AstNode | null | undefined
|
|
586
|
+
): string | undefined => projectAstFields(node)?.kind;
|
|
587
|
+
|
|
588
|
+
export const getNodeLeft = (
|
|
589
|
+
node: AstNode | null | undefined
|
|
590
|
+
): AstNode | undefined => projectAstFields(node)?.left;
|
|
591
|
+
|
|
592
|
+
export const getNodeLocal = (
|
|
593
|
+
node: AstNode | null | undefined
|
|
594
|
+
): AstNode | undefined => projectAstFields(node)?.local;
|
|
595
|
+
|
|
596
|
+
export const getNodeName = (
|
|
597
|
+
node: AstNode | null | undefined
|
|
598
|
+
): string | undefined => projectAstFields(node)?.name;
|
|
599
|
+
|
|
600
|
+
export const getNodeObject = (
|
|
601
|
+
node: AstNode | null | undefined
|
|
602
|
+
): AstNode | undefined => (isMemberExpression(node) ? node.object : undefined);
|
|
603
|
+
|
|
604
|
+
export const getNodeOperator = (
|
|
605
|
+
node: AstNode | null | undefined
|
|
606
|
+
): string | undefined => projectAstFields(node)?.operator;
|
|
607
|
+
|
|
608
|
+
export const getNodeParam = (
|
|
609
|
+
node: AstNode | null | undefined
|
|
610
|
+
): AstNode | undefined => projectAstFields(node)?.param;
|
|
611
|
+
|
|
612
|
+
export const getNodeParams = (
|
|
613
|
+
node: AstNode | null | undefined
|
|
614
|
+
): readonly AstNode[] => (isFunctionLike(node) ? (node.params ?? []) : []);
|
|
615
|
+
|
|
616
|
+
export const getNodeProperties = (
|
|
617
|
+
node: AstNode | null | undefined
|
|
618
|
+
): readonly AstNode[] =>
|
|
619
|
+
isObjectExpression(node) ? (node.properties ?? []) : [];
|
|
620
|
+
|
|
621
|
+
export const getNodeProperty = (
|
|
622
|
+
node: AstNode | null | undefined
|
|
623
|
+
): AstNode | undefined =>
|
|
624
|
+
isMemberExpression(node) ? node.property : undefined;
|
|
625
|
+
|
|
626
|
+
export const getNodeReturnType = (
|
|
627
|
+
node: AstNode | null | undefined
|
|
628
|
+
): AstNode | undefined => projectAstFields(node)?.returnType;
|
|
629
|
+
|
|
630
|
+
export const getNodeRight = (
|
|
631
|
+
node: AstNode | null | undefined
|
|
632
|
+
): AstNode | undefined => projectAstFields(node)?.right;
|
|
633
|
+
|
|
634
|
+
export const getNodeSource = (
|
|
635
|
+
node: AstNode | null | undefined
|
|
636
|
+
): AstNode | undefined => projectAstFields(node)?.source;
|
|
637
|
+
|
|
638
|
+
export const getNodeSpecifiers = (
|
|
639
|
+
node: AstNode | null | undefined
|
|
640
|
+
): readonly AstNode[] => projectAstFields(node)?.specifiers ?? [];
|
|
641
|
+
|
|
642
|
+
export const getNodeSuperClass = (
|
|
643
|
+
node: AstNode | null | undefined
|
|
644
|
+
): AstNode | undefined => projectAstFields(node)?.superClass;
|
|
645
|
+
|
|
646
|
+
export const getNodeTest = (
|
|
647
|
+
node: AstNode | null | undefined
|
|
648
|
+
): AstNode | undefined => projectAstFields(node)?.test;
|
|
649
|
+
|
|
650
|
+
export const getNodeTypeAnnotation = (
|
|
651
|
+
node: AstNode | null | undefined
|
|
652
|
+
): AstNode | undefined => projectAstFields(node)?.typeAnnotation;
|
|
653
|
+
|
|
654
|
+
export const getNodeValue = (node: AstNode | null | undefined): unknown =>
|
|
655
|
+
projectAstFields(node)?.value;
|
|
656
|
+
|
|
657
|
+
export const getNodeValueNode = (
|
|
658
|
+
node: AstNode | null | undefined
|
|
659
|
+
): AstNode | undefined => {
|
|
660
|
+
const value = getNodeValue(node);
|
|
661
|
+
return isAstNode(value) ? value : undefined;
|
|
662
|
+
};
|
|
663
|
+
|
|
38
664
|
// ---------------------------------------------------------------------------
|
|
39
665
|
// Parser
|
|
40
666
|
// ---------------------------------------------------------------------------
|
|
@@ -49,6 +675,46 @@ export const parse = (filePath: string, sourceCode: string): AstNode | null => {
|
|
|
49
675
|
}
|
|
50
676
|
};
|
|
51
677
|
|
|
678
|
+
/**
|
|
679
|
+
* Parse TypeScript source and surface parser diagnostics. OXC can recover a
|
|
680
|
+
* partial program for malformed input, so rewrite tooling should use this
|
|
681
|
+
* helper when applying edits would be unsafe after syntax errors.
|
|
682
|
+
*/
|
|
683
|
+
export const parseWithDiagnostics = (
|
|
684
|
+
filePath: string,
|
|
685
|
+
sourceCode: string
|
|
686
|
+
): AstParseResult => {
|
|
687
|
+
try {
|
|
688
|
+
const result = parseSync(filePath, sourceCode, { sourceType: 'module' });
|
|
689
|
+
return {
|
|
690
|
+
ast: result.program as unknown as AstNode,
|
|
691
|
+
diagnostics: result.errors.map((error) => ({
|
|
692
|
+
helpMessage: error.helpMessage,
|
|
693
|
+
labels: error.labels.map((label) => ({
|
|
694
|
+
end: label.end,
|
|
695
|
+
message: label.message,
|
|
696
|
+
start: label.start,
|
|
697
|
+
})),
|
|
698
|
+
message: error.message,
|
|
699
|
+
severity: error.severity,
|
|
700
|
+
})),
|
|
701
|
+
};
|
|
702
|
+
} catch (error) {
|
|
703
|
+
return {
|
|
704
|
+
ast: null,
|
|
705
|
+
diagnostics: [
|
|
706
|
+
{
|
|
707
|
+
helpMessage: null,
|
|
708
|
+
labels: [],
|
|
709
|
+
message:
|
|
710
|
+
error instanceof Error ? error.message : 'Unable to parse source.',
|
|
711
|
+
severity: 'Error',
|
|
712
|
+
},
|
|
713
|
+
],
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
|
|
52
718
|
// ---------------------------------------------------------------------------
|
|
53
719
|
// Walker
|
|
54
720
|
// ---------------------------------------------------------------------------
|
|
@@ -83,6 +749,94 @@ export const walk: WalkFn = (node, visit) => {
|
|
|
83
749
|
walkChildren(n, visit, walk);
|
|
84
750
|
};
|
|
85
751
|
|
|
752
|
+
const toAstParentContext = (
|
|
753
|
+
parent: unknown,
|
|
754
|
+
ctx: WalkerCallbackContext
|
|
755
|
+
): AstParentContext => ({
|
|
756
|
+
index: ctx.index,
|
|
757
|
+
key: ctx.key,
|
|
758
|
+
parent: isAstNode(parent) ? parent : null,
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
const toScopeDeclaration = (
|
|
762
|
+
declaration: ScopeTrackerNode | null
|
|
763
|
+
): AstScopeDeclaration | null => {
|
|
764
|
+
if (!declaration) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return {
|
|
769
|
+
end: declaration.end,
|
|
770
|
+
node: declaration.node as unknown as AstNode,
|
|
771
|
+
start: declaration.start,
|
|
772
|
+
type: declaration.type,
|
|
773
|
+
};
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
const walkWithOxcFacade = (
|
|
777
|
+
node: unknown,
|
|
778
|
+
enter: (node: AstNode, context: AstParentContext) => void,
|
|
779
|
+
scopeTracker?: ScopeTracker
|
|
780
|
+
): void => {
|
|
781
|
+
if (!isAstNode(node)) {
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const options: Partial<WalkOptions> = {
|
|
786
|
+
enter(candidate, parent, ctx) {
|
|
787
|
+
if (!isAstNode(candidate)) {
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
enter(candidate, toAstParentContext(parent, ctx));
|
|
791
|
+
},
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
if (scopeTracker) {
|
|
795
|
+
options.scopeTracker = scopeTracker;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
walkWithOxc(node as never, options);
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Walk an AST node tree with parent, key, and index context for each visited
|
|
803
|
+
* node. This is the supported Warden facade over `oxc-walker` for rules and
|
|
804
|
+
* regrades that need structural context.
|
|
805
|
+
*/
|
|
806
|
+
export const walkWithParents = (
|
|
807
|
+
node: unknown,
|
|
808
|
+
visit: (node: AstNode, context: AstParentContext) => void
|
|
809
|
+
): void => {
|
|
810
|
+
walkWithOxcFacade(node, visit);
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Walk an AST node tree with parent context and a scope query facade. The
|
|
815
|
+
* concrete `oxc-walker` tracker stays behind this helper so rule authors can
|
|
816
|
+
* ask Warden-shaped questions without depending on walker internals.
|
|
817
|
+
*/
|
|
818
|
+
export const walkWithScopeContext = (
|
|
819
|
+
node: unknown,
|
|
820
|
+
visit: (node: AstNode, context: AstScopeContext) => void
|
|
821
|
+
): void => {
|
|
822
|
+
const scopeTracker = new ScopeTracker();
|
|
823
|
+
|
|
824
|
+
walkWithOxcFacade(
|
|
825
|
+
node,
|
|
826
|
+
(candidate, context) => {
|
|
827
|
+
visit(candidate, {
|
|
828
|
+
...context,
|
|
829
|
+
currentScope: scopeTracker.getCurrentScope(),
|
|
830
|
+
getDeclaration: (name) =>
|
|
831
|
+
toScopeDeclaration(scopeTracker.getDeclaration(name)),
|
|
832
|
+
isCurrentScopeUnder: (scope) => scopeTracker.isCurrentScopeUnder(scope),
|
|
833
|
+
isDeclared: (name) => scopeTracker.isDeclared(name),
|
|
834
|
+
});
|
|
835
|
+
},
|
|
836
|
+
scopeTracker
|
|
837
|
+
);
|
|
838
|
+
};
|
|
839
|
+
|
|
86
840
|
const NESTED_SCOPE_TYPES = new Set([
|
|
87
841
|
'ArrowFunctionExpression',
|
|
88
842
|
'FunctionExpression',
|
|
@@ -135,6 +889,81 @@ export const offsetToLine = (sourceCode: string, offset: number): number => {
|
|
|
135
889
|
return line;
|
|
136
890
|
};
|
|
137
891
|
|
|
892
|
+
/** Find the byte offset's line and column (1-based) in source code. */
|
|
893
|
+
export const offsetToLineColumn = (
|
|
894
|
+
sourceCode: string,
|
|
895
|
+
offset: number
|
|
896
|
+
): SourceLocation => {
|
|
897
|
+
let line = 1;
|
|
898
|
+
let column = 1;
|
|
899
|
+
const limit = Math.min(Math.max(offset, 0), sourceCode.length);
|
|
900
|
+
|
|
901
|
+
for (let i = 0; i < limit; i += 1) {
|
|
902
|
+
if (sourceCode[i] === '\n') {
|
|
903
|
+
line += 1;
|
|
904
|
+
column = 1;
|
|
905
|
+
} else {
|
|
906
|
+
column += 1;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return { column, line };
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
export const createSourceEdit = (
|
|
914
|
+
start: number,
|
|
915
|
+
end: number,
|
|
916
|
+
replacement: string
|
|
917
|
+
): SourceEdit => ({ end, replacement, start });
|
|
918
|
+
|
|
919
|
+
export const validateSourceEdits = (
|
|
920
|
+
edits: readonly SourceEdit[],
|
|
921
|
+
sourceLength?: number
|
|
922
|
+
): readonly SourceEdit[] => {
|
|
923
|
+
const ordered = [...edits].toSorted(
|
|
924
|
+
(left, right) => left.start - right.start
|
|
925
|
+
);
|
|
926
|
+
for (let i = 0; i < ordered.length; i += 1) {
|
|
927
|
+
const edit = ordered[i];
|
|
928
|
+
if (!edit) {
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
if (
|
|
932
|
+
!Number.isSafeInteger(edit.start) ||
|
|
933
|
+
!Number.isSafeInteger(edit.end) ||
|
|
934
|
+
edit.start < 0 ||
|
|
935
|
+
edit.end < edit.start ||
|
|
936
|
+
(sourceLength !== undefined && edit.end > sourceLength)
|
|
937
|
+
) {
|
|
938
|
+
throw new Error(`Invalid source edit range ${edit.start}-${edit.end}.`);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const previous = ordered[i - 1];
|
|
942
|
+
if (previous && edit.start < previous.end) {
|
|
943
|
+
throw new Error(
|
|
944
|
+
`Overlapping source edits ${previous.start}-${previous.end} and ${edit.start}-${edit.end}.`
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return ordered;
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
export const applySourceEdits = (
|
|
953
|
+
sourceCode: string,
|
|
954
|
+
edits: readonly SourceEdit[]
|
|
955
|
+
): string => {
|
|
956
|
+
validateSourceEdits(edits, sourceCode.length);
|
|
957
|
+
|
|
958
|
+
return [...edits]
|
|
959
|
+
.toSorted((left, right) => right.start - left.start)
|
|
960
|
+
.reduce(
|
|
961
|
+
(output, edit) =>
|
|
962
|
+
output.slice(0, edit.start) + edit.replacement + output.slice(edit.end),
|
|
963
|
+
sourceCode
|
|
964
|
+
);
|
|
965
|
+
};
|
|
966
|
+
|
|
138
967
|
/** Get the name of an Identifier node, or null. */
|
|
139
968
|
export const identifierName = (node: AstNode | undefined): string | null => {
|
|
140
969
|
if (node?.type !== 'Identifier') {
|
|
@@ -1865,7 +2694,9 @@ const extractImportSpecifierAlias = (
|
|
|
1865
2694
|
};
|
|
1866
2695
|
|
|
1867
2696
|
/**
|
|
1868
|
-
* Collect `import {
|
|
2697
|
+
* Collect `import {
|
|
2698
|
+
foo as bar
|
|
2699
|
+
} from '...';` and `import bar from '...'`
|
|
1869
2700
|
* specifier mappings keyed by local binding name. The value is the original
|
|
1870
2701
|
* exported name for named imports. Default imports map to themselves because
|
|
1871
2702
|
* the exported name cannot be recovered statically — callers should fall
|
|
@@ -2421,8 +3252,8 @@ const extractBlazeFromConfig = (config: AstNode): AstNode[] => {
|
|
|
2421
3252
|
}
|
|
2422
3253
|
for (const prop of properties) {
|
|
2423
3254
|
if (
|
|
2424
|
-
prop
|
|
2425
|
-
prop.key
|
|
3255
|
+
isProperty(prop) &&
|
|
3256
|
+
identifierName(prop.key) === 'blaze' &&
|
|
2426
3257
|
isAstNode(prop.value)
|
|
2427
3258
|
) {
|
|
2428
3259
|
bodies.push(prop.value);
|