@rcrsr/rill-cli 0.6.0

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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/dist/check/config.d.ts +20 -0
  3. package/dist/check/config.d.ts.map +1 -0
  4. package/dist/check/config.js +151 -0
  5. package/dist/check/config.js.map +1 -0
  6. package/dist/check/fixer.d.ts +39 -0
  7. package/dist/check/fixer.d.ts.map +1 -0
  8. package/dist/check/fixer.js +119 -0
  9. package/dist/check/fixer.js.map +1 -0
  10. package/dist/check/index.d.ts +10 -0
  11. package/dist/check/index.d.ts.map +1 -0
  12. package/dist/check/index.js +21 -0
  13. package/dist/check/index.js.map +1 -0
  14. package/dist/check/rules/anti-patterns.d.ts +65 -0
  15. package/dist/check/rules/anti-patterns.d.ts.map +1 -0
  16. package/dist/check/rules/anti-patterns.js +481 -0
  17. package/dist/check/rules/anti-patterns.js.map +1 -0
  18. package/dist/check/rules/closures.d.ts +66 -0
  19. package/dist/check/rules/closures.d.ts.map +1 -0
  20. package/dist/check/rules/closures.js +370 -0
  21. package/dist/check/rules/closures.js.map +1 -0
  22. package/dist/check/rules/collections.d.ts +90 -0
  23. package/dist/check/rules/collections.d.ts.map +1 -0
  24. package/dist/check/rules/collections.js +373 -0
  25. package/dist/check/rules/collections.js.map +1 -0
  26. package/dist/check/rules/conditionals.d.ts +41 -0
  27. package/dist/check/rules/conditionals.d.ts.map +1 -0
  28. package/dist/check/rules/conditionals.js +134 -0
  29. package/dist/check/rules/conditionals.js.map +1 -0
  30. package/dist/check/rules/flow.d.ts +46 -0
  31. package/dist/check/rules/flow.d.ts.map +1 -0
  32. package/dist/check/rules/flow.js +206 -0
  33. package/dist/check/rules/flow.js.map +1 -0
  34. package/dist/check/rules/formatting.d.ts +143 -0
  35. package/dist/check/rules/formatting.d.ts.map +1 -0
  36. package/dist/check/rules/formatting.js +656 -0
  37. package/dist/check/rules/formatting.js.map +1 -0
  38. package/dist/check/rules/helpers.d.ts +26 -0
  39. package/dist/check/rules/helpers.d.ts.map +1 -0
  40. package/dist/check/rules/helpers.js +66 -0
  41. package/dist/check/rules/helpers.js.map +1 -0
  42. package/dist/check/rules/index.d.ts +21 -0
  43. package/dist/check/rules/index.d.ts.map +1 -0
  44. package/dist/check/rules/index.js +78 -0
  45. package/dist/check/rules/index.js.map +1 -0
  46. package/dist/check/rules/loops.d.ts +77 -0
  47. package/dist/check/rules/loops.d.ts.map +1 -0
  48. package/dist/check/rules/loops.js +310 -0
  49. package/dist/check/rules/loops.js.map +1 -0
  50. package/dist/check/rules/naming.d.ts +21 -0
  51. package/dist/check/rules/naming.d.ts.map +1 -0
  52. package/dist/check/rules/naming.js +174 -0
  53. package/dist/check/rules/naming.js.map +1 -0
  54. package/dist/check/rules/strings.d.ts +28 -0
  55. package/dist/check/rules/strings.d.ts.map +1 -0
  56. package/dist/check/rules/strings.js +79 -0
  57. package/dist/check/rules/strings.js.map +1 -0
  58. package/dist/check/rules/types.d.ts +41 -0
  59. package/dist/check/rules/types.d.ts.map +1 -0
  60. package/dist/check/rules/types.js +167 -0
  61. package/dist/check/rules/types.js.map +1 -0
  62. package/dist/check/types.d.ts +112 -0
  63. package/dist/check/types.d.ts.map +1 -0
  64. package/dist/check/types.js +6 -0
  65. package/dist/check/types.js.map +1 -0
  66. package/dist/check/validator.d.ts +18 -0
  67. package/dist/check/validator.d.ts.map +1 -0
  68. package/dist/check/validator.js +110 -0
  69. package/dist/check/validator.js.map +1 -0
  70. package/dist/check/visitor.d.ts +33 -0
  71. package/dist/check/visitor.d.ts.map +1 -0
  72. package/dist/check/visitor.js +259 -0
  73. package/dist/check/visitor.js.map +1 -0
  74. package/dist/cli-check.d.ts +43 -0
  75. package/dist/cli-check.d.ts.map +1 -0
  76. package/dist/cli-check.js +366 -0
  77. package/dist/cli-check.js.map +1 -0
  78. package/dist/cli-error-enrichment.d.ts +73 -0
  79. package/dist/cli-error-enrichment.d.ts.map +1 -0
  80. package/dist/cli-error-enrichment.js +205 -0
  81. package/dist/cli-error-enrichment.js.map +1 -0
  82. package/dist/cli-error-formatter.d.ts +45 -0
  83. package/dist/cli-error-formatter.d.ts.map +1 -0
  84. package/dist/cli-error-formatter.js +218 -0
  85. package/dist/cli-error-formatter.js.map +1 -0
  86. package/dist/cli-eval.d.ts +15 -0
  87. package/dist/cli-eval.d.ts.map +1 -0
  88. package/dist/cli-eval.js +116 -0
  89. package/dist/cli-eval.js.map +1 -0
  90. package/dist/cli-exec.d.ts +58 -0
  91. package/dist/cli-exec.d.ts.map +1 -0
  92. package/dist/cli-exec.js +326 -0
  93. package/dist/cli-exec.js.map +1 -0
  94. package/dist/cli-explain.d.ts +24 -0
  95. package/dist/cli-explain.d.ts.map +1 -0
  96. package/dist/cli-explain.js +68 -0
  97. package/dist/cli-explain.js.map +1 -0
  98. package/dist/cli-lsp-diagnostic.d.ts +35 -0
  99. package/dist/cli-lsp-diagnostic.d.ts.map +1 -0
  100. package/dist/cli-lsp-diagnostic.js +98 -0
  101. package/dist/cli-lsp-diagnostic.js.map +1 -0
  102. package/dist/cli-module-loader.d.ts +19 -0
  103. package/dist/cli-module-loader.d.ts.map +1 -0
  104. package/dist/cli-module-loader.js +83 -0
  105. package/dist/cli-module-loader.js.map +1 -0
  106. package/dist/cli-shared.d.ts +62 -0
  107. package/dist/cli-shared.d.ts.map +1 -0
  108. package/dist/cli-shared.js +158 -0
  109. package/dist/cli-shared.js.map +1 -0
  110. package/dist/cli.d.ts +13 -0
  111. package/dist/cli.d.ts.map +1 -0
  112. package/dist/cli.js +62 -0
  113. package/dist/cli.js.map +1 -0
  114. package/dist/test-internal-import.d.ts +2 -0
  115. package/dist/test-internal-import.d.ts.map +1 -0
  116. package/dist/test-internal-import.js +7 -0
  117. package/dist/test-internal-import.js.map +1 -0
  118. package/package.json +24 -0
  119. package/src/check/config.ts +202 -0
  120. package/src/check/fixer.ts +174 -0
  121. package/src/check/index.ts +39 -0
  122. package/src/check/rules/anti-patterns.ts +585 -0
  123. package/src/check/rules/closures.ts +445 -0
  124. package/src/check/rules/collections.ts +437 -0
  125. package/src/check/rules/conditionals.ts +155 -0
  126. package/src/check/rules/flow.ts +262 -0
  127. package/src/check/rules/formatting.ts +811 -0
  128. package/src/check/rules/helpers.ts +89 -0
  129. package/src/check/rules/index.ts +140 -0
  130. package/src/check/rules/loops.ts +372 -0
  131. package/src/check/rules/naming.ts +242 -0
  132. package/src/check/rules/strings.ts +104 -0
  133. package/src/check/rules/types.ts +214 -0
  134. package/src/check/types.ts +163 -0
  135. package/src/check/validator.ts +136 -0
  136. package/src/check/visitor.ts +338 -0
  137. package/src/cli-check.ts +456 -0
  138. package/src/cli-error-enrichment.ts +274 -0
  139. package/src/cli-error-formatter.ts +313 -0
  140. package/src/cli-eval.ts +145 -0
  141. package/src/cli-exec.ts +408 -0
  142. package/src/cli-explain.ts +76 -0
  143. package/src/cli-lsp-diagnostic.ts +132 -0
  144. package/src/cli-module-loader.ts +101 -0
  145. package/src/cli-shared.ts +187 -0
  146. package/tests/check/cli-check.test.ts +189 -0
  147. package/tests/check/config.test.ts +350 -0
  148. package/tests/check/fixer.test.ts +373 -0
  149. package/tests/check/format-diagnostics.test.ts +327 -0
  150. package/tests/check/rules/anti-patterns.test.ts +467 -0
  151. package/tests/check/rules/closures.test.ts +192 -0
  152. package/tests/check/rules/collections.test.ts +380 -0
  153. package/tests/check/rules/conditionals.test.ts +185 -0
  154. package/tests/check/rules/flow.test.ts +250 -0
  155. package/tests/check/rules/formatting.test.ts +755 -0
  156. package/tests/check/rules/loops.test.ts +334 -0
  157. package/tests/check/rules/naming.test.ts +336 -0
  158. package/tests/check/rules/strings.test.ts +129 -0
  159. package/tests/check/rules/types.test.ts +257 -0
  160. package/tests/check/validator.test.ts +444 -0
  161. package/tests/check/visitor.test.ts +171 -0
  162. package/tests/cli/check.test.ts +801 -0
  163. package/tests/cli/error-enrichment.test.ts +510 -0
  164. package/tests/cli/error-formatter.test.ts +631 -0
  165. package/tests/cli/eval.test.ts +85 -0
  166. package/tests/cli/exec.test.ts +537 -0
  167. package/tests/cli-explain.test.ts +249 -0
  168. package/tests/cli-lsp-diagnostic.test.ts +202 -0
  169. package/tests/cli-shared.test.ts +439 -0
  170. package/tsconfig.json +9 -0
  171. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Check Types
3
+ * Type definitions for the rill-check static analysis tool.
4
+ */
5
+
6
+ import type {
7
+ SourceLocation,
8
+ SourceSpan,
9
+ ScriptNode,
10
+ NodeType,
11
+ ASTNode,
12
+ } from '@rcrsr/rill';
13
+
14
+ // ============================================================
15
+ // SEVERITY AND RULE STATE
16
+ // ============================================================
17
+
18
+ /** Diagnostic severity levels */
19
+ export type Severity = 'error' | 'warning' | 'info';
20
+
21
+ /** Rule state configuration */
22
+ export type RuleState = 'on' | 'off' | 'warn';
23
+
24
+ // ============================================================
25
+ // DIAGNOSTIC DATA
26
+ // ============================================================
27
+
28
+ /**
29
+ * Fix suggestion for a diagnostic.
30
+ * Provides automated fix information that can be applied to source code.
31
+ */
32
+ export interface Fix {
33
+ /** Human-readable description of what the fix does */
34
+ readonly description: string;
35
+ /** Whether the fix can be safely applied automatically */
36
+ readonly applicable: boolean;
37
+ /** Source range to replace */
38
+ readonly range: SourceSpan;
39
+ /** Replacement text */
40
+ readonly replacement: string;
41
+ }
42
+
43
+ /**
44
+ * A single diagnostic issue found during validation.
45
+ * Represents errors, warnings, or informational messages from static analysis.
46
+ */
47
+ export interface Diagnostic {
48
+ /** Location of the issue in source */
49
+ readonly location: SourceLocation;
50
+ /** Severity level */
51
+ readonly severity: Severity;
52
+ /** Rule code (e.g., NAMING_SNAKE_CASE) */
53
+ readonly code: string;
54
+ /** Human-readable description */
55
+ readonly message: string;
56
+ /** Source line containing the issue */
57
+ readonly context: string;
58
+ /** Optional automatic fix */
59
+ readonly fix: Fix | null;
60
+ }
61
+
62
+ // ============================================================
63
+ // CHECK CONFIGURATION
64
+ // ============================================================
65
+
66
+ /**
67
+ * Configuration for check rules and severity overrides.
68
+ * Controls which rules are active and at what severity level.
69
+ */
70
+ export interface CheckConfig {
71
+ /** Per-rule enable/disable/warn state */
72
+ readonly rules: Record<string, RuleState>;
73
+ /** Severity overrides by rule code */
74
+ readonly severity: Record<string, Severity>;
75
+ }
76
+
77
+ // ============================================================
78
+ // VALIDATION CONTEXT
79
+ // ============================================================
80
+
81
+ /**
82
+ * Context for validation passes.
83
+ * Tracks source, AST, configuration, and accumulated diagnostics.
84
+ */
85
+ export interface ValidationContext {
86
+ /** Original source text */
87
+ readonly source: string;
88
+ /** Parsed AST */
89
+ readonly ast: ScriptNode;
90
+ /** Active configuration */
91
+ readonly config: CheckConfig;
92
+ /** Accumulated diagnostics */
93
+ readonly diagnostics: Diagnostic[];
94
+ /** Variable definitions for collision detection */
95
+ readonly variables: Map<string, SourceLocation>;
96
+ /** HostCall nodes that are wrapped in type assertions */
97
+ readonly assertedHostCalls: Set<ASTNode>;
98
+ /** Closure scope IDs for variables (maps variable name to closure AST node) */
99
+ readonly variableScopes: Map<string, ASTNode | null>;
100
+ /** Current closure scope stack during traversal */
101
+ readonly scopeStack: ASTNode[];
102
+ }
103
+
104
+ /**
105
+ * Context for fix generation.
106
+ * Provides access to source and AST for computing fix replacements.
107
+ */
108
+ export interface FixContext {
109
+ /** Original source text */
110
+ readonly source: string;
111
+ /** Parsed AST */
112
+ readonly ast: ScriptNode;
113
+ }
114
+
115
+ // ============================================================
116
+ // VALIDATION RULES
117
+ // ============================================================
118
+
119
+ /** Rule category for grouping and organization */
120
+ export type RuleCategory =
121
+ | 'naming'
122
+ | 'flow'
123
+ | 'collections'
124
+ | 'loops'
125
+ | 'conditionals'
126
+ | 'closures'
127
+ | 'types'
128
+ | 'strings'
129
+ | 'errors'
130
+ | 'formatting'
131
+ | 'anti-patterns';
132
+
133
+ /**
134
+ * Validation rule interface.
135
+ * Rules are stateless - all context passed via ValidationContext.
136
+ * Rules return diagnostics, never throw.
137
+ * fix() must preserve semantics (no behavior changes).
138
+ */
139
+ export interface ValidationRule {
140
+ /** Unique rule code (e.g., NAMING_SNAKE_CASE) */
141
+ readonly code: string;
142
+
143
+ /** Rule category for grouping */
144
+ readonly category: RuleCategory;
145
+
146
+ /** Default severity level */
147
+ readonly severity: Severity;
148
+
149
+ /** Node types this rule applies to */
150
+ readonly nodeTypes: NodeType[];
151
+
152
+ /**
153
+ * Validate a node, returning diagnostics for violations.
154
+ * Called for each node matching nodeTypes.
155
+ */
156
+ validate(node: ASTNode, context: ValidationContext): Diagnostic[];
157
+
158
+ /**
159
+ * Optionally generate a fix for a diagnostic.
160
+ * Returns null if fix not applicable.
161
+ */
162
+ fix?(node: ASTNode, context: FixContext): Fix | null;
163
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Script Validator
3
+ * Orchestrates validation by traversing AST and invoking enabled rules.
4
+ */
5
+
6
+ import type {
7
+ ScriptNode,
8
+ ASTNode,
9
+ CaptureNode,
10
+ TypeAssertionNode,
11
+ } from '@rcrsr/rill';
12
+ import type { CheckConfig, Diagnostic, ValidationContext } from './types.js';
13
+ import { visitNode, type RuleVisitor } from './visitor.js';
14
+ import { VALIDATION_RULES } from './rules/index.js';
15
+
16
+ // ============================================================
17
+ // VALIDATION ORCHESTRATOR
18
+ // ============================================================
19
+
20
+ /**
21
+ * Validate script AST against all enabled rules.
22
+ * Traverses AST using visitor pattern, invoking enabled rules for matching nodes.
23
+ * Returns diagnostics sorted by line number, then column.
24
+ *
25
+ * @param ast - Parsed script AST to validate
26
+ * @param source - Original source text for context extraction
27
+ * @param config - Configuration determining which rules are active
28
+ * @returns Array of diagnostics sorted by location
29
+ */
30
+ export function validateScript(
31
+ ast: ScriptNode,
32
+ source: string,
33
+ config: CheckConfig
34
+ ): Diagnostic[] {
35
+ // Create validation context
36
+ const context: ValidationContext = {
37
+ source,
38
+ ast,
39
+ config,
40
+ diagnostics: [],
41
+ variables: new Map(),
42
+ assertedHostCalls: new Set(),
43
+ variableScopes: new Map(),
44
+ scopeStack: [],
45
+ };
46
+
47
+ // Create visitor that invokes enabled rules
48
+ const visitor: RuleVisitor = {
49
+ enter(node: ASTNode, ctx: ValidationContext): void {
50
+ // Track closure scope entry
51
+ if (node.type === 'Closure') {
52
+ ctx.scopeStack.push(node);
53
+ }
54
+
55
+ // Track HostCall nodes wrapped in TypeAssertion BEFORE rules check
56
+ if (node.type === 'TypeAssertion') {
57
+ const operand = (node as TypeAssertionNode).operand;
58
+ if (operand?.primary.type === 'HostCall') {
59
+ ctx.assertedHostCalls.add(operand.primary);
60
+ }
61
+ }
62
+
63
+ // For each enabled rule that applies to this node type
64
+ for (const rule of VALIDATION_RULES) {
65
+ // Skip if rule not enabled
66
+ if (!isRuleEnabled(rule.code, ctx.config)) {
67
+ continue;
68
+ }
69
+
70
+ // Skip if rule doesn't apply to this node type
71
+ if (!rule.nodeTypes.includes(node.type)) {
72
+ continue;
73
+ }
74
+
75
+ // Invoke rule validation and accumulate diagnostics
76
+ const ruleDiagnostics = rule.validate(node, ctx);
77
+ ctx.diagnostics.push(...ruleDiagnostics);
78
+ }
79
+
80
+ // Track variable captures AFTER rules check (for reassignment detection)
81
+ if (node.type === 'Capture') {
82
+ const captureNode = node as CaptureNode;
83
+ if (!ctx.variables.has(captureNode.name)) {
84
+ ctx.variables.set(captureNode.name, node.span.start);
85
+ // Track which closure scope this variable belongs to
86
+ const currentScope =
87
+ ctx.scopeStack.length > 0
88
+ ? ctx.scopeStack[ctx.scopeStack.length - 1]
89
+ : null;
90
+ ctx.variableScopes.set(captureNode.name, currentScope ?? null);
91
+ }
92
+ }
93
+ },
94
+
95
+ exit(node: ASTNode, ctx: ValidationContext): void {
96
+ // Track closure scope exit
97
+ if (node.type === 'Closure') {
98
+ ctx.scopeStack.pop();
99
+ }
100
+ },
101
+ };
102
+
103
+ // Traverse AST with visitor
104
+ visitNode(ast, context, visitor);
105
+
106
+ // Sort diagnostics by location (line first, then column)
107
+ return sortDiagnostics(context.diagnostics);
108
+ }
109
+
110
+ // ============================================================
111
+ // HELPERS
112
+ // ============================================================
113
+
114
+ /**
115
+ * Check if a rule is enabled based on configuration.
116
+ * Rules are enabled if state is 'on' or 'warn'.
117
+ */
118
+ function isRuleEnabled(ruleCode: string, config: CheckConfig): boolean {
119
+ const state = config.rules[ruleCode];
120
+ return state === 'on' || state === 'warn';
121
+ }
122
+
123
+ /**
124
+ * Sort diagnostics by line number first, then column number.
125
+ * Stable sort preserves original order for diagnostics at same location.
126
+ */
127
+ function sortDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
128
+ return [...diagnostics].sort((a, b) => {
129
+ // Sort by line first
130
+ if (a.location.line !== b.location.line) {
131
+ return a.location.line - b.location.line;
132
+ }
133
+ // Then by column
134
+ return a.location.column - b.location.column;
135
+ });
136
+ }
@@ -0,0 +1,338 @@
1
+ /**
2
+ * AST Visitor
3
+ * Recursive traversal with enter/exit callbacks for validation rules.
4
+ */
5
+
6
+ import type { ASTNode } from '@rcrsr/rill';
7
+ import type { ValidationContext } from './types.js';
8
+
9
+ // ============================================================
10
+ // VISITOR INTERFACE
11
+ // ============================================================
12
+
13
+ /**
14
+ * Visitor pattern interface for AST traversal.
15
+ * Provides enter/exit callbacks invoked before and after visiting children.
16
+ */
17
+ export interface RuleVisitor {
18
+ /**
19
+ * Called before visiting node's children.
20
+ * Use for pre-order traversal validation.
21
+ */
22
+ enter(node: ASTNode, context: ValidationContext): void;
23
+
24
+ /**
25
+ * Called after visiting node's children.
26
+ * Use for post-order traversal validation.
27
+ */
28
+ exit(node: ASTNode, context: ValidationContext): void;
29
+ }
30
+
31
+ // ============================================================
32
+ // VISITOR FUNCTION
33
+ // ============================================================
34
+
35
+ /**
36
+ * Recursively visit AST nodes with enter/exit callbacks.
37
+ * Handles all 46 node types from ASTNode union.
38
+ *
39
+ * Traversal order:
40
+ * 1. visitor.enter(node)
41
+ * 2. Recurse into children
42
+ * 3. visitor.exit(node)
43
+ */
44
+ export function visitNode(
45
+ node: ASTNode,
46
+ context: ValidationContext,
47
+ visitor: RuleVisitor
48
+ ): void {
49
+ // Enter callback before children
50
+ visitor.enter(node, context);
51
+
52
+ // Recurse based on node type
53
+ switch (node.type) {
54
+ case 'Script':
55
+ if (node.frontmatter) {
56
+ visitNode(node.frontmatter, context, visitor);
57
+ }
58
+ for (const stmt of node.statements) {
59
+ visitNode(stmt, context, visitor);
60
+ }
61
+ break;
62
+
63
+ case 'Frontmatter':
64
+ // Leaf node - no children
65
+ break;
66
+
67
+ case 'Statement':
68
+ visitNode(node.expression, context, visitor);
69
+ break;
70
+
71
+ case 'AnnotatedStatement':
72
+ for (const arg of node.annotations) {
73
+ visitNode(arg, context, visitor);
74
+ }
75
+ visitNode(node.statement, context, visitor);
76
+ break;
77
+
78
+ case 'NamedArg':
79
+ visitNode(node.value, context, visitor);
80
+ break;
81
+
82
+ case 'SpreadArg':
83
+ visitNode(node.expression, context, visitor);
84
+ break;
85
+
86
+ case 'PipeChain':
87
+ visitNode(node.head, context, visitor);
88
+ for (const pipe of node.pipes) {
89
+ visitNode(pipe, context, visitor);
90
+ }
91
+ if (node.terminator) {
92
+ visitNode(node.terminator, context, visitor);
93
+ }
94
+ break;
95
+
96
+ case 'PostfixExpr':
97
+ visitNode(node.primary, context, visitor);
98
+ for (const method of node.methods) {
99
+ visitNode(method, context, visitor);
100
+ }
101
+ break;
102
+
103
+ case 'BinaryExpr':
104
+ visitNode(node.left, context, visitor);
105
+ visitNode(node.right, context, visitor);
106
+ break;
107
+
108
+ case 'UnaryExpr':
109
+ visitNode(node.operand, context, visitor);
110
+ break;
111
+
112
+ case 'GroupedExpr':
113
+ visitNode(node.expression, context, visitor);
114
+ break;
115
+
116
+ case 'StringLiteral':
117
+ for (const part of node.parts) {
118
+ if (typeof part !== 'string') {
119
+ visitNode(part, context, visitor);
120
+ }
121
+ }
122
+ break;
123
+
124
+ case 'Interpolation':
125
+ visitNode(node.expression, context, visitor);
126
+ break;
127
+
128
+ case 'NumberLiteral':
129
+ case 'BoolLiteral':
130
+ // Leaf nodes - no children
131
+ break;
132
+
133
+ case 'Tuple':
134
+ for (const element of node.elements) {
135
+ visitNode(element, context, visitor);
136
+ }
137
+ break;
138
+
139
+ case 'ListSpread':
140
+ visitNode(node.expression, context, visitor);
141
+ break;
142
+
143
+ case 'Dict':
144
+ for (const entry of node.entries) {
145
+ visitNode(entry, context, visitor);
146
+ }
147
+ break;
148
+
149
+ case 'DictEntry':
150
+ visitNode(node.value, context, visitor);
151
+ break;
152
+
153
+ case 'Closure':
154
+ for (const param of node.params) {
155
+ visitNode(param, context, visitor);
156
+ }
157
+ visitNode(node.body, context, visitor);
158
+ break;
159
+
160
+ case 'ClosureParam':
161
+ if (node.defaultValue) {
162
+ visitNode(node.defaultValue, context, visitor);
163
+ }
164
+ break;
165
+
166
+ case 'Variable':
167
+ if (node.defaultValue) {
168
+ visitNode(node.defaultValue, context, visitor);
169
+ }
170
+ break;
171
+
172
+ case 'HostCall':
173
+ for (const arg of node.args) {
174
+ visitNode(arg, context, visitor);
175
+ }
176
+ break;
177
+
178
+ case 'ClosureCall':
179
+ for (const arg of node.args) {
180
+ visitNode(arg, context, visitor);
181
+ }
182
+ break;
183
+
184
+ case 'MethodCall':
185
+ for (const arg of node.args) {
186
+ visitNode(arg, context, visitor);
187
+ }
188
+ break;
189
+
190
+ case 'Invoke':
191
+ for (const arg of node.args) {
192
+ visitNode(arg, context, visitor);
193
+ }
194
+ break;
195
+
196
+ case 'PipeInvoke':
197
+ for (const arg of node.args) {
198
+ visitNode(arg, context, visitor);
199
+ }
200
+ break;
201
+
202
+ case 'Conditional':
203
+ if (node.input) {
204
+ visitNode(node.input, context, visitor);
205
+ }
206
+ if (node.condition) {
207
+ visitNode(node.condition, context, visitor);
208
+ }
209
+ visitNode(node.thenBranch, context, visitor);
210
+ if (node.elseBranch) {
211
+ visitNode(node.elseBranch, context, visitor);
212
+ }
213
+ break;
214
+
215
+ case 'WhileLoop':
216
+ visitNode(node.condition, context, visitor);
217
+ visitNode(node.body, context, visitor);
218
+ break;
219
+
220
+ case 'DoWhileLoop':
221
+ if (node.input) {
222
+ visitNode(node.input, context, visitor);
223
+ }
224
+ visitNode(node.body, context, visitor);
225
+ visitNode(node.condition, context, visitor);
226
+ break;
227
+
228
+ case 'Block':
229
+ for (const stmt of node.statements) {
230
+ visitNode(stmt, context, visitor);
231
+ }
232
+ break;
233
+
234
+ case 'EachExpr':
235
+ visitNode(node.body, context, visitor);
236
+ if (node.accumulator) {
237
+ visitNode(node.accumulator, context, visitor);
238
+ }
239
+ break;
240
+
241
+ case 'MapExpr':
242
+ visitNode(node.body, context, visitor);
243
+ break;
244
+
245
+ case 'FoldExpr':
246
+ visitNode(node.body, context, visitor);
247
+ if (node.accumulator) {
248
+ visitNode(node.accumulator, context, visitor);
249
+ }
250
+ break;
251
+
252
+ case 'FilterExpr':
253
+ visitNode(node.body, context, visitor);
254
+ break;
255
+
256
+ case 'ClosureChain':
257
+ visitNode(node.target, context, visitor);
258
+ break;
259
+
260
+ case 'Destructure':
261
+ for (const element of node.elements) {
262
+ visitNode(element, context, visitor);
263
+ }
264
+ break;
265
+
266
+ case 'DestructPattern':
267
+ if (node.nested) {
268
+ visitNode(node.nested, context, visitor);
269
+ }
270
+ break;
271
+
272
+ case 'Slice':
273
+ if (node.start) {
274
+ visitNode(node.start, context, visitor);
275
+ }
276
+ if (node.stop) {
277
+ visitNode(node.stop, context, visitor);
278
+ }
279
+ if (node.step) {
280
+ visitNode(node.step, context, visitor);
281
+ }
282
+ break;
283
+
284
+ case 'Spread':
285
+ if (node.operand) {
286
+ visitNode(node.operand, context, visitor);
287
+ }
288
+ break;
289
+
290
+ case 'TypeAssertion':
291
+ if (node.operand) {
292
+ visitNode(node.operand, context, visitor);
293
+ }
294
+ break;
295
+
296
+ case 'TypeCheck':
297
+ if (node.operand) {
298
+ visitNode(node.operand, context, visitor);
299
+ }
300
+ break;
301
+
302
+ case 'Assert':
303
+ visitNode(node.condition, context, visitor);
304
+ if (node.message) {
305
+ visitNode(node.message, context, visitor);
306
+ }
307
+ break;
308
+
309
+ case 'Capture':
310
+ case 'Break':
311
+ case 'Return':
312
+ case 'Pass':
313
+ // Leaf nodes - no children
314
+ break;
315
+
316
+ case 'RecoveryError':
317
+ // Recovery error node - no children to visit
318
+ break;
319
+
320
+ case 'Error':
321
+ // Error statement node - visit message if present
322
+ if (node.message) {
323
+ visitNode(node.message, context, visitor);
324
+ }
325
+ break;
326
+
327
+ default: {
328
+ // Exhaustive check: if we reach here, a node type is missing
329
+ const _exhaustive: never = node;
330
+ throw new Error(
331
+ `Unhandled node type in visitor: ${(_exhaustive as ASTNode).type}`
332
+ );
333
+ }
334
+ }
335
+
336
+ // Exit callback after children
337
+ visitor.exit(node, context);
338
+ }