@rcrsr/rill-cli 0.18.0 → 0.18.2
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.
|
@@ -59,14 +59,31 @@ export const CLOSURE_BARE_DOLLAR = {
|
|
|
59
59
|
*/
|
|
60
60
|
function containsBareReference(node) {
|
|
61
61
|
let found = false;
|
|
62
|
+
let scopeDepth = 0;
|
|
63
|
+
const scopeTypes = new Set([
|
|
64
|
+
'Closure',
|
|
65
|
+
'FilterExpr',
|
|
66
|
+
'MapExpr',
|
|
67
|
+
'EachExpr',
|
|
68
|
+
]);
|
|
62
69
|
const ctx = {};
|
|
63
70
|
visitNode(node, ctx, {
|
|
64
71
|
enter(n) {
|
|
72
|
+
if (scopeTypes.has(n.type)) {
|
|
73
|
+
scopeDepth++;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (scopeDepth > 0)
|
|
77
|
+
return;
|
|
65
78
|
if (n.type === 'Variable' && n.isPipeVar) {
|
|
66
79
|
found = true;
|
|
67
80
|
}
|
|
68
81
|
},
|
|
69
|
-
exit() {
|
|
82
|
+
exit(n) {
|
|
83
|
+
if (scopeTypes.has(n.type)) {
|
|
84
|
+
scopeDepth--;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
70
87
|
});
|
|
71
88
|
return found;
|
|
72
89
|
}
|
|
@@ -168,11 +185,15 @@ export const CLOSURE_LATE_BINDING = {
|
|
|
168
185
|
validate(node, context) {
|
|
169
186
|
const eachNode = node;
|
|
170
187
|
const body = eachNode.body;
|
|
188
|
+
// For named parameter closures (|entry| { ... }), the body itself is a
|
|
189
|
+
// Closure. Look inside the inner block for nested closure creations,
|
|
190
|
+
// not the body closure itself.
|
|
191
|
+
const innerBody = body.type === 'Closure' ? body.body : body;
|
|
171
192
|
// Check if body contains a closure creation
|
|
172
|
-
const hasClosureCreation = containsClosureCreation(
|
|
193
|
+
const hasClosureCreation = containsClosureCreation(innerBody);
|
|
173
194
|
if (hasClosureCreation) {
|
|
174
195
|
// Check if there's an explicit capture before the closure
|
|
175
|
-
const hasExplicitCapture = containsExplicitCapture(
|
|
196
|
+
const hasExplicitCapture = containsExplicitCapture(innerBody);
|
|
176
197
|
if (!hasExplicitCapture) {
|
|
177
198
|
return [
|
|
178
199
|
{
|
|
@@ -24,6 +24,23 @@ function containsBreak(node) {
|
|
|
24
24
|
});
|
|
25
25
|
return found;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if an AST subtree contains side-effecting operations.
|
|
29
|
+
* Detects HostCall (log, host functions) and ClosureCall ($fn(), $obj.method()).
|
|
30
|
+
*/
|
|
31
|
+
function containsSideEffects(node) {
|
|
32
|
+
let found = false;
|
|
33
|
+
const ctx = {};
|
|
34
|
+
visitNode(node, ctx, {
|
|
35
|
+
enter(n) {
|
|
36
|
+
if (n.type === 'HostCall' || n.type === 'ClosureCall') {
|
|
37
|
+
found = true;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
exit() { },
|
|
41
|
+
});
|
|
42
|
+
return found;
|
|
43
|
+
}
|
|
27
44
|
/**
|
|
28
45
|
* Check if a body is a simple method shorthand.
|
|
29
46
|
* Body structure for .method shorthand is PostfixExpr with MethodCall as primary.
|
|
@@ -179,9 +196,13 @@ export const PREFER_MAP = {
|
|
|
179
196
|
return [];
|
|
180
197
|
}
|
|
181
198
|
}
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
199
|
+
// Check for side effects: host calls (log, etc.) and closure calls ($fn())
|
|
200
|
+
const innerBody = eachExpr.body.type === 'Closure'
|
|
201
|
+
? eachExpr.body.body
|
|
202
|
+
: eachExpr.body;
|
|
203
|
+
if (containsSideEffects(innerBody)) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
185
206
|
return [
|
|
186
207
|
{
|
|
187
208
|
location: node.span.start,
|
|
@@ -189,7 +210,7 @@ export const PREFER_MAP = {
|
|
|
189
210
|
code: 'PREFER_MAP',
|
|
190
211
|
message: "Consider using 'map' instead of 'each' for pure transformations (no side effects)",
|
|
191
212
|
context: extractContextLine(node.span.start.line, context.source),
|
|
192
|
-
fix: null,
|
|
213
|
+
fix: null,
|
|
193
214
|
},
|
|
194
215
|
];
|
|
195
216
|
},
|
|
@@ -125,7 +125,6 @@ export const SPACING_BRACES = {
|
|
|
125
125
|
const span = node.span;
|
|
126
126
|
const lines = context.source.split('\n');
|
|
127
127
|
const openLine = lines[span.start.line - 1] ?? '';
|
|
128
|
-
const closeLine = lines[span.end.line - 1] ?? '';
|
|
129
128
|
// Check for opening brace without space after
|
|
130
129
|
// Only examine the opening line (from the { onward)
|
|
131
130
|
// Use ^ anchor to only check the block's opening brace, not string interpolation
|
|
@@ -141,11 +140,16 @@ export const SPACING_BRACES = {
|
|
|
141
140
|
});
|
|
142
141
|
}
|
|
143
142
|
// Check for closing brace without space before
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
const
|
|
143
|
+
// For Closure nodes with return type annotations, span.end extends past }
|
|
144
|
+
// to include the type annotation. Use body.span.end to find the actual }.
|
|
145
|
+
const closeSpan = node.type === 'Closure' ? node.body.span : span;
|
|
146
|
+
const closeEnd = closeSpan.end;
|
|
147
|
+
const closeLineActual = lines[closeEnd.line - 1] ?? '';
|
|
148
|
+
// closeEnd.column is 1-indexed and points AFTER the }, so:
|
|
149
|
+
// - } is at 0-index: closeEnd.column - 2
|
|
150
|
+
// - Character before } is at 0-index: closeEnd.column - 3
|
|
151
|
+
const charBeforeClose = closeLineActual[closeEnd.column - 3];
|
|
152
|
+
const isCloseOnOwnLine = /^\s*$/.test(closeLineActual.substring(0, closeEnd.column - 2));
|
|
149
153
|
if (charBeforeClose && !/\s/.test(charBeforeClose) && !isCloseOnOwnLine) {
|
|
150
154
|
diagnostics.push({
|
|
151
155
|
location: span.end,
|
package/dist/cli-run.js
CHANGED
|
@@ -11,7 +11,7 @@ import { parse, execute, createRuntimeContext, invokeCallable, isScriptCallable,
|
|
|
11
11
|
import { resolveConfigPath, loadProject, parseMainField, introspectHandler, marshalCliArgs, ConfigError, } from '@rcrsr/rill-config';
|
|
12
12
|
import { CLI_VERSION } from './cli-shared.js';
|
|
13
13
|
import { explainError } from './cli-explain.js';
|
|
14
|
-
import { runScript } from './run/runner.js';
|
|
14
|
+
import { formatOutput, runScript } from './run/runner.js';
|
|
15
15
|
// ============================================================
|
|
16
16
|
// HELP TEXT
|
|
17
17
|
// ============================================================
|
|
@@ -269,9 +269,16 @@ export async function main() {
|
|
|
269
269
|
throw err;
|
|
270
270
|
}
|
|
271
271
|
ctx.pipeValue = handlerArgs;
|
|
272
|
+
// Map handler args to positional args in param order so marshalArgs
|
|
273
|
+
// can bind them to the closure's declared parameters.
|
|
274
|
+
// Omitted optional params stay undefined so closure defaults hydrate.
|
|
275
|
+
// pipeValue is kept for zero-param closures that access $ directly.
|
|
276
|
+
const positionalArgs = introspection.params.map((p) => (Object.prototype.hasOwnProperty.call(handlerArgs, p.name)
|
|
277
|
+
? handlerArgs[p.name]
|
|
278
|
+
: undefined));
|
|
272
279
|
let handlerResult;
|
|
273
280
|
try {
|
|
274
|
-
handlerResult = await invokeCallable(handlerValue,
|
|
281
|
+
handlerResult = await invokeCallable(handlerValue, positionalArgs, ctx);
|
|
275
282
|
}
|
|
276
283
|
catch (err) {
|
|
277
284
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -288,10 +295,13 @@ export async function main() {
|
|
|
288
295
|
}
|
|
289
296
|
}
|
|
290
297
|
}
|
|
291
|
-
if (
|
|
292
|
-
|
|
298
|
+
if (handlerResult !== false &&
|
|
299
|
+
handlerResult !== '' &&
|
|
300
|
+
handlerResult !== undefined) {
|
|
301
|
+
const output = formatOutput(handlerResult, opts.format);
|
|
302
|
+
process.stdout.write(output + '\n');
|
|
293
303
|
}
|
|
294
|
-
process.exit(0);
|
|
304
|
+
process.exit(handlerResult === false || handlerResult === '' ? 1 : 0);
|
|
295
305
|
}
|
|
296
306
|
// Module mode: main field in config is required
|
|
297
307
|
if (mainField === undefined) {
|
package/dist/run/runner.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface RunResult {
|
|
|
17
17
|
* - `module:alias` → `{dir}/index.rill`
|
|
18
18
|
*/
|
|
19
19
|
export declare function buildModuleResolver(modulesConfig: Record<string, string>, configDir: string): SchemeResolver;
|
|
20
|
+
export declare function formatOutput(value: RillValue, format: RunCliOptions['format']): string;
|
|
20
21
|
/**
|
|
21
22
|
* Run a rill script file with the given extension tree and config.
|
|
22
23
|
*/
|
package/dist/run/runner.js
CHANGED
|
@@ -59,7 +59,7 @@ function mapResultToRunResult(result, format) {
|
|
|
59
59
|
const formatted = formatOutput(result, format);
|
|
60
60
|
return { exitCode: 0, output: formatted };
|
|
61
61
|
}
|
|
62
|
-
function formatOutput(value, format) {
|
|
62
|
+
export function formatOutput(value, format) {
|
|
63
63
|
const native = toNative(value);
|
|
64
64
|
if (format === 'json' || format === 'compact') {
|
|
65
65
|
return JSON.stringify(native.value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rcrsr/rill-cli",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"description": "CLI tools for the rill scripting language",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Andre Bremer",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"dotenv": "^16.0.0",
|
|
23
23
|
"yaml": "^2.8.2",
|
|
24
|
-
"@rcrsr/rill": "^0.18.
|
|
25
|
-
"@rcrsr/rill-config": "^0.18.
|
|
24
|
+
"@rcrsr/rill": "^0.18.2",
|
|
25
|
+
"@rcrsr/rill-config": "^0.18.1"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"dist"
|