@ngcompass/ast 0.1.1-beta

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.
@@ -0,0 +1,523 @@
1
+ import { Program } from 'oxc-parser';
2
+
3
+ /**
4
+ * Minimal AST Type Definitions (Zero-Copy, ESTree-like)
5
+ *
6
+ * These types match Oxc's ESTree-compatible AST structure.
7
+ * Only include fields we actually use to minimize overhead.
8
+ */
9
+ interface Node {
10
+ readonly type: string;
11
+ readonly span?: {
12
+ start: number;
13
+ end: number;
14
+ };
15
+ readonly start?: number;
16
+ readonly end?: number;
17
+ }
18
+ interface Identifier extends Node {
19
+ readonly type: 'Identifier';
20
+ readonly name: string;
21
+ }
22
+ interface Decorator extends Node {
23
+ readonly type: 'Decorator';
24
+ readonly expression?: CallExpression | Identifier;
25
+ }
26
+ interface CallExpression extends Node {
27
+ readonly type: 'CallExpression';
28
+ readonly callee: Expression;
29
+ readonly arguments: ReadonlyArray<Expression>;
30
+ }
31
+ interface NewExpression extends Node {
32
+ readonly type: 'NewExpression';
33
+ readonly callee: Expression;
34
+ readonly arguments: ReadonlyArray<Expression>;
35
+ }
36
+ interface MemberExpression extends Node {
37
+ readonly type: 'MemberExpression' | 'StaticMemberExpression';
38
+ readonly object: Expression;
39
+ readonly property: Identifier;
40
+ }
41
+ interface ObjectExpression extends Node {
42
+ readonly type: 'ObjectExpression';
43
+ readonly properties: ReadonlyArray<ObjectProperty | SpreadElement>;
44
+ }
45
+ interface ObjectProperty extends Node {
46
+ readonly type: 'ObjectProperty' | 'Property';
47
+ readonly key: Expression;
48
+ readonly value: Expression;
49
+ }
50
+ interface SpreadElement extends Node {
51
+ readonly type: 'SpreadElement';
52
+ }
53
+ interface ArrayExpression extends Node {
54
+ readonly type: 'ArrayExpression';
55
+ readonly elements: ReadonlyArray<Expression | SpreadElement | null>;
56
+ }
57
+ interface StringLiteral extends Node {
58
+ readonly type: 'StringLiteral' | 'Literal';
59
+ readonly value: string;
60
+ }
61
+ interface BooleanLiteral extends Node {
62
+ readonly type: 'BooleanLiteral' | 'Literal';
63
+ readonly value: boolean;
64
+ }
65
+ interface ArrowFunctionExpression extends Node {
66
+ readonly type: 'ArrowFunctionExpression';
67
+ readonly body: Expression | BlockStatement;
68
+ readonly expression: boolean;
69
+ }
70
+ interface FunctionExpression extends Node {
71
+ readonly type: 'FunctionExpression';
72
+ readonly body: BlockStatement;
73
+ }
74
+ interface BlockStatement extends Node {
75
+ readonly type: 'BlockStatement';
76
+ readonly body: ReadonlyArray<Node>;
77
+ }
78
+ interface ExpressionStatement extends Node {
79
+ readonly type: 'ExpressionStatement';
80
+ readonly expression: Expression;
81
+ }
82
+ interface AssignmentExpression extends Node {
83
+ readonly type: 'AssignmentExpression';
84
+ readonly left: Expression;
85
+ readonly right: Expression;
86
+ readonly operator: string;
87
+ }
88
+ interface UpdateExpression extends Node {
89
+ readonly type: 'UpdateExpression';
90
+ readonly operator: string;
91
+ readonly argument: Expression;
92
+ readonly prefix: boolean;
93
+ }
94
+ interface IfStatement extends Node {
95
+ readonly type: 'IfStatement';
96
+ readonly test: Expression;
97
+ readonly consequent: Node;
98
+ readonly alternate?: Node;
99
+ }
100
+ interface ReturnStatement extends Node {
101
+ readonly type: 'ReturnStatement';
102
+ readonly argument?: Expression;
103
+ }
104
+ interface ClassDeclaration extends Node {
105
+ readonly type: 'ClassDeclaration';
106
+ readonly id?: Identifier;
107
+ readonly decorators?: ReadonlyArray<Decorator>;
108
+ readonly body?: ClassBody;
109
+ }
110
+ interface ClassBody extends Node {
111
+ readonly type: 'ClassBody';
112
+ readonly body: ReadonlyArray<PropertyDefinition | MethodDefinition>;
113
+ }
114
+ interface PropertyDefinition extends Node {
115
+ readonly type: 'PropertyDefinition';
116
+ readonly key: Expression;
117
+ readonly value?: Expression;
118
+ readonly decorators?: ReadonlyArray<Decorator>;
119
+ }
120
+ interface MethodDefinition extends Node {
121
+ readonly type: 'MethodDefinition';
122
+ readonly key: Expression;
123
+ }
124
+ interface TemplateBlock extends Node {
125
+ readonly type: 'Block';
126
+ readonly name: string;
127
+ readonly parameters: ReadonlyArray<TemplateBlockParameter>;
128
+ readonly children: ReadonlyArray<Node>;
129
+ }
130
+ interface TemplateBlockParameter extends Node {
131
+ readonly type: 'BlockParameter';
132
+ readonly expression: string;
133
+ }
134
+ type Expression = Identifier | CallExpression | NewExpression | MemberExpression | ObjectExpression | ArrayExpression | StringLiteral | BooleanLiteral | ArrowFunctionExpression | FunctionExpression | AssignmentExpression | UpdateExpression | TemplateBlock | TemplateBlockParameter | Node;
135
+
136
+ /**
137
+ * AST Matchers (Zero-Allocation, Pure Functions)
138
+ *
139
+ * PERFORMANCE RULES:
140
+ * - No object creation in hot paths
141
+ * - No array allocations
142
+ * - No string concatenation
143
+ * - Return primitives or pre-existing references only
144
+ *
145
+ * "Unsafe" suffix convention: May return undefined, caller must handle.
146
+ */
147
+
148
+ /**
149
+ * Checks if a node has a specific decorator.
150
+ *
151
+ * @returns boolean (primitive, zero allocation)
152
+ */
153
+ declare const hasDecorator: (classNode: ClassDeclaration, decoratorName: string) => boolean;
154
+ /**
155
+ * Gets decorator name (unsafe: may return undefined).
156
+ *
157
+ * PERFORMANCE: Returns string reference from AST (zero copy).
158
+ * Rules must handle undefined.
159
+ */
160
+ declare const getDecoratorNameUnsafe: (decorator: Decorator) => string | undefined;
161
+ /**
162
+ * Gets first decorator argument if it's an object literal.
163
+ *
164
+ * PERFORMANCE: Returns AST reference (zero copy).
165
+ */
166
+ declare const getDecoratorObjectArgUnsafe: (decorator: Decorator) => ObjectExpression | undefined;
167
+ /**
168
+ * Checks if object has a property with given key.
169
+ *
170
+ * PERFORMANCE: No allocation, early return.
171
+ */
172
+ declare const hasObjectProperty: (objectExpr: ObjectExpression, keyName: string) => boolean;
173
+ /**
174
+ * Gets property value by key name (unsafe: may return undefined).
175
+ *
176
+ * PERFORMANCE: Returns AST reference (zero copy).
177
+ */
178
+ declare const getObjectPropertyUnsafe: (objectExpr: ObjectExpression, keyName: string) => Expression | undefined;
179
+ /**
180
+ * Gets key name from object key (unsafe).
181
+ *
182
+ * PERFORMANCE: Returns string reference from AST.
183
+ */
184
+ declare const getKeyNameUnsafe: (key: Expression) => string | undefined;
185
+ /**
186
+ * Checks if member expression matches pattern (e.g., ChangeDetectionStrategy.OnPush).
187
+ *
188
+ * PERFORMANCE: No allocation, early return.
189
+ */
190
+ declare const matchesMemberExpression: (expr: Expression, objectName: string, propertyName: string) => boolean;
191
+ /**
192
+ * Gets literal string value (unsafe: may return undefined).
193
+ *
194
+ * PERFORMANCE: Returns string reference from AST.
195
+ */
196
+ declare const getLiteralStringValueUnsafe: (node: Expression) => string | undefined;
197
+ /**
198
+ * Gets literal boolean value (unsafe: may return undefined).
199
+ *
200
+ * PERFORMANCE: Returns boolean primitive from AST.
201
+ */
202
+ declare const getLiteralBooleanValueUnsafe: (node: Expression) => boolean | undefined;
203
+ /**
204
+ * Checks if expression is an Angular input() signal call.
205
+ */
206
+ declare const isInputSignal: (expr: Expression) => boolean;
207
+ /**
208
+ * Extracts alias from input() signal call if present.
209
+ */
210
+ declare const getInputSignalAliasUnsafe: (callExpr: CallExpression) => string | undefined;
211
+
212
+ type LiteralValue<T> = {
213
+ readonly kind: 'literal';
214
+ readonly value: T;
215
+ };
216
+ type NonLiteralValue = {
217
+ readonly kind: 'non-literal';
218
+ };
219
+ type MissingValue = {
220
+ readonly kind: 'missing';
221
+ };
222
+ type MetadataValue<T> = LiteralValue<T> | NonLiteralValue | MissingValue;
223
+ declare enum ChangeDetectionStrategy {
224
+ Default = 0,
225
+ OnPush = 1
226
+ }
227
+ interface HostDirectiveMetadata {
228
+ readonly directive: string | undefined;
229
+ readonly inputs: ReadonlyArray<{
230
+ readonly internal: string;
231
+ readonly external: string;
232
+ }>;
233
+ readonly outputs: ReadonlyArray<{
234
+ readonly internal: string;
235
+ readonly external: string;
236
+ }>;
237
+ }
238
+ interface ComponentMetadata {
239
+ readonly className: string | undefined;
240
+ readonly selector: MetadataValue<string>;
241
+ readonly changeDetection: MetadataValue<ChangeDetectionStrategy>;
242
+ readonly standalone: MetadataValue<boolean>;
243
+ readonly templateUrl: MetadataValue<string>;
244
+ readonly template: MetadataValue<string>;
245
+ readonly hostDirectives: MetadataValue<ReadonlyArray<HostDirectiveMetadata>>;
246
+ readonly decoratorStart: number;
247
+ readonly type: 'Component' | 'Directive';
248
+ }
249
+ interface CacheStatsAccumulator {
250
+ hits: number;
251
+ misses: number;
252
+ }
253
+ declare const getComponentCacheStats: () => Readonly<CacheStatsAccumulator>;
254
+ declare const resetComponentCacheStats: () => void;
255
+ /**
256
+ * Analyzes @Component / @Directive decorator metadata.
257
+ * Returns null if the class has neither decorator.
258
+ * Results are cached in a WeakMap — O(1) on subsequent calls.
259
+ */
260
+ declare const analyzeComponent: (classNode: ClassDeclaration) => ComponentMetadata | null;
261
+ declare const isComponent: (classNode: ClassDeclaration) => boolean;
262
+ declare const usesOnPush: (classNode: ClassDeclaration) => boolean;
263
+ declare const isStandalone: (classNode: ClassDeclaration) => boolean;
264
+
265
+ /**
266
+ * Node Streams (Pre-Filtered Semantic Dispatch)
267
+ *
268
+ * PERFORMANCE RULE:
269
+ * Rules must subscribe to the most specific stream possible.
270
+ *
271
+ * FORBIDDEN:
272
+ * - Rules checking "is this a component?" (use AngularClassStream)
273
+ * - Rules checking "is this decorated?" (use DecoratedPropertyStream)
274
+ * - Rules checking node types (dispatcher handles this)
275
+ */
276
+
277
+ interface TemplateExpressionNode {
278
+ readonly expression: Expression;
279
+ readonly sourceSpan: {
280
+ start: number;
281
+ end: number;
282
+ };
283
+ }
284
+ interface TemplateAttributeNode {
285
+ readonly name: string;
286
+ readonly value?: string;
287
+ readonly sourceSpan: {
288
+ start: number;
289
+ end: number;
290
+ };
291
+ }
292
+ interface TemplateBlockNode {
293
+ readonly name: string;
294
+ readonly parameters: ReadonlyArray<{
295
+ readonly expression: string;
296
+ readonly sourceSpan: {
297
+ start: number;
298
+ end: number;
299
+ };
300
+ }>;
301
+ readonly sourceSpan: {
302
+ start: number;
303
+ end: number;
304
+ };
305
+ }
306
+ interface TemplateAnalysis {
307
+ readonly expressions: ReadonlyArray<TemplateExpressionNode>;
308
+ readonly attributes: ReadonlyArray<TemplateAttributeNode>;
309
+ readonly blocks: ReadonlyArray<TemplateBlockNode>;
310
+ }
311
+ /**
312
+ * Angular Decorator Stream: ClassDeclaration nodes with @Component or @Directive.
313
+ *
314
+ * Rules subscribing to this stream are guaranteed:
315
+ * - Node is a ClassDeclaration
316
+ * - Node has @Component or @Directive decorator
317
+ * - Metadata is pre-analyzed and cached
318
+ */
319
+ interface AngularClassNode {
320
+ readonly node: ClassDeclaration;
321
+ readonly metadata: ComponentMetadata;
322
+ }
323
+ /**
324
+ * Any Angular Decorated Class Stream: ClassDeclaration nodes with ANY Angular decorator.
325
+ *
326
+ * Rules subscribing to this stream are guaranteed:
327
+ * - Node is a ClassDeclaration
328
+ * - Node has at least one of: @Component, @Directive, @Pipe, @Injectable, @NgModule
329
+ *
330
+ * Use this when the rule applies beyond @Component/@Directive (e.g. naming rules
331
+ * for pipes, services, or guards).
332
+ */
333
+ interface AnyAngularClassNode {
334
+ readonly node: ClassDeclaration;
335
+ readonly decoratorName: string;
336
+ readonly className: string | undefined;
337
+ readonly decoratorStart: number;
338
+ }
339
+ /**
340
+ * Decorated Property Stream: PropertyDefinition nodes with decorators.
341
+ *
342
+ * Rules subscribing to this stream are guaranteed:
343
+ * - Node is a PropertyDefinition
344
+ * - Node has at least one decorator (@Input, @Output, @ViewChild, etc.)
345
+ */
346
+ interface DecoratedPropertyNode {
347
+ readonly node: PropertyDefinition;
348
+ readonly decorators: ReadonlyArray<Decorator>;
349
+ }
350
+ /**
351
+ * Filters ClassDeclaration nodes to Angular components or directives.
352
+ *
353
+ * PERFORMANCE: O(1) after first call (cached).
354
+ * Called by engine during traversal, not by rules.
355
+ */
356
+ declare const toAngularClassStream: (classNode: ClassDeclaration) => AngularClassNode | null;
357
+ /**
358
+ * Filters ClassDeclaration nodes to ANY Angular-decorated class.
359
+ *
360
+ * PERFORMANCE: O(D) where D = number of decorators on the class (usually 1).
361
+ * Called by engine during traversal, not by rules.
362
+ */
363
+ declare const toAnyAngularClassStream: (classNode: ClassDeclaration) => AnyAngularClassNode | null;
364
+ /**
365
+ * Filters PropertyDefinition nodes to decorated properties.
366
+ *
367
+ * PERFORMANCE: O(1) decorator array access.
368
+ * Called by engine, not by rules.
369
+ */
370
+ declare const toDecoratedPropertyStream: (propertyNode: PropertyDefinition) => DecoratedPropertyNode | null;
371
+ /**
372
+ * Pass-through filter for CallExpression stream.
373
+ */
374
+ declare const toCallExpressionStream: (node: CallExpression) => CallExpression;
375
+ /**
376
+ * Pass-through filter for NewExpression stream.
377
+ */
378
+ declare const toNewExpressionStream: (node: NewExpression) => NewExpression;
379
+
380
+ interface HtmlParserResult$1 {
381
+ rootNodes: readonly unknown[];
382
+ }
383
+
384
+ declare const analyzeTemplate: (htmlResult: HtmlParserResult$1) => {
385
+ expressions: TemplateExpressionNode[];
386
+ attributes: TemplateAttributeNode[];
387
+ blocks: TemplateBlockNode[];
388
+ };
389
+
390
+ interface TsParserResult {
391
+ program: Program;
392
+ errors: unknown[];
393
+ }
394
+ /**
395
+ * Parses TypeScript / TSX source code using Oxc.
396
+ *
397
+ * @param content - Source text
398
+ * @param filePath - Source filename (used for diagnostics)
399
+ * @returns Parsed program and parse errors
400
+ */
401
+ declare const parseTs: (content: string, filePath: string) => TsParserResult;
402
+
403
+ interface HtmlParserResult {
404
+ rootNodes: unknown[];
405
+ errors: unknown[];
406
+ /**
407
+ * Byte offset in the *source file* where the template content begins.
408
+ *
409
+ * - For external `.html` files this is always `0` — the HTML file is the
410
+ * template, so its offsets are already file-absolute.
411
+ * - For inline templates inside a `.ts` file this is the position of the
412
+ * first content character (right after the opening quote/backtick) in
413
+ * the TypeScript source.
414
+ *
415
+ * Template rules MUST add this value to `node.sourceSpan.start` before
416
+ * calling `context.locator.location()` so that reported line/column
417
+ * numbers are correct for both inline and external templates.
418
+ */
419
+ templateStartOffset: number;
420
+ }
421
+ /**
422
+ * Parses Angular HTML template source.
423
+ *
424
+ * @param content - Template source text (content only, no surrounding quotes)
425
+ * @param templateStartOffset - Byte offset in the original source file where
426
+ * `content[0]` lives. Use `0` for external `.html`
427
+ * files; supply the value from `extractTemplateFromProgram`
428
+ * for inline templates in `.ts` files.
429
+ * @returns Parsed root nodes, parse errors, and the start offset.
430
+ */
431
+ declare const parseHtml: (content: string, templateStartOffset?: number) => HtmlParserResult;
432
+
433
+ interface CssParserResult {
434
+ code: Buffer | Uint8Array;
435
+ map?: Buffer | Uint8Array | void;
436
+ }
437
+ /**
438
+ * Result type for CSS parsing and validation.
439
+ */
440
+ type CssResult = {
441
+ ok: true;
442
+ code: Buffer | Uint8Array;
443
+ map?: Buffer | Uint8Array | void;
444
+ } | {
445
+ ok: false;
446
+ error: unknown;
447
+ };
448
+ /**
449
+ * Parses and validates CSS using Lightning CSS.
450
+ *
451
+ * @param content - CSS source text
452
+ * @param filePath - Source filename for diagnostics
453
+ * @returns CssResult with transformed output or an error
454
+ */
455
+ declare const parseCss: (content: string, filePath: string) => CssResult;
456
+
457
+ /**
458
+ * Template Extractor
459
+ *
460
+ * Extracts the inline template string from an Oxc-parsed TypeScript program.
461
+ * Moved from orchestrator.ts to make it independently testable and reusable.
462
+ *
463
+ * Uses walkProgram() (the shared visitor) instead of a hand-rolled recursion,
464
+ * which is consistent with how the rules engine traverses the AST and
465
+ * benefits from the same early-exit optimisation (returning false stops descent).
466
+ *
467
+ * Handles both:
468
+ * - StringLiteral: template: '<h1>Hello</h1>'
469
+ * - TemplateLiteral: template: `<h1>{{ name }}</h1>`
470
+ */
471
+
472
+ /**
473
+ * Result of extracting an inline template from a TypeScript program.
474
+ */
475
+ interface ExtractedTemplate {
476
+ /** The raw HTML content of the template (no surrounding quotes/backticks). */
477
+ readonly content: string;
478
+ /**
479
+ * Byte offset in the TypeScript source file where `content[0]` lives.
480
+ *
481
+ * This is used to convert template-relative offsets (produced by the HTML
482
+ * parser, which only sees the template content) back into file-absolute
483
+ * offsets (required by `Locator.location()`).
484
+ *
485
+ * For external .html files this value is always 0 — the HTML file IS the
486
+ * template, so its offsets are already file-absolute.
487
+ */
488
+ readonly startOffset: number;
489
+ }
490
+ /**
491
+ * Extracts the inline template string from the first @Component class found
492
+ * in the given Oxc program.
493
+ *
494
+ * @param program - Oxc-parsed Program node
495
+ * @returns ExtractedTemplate with content and its start offset in the file,
496
+ * or `{ content: '', startOffset: 0 }` if no template was found.
497
+ */
498
+ declare function extractTemplateFromProgram(program: Program): ExtractedTemplate;
499
+
500
+ interface TraversableNode {
501
+ readonly type: string;
502
+ }
503
+ /**
504
+ * Iterative pre-order DFS walker for Oxc AST.
505
+ *
506
+ * Replaces the previous recursive implementation to eliminate two hotpath costs:
507
+ * 1. Call-stack overflow risk on deeply nested ASTs (large files with many nested
508
+ * arrow functions, ternaries, optional chaining, etc.).
509
+ * 2. `Object.keys()` allocation on every node — on a 10 000-node AST that was
510
+ * 10 000 short-lived string arrays per file. `for...in` iterates without
511
+ * allocating the intermediate array.
512
+ *
513
+ * Traversal order: identical to the recursive version (pre-order DFS, children
514
+ * visited in property-key insertion order). The engine relies only on the guarantee
515
+ * that every node is visited; it does NOT depend on parent-before-child ordering
516
+ * beyond what pre-order naturally provides.
517
+ *
518
+ * @param root - The Program (or any sub-tree root) to walk
519
+ * @param visitor - Called for each node. Return `false` to skip that node's children.
520
+ */
521
+ declare function walkProgram(root: TraversableNode | null | undefined, visitor: (node: TraversableNode) => void | boolean): void;
522
+
523
+ export { type AngularClassNode, type AnyAngularClassNode, type ArrayExpression, type ArrowFunctionExpression, type AssignmentExpression, type BlockStatement, type BooleanLiteral, type CallExpression, ChangeDetectionStrategy, type ClassBody, type ClassDeclaration, type ComponentMetadata, type CssParserResult, type CssResult, type DecoratedPropertyNode, type Decorator, type Expression, type ExpressionStatement, type ExtractedTemplate, type FunctionExpression, type HtmlParserResult, type Identifier, type IfStatement, type LiteralValue, type MemberExpression, type MetadataValue, type MethodDefinition, type MissingValue, type NewExpression, type Node, type NonLiteralValue, type ObjectExpression, type ObjectProperty, type PropertyDefinition, type ReturnStatement, type SpreadElement, type StringLiteral, type TemplateAnalysis, type TemplateAttributeNode, type TemplateBlock, type TemplateBlockNode, type TemplateBlockParameter, type TemplateExpressionNode, type TsParserResult, type UpdateExpression, analyzeComponent, analyzeTemplate, extractTemplateFromProgram, getComponentCacheStats, getDecoratorNameUnsafe, getDecoratorObjectArgUnsafe, getInputSignalAliasUnsafe, getKeyNameUnsafe, getLiteralBooleanValueUnsafe, getLiteralStringValueUnsafe, getObjectPropertyUnsafe, hasDecorator, hasObjectProperty, isComponent, isInputSignal, isStandalone, matchesMemberExpression, parseCss, parseHtml, parseTs, resetComponentCacheStats, toAngularClassStream, toAnyAngularClassStream, toCallExpressionStream, toDecoratedPropertyStream, toNewExpressionStream, usesOnPush, walkProgram };