@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
package/src/cli-check.ts
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI Check Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Implements argument parsing for rill-check.
|
|
6
|
+
* Validates Rill source files against linting rules.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Diagnostic } from './check/index.js';
|
|
10
|
+
import {
|
|
11
|
+
VALIDATION_RULES,
|
|
12
|
+
loadConfig,
|
|
13
|
+
createDefaultConfig,
|
|
14
|
+
validateScript,
|
|
15
|
+
applyFixes,
|
|
16
|
+
} from './check/index.js';
|
|
17
|
+
import { parseWithRecovery } from '@rcrsr/rill';
|
|
18
|
+
import { VERSION, detectHelpVersionFlag } from './cli-shared.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parsed command-line arguments for rill-check
|
|
22
|
+
*/
|
|
23
|
+
export type ParsedCheckArgs =
|
|
24
|
+
| {
|
|
25
|
+
mode: 'check';
|
|
26
|
+
file: string;
|
|
27
|
+
fix: boolean;
|
|
28
|
+
verbose: boolean;
|
|
29
|
+
format: 'text' | 'json';
|
|
30
|
+
}
|
|
31
|
+
| { mode: 'help' | 'version' };
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse command-line arguments for rill-check
|
|
35
|
+
*
|
|
36
|
+
* @param argv - Raw command-line arguments (typically process.argv.slice(2))
|
|
37
|
+
* @returns Parsed command object
|
|
38
|
+
*/
|
|
39
|
+
export function parseCheckArgs(argv: string[]): ParsedCheckArgs {
|
|
40
|
+
// Check for --help or --version flags in any position
|
|
41
|
+
const helpVersionFlag = detectHelpVersionFlag(argv);
|
|
42
|
+
if (helpVersionFlag !== null) {
|
|
43
|
+
return helpVersionFlag;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Extract flags
|
|
47
|
+
const fix = argv.includes('--fix');
|
|
48
|
+
const verbose = argv.includes('--verbose');
|
|
49
|
+
|
|
50
|
+
// Extract format flag
|
|
51
|
+
let format: 'text' | 'json' = 'text';
|
|
52
|
+
const formatIndex = argv.indexOf('--format');
|
|
53
|
+
if (formatIndex !== -1) {
|
|
54
|
+
const formatValue = argv[formatIndex + 1];
|
|
55
|
+
if (formatValue === 'text' || formatValue === 'json') {
|
|
56
|
+
format = formatValue;
|
|
57
|
+
} else if (!formatValue || formatValue.startsWith('-')) {
|
|
58
|
+
throw new Error('--format requires argument: text or json');
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error(`Invalid format: ${formatValue}. Expected text or json`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check for unknown flags
|
|
65
|
+
const knownFlags = new Set([
|
|
66
|
+
'--help',
|
|
67
|
+
'-h',
|
|
68
|
+
'--version',
|
|
69
|
+
'-v',
|
|
70
|
+
'--fix',
|
|
71
|
+
'--verbose',
|
|
72
|
+
'--format',
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < argv.length; i++) {
|
|
76
|
+
const arg = argv[i];
|
|
77
|
+
if (!arg) continue;
|
|
78
|
+
|
|
79
|
+
// Skip non-flag arguments
|
|
80
|
+
if (!arg.startsWith('-')) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Skip format value argument
|
|
85
|
+
if (i > 0 && argv[i - 1] === '--format') {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check if unknown flag
|
|
90
|
+
if (!knownFlags.has(arg)) {
|
|
91
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Extract file path (first non-flag argument)
|
|
96
|
+
let file: string | undefined;
|
|
97
|
+
for (let i = 0; i < argv.length; i++) {
|
|
98
|
+
const arg = argv[i];
|
|
99
|
+
if (!arg) continue;
|
|
100
|
+
|
|
101
|
+
// Skip flags
|
|
102
|
+
if (arg.startsWith('-')) {
|
|
103
|
+
// Skip --format and its value
|
|
104
|
+
if (arg === '--format') {
|
|
105
|
+
i++; // Skip next argument (the format value)
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// First non-flag argument is the file
|
|
111
|
+
file = arg;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!file) {
|
|
116
|
+
throw new Error('Missing file argument');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { mode: 'check', file, fix, verbose, format };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================================
|
|
123
|
+
// DIAGNOSTIC FORMATTING
|
|
124
|
+
// ============================================================
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Format diagnostics for output
|
|
128
|
+
*
|
|
129
|
+
* Adapts pattern from cli-shared.ts formatError function.
|
|
130
|
+
* Text format: file:line:col: severity: message (code)
|
|
131
|
+
* JSON format: complete schema with errors array and summary
|
|
132
|
+
* Verbose mode: adds category field to diagnostics
|
|
133
|
+
*
|
|
134
|
+
* @param file - File path being checked
|
|
135
|
+
* @param diagnostics - Array of diagnostics to format
|
|
136
|
+
* @param format - Output format ('text' or 'json')
|
|
137
|
+
* @param verbose - Whether to include category and doc references
|
|
138
|
+
* @returns Formatted output string
|
|
139
|
+
*/
|
|
140
|
+
export function formatDiagnostics(
|
|
141
|
+
file: string,
|
|
142
|
+
diagnostics: Diagnostic[],
|
|
143
|
+
format: 'text' | 'json',
|
|
144
|
+
verbose: boolean
|
|
145
|
+
): string {
|
|
146
|
+
if (format === 'json') {
|
|
147
|
+
return formatDiagnosticsJSON(file, diagnostics, verbose);
|
|
148
|
+
}
|
|
149
|
+
return formatDiagnosticsText(file, diagnostics);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Format diagnostics as text
|
|
154
|
+
* Pattern: file:line:col: severity: message (code)
|
|
155
|
+
*/
|
|
156
|
+
function formatDiagnosticsText(
|
|
157
|
+
file: string,
|
|
158
|
+
diagnostics: Diagnostic[]
|
|
159
|
+
): string {
|
|
160
|
+
return diagnostics
|
|
161
|
+
.map((d) => {
|
|
162
|
+
const { line, column } = d.location;
|
|
163
|
+
return `${file}:${line}:${column}: ${d.severity}: ${d.message} (${d.code})`;
|
|
164
|
+
})
|
|
165
|
+
.join('\n');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Format diagnostics as JSON
|
|
170
|
+
* Includes file, errors array, and summary
|
|
171
|
+
*/
|
|
172
|
+
function formatDiagnosticsJSON(
|
|
173
|
+
file: string,
|
|
174
|
+
diagnostics: Diagnostic[],
|
|
175
|
+
verbose: boolean
|
|
176
|
+
): string {
|
|
177
|
+
// Build category lookup map from validation rules
|
|
178
|
+
const categoryMap = new Map<string, string>();
|
|
179
|
+
for (const rule of VALIDATION_RULES) {
|
|
180
|
+
categoryMap.set(rule.code, rule.category);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Format each diagnostic
|
|
184
|
+
const errors = diagnostics.map((d) => {
|
|
185
|
+
const error: Record<string, unknown> = {
|
|
186
|
+
location: {
|
|
187
|
+
line: d.location.line,
|
|
188
|
+
column: d.location.column,
|
|
189
|
+
offset: d.location.offset,
|
|
190
|
+
},
|
|
191
|
+
severity: d.severity,
|
|
192
|
+
code: d.code,
|
|
193
|
+
message: d.message,
|
|
194
|
+
context: d.context,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Add category if verbose mode
|
|
198
|
+
if (verbose) {
|
|
199
|
+
const category = categoryMap.get(d.code);
|
|
200
|
+
if (category) {
|
|
201
|
+
error['category'] = category;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Add fix if present
|
|
206
|
+
if (d.fix) {
|
|
207
|
+
error['fix'] = {
|
|
208
|
+
description: d.fix.description,
|
|
209
|
+
applicable: d.fix.applicable,
|
|
210
|
+
range: {
|
|
211
|
+
start: {
|
|
212
|
+
line: d.fix.range.start.line,
|
|
213
|
+
column: d.fix.range.start.column,
|
|
214
|
+
offset: d.fix.range.start.offset,
|
|
215
|
+
},
|
|
216
|
+
end: {
|
|
217
|
+
line: d.fix.range.end.line,
|
|
218
|
+
column: d.fix.range.end.column,
|
|
219
|
+
offset: d.fix.range.end.offset,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
replacement: d.fix.replacement,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return error;
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Count diagnostics by severity
|
|
230
|
+
const summary = {
|
|
231
|
+
total: diagnostics.length,
|
|
232
|
+
errors: diagnostics.filter((d) => d.severity === 'error').length,
|
|
233
|
+
warnings: diagnostics.filter((d) => d.severity === 'warning').length,
|
|
234
|
+
info: diagnostics.filter((d) => d.severity === 'info').length,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const output = {
|
|
238
|
+
file,
|
|
239
|
+
errors,
|
|
240
|
+
summary,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
return JSON.stringify(output, null, 2);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ============================================================
|
|
247
|
+
// MAIN ENTRY POINT
|
|
248
|
+
// ============================================================
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Main entry point for rill-check CLI.
|
|
252
|
+
* Orchestrates argument parsing, file reading, validation, fixing, and output.
|
|
253
|
+
*/
|
|
254
|
+
async function main(): Promise<void> {
|
|
255
|
+
try {
|
|
256
|
+
// Parse command-line arguments
|
|
257
|
+
const args = parseCheckArgs(process.argv.slice(2));
|
|
258
|
+
|
|
259
|
+
// Handle help mode
|
|
260
|
+
if (args.mode === 'help') {
|
|
261
|
+
console.log(`rill-check - Validate Rill scripts
|
|
262
|
+
|
|
263
|
+
Usage: rill-check [options] <file>
|
|
264
|
+
|
|
265
|
+
Options:
|
|
266
|
+
--fix Apply automatic fixes
|
|
267
|
+
--format <fmt> Output format: text (default) or json
|
|
268
|
+
--verbose Include category and documentation references
|
|
269
|
+
-h, --help Show this help message
|
|
270
|
+
-v, --version Show version number`);
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Handle version mode
|
|
275
|
+
if (args.mode === 'version') {
|
|
276
|
+
console.log(VERSION);
|
|
277
|
+
process.exit(0);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// At this point, args.mode must be 'check'
|
|
281
|
+
// TypeScript needs explicit assertion after early returns
|
|
282
|
+
if (args.mode !== 'check') {
|
|
283
|
+
throw new Error('Unexpected mode');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Load configuration from cwd (null if not present)
|
|
287
|
+
const config = loadConfig(process.cwd()) ?? createDefaultConfig();
|
|
288
|
+
|
|
289
|
+
// Read source file
|
|
290
|
+
let source: string;
|
|
291
|
+
try {
|
|
292
|
+
const fs = await import('node:fs');
|
|
293
|
+
|
|
294
|
+
// Check if file exists
|
|
295
|
+
if (!fs.existsSync(args.file)) {
|
|
296
|
+
console.error(`Error [RILL-C001]: File not found: ${args.file}`);
|
|
297
|
+
process.exit(2);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check if path is a directory
|
|
301
|
+
const stats = fs.statSync(args.file);
|
|
302
|
+
if (stats.isDirectory()) {
|
|
303
|
+
console.error(`Error [RILL-C002]: Path is a directory: ${args.file}`);
|
|
304
|
+
process.exit(2);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Read file contents
|
|
308
|
+
source = fs.readFileSync(args.file, 'utf-8');
|
|
309
|
+
} catch (err) {
|
|
310
|
+
// Handle read errors (permissions, etc.)
|
|
311
|
+
if (
|
|
312
|
+
err instanceof Error &&
|
|
313
|
+
'code' in err &&
|
|
314
|
+
typeof (err as { code?: string }).code === 'string'
|
|
315
|
+
) {
|
|
316
|
+
const code = (err as { code: string }).code;
|
|
317
|
+
if (code === 'ENOENT') {
|
|
318
|
+
console.error(`Error [RILL-C001]: File not found: ${args.file}`);
|
|
319
|
+
} else if (code === 'EISDIR') {
|
|
320
|
+
console.error(`Error [RILL-C002]: Path is a directory: ${args.file}`);
|
|
321
|
+
} else {
|
|
322
|
+
console.error(`Error [RILL-C002]: Cannot read file: ${args.file}`);
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
console.error(`Error [RILL-C002]: Cannot read file: ${args.file}`);
|
|
326
|
+
}
|
|
327
|
+
process.exit(2);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Parse AST with recovery to collect all errors
|
|
331
|
+
const parseResult = parseWithRecovery(source);
|
|
332
|
+
|
|
333
|
+
// Convert parse errors to diagnostics
|
|
334
|
+
// Only report the first parse error; subsequent errors are usually cascade noise
|
|
335
|
+
const parseDiagnostics: Diagnostic[] = parseResult.errors
|
|
336
|
+
.slice(0, 1)
|
|
337
|
+
.map((err) => {
|
|
338
|
+
const location = err.location ?? { line: 1, column: 1, offset: 0 };
|
|
339
|
+
const lineContent = source.split('\n')[location.line - 1]?.trim() ?? '';
|
|
340
|
+
return {
|
|
341
|
+
code: 'parse-error',
|
|
342
|
+
severity: 'error' as const,
|
|
343
|
+
message: err.message.replace(/ at \d+:\d+$/, ''),
|
|
344
|
+
location,
|
|
345
|
+
context: lineContent,
|
|
346
|
+
fix: null,
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// If there are parse errors, report them and exit
|
|
351
|
+
if (parseDiagnostics.length > 0) {
|
|
352
|
+
const output = formatDiagnostics(
|
|
353
|
+
args.file,
|
|
354
|
+
parseDiagnostics,
|
|
355
|
+
args.format,
|
|
356
|
+
args.verbose
|
|
357
|
+
);
|
|
358
|
+
console.log(output);
|
|
359
|
+
|
|
360
|
+
// If --fix was requested, report that fixes cannot be applied
|
|
361
|
+
if (args.fix) {
|
|
362
|
+
console.error('Cannot apply fixes: file has parse errors');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
process.exit(3);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const ast = parseResult.ast;
|
|
369
|
+
|
|
370
|
+
// Run validation
|
|
371
|
+
const diagnostics = validateScript(ast, source, config);
|
|
372
|
+
|
|
373
|
+
// Apply fixes if requested
|
|
374
|
+
if (args.fix && diagnostics.length > 0) {
|
|
375
|
+
const result = applyFixes(source, diagnostics, {
|
|
376
|
+
source,
|
|
377
|
+
ast,
|
|
378
|
+
config,
|
|
379
|
+
diagnostics: [],
|
|
380
|
+
variables: new Map(),
|
|
381
|
+
assertedHostCalls: new Set(),
|
|
382
|
+
variableScopes: new Map(),
|
|
383
|
+
scopeStack: [],
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Write fixed source back to file
|
|
387
|
+
if (result.applied > 0) {
|
|
388
|
+
const fs = await import('node:fs');
|
|
389
|
+
fs.writeFileSync(args.file, result.modified, 'utf-8');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Report fix results to stderr
|
|
393
|
+
if (result.applied > 0 || result.skipped > 0) {
|
|
394
|
+
if (result.applied > 0) {
|
|
395
|
+
console.error(
|
|
396
|
+
`Applied ${result.applied} fix${result.applied === 1 ? '' : 'es'}`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
if (result.skipped > 0) {
|
|
400
|
+
console.error(
|
|
401
|
+
`Skipped ${result.skipped} fix${result.skipped === 1 ? '' : 'es'}`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Format and output diagnostics
|
|
408
|
+
if (diagnostics.length === 0) {
|
|
409
|
+
// No errors - success
|
|
410
|
+
if (args.format === 'json') {
|
|
411
|
+
console.log(
|
|
412
|
+
JSON.stringify(
|
|
413
|
+
{
|
|
414
|
+
file: args.file,
|
|
415
|
+
errors: [],
|
|
416
|
+
summary: { total: 0, errors: 0, warnings: 0, info: 0 },
|
|
417
|
+
},
|
|
418
|
+
null,
|
|
419
|
+
2
|
|
420
|
+
)
|
|
421
|
+
);
|
|
422
|
+
} else {
|
|
423
|
+
console.log('No issues found');
|
|
424
|
+
}
|
|
425
|
+
process.exit(0);
|
|
426
|
+
} else {
|
|
427
|
+
// Output diagnostics
|
|
428
|
+
const output = formatDiagnostics(
|
|
429
|
+
args.file,
|
|
430
|
+
diagnostics,
|
|
431
|
+
args.format,
|
|
432
|
+
args.verbose
|
|
433
|
+
);
|
|
434
|
+
console.log(output);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
} catch (err) {
|
|
438
|
+
// Handle unexpected errors
|
|
439
|
+
if (err instanceof Error) {
|
|
440
|
+
console.error(`Error: ${err.message}`);
|
|
441
|
+
} else {
|
|
442
|
+
console.error(`Error: ${String(err)}`);
|
|
443
|
+
}
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Only run main if this is the entry point (not imported)
|
|
449
|
+
const shouldRunMain =
|
|
450
|
+
process.env['NODE_ENV'] !== 'test' &&
|
|
451
|
+
!process.env['VITEST'] &&
|
|
452
|
+
!process.env['VITEST_WORKER_ID'];
|
|
453
|
+
|
|
454
|
+
if (shouldRunMain) {
|
|
455
|
+
main();
|
|
456
|
+
}
|