@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.
- package/LICENSE +21 -0
- package/dist/check/config.d.ts +20 -0
- package/dist/check/config.d.ts.map +1 -0
- package/dist/check/config.js +151 -0
- package/dist/check/config.js.map +1 -0
- package/dist/check/fixer.d.ts +39 -0
- package/dist/check/fixer.d.ts.map +1 -0
- package/dist/check/fixer.js +119 -0
- package/dist/check/fixer.js.map +1 -0
- package/dist/check/index.d.ts +10 -0
- package/dist/check/index.d.ts.map +1 -0
- package/dist/check/index.js +21 -0
- package/dist/check/index.js.map +1 -0
- package/dist/check/rules/anti-patterns.d.ts +65 -0
- package/dist/check/rules/anti-patterns.d.ts.map +1 -0
- package/dist/check/rules/anti-patterns.js +481 -0
- package/dist/check/rules/anti-patterns.js.map +1 -0
- package/dist/check/rules/closures.d.ts +66 -0
- package/dist/check/rules/closures.d.ts.map +1 -0
- package/dist/check/rules/closures.js +370 -0
- package/dist/check/rules/closures.js.map +1 -0
- package/dist/check/rules/collections.d.ts +90 -0
- package/dist/check/rules/collections.d.ts.map +1 -0
- package/dist/check/rules/collections.js +373 -0
- package/dist/check/rules/collections.js.map +1 -0
- package/dist/check/rules/conditionals.d.ts +41 -0
- package/dist/check/rules/conditionals.d.ts.map +1 -0
- package/dist/check/rules/conditionals.js +134 -0
- package/dist/check/rules/conditionals.js.map +1 -0
- package/dist/check/rules/flow.d.ts +46 -0
- package/dist/check/rules/flow.d.ts.map +1 -0
- package/dist/check/rules/flow.js +206 -0
- package/dist/check/rules/flow.js.map +1 -0
- package/dist/check/rules/formatting.d.ts +143 -0
- package/dist/check/rules/formatting.d.ts.map +1 -0
- package/dist/check/rules/formatting.js +656 -0
- package/dist/check/rules/formatting.js.map +1 -0
- package/dist/check/rules/helpers.d.ts +26 -0
- package/dist/check/rules/helpers.d.ts.map +1 -0
- package/dist/check/rules/helpers.js +66 -0
- package/dist/check/rules/helpers.js.map +1 -0
- package/dist/check/rules/index.d.ts +21 -0
- package/dist/check/rules/index.d.ts.map +1 -0
- package/dist/check/rules/index.js +78 -0
- package/dist/check/rules/index.js.map +1 -0
- package/dist/check/rules/loops.d.ts +77 -0
- package/dist/check/rules/loops.d.ts.map +1 -0
- package/dist/check/rules/loops.js +310 -0
- package/dist/check/rules/loops.js.map +1 -0
- package/dist/check/rules/naming.d.ts +21 -0
- package/dist/check/rules/naming.d.ts.map +1 -0
- package/dist/check/rules/naming.js +174 -0
- package/dist/check/rules/naming.js.map +1 -0
- package/dist/check/rules/strings.d.ts +28 -0
- package/dist/check/rules/strings.d.ts.map +1 -0
- package/dist/check/rules/strings.js +79 -0
- package/dist/check/rules/strings.js.map +1 -0
- package/dist/check/rules/types.d.ts +41 -0
- package/dist/check/rules/types.d.ts.map +1 -0
- package/dist/check/rules/types.js +167 -0
- package/dist/check/rules/types.js.map +1 -0
- package/dist/check/types.d.ts +112 -0
- package/dist/check/types.d.ts.map +1 -0
- package/dist/check/types.js +6 -0
- package/dist/check/types.js.map +1 -0
- package/dist/check/validator.d.ts +18 -0
- package/dist/check/validator.d.ts.map +1 -0
- package/dist/check/validator.js +110 -0
- package/dist/check/validator.js.map +1 -0
- package/dist/check/visitor.d.ts +33 -0
- package/dist/check/visitor.d.ts.map +1 -0
- package/dist/check/visitor.js +259 -0
- package/dist/check/visitor.js.map +1 -0
- package/dist/cli-check.d.ts +43 -0
- package/dist/cli-check.d.ts.map +1 -0
- package/dist/cli-check.js +366 -0
- package/dist/cli-check.js.map +1 -0
- package/dist/cli-error-enrichment.d.ts +73 -0
- package/dist/cli-error-enrichment.d.ts.map +1 -0
- package/dist/cli-error-enrichment.js +205 -0
- package/dist/cli-error-enrichment.js.map +1 -0
- package/dist/cli-error-formatter.d.ts +45 -0
- package/dist/cli-error-formatter.d.ts.map +1 -0
- package/dist/cli-error-formatter.js +218 -0
- package/dist/cli-error-formatter.js.map +1 -0
- package/dist/cli-eval.d.ts +15 -0
- package/dist/cli-eval.d.ts.map +1 -0
- package/dist/cli-eval.js +116 -0
- package/dist/cli-eval.js.map +1 -0
- package/dist/cli-exec.d.ts +58 -0
- package/dist/cli-exec.d.ts.map +1 -0
- package/dist/cli-exec.js +326 -0
- package/dist/cli-exec.js.map +1 -0
- package/dist/cli-explain.d.ts +24 -0
- package/dist/cli-explain.d.ts.map +1 -0
- package/dist/cli-explain.js +68 -0
- package/dist/cli-explain.js.map +1 -0
- package/dist/cli-lsp-diagnostic.d.ts +35 -0
- package/dist/cli-lsp-diagnostic.d.ts.map +1 -0
- package/dist/cli-lsp-diagnostic.js +98 -0
- package/dist/cli-lsp-diagnostic.js.map +1 -0
- package/dist/cli-module-loader.d.ts +19 -0
- package/dist/cli-module-loader.d.ts.map +1 -0
- package/dist/cli-module-loader.js +83 -0
- package/dist/cli-module-loader.js.map +1 -0
- package/dist/cli-shared.d.ts +62 -0
- package/dist/cli-shared.d.ts.map +1 -0
- package/dist/cli-shared.js +158 -0
- package/dist/cli-shared.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -0
- package/dist/test-internal-import.d.ts +2 -0
- package/dist/test-internal-import.d.ts.map +1 -0
- package/dist/test-internal-import.js +7 -0
- package/dist/test-internal-import.js.map +1 -0
- package/package.json +24 -0
- package/src/check/config.ts +202 -0
- package/src/check/fixer.ts +174 -0
- package/src/check/index.ts +39 -0
- package/src/check/rules/anti-patterns.ts +585 -0
- package/src/check/rules/closures.ts +445 -0
- package/src/check/rules/collections.ts +437 -0
- package/src/check/rules/conditionals.ts +155 -0
- package/src/check/rules/flow.ts +262 -0
- package/src/check/rules/formatting.ts +811 -0
- package/src/check/rules/helpers.ts +89 -0
- package/src/check/rules/index.ts +140 -0
- package/src/check/rules/loops.ts +372 -0
- package/src/check/rules/naming.ts +242 -0
- package/src/check/rules/strings.ts +104 -0
- package/src/check/rules/types.ts +214 -0
- package/src/check/types.ts +163 -0
- package/src/check/validator.ts +136 -0
- package/src/check/visitor.ts +338 -0
- package/src/cli-check.ts +456 -0
- package/src/cli-error-enrichment.ts +274 -0
- package/src/cli-error-formatter.ts +313 -0
- package/src/cli-eval.ts +145 -0
- package/src/cli-exec.ts +408 -0
- package/src/cli-explain.ts +76 -0
- package/src/cli-lsp-diagnostic.ts +132 -0
- package/src/cli-module-loader.ts +101 -0
- package/src/cli-shared.ts +187 -0
- package/tests/check/cli-check.test.ts +189 -0
- package/tests/check/config.test.ts +350 -0
- package/tests/check/fixer.test.ts +373 -0
- package/tests/check/format-diagnostics.test.ts +327 -0
- package/tests/check/rules/anti-patterns.test.ts +467 -0
- package/tests/check/rules/closures.test.ts +192 -0
- package/tests/check/rules/collections.test.ts +380 -0
- package/tests/check/rules/conditionals.test.ts +185 -0
- package/tests/check/rules/flow.test.ts +250 -0
- package/tests/check/rules/formatting.test.ts +755 -0
- package/tests/check/rules/loops.test.ts +334 -0
- package/tests/check/rules/naming.test.ts +336 -0
- package/tests/check/rules/strings.test.ts +129 -0
- package/tests/check/rules/types.test.ts +257 -0
- package/tests/check/validator.test.ts +444 -0
- package/tests/check/visitor.test.ts +171 -0
- package/tests/cli/check.test.ts +801 -0
- package/tests/cli/error-enrichment.test.ts +510 -0
- package/tests/cli/error-formatter.test.ts +631 -0
- package/tests/cli/eval.test.ts +85 -0
- package/tests/cli/exec.test.ts +537 -0
- package/tests/cli-explain.test.ts +249 -0
- package/tests/cli-lsp-diagnostic.test.ts +202 -0
- package/tests/cli-shared.test.ts +439 -0
- package/tsconfig.json +9 -0
- 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
|
+
};
|