@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,262 @@
1
+ /**
2
+ * Flow and Capture Rules
3
+ * Enforces conventions for capture placement and flow patterns.
4
+ */
5
+
6
+ import type {
7
+ ValidationRule,
8
+ Diagnostic,
9
+ ValidationContext,
10
+ } from '../types.js';
11
+ import type {
12
+ ASTNode,
13
+ StatementNode,
14
+ PipeChainNode,
15
+ CaptureNode,
16
+ ConditionalNode,
17
+ BodyNode,
18
+ PostfixExprNode,
19
+ } from '@rcrsr/rill';
20
+ import { extractContextLine } from './helpers.js';
21
+
22
+ // ============================================================
23
+ // HELPER FUNCTIONS
24
+ // ============================================================
25
+
26
+ /**
27
+ * Check if a node is a capture node.
28
+ */
29
+ function isCaptureNode(node: unknown): node is CaptureNode {
30
+ return (
31
+ typeof node === 'object' &&
32
+ node !== null &&
33
+ 'type' in node &&
34
+ node.type === 'Capture'
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Check if a node contains a variable reference.
40
+ * For $ (pipe variable), checks for isPipeVar: true.
41
+ * For named variables, checks for the variable name.
42
+ * This is a simplified check - a full implementation would traverse the AST.
43
+ */
44
+ function referencesVariable(node: BodyNode | null, varName: string): boolean {
45
+ if (!node) return false;
46
+
47
+ // Convert node to string representation and check for variable usage
48
+ // This is a heuristic - proper implementation would need AST traversal
49
+ const nodeStr = JSON.stringify(node);
50
+
51
+ if (varName === '$') {
52
+ // Pipe variable has isPipeVar: true
53
+ return nodeStr.includes('"isPipeVar":true');
54
+ } else {
55
+ // Named variable
56
+ return nodeStr.includes(`"name":"${varName}"`);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Get the primary expression from a PipeChain's head.
62
+ * ArithHead can be BinaryExprNode, UnaryExprNode, or PostfixExprNode.
63
+ */
64
+ function getPrimaryFromHead(chain: PipeChainNode): ASTNode | null {
65
+ const head = chain.head;
66
+
67
+ // If head is PostfixExprNode, get its primary
68
+ if (head.type === 'PostfixExpr') {
69
+ return (head as PostfixExprNode).primary;
70
+ }
71
+
72
+ // For BinaryExprNode or UnaryExprNode, we can't easily get a single primary
73
+ return null;
74
+ }
75
+
76
+ // ============================================================
77
+ // CAPTURE_INLINE_CHAIN RULE
78
+ // ============================================================
79
+
80
+ /**
81
+ * Validates that captures use inline syntax when continuing the chain.
82
+ *
83
+ * Detects separate capture followed by variable usage:
84
+ * prompt("Read file") => $raw
85
+ * $raw -> log
86
+ *
87
+ * Suggests inline capture:
88
+ * prompt("Read file") => $raw -> log
89
+ *
90
+ * This is an informational rule - both patterns work, but inline is clearer.
91
+ *
92
+ * References:
93
+ * - docs/guide-conventions.md:56-74
94
+ */
95
+ export const CAPTURE_INLINE_CHAIN: ValidationRule = {
96
+ code: 'CAPTURE_INLINE_CHAIN',
97
+ category: 'flow',
98
+ severity: 'info',
99
+ nodeTypes: ['Statement'],
100
+
101
+ validate(node: ASTNode, context: ValidationContext): Diagnostic[] {
102
+ const statement = node as StatementNode;
103
+ const chain = statement.expression;
104
+
105
+ // Check if this chain ends with a capture
106
+ // Captures can be in terminator OR as the last pipe element
107
+ let captureNode: CaptureNode | null = null;
108
+
109
+ if (chain.terminator && isCaptureNode(chain.terminator)) {
110
+ captureNode = chain.terminator;
111
+ } else if (chain.pipes.length > 0) {
112
+ const lastPipe = chain.pipes[chain.pipes.length - 1];
113
+ if (lastPipe && isCaptureNode(lastPipe)) {
114
+ captureNode = lastPipe;
115
+ }
116
+ }
117
+
118
+ if (!captureNode) {
119
+ return [];
120
+ }
121
+
122
+ const capturedVarName = captureNode.name;
123
+
124
+ // Get all statements from the script
125
+ const statements = context.ast.statements;
126
+ const currentIndex = statements.indexOf(statement);
127
+
128
+ if (currentIndex === -1 || currentIndex === statements.length - 1) {
129
+ return [];
130
+ }
131
+
132
+ const nextStatement = statements[currentIndex + 1];
133
+ if (!nextStatement) return [];
134
+
135
+ // Check if next statement is a Statement wrapping a PipeChain
136
+ if (nextStatement.type !== 'Statement') {
137
+ return [];
138
+ }
139
+
140
+ const nextStmt = nextStatement as StatementNode;
141
+ const nextChain = nextStmt.expression;
142
+
143
+ // Check if the head of the next chain is the captured variable
144
+ const headPrimary = getPrimaryFromHead(nextChain);
145
+ if (
146
+ headPrimary &&
147
+ headPrimary.type === 'Variable' &&
148
+ 'name' in headPrimary &&
149
+ headPrimary.name === capturedVarName
150
+ ) {
151
+ // Found pattern: capture on one line, immediate usage on next line
152
+ return [
153
+ {
154
+ location: captureNode.span.start,
155
+ severity: 'info',
156
+ code: 'CAPTURE_INLINE_CHAIN',
157
+ message: `Consider inline capture: '=> $${capturedVarName} -> ...' instead of separate statements`,
158
+ context: extractContextLine(
159
+ captureNode.span.start.line,
160
+ context.source
161
+ ),
162
+ fix: null, // Complex fix - requires merging statements
163
+ },
164
+ ];
165
+ }
166
+
167
+ return [];
168
+ },
169
+ };
170
+
171
+ // ============================================================
172
+ // CAPTURE_BEFORE_BRANCH RULE
173
+ // ============================================================
174
+
175
+ /**
176
+ * Validates that values used in multiple branches are captured before the conditional.
177
+ *
178
+ * Detects conditionals where a function call or expression appears in multiple branches:
179
+ * checkStatus() -> .contains("OK") ? {
180
+ * "Success: {checkStatus()}"
181
+ * } ! {
182
+ * "Failed: {checkStatus()}"
183
+ * }
184
+ *
185
+ * Suggests capturing before branching:
186
+ * checkStatus() => $result
187
+ * $result -> .contains("OK") ? {
188
+ * "Success: {$result}"
189
+ * } ! {
190
+ * "Failed: {$result}"
191
+ * }
192
+ *
193
+ * This is an informational rule - detects potential inefficiency and clarity issues.
194
+ *
195
+ * References:
196
+ * - docs/guide-conventions.md:76-88
197
+ */
198
+ export const CAPTURE_BEFORE_BRANCH: ValidationRule = {
199
+ code: 'CAPTURE_BEFORE_BRANCH',
200
+ category: 'flow',
201
+ severity: 'info',
202
+ nodeTypes: ['Conditional'],
203
+
204
+ validate(node: ASTNode, context: ValidationContext): Diagnostic[] {
205
+ const conditional = node as ConditionalNode;
206
+
207
+ // Check if both branches exist
208
+ if (!conditional.elseBranch) {
209
+ return [];
210
+ }
211
+
212
+ // For piped conditionals (input is null), we can still suggest capturing
213
+ // For explicit conditionals, check if input is complex
214
+ const inputExpr = conditional.input;
215
+
216
+ // If input exists and is already a simple variable, no need to capture
217
+ if (inputExpr && inputExpr.type === 'PipeChain') {
218
+ const headPrimary = getPrimaryFromHead(inputExpr);
219
+ if (
220
+ headPrimary &&
221
+ headPrimary.type === 'Variable' &&
222
+ inputExpr.pipes.length === 0 &&
223
+ !inputExpr.terminator
224
+ ) {
225
+ return [];
226
+ }
227
+ }
228
+
229
+ // Look for patterns where the input value might be used in both branches
230
+ // This is heuristic-based: we check if there's a $ reference in both branches
231
+ // which would be the piped value from the conditional input
232
+
233
+ const thenReferences = referencesVariable(conditional.thenBranch, '$');
234
+ // elseBranch can be BodyNode or ConditionalNode (for else-if chains)
235
+ const elseBranch = conditional.elseBranch;
236
+ const elseReferences =
237
+ elseBranch && elseBranch.type !== 'Conditional'
238
+ ? referencesVariable(elseBranch, '$')
239
+ : false;
240
+
241
+ // If $ is used in both branches, suggest capturing the input value
242
+ // This makes it available in both branches with a clear name
243
+ if (thenReferences && elseReferences) {
244
+ return [
245
+ {
246
+ location: conditional.span.start,
247
+ severity: 'info',
248
+ code: 'CAPTURE_BEFORE_BRANCH',
249
+ message:
250
+ 'Consider capturing value before conditional when used in multiple branches',
251
+ context: extractContextLine(
252
+ conditional.span.start.line,
253
+ context.source
254
+ ),
255
+ fix: null, // Complex fix - requires AST restructuring
256
+ },
257
+ ];
258
+ }
259
+
260
+ return [];
261
+ },
262
+ };