@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,481 @@
1
+ /**
2
+ * Anti-Pattern Rules
3
+ * Enforces best practices from docs/guide-conventions.md:411-462.
4
+ */
5
+ import { extractContextLine } from './helpers.js';
6
+ // ============================================================
7
+ // AVOID_REASSIGNMENT RULE
8
+ // ============================================================
9
+ /**
10
+ * Warns on variable reassignment patterns.
11
+ * Variables lock to their first type, and reassignment suggests confusing
12
+ * flow control. Prefer functional style or new variables.
13
+ *
14
+ * Detection:
15
+ * - Capture node (=> $var) where $var already exists in validation context
16
+ * - Tracks variables seen during validation pass
17
+ *
18
+ * Valid alternatives:
19
+ * - Use new variable: $result1, $result2
20
+ * - Functional chains: value -> op1 -> op2
21
+ *
22
+ * References:
23
+ * - docs/guide-conventions.md:413-424
24
+ */
25
+ export const AVOID_REASSIGNMENT = {
26
+ code: 'AVOID_REASSIGNMENT',
27
+ category: 'anti-patterns',
28
+ severity: 'warning',
29
+ nodeTypes: ['Capture'],
30
+ validate(node, context) {
31
+ const captureNode = node;
32
+ const varName = captureNode.name;
33
+ // Check if this variable was already captured before
34
+ if (context.variables.has(varName)) {
35
+ const firstLocation = context.variables.get(varName);
36
+ const variableScope = context.variableScopes.get(varName) ?? null;
37
+ // Get the current closure scope (if we're inside a closure)
38
+ const currentClosureScope = context.scopeStack.length > 0
39
+ ? context.scopeStack[context.scopeStack.length - 1]
40
+ : null;
41
+ // Only warn if the variable is truly in the same scope or a parent scope
42
+ // Variables in sibling closures are independent and should not trigger warnings
43
+ const isInSameOrParentScope = isVariableInParentScope(variableScope, currentClosureScope, context.scopeStack);
44
+ if (isInSameOrParentScope) {
45
+ return [
46
+ {
47
+ location: captureNode.span.start,
48
+ severity: 'warning',
49
+ code: 'AVOID_REASSIGNMENT',
50
+ message: `Variable reassignment detected: '$${varName}' first defined at line ${firstLocation.line}. Prefer new variable or functional style.`,
51
+ context: extractContextLine(captureNode.span.start.line, context.source),
52
+ fix: null, // Cannot auto-fix without understanding intent
53
+ },
54
+ ];
55
+ }
56
+ }
57
+ return [];
58
+ },
59
+ };
60
+ // ============================================================
61
+ // COMPLEX_CONDITION RULE
62
+ // ============================================================
63
+ /**
64
+ * Warns on complex nested boolean conditions.
65
+ * Complex conditions with multiple nested operators are hard to read.
66
+ * Extract to named variables for clarity.
67
+ *
68
+ * Detection:
69
+ * - Conditional nodes with conditions containing 3+ boolean operators (&&, ||)
70
+ * - Nesting depth > 2 for boolean expressions
71
+ *
72
+ * Valid alternatives:
73
+ * - Extract sub-conditions to named variables
74
+ * - Split complex checks into multiple smaller checks
75
+ *
76
+ * References:
77
+ * - docs/guide-conventions.md:451-461
78
+ */
79
+ export const COMPLEX_CONDITION = {
80
+ code: 'COMPLEX_CONDITION',
81
+ category: 'anti-patterns',
82
+ severity: 'info',
83
+ nodeTypes: ['Conditional'],
84
+ validate(node, context) {
85
+ const conditionalNode = node;
86
+ const condition = conditionalNode.condition;
87
+ if (!condition) {
88
+ return [];
89
+ }
90
+ // Unwrap GroupedExpr to get to the actual condition
91
+ let unwrappedCondition = condition;
92
+ if (unwrappedCondition.type === 'GroupedExpr') {
93
+ unwrappedCondition = unwrappedCondition.expression;
94
+ }
95
+ // Count boolean operators, boolean nesting depth, and parenthetical nesting
96
+ const operatorCount = countBooleanOperators(unwrappedCondition);
97
+ const booleanDepth = getBooleanNestingDepth(unwrappedCondition);
98
+ const parenDepth = getParenNestingDepth(unwrappedCondition);
99
+ // Flag if 3+ operators, boolean nesting > 2, or excessive parentheses (> 2)
100
+ if (operatorCount >= 3 || booleanDepth > 2 || parenDepth > 2) {
101
+ return [
102
+ {
103
+ location: conditionalNode.span.start,
104
+ severity: 'info',
105
+ code: 'COMPLEX_CONDITION',
106
+ message: 'Complex condition with multiple operators. Extract to named checks for clarity.',
107
+ context: extractContextLine(conditionalNode.span.start.line, context.source),
108
+ fix: null, // Auto-fix would require semantic understanding
109
+ },
110
+ ];
111
+ }
112
+ return [];
113
+ },
114
+ };
115
+ /**
116
+ * Count boolean operators (&&, ||) in an expression tree.
117
+ */
118
+ function countBooleanOperators(node) {
119
+ let count = 0;
120
+ if (node.type === 'BinaryExpr') {
121
+ const binaryNode = node;
122
+ if (binaryNode.op === '&&' || binaryNode.op === '||') {
123
+ count = 1;
124
+ }
125
+ count += countBooleanOperators(binaryNode.left);
126
+ count += countBooleanOperators(binaryNode.right);
127
+ }
128
+ // Traverse other node types that might contain expressions
129
+ switch (node.type) {
130
+ case 'UnaryExpr': {
131
+ const unaryNode = node;
132
+ count += countBooleanOperators(unaryNode.operand);
133
+ break;
134
+ }
135
+ case 'GroupedExpr': {
136
+ const groupedNode = node;
137
+ count += countBooleanOperators(groupedNode.expression);
138
+ break;
139
+ }
140
+ case 'PipeChain': {
141
+ const pipeNode = node;
142
+ if (pipeNode.head)
143
+ count += countBooleanOperators(pipeNode.head);
144
+ if (pipeNode.pipes) {
145
+ for (const pipe of pipeNode.pipes) {
146
+ count += countBooleanOperators(pipe);
147
+ }
148
+ }
149
+ break;
150
+ }
151
+ case 'PostfixExpr': {
152
+ const postfixNode = node;
153
+ if (postfixNode.primary)
154
+ count += countBooleanOperators(postfixNode.primary);
155
+ break;
156
+ }
157
+ }
158
+ return count;
159
+ }
160
+ /**
161
+ * Calculate maximum nesting depth of boolean operators.
162
+ */
163
+ function getBooleanNestingDepth(node, currentDepth = 0) {
164
+ let maxDepth = currentDepth;
165
+ if (node.type === 'BinaryExpr') {
166
+ const binaryNode = node;
167
+ const depth = binaryNode.op === '&&' || binaryNode.op === '||'
168
+ ? currentDepth + 1
169
+ : currentDepth;
170
+ const leftDepth = getBooleanNestingDepth(binaryNode.left, depth);
171
+ const rightDepth = getBooleanNestingDepth(binaryNode.right, depth);
172
+ maxDepth = Math.max(maxDepth, leftDepth, rightDepth);
173
+ }
174
+ // Traverse other node types
175
+ switch (node.type) {
176
+ case 'UnaryExpr': {
177
+ const unaryNode = node;
178
+ maxDepth = Math.max(maxDepth, getBooleanNestingDepth(unaryNode.operand, currentDepth));
179
+ break;
180
+ }
181
+ case 'GroupedExpr': {
182
+ const groupedNode = node;
183
+ maxDepth = Math.max(maxDepth, getBooleanNestingDepth(groupedNode.expression, currentDepth));
184
+ break;
185
+ }
186
+ case 'PipeChain': {
187
+ const pipeNode = node;
188
+ if (pipeNode.head) {
189
+ maxDepth = Math.max(maxDepth, getBooleanNestingDepth(pipeNode.head, currentDepth));
190
+ }
191
+ if (pipeNode.pipes) {
192
+ for (const pipe of pipeNode.pipes) {
193
+ maxDepth = Math.max(maxDepth, getBooleanNestingDepth(pipe, currentDepth));
194
+ }
195
+ }
196
+ break;
197
+ }
198
+ case 'PostfixExpr': {
199
+ const postfixNode = node;
200
+ if (postfixNode.primary) {
201
+ maxDepth = Math.max(maxDepth, getBooleanNestingDepth(postfixNode.primary, currentDepth));
202
+ }
203
+ break;
204
+ }
205
+ }
206
+ return maxDepth;
207
+ }
208
+ // ============================================================
209
+ // LOOP_OUTER_CAPTURE RULE
210
+ // ============================================================
211
+ /**
212
+ * Detects attempts to modify outer-scope variables from inside loops.
213
+ * This is a common LLM-generated anti-pattern that never works in Rill.
214
+ *
215
+ * Rill's scoping rules mean that captures inside loop bodies create LOCAL
216
+ * variables that don't affect outer scope. This is a fundamental language
217
+ * constraint, not a style preference.
218
+ *
219
+ * WRONG - this pattern NEVER works:
220
+ * 0 => $count
221
+ * [1, 2, 3] -> each { $count + 1 => $count } # creates LOCAL $count
222
+ * $count # still 0!
223
+ *
224
+ * RIGHT - use accumulators:
225
+ * [1, 2, 3] -> fold(0) { $@ + 1 } # returns 3
226
+ * [1, 2, 3] -> each(0) { $@ + 1 } # returns [1, 2, 3]
227
+ *
228
+ * This rule catches captures inside loop/collection bodies where the
229
+ * variable name matches an outer-scope variable.
230
+ *
231
+ * References:
232
+ * - docs/ref-llm.txt (LOOP STATE PATTERNS)
233
+ * - docs/topic-variables.md (Scope Rules)
234
+ */
235
+ export const LOOP_OUTER_CAPTURE = {
236
+ code: 'LOOP_OUTER_CAPTURE',
237
+ category: 'anti-patterns',
238
+ severity: 'warning',
239
+ nodeTypes: [
240
+ 'EachExpr',
241
+ 'MapExpr',
242
+ 'FilterExpr',
243
+ 'FoldExpr',
244
+ 'WhileLoop',
245
+ 'DoWhileLoop',
246
+ ],
247
+ validate(node, context) {
248
+ const diagnostics = [];
249
+ // Get the loop body based on node type
250
+ let body = null;
251
+ switch (node.type) {
252
+ case 'EachExpr':
253
+ body = node.body;
254
+ break;
255
+ case 'MapExpr':
256
+ body = node.body;
257
+ break;
258
+ case 'FilterExpr':
259
+ body = node.body;
260
+ break;
261
+ case 'FoldExpr':
262
+ body = node.body;
263
+ break;
264
+ case 'WhileLoop':
265
+ body = node.body;
266
+ break;
267
+ case 'DoWhileLoop':
268
+ body = node.body;
269
+ break;
270
+ }
271
+ if (!body)
272
+ return diagnostics;
273
+ // Find all captures in the body
274
+ const captures = findCapturesInBody(body);
275
+ // Get the current closure scope (if we're inside a closure)
276
+ const currentClosureScope = context.scopeStack.length > 0
277
+ ? context.scopeStack[context.scopeStack.length - 1]
278
+ : null;
279
+ // Check if any capture targets an outer-scope variable
280
+ for (const capture of captures) {
281
+ if (context.variables.has(capture.name)) {
282
+ const outerLocation = context.variables.get(capture.name);
283
+ const variableScope = context.variableScopes.get(capture.name) ?? null;
284
+ // Only flag if the variable is in a parent scope, not a sibling closure
285
+ // Variable is "outer" if:
286
+ // 1. It was defined in script scope (variableScope === null), OR
287
+ // 2. It was defined in a parent closure that contains the current closure
288
+ const isOuterScope = isVariableInParentScope(variableScope, currentClosureScope, context.scopeStack);
289
+ if (isOuterScope) {
290
+ diagnostics.push({
291
+ location: capture.span.start,
292
+ severity: 'warning',
293
+ code: 'LOOP_OUTER_CAPTURE',
294
+ message: `Cannot modify outer variable '$${capture.name}' from inside loop. ` +
295
+ `Captures inside loops create LOCAL variables. ` +
296
+ `Use fold(init) with $@ accumulator, or pack state into $ as a dict. ` +
297
+ `(Outer '$${capture.name}' defined at line ${outerLocation.line})`,
298
+ context: extractContextLine(capture.span.start.line, context.source),
299
+ fix: null,
300
+ });
301
+ }
302
+ }
303
+ }
304
+ return diagnostics;
305
+ },
306
+ };
307
+ /**
308
+ * Check if a variable's scope is in the parent scope chain.
309
+ * Returns true if the variable is accessible from the current scope.
310
+ *
311
+ * A variable is "outer" (parent scope) if:
312
+ * - It was defined at script level (variableScope === null), OR
313
+ * - It was defined in the SAME closure as the loop (same scope), OR
314
+ * - It was defined in a closure that is an ancestor of the current closure
315
+ *
316
+ * A variable is NOT outer (sibling scope) if:
317
+ * - It was defined in a different closure that is not an ancestor
318
+ */
319
+ function isVariableInParentScope(variableScope, currentClosureScope, scopeStack) {
320
+ // Variable defined at script level is always outer
321
+ if (variableScope === null) {
322
+ return true;
323
+ }
324
+ // If we're not in a closure, variable can't be outer to us
325
+ if (currentClosureScope === null) {
326
+ return variableScope === null;
327
+ }
328
+ // Variable is outer if its scope is the same as current closure
329
+ // (loop body creates new scope within the closure)
330
+ if (variableScope === currentClosureScope) {
331
+ return true;
332
+ }
333
+ // Variable is outer if its scope is in our parent chain
334
+ // Check if variableScope appears in scopeStack before currentClosureScope
335
+ const currentIndex = scopeStack.indexOf(currentClosureScope);
336
+ const variableIndex = scopeStack.indexOf(variableScope);
337
+ // If variable scope is not in stack, it's not accessible
338
+ if (variableIndex === -1) {
339
+ return false;
340
+ }
341
+ // Variable is outer if it appears before current scope in stack (ancestor)
342
+ return variableIndex < currentIndex;
343
+ }
344
+ /**
345
+ * Recursively find all Capture nodes in a loop body.
346
+ */
347
+ function findCapturesInBody(node) {
348
+ const captures = [];
349
+ function traverse(n) {
350
+ if (n.type === 'Capture') {
351
+ captures.push(n);
352
+ return;
353
+ }
354
+ // Traverse children based on node type
355
+ switch (n.type) {
356
+ case 'Block':
357
+ for (const stmt of n.statements)
358
+ traverse(stmt);
359
+ break;
360
+ case 'Statement':
361
+ traverse(n.expression);
362
+ break;
363
+ case 'AnnotatedStatement':
364
+ traverse(n.statement);
365
+ break;
366
+ case 'PipeChain':
367
+ traverse(n.head);
368
+ for (const pipe of n.pipes)
369
+ traverse(pipe);
370
+ if (n.terminator)
371
+ traverse(n.terminator);
372
+ break;
373
+ case 'PostfixExpr':
374
+ traverse(n.primary);
375
+ for (const method of n.methods)
376
+ traverse(method);
377
+ break;
378
+ case 'BinaryExpr':
379
+ traverse(n.left);
380
+ traverse(n.right);
381
+ break;
382
+ case 'UnaryExpr':
383
+ traverse(n.operand);
384
+ break;
385
+ case 'GroupedExpr':
386
+ traverse(n.expression);
387
+ break;
388
+ case 'Conditional':
389
+ if (n.input)
390
+ traverse(n.input);
391
+ if (n.condition)
392
+ traverse(n.condition);
393
+ traverse(n.thenBranch);
394
+ if (n.elseBranch)
395
+ traverse(n.elseBranch);
396
+ break;
397
+ case 'Closure':
398
+ // Don't traverse into closures - they have their own scope
399
+ break;
400
+ // Nested loops - traverse their bodies too
401
+ case 'WhileLoop':
402
+ traverse(n.body);
403
+ break;
404
+ case 'DoWhileLoop':
405
+ traverse(n.body);
406
+ break;
407
+ case 'EachExpr':
408
+ case 'MapExpr':
409
+ case 'FilterExpr':
410
+ case 'FoldExpr':
411
+ traverse(n.body);
412
+ break;
413
+ }
414
+ }
415
+ traverse(node);
416
+ return captures;
417
+ }
418
+ /**
419
+ * Calculate maximum consecutive GroupedExpr (parenthetical) nesting depth.
420
+ * Counts chains of nested parentheses like ((($x))).
421
+ * Treats PipeChain (single head) and PostfixExpr (primary only) as transparent wrappers.
422
+ */
423
+ function getParenNestingDepth(node) {
424
+ let maxDepth = 0;
425
+ function traverse(n, consecutiveDepth) {
426
+ if (n.type === 'GroupedExpr') {
427
+ const groupedNode = n;
428
+ const newDepth = consecutiveDepth + 1;
429
+ maxDepth = Math.max(maxDepth, newDepth);
430
+ traverse(groupedNode.expression, newDepth);
431
+ }
432
+ else if (n.type === 'PipeChain') {
433
+ // Treat simple PipeChain (head only) as transparent for nesting
434
+ const pipeNode = n;
435
+ if (pipeNode.head && (!pipeNode.pipes || pipeNode.pipes.length === 0)) {
436
+ // Transparent: pass through consecutive depth
437
+ traverse(pipeNode.head, consecutiveDepth);
438
+ }
439
+ else {
440
+ // Complex pipe chain: reset depth but continue traversing
441
+ if (pipeNode.head)
442
+ traverse(pipeNode.head, 0);
443
+ if (pipeNode.pipes) {
444
+ for (const pipe of pipeNode.pipes) {
445
+ traverse(pipe, 0);
446
+ }
447
+ }
448
+ }
449
+ }
450
+ else if (n.type === 'PostfixExpr') {
451
+ // Treat simple PostfixExpr (primary only) as transparent for nesting
452
+ const postfixNode = n;
453
+ if (postfixNode.primary &&
454
+ (!postfixNode.methods || postfixNode.methods.length === 0)) {
455
+ // Transparent: pass through consecutive depth
456
+ traverse(postfixNode.primary, consecutiveDepth);
457
+ }
458
+ else {
459
+ // Complex postfix: reset depth
460
+ if (postfixNode.primary)
461
+ traverse(postfixNode.primary, 0);
462
+ }
463
+ }
464
+ else {
465
+ // Reset consecutive depth when we hit a structural node
466
+ // but continue traversing children
467
+ if (n.type === 'BinaryExpr') {
468
+ const binaryNode = n;
469
+ traverse(binaryNode.left, 0);
470
+ traverse(binaryNode.right, 0);
471
+ }
472
+ else if (n.type === 'UnaryExpr') {
473
+ const unaryNode = n;
474
+ traverse(unaryNode.operand, 0);
475
+ }
476
+ }
477
+ }
478
+ traverse(node, 0);
479
+ return maxDepth;
480
+ }
481
+ //# sourceMappingURL=anti-patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anti-patterns.js","sourceRoot":"","sources":["../../../src/check/rules/anti-patterns.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Closure Convention Rules
3
+ * Enforces closure best practices from docs/guide-conventions.md:237-286.
4
+ */
5
+ import type { ValidationRule } from '../types.js';
6
+ /**
7
+ * Warns on bare $ in stored closures without parameters.
8
+ * Bare $ in stored closures has ambiguous binding - it refers to the
9
+ * pipe value at closure invocation time, not definition time.
10
+ *
11
+ * Detection:
12
+ * - Zero-parameter closures (|| { }) used outside dict context
13
+ * - Body contains bare $ references (VariableNode with name '$')
14
+ *
15
+ * Valid patterns:
16
+ * - Dict closures: [count: ||{ $.items -> .len }] ($ binds to dict)
17
+ * - Parameterized closures: |x|{ $x } (explicit params)
18
+ * - Inline blocks: -> { $ * 2 } (immediate evaluation)
19
+ *
20
+ * References:
21
+ * - docs/guide-conventions.md:251-261
22
+ * - docs/topic-closures.md: Late binding section
23
+ */
24
+ export declare const CLOSURE_BARE_DOLLAR: ValidationRule;
25
+ /**
26
+ * Enforces braces for complex closure bodies.
27
+ * Simple expressions can use parentheses, but complex bodies need braces.
28
+ *
29
+ * Complex body criteria:
30
+ * - Contains Block (multiple statements)
31
+ * - Contains Conditional
32
+ * - Contains loop constructs
33
+ *
34
+ * Simple bodies (parentheses OK):
35
+ * - Single expression: |x|($x * 2)
36
+ * - Single method chain: |s|($s.trim.lower)
37
+ *
38
+ * Complex bodies (braces required):
39
+ * - Conditionals: |n| { ($n < 1) ? 1 ! ($n * $fact($n - 1)) }
40
+ * - Multiple statements: |x| { $x => $y; $y * 2 }
41
+ *
42
+ * References:
43
+ * - docs/guide-conventions.md:239-249
44
+ */
45
+ export declare const CLOSURE_BRACES: ValidationRule;
46
+ /**
47
+ * Detects closures created in loops that may suffer from late binding issues.
48
+ * When creating closures inside loops, variables are captured by reference,
49
+ * not by value. This causes all closures to share the final loop value.
50
+ *
51
+ * Detection:
52
+ * - Each loop body creates a Closure node
53
+ * - Closure references loop variable ($) without explicit capture
54
+ *
55
+ * Solution: Explicit capture per iteration:
56
+ * [1, 2, 3] -> each {
57
+ * $ => $item
58
+ * || { $item }
59
+ * }
60
+ *
61
+ * References:
62
+ * - docs/guide-conventions.md:251-261
63
+ * - docs/topic-closures.md: Late binding section
64
+ */
65
+ export declare const CLOSURE_LATE_BINDING: ValidationRule;
66
+ //# sourceMappingURL=closures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"closures.d.ts","sourceRoot":"","sources":["../../../src/check/rules/closures.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,aAAa,CAAC;AAuBrB;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,EAAE,cAoCjC,CAAC;AAmIF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,cAAc,EAAE,cAsD5B,CAAC;AAMF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,oBAAoB,EAAE,cAqClC,CAAC"}