@rcrsr/rill-cli 0.18.0 → 0.18.1

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.
@@ -168,11 +168,15 @@ export const CLOSURE_LATE_BINDING = {
168
168
  validate(node, context) {
169
169
  const eachNode = node;
170
170
  const body = eachNode.body;
171
+ // For named parameter closures (|entry| { ... }), the body itself is a
172
+ // Closure. Look inside the inner block for nested closure creations,
173
+ // not the body closure itself.
174
+ const innerBody = body.type === 'Closure' ? body.body : body;
171
175
  // Check if body contains a closure creation
172
- const hasClosureCreation = containsClosureCreation(body);
176
+ const hasClosureCreation = containsClosureCreation(innerBody);
173
177
  if (hasClosureCreation) {
174
178
  // Check if there's an explicit capture before the closure
175
- const hasExplicitCapture = containsExplicitCapture(body);
179
+ const hasExplicitCapture = containsExplicitCapture(innerBody);
176
180
  if (!hasExplicitCapture) {
177
181
  return [
178
182
  {
@@ -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
- // Simple heuristic: if body is pure (no side effects), suggest map
183
- // For now, suggest map for simple transformations
184
- // Full implementation would check for host calls, logging, etc.
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, // Could generate fix by replacing 'each' with 'map'
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
- // span.end.column is 1-indexed and points AFTER the }, so:
145
- // - } is at 0-index: span.end.column - 2
146
- // - Character before } is at 0-index: span.end.column - 3
147
- const charBeforeClose = closeLine[span.end.column - 3];
148
- const isCloseOnOwnLine = /^\s*$/.test(closeLine.substring(0, span.end.column - 2));
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
  // ============================================================
@@ -288,10 +288,13 @@ export async function main() {
288
288
  }
289
289
  }
290
290
  }
291
- if (typeof handlerResult === 'string' && handlerResult.length > 0) {
292
- process.stdout.write(handlerResult + '\n');
291
+ if (handlerResult !== false &&
292
+ handlerResult !== '' &&
293
+ handlerResult !== undefined) {
294
+ const output = formatOutput(handlerResult, opts.format);
295
+ process.stdout.write(output + '\n');
293
296
  }
294
- process.exit(0);
297
+ process.exit(handlerResult === false || handlerResult === '' ? 1 : 0);
295
298
  }
296
299
  // Module mode: main field in config is required
297
300
  if (mainField === undefined) {
@@ -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
  */
@@ -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.0",
3
+ "version": "0.18.1",
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.0",
25
- "@rcrsr/rill-config": "^0.18.0"
24
+ "@rcrsr/rill-config": "^0.18.1",
25
+ "@rcrsr/rill": "^0.18.0"
26
26
  },
27
27
  "files": [
28
28
  "dist"