@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,801 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rill CLI Tests: rill-check command
|
|
3
|
+
*
|
|
4
|
+
* Test Coverage Matrix (maps test cases to specification requirements):
|
|
5
|
+
* AC-S1: Validate file with diagnostics
|
|
6
|
+
* AC-S2: Apply fixes with --fix
|
|
7
|
+
* AC-S3: JSON output format
|
|
8
|
+
* AC-S4: Verbose mode output
|
|
9
|
+
* AC-S5: --version flag
|
|
10
|
+
* AC-S6: --help flag
|
|
11
|
+
* AC-S7: Config override
|
|
12
|
+
* AC-E1: File not found (exit 2)
|
|
13
|
+
* AC-E2: Parse error (exit 3)
|
|
14
|
+
* AC-E3: Parse error + --fix message
|
|
15
|
+
* AC-E4: Unknown flag error
|
|
16
|
+
* AC-E5: Invalid config error
|
|
17
|
+
* AC-B1: Empty file (no diagnostics)
|
|
18
|
+
* AC-B2: Parse-only errors
|
|
19
|
+
* AC-B5: (removed - 10K line perf test unnecessary for draft language)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { describe, expect, it, beforeAll, afterAll, afterEach } from 'vitest';
|
|
23
|
+
import { parseCheckArgs, formatDiagnostics } from '../../src/cli-check.js';
|
|
24
|
+
import { ParseError } from '@rcrsr/rill';
|
|
25
|
+
import {
|
|
26
|
+
type Diagnostic,
|
|
27
|
+
validateScript,
|
|
28
|
+
loadConfig,
|
|
29
|
+
createDefaultConfig,
|
|
30
|
+
applyFixes,
|
|
31
|
+
} from '../../src/check/index.js';
|
|
32
|
+
import { parse } from '@rcrsr/rill';
|
|
33
|
+
import * as fs from 'fs/promises';
|
|
34
|
+
import * as fssync from 'fs';
|
|
35
|
+
import * as path from 'path';
|
|
36
|
+
import * as os from 'os';
|
|
37
|
+
import { spawn } from 'child_process';
|
|
38
|
+
|
|
39
|
+
describe('rill-check CLI', () => {
|
|
40
|
+
let tempDir: string;
|
|
41
|
+
|
|
42
|
+
beforeAll(async () => {
|
|
43
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'rill-check-test-'));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterAll(async () => {
|
|
47
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Clean up config file after each test to prevent pollution between tests
|
|
51
|
+
afterEach(async () => {
|
|
52
|
+
const configPath = path.join(tempDir, '.rill-check.json');
|
|
53
|
+
try {
|
|
54
|
+
await fs.unlink(configPath);
|
|
55
|
+
} catch {
|
|
56
|
+
// Ignore if file doesn't exist
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Write a file to the temp directory and return its path.
|
|
62
|
+
*/
|
|
63
|
+
async function writeFile(name: string, content: string): Promise<string> {
|
|
64
|
+
const filePath = path.join(tempDir, name);
|
|
65
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
66
|
+
return filePath;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validate a script file in-process (without spawning CLI).
|
|
71
|
+
* Returns diagnostics array.
|
|
72
|
+
*/
|
|
73
|
+
function validateFile(filePath: string): Diagnostic[] {
|
|
74
|
+
const source = fssync.readFileSync(filePath, 'utf-8');
|
|
75
|
+
const ast = parse(source);
|
|
76
|
+
const config = loadConfig(path.dirname(filePath)) ?? createDefaultConfig();
|
|
77
|
+
return validateScript(ast, source, config);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Apply fixes to a script file in-process (without spawning CLI).
|
|
82
|
+
* Returns number of fixes applied.
|
|
83
|
+
*/
|
|
84
|
+
function applyFixesToFile(filePath: string): number {
|
|
85
|
+
const source = fssync.readFileSync(filePath, 'utf-8');
|
|
86
|
+
const ast = parse(source);
|
|
87
|
+
const config = loadConfig(path.dirname(filePath)) ?? createDefaultConfig();
|
|
88
|
+
const diagnostics = validateScript(ast, source, config);
|
|
89
|
+
|
|
90
|
+
if (diagnostics.length === 0) {
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const result = applyFixes(source, diagnostics, {
|
|
95
|
+
source,
|
|
96
|
+
ast,
|
|
97
|
+
config,
|
|
98
|
+
diagnostics: [],
|
|
99
|
+
variables: new Map(),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (result.applied > 0) {
|
|
103
|
+
fssync.writeFileSync(filePath, result.modified, 'utf-8');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result.applied;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Execute rill-check CLI command and capture output.
|
|
111
|
+
* Returns exit code, stdout, and stderr.
|
|
112
|
+
*/
|
|
113
|
+
async function execCheck(
|
|
114
|
+
args: string[]
|
|
115
|
+
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
const checkPath = path.join(process.cwd(), 'dist', 'cli-check.js');
|
|
118
|
+
const env = { ...process.env };
|
|
119
|
+
delete env['VITEST'];
|
|
120
|
+
delete env['VITEST_WORKER_ID'];
|
|
121
|
+
delete env['NODE_ENV'];
|
|
122
|
+
const proc = spawn('node', [checkPath, ...args], {
|
|
123
|
+
cwd: tempDir,
|
|
124
|
+
env,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
let stdout = '';
|
|
128
|
+
let stderr = '';
|
|
129
|
+
|
|
130
|
+
proc.stdout.on('data', (data) => {
|
|
131
|
+
stdout += data.toString();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
proc.stderr.on('data', (data) => {
|
|
135
|
+
stderr += data.toString();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
proc.on('close', (code) => {
|
|
139
|
+
resolve({
|
|
140
|
+
exitCode: code ?? 0,
|
|
141
|
+
stdout,
|
|
142
|
+
stderr,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ============================================================
|
|
149
|
+
// ARGUMENT PARSING
|
|
150
|
+
// ============================================================
|
|
151
|
+
|
|
152
|
+
describe('parseCheckArgs', () => {
|
|
153
|
+
it('parses file path', () => {
|
|
154
|
+
const parsed = parseCheckArgs(['test.rill']);
|
|
155
|
+
expect(parsed).toEqual({
|
|
156
|
+
mode: 'check',
|
|
157
|
+
file: 'test.rill',
|
|
158
|
+
fix: false,
|
|
159
|
+
verbose: false,
|
|
160
|
+
format: 'text',
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('parses --help flag [AC-S6]', () => {
|
|
165
|
+
expect(parseCheckArgs(['--help'])).toEqual({ mode: 'help' });
|
|
166
|
+
expect(parseCheckArgs(['-h'])).toEqual({ mode: 'help' });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('parses --version flag [AC-S5]', () => {
|
|
170
|
+
expect(parseCheckArgs(['--version'])).toEqual({ mode: 'version' });
|
|
171
|
+
expect(parseCheckArgs(['-v'])).toEqual({ mode: 'version' });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('parses --fix flag [AC-S2]', () => {
|
|
175
|
+
const parsed = parseCheckArgs(['test.rill', '--fix']);
|
|
176
|
+
expect(parsed.mode).toBe('check');
|
|
177
|
+
if (parsed.mode === 'check') {
|
|
178
|
+
expect(parsed.fix).toBe(true);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('parses --verbose flag [AC-S4]', () => {
|
|
183
|
+
const parsed = parseCheckArgs(['test.rill', '--verbose']);
|
|
184
|
+
expect(parsed.mode).toBe('check');
|
|
185
|
+
if (parsed.mode === 'check') {
|
|
186
|
+
expect(parsed.verbose).toBe(true);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('parses --format json [AC-S3]', () => {
|
|
191
|
+
const parsed = parseCheckArgs(['test.rill', '--format', 'json']);
|
|
192
|
+
expect(parsed.mode).toBe('check');
|
|
193
|
+
if (parsed.mode === 'check') {
|
|
194
|
+
expect(parsed.format).toBe('json');
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('throws on unknown flag [AC-E4]', () => {
|
|
199
|
+
expect(() => parseCheckArgs(['--unknown'])).toThrow(
|
|
200
|
+
'Unknown option: --unknown'
|
|
201
|
+
);
|
|
202
|
+
expect(() => parseCheckArgs(['-x'])).toThrow('Unknown option: -x');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('throws when missing file argument', () => {
|
|
206
|
+
expect(() => parseCheckArgs([])).toThrow('Missing file argument');
|
|
207
|
+
expect(() => parseCheckArgs(['--fix'])).toThrow('Missing file argument');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// ============================================================
|
|
212
|
+
// DIAGNOSTIC FORMATTING
|
|
213
|
+
// ============================================================
|
|
214
|
+
|
|
215
|
+
describe('formatDiagnostics', () => {
|
|
216
|
+
it('formats text output', () => {
|
|
217
|
+
const diagnostics: Diagnostic[] = [
|
|
218
|
+
{
|
|
219
|
+
location: { line: 5, column: 10, offset: 50 },
|
|
220
|
+
severity: 'error',
|
|
221
|
+
code: 'TEST_ERROR',
|
|
222
|
+
message: 'Test error message',
|
|
223
|
+
context: '"hello"',
|
|
224
|
+
fix: null,
|
|
225
|
+
},
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const output = formatDiagnostics('test.rill', diagnostics, 'text', false);
|
|
229
|
+
expect(output).toBe(
|
|
230
|
+
'test.rill:5:10: error: Test error message (TEST_ERROR)'
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('formats JSON output [AC-S3]', () => {
|
|
235
|
+
const diagnostics: Diagnostic[] = [
|
|
236
|
+
{
|
|
237
|
+
location: { line: 5, column: 10, offset: 50 },
|
|
238
|
+
severity: 'error',
|
|
239
|
+
code: 'TEST_ERROR',
|
|
240
|
+
message: 'Test error message',
|
|
241
|
+
context: '"hello"',
|
|
242
|
+
fix: null,
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
const output = formatDiagnostics('test.rill', diagnostics, 'json', false);
|
|
247
|
+
const parsed = JSON.parse(output);
|
|
248
|
+
|
|
249
|
+
expect(parsed).toEqual({
|
|
250
|
+
file: 'test.rill',
|
|
251
|
+
errors: [
|
|
252
|
+
{
|
|
253
|
+
location: { line: 5, column: 10, offset: 50 },
|
|
254
|
+
severity: 'error',
|
|
255
|
+
code: 'TEST_ERROR',
|
|
256
|
+
message: 'Test error message',
|
|
257
|
+
context: '"hello"',
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
summary: { total: 1, errors: 1, warnings: 0, info: 0 },
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('formats empty diagnostics as empty array', () => {
|
|
265
|
+
const output = formatDiagnostics('test.rill', [], 'text', false);
|
|
266
|
+
expect(output).toBe('');
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// ============================================================
|
|
271
|
+
// SUCCESS CASES
|
|
272
|
+
// ============================================================
|
|
273
|
+
|
|
274
|
+
describe('success cases', () => {
|
|
275
|
+
it('validates file with no diagnostics [AC-B1]', async () => {
|
|
276
|
+
const script = await writeFile('valid.rill', '"hello"');
|
|
277
|
+
const diagnostics = validateFile(script);
|
|
278
|
+
|
|
279
|
+
expect(diagnostics).toEqual([]);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('validates empty file [AC-B1]', async () => {
|
|
283
|
+
const script = await writeFile('empty.rill', '');
|
|
284
|
+
const diagnostics = validateFile(script);
|
|
285
|
+
|
|
286
|
+
expect(diagnostics).toEqual([]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('outputs JSON format when no diagnostics [AC-S3]', async () => {
|
|
290
|
+
const script = await writeFile('valid-json.rill', '"hello"');
|
|
291
|
+
const diagnostics = validateFile(script);
|
|
292
|
+
|
|
293
|
+
expect(diagnostics).toEqual([]);
|
|
294
|
+
|
|
295
|
+
const output = formatDiagnostics(script, diagnostics, 'json', false);
|
|
296
|
+
const parsed = JSON.parse(output);
|
|
297
|
+
expect(parsed.file).toBe(script);
|
|
298
|
+
expect(parsed.errors).toEqual([]);
|
|
299
|
+
expect(parsed.summary).toEqual({
|
|
300
|
+
total: 0,
|
|
301
|
+
errors: 0,
|
|
302
|
+
warnings: 0,
|
|
303
|
+
info: 0,
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('shows help message [AC-S6]', async () => {
|
|
308
|
+
const result = await execCheck(['--help']);
|
|
309
|
+
|
|
310
|
+
expect(result.exitCode).toBe(0);
|
|
311
|
+
expect(result.stdout).toContain('rill-check');
|
|
312
|
+
expect(result.stdout).toContain('Usage:');
|
|
313
|
+
expect(result.stdout).toContain('--fix');
|
|
314
|
+
expect(result.stdout).toContain('--format');
|
|
315
|
+
expect(result.stdout).toContain('--verbose');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('shows version number [AC-S5]', async () => {
|
|
319
|
+
const result = await execCheck(['--version']);
|
|
320
|
+
|
|
321
|
+
expect(result.exitCode).toBe(0);
|
|
322
|
+
expect(result.stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/);
|
|
323
|
+
|
|
324
|
+
// Verify version matches package.json
|
|
325
|
+
const { readFile } = await import('fs/promises');
|
|
326
|
+
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
|
|
327
|
+
const packageJson = JSON.parse(
|
|
328
|
+
await readFile(packageJsonPath, 'utf-8')
|
|
329
|
+
) as { version: string };
|
|
330
|
+
expect(result.stdout.trim()).toBe(packageJson.version);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// ============================================================
|
|
335
|
+
// ERROR CASES
|
|
336
|
+
// ============================================================
|
|
337
|
+
|
|
338
|
+
describe('error cases', () => {
|
|
339
|
+
it('exits with code 2 for file not found [AC-E1]', async () => {
|
|
340
|
+
const result = await execCheck(['/nonexistent/file.rill']);
|
|
341
|
+
|
|
342
|
+
expect(result.exitCode).toBe(2);
|
|
343
|
+
expect(result.stderr).toContain('File not found');
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('exits with code 2 for directory path [AC-E1]', async () => {
|
|
347
|
+
const result = await execCheck([tempDir]);
|
|
348
|
+
|
|
349
|
+
expect(result.exitCode).toBe(2);
|
|
350
|
+
expect(result.stderr).toContain('directory');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('exits with code 3 for parse error [AC-E2]', async () => {
|
|
354
|
+
const script = await writeFile('parse-error.rill', '|x| x }');
|
|
355
|
+
const result = await execCheck([script]);
|
|
356
|
+
|
|
357
|
+
expect(result.exitCode).toBe(3);
|
|
358
|
+
// Parse errors now reported as diagnostics to stdout
|
|
359
|
+
expect(result.stdout).toContain('parse-error');
|
|
360
|
+
expect(result.stdout).toContain('error:');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('reports parse error with location [AC-B2]', async () => {
|
|
364
|
+
const script = await writeFile('parse-location.rill', 'invalid {');
|
|
365
|
+
|
|
366
|
+
expect(() => {
|
|
367
|
+
const source = fssync.readFileSync(script, 'utf-8');
|
|
368
|
+
parse(source);
|
|
369
|
+
}).toThrow(ParseError);
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
const source = fssync.readFileSync(script, 'utf-8');
|
|
373
|
+
parse(source);
|
|
374
|
+
} catch (err) {
|
|
375
|
+
expect(err).toBeInstanceOf(ParseError);
|
|
376
|
+
if (err instanceof ParseError) {
|
|
377
|
+
expect(err.location).toBeDefined();
|
|
378
|
+
expect(err.location?.line).toBeGreaterThan(0);
|
|
379
|
+
expect(err.location?.column).toBeGreaterThan(0);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('reports cannot apply fixes on parse error [AC-E3]', async () => {
|
|
385
|
+
const script = await writeFile('parse-fix.rill', '|x| x }');
|
|
386
|
+
|
|
387
|
+
expect(() => {
|
|
388
|
+
const source = fssync.readFileSync(script, 'utf-8');
|
|
389
|
+
parse(source);
|
|
390
|
+
}).toThrow(ParseError);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('reports lexer errors as diagnostics instead of crashing', async () => {
|
|
394
|
+
// Single quote character is invalid in rill (causes LexerError)
|
|
395
|
+
const script = await writeFile('lex-error.rill', "test' invalid");
|
|
396
|
+
const result = await execCheck([script]);
|
|
397
|
+
|
|
398
|
+
// Should exit with code 3 (parse error) not crash
|
|
399
|
+
expect(result.exitCode).toBe(3);
|
|
400
|
+
// Should show diagnostic output, not an unhandled exception
|
|
401
|
+
expect(result.stdout).toContain('parse-error');
|
|
402
|
+
expect(result.stdout).toContain('lex-error.rill');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('exits with code 1 for unknown flag [AC-E4]', async () => {
|
|
406
|
+
expect(() => parseCheckArgs(['--unknown'])).toThrow('Unknown option');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('exits with code 1 for invalid config [AC-E5]', async () => {
|
|
410
|
+
// Write invalid config file in temp directory
|
|
411
|
+
await writeFile('.rill-check.json', '{ invalid json }');
|
|
412
|
+
|
|
413
|
+
expect(() => loadConfig(tempDir)).toThrow(/invalid JSON/i);
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// ============================================================
|
|
418
|
+
// CONFIG OVERRIDE
|
|
419
|
+
// ============================================================
|
|
420
|
+
|
|
421
|
+
describe('config override [AC-S7]', () => {
|
|
422
|
+
it('loads config from working directory', async () => {
|
|
423
|
+
// Write valid empty config
|
|
424
|
+
await writeFile('.rill-check.json', JSON.stringify({ rules: {} }));
|
|
425
|
+
const script = await writeFile('config-test.rill', '"hello"');
|
|
426
|
+
|
|
427
|
+
const config = loadConfig(tempDir);
|
|
428
|
+
expect(config).toBeDefined();
|
|
429
|
+
expect(config?.rules).toBeDefined();
|
|
430
|
+
|
|
431
|
+
const diagnostics = validateFile(script);
|
|
432
|
+
expect(diagnostics).toEqual([]);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('uses default config when no config file present', async () => {
|
|
436
|
+
// Create subdirectory without config
|
|
437
|
+
const subdir = path.join(tempDir, 'no-config');
|
|
438
|
+
await fs.mkdir(subdir, { recursive: true });
|
|
439
|
+
const script = path.join(subdir, 'test.rill');
|
|
440
|
+
await fs.writeFile(script, '"hello"', 'utf-8');
|
|
441
|
+
|
|
442
|
+
const config = loadConfig(subdir);
|
|
443
|
+
expect(config).toBeNull();
|
|
444
|
+
|
|
445
|
+
const defaultConfig = createDefaultConfig();
|
|
446
|
+
expect(defaultConfig).toBeDefined();
|
|
447
|
+
expect(defaultConfig.rules).toBeDefined();
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// ============================================================
|
|
452
|
+
// FIX APPLICATION
|
|
453
|
+
// ============================================================
|
|
454
|
+
|
|
455
|
+
describe('fix application [AC-S2]', () => {
|
|
456
|
+
it('applies fixes when --fix flag present', async () => {
|
|
457
|
+
// Note: Since no validation rules exist yet, we can't test actual fix application
|
|
458
|
+
// This test verifies the --fix flag is processed without error
|
|
459
|
+
const script = await writeFile('fix-test.rill', '"hello"');
|
|
460
|
+
const applied = applyFixesToFile(script);
|
|
461
|
+
|
|
462
|
+
// Should complete successfully with no fixes
|
|
463
|
+
expect(applied).toBe(0);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('reports applied fix count to stderr', async () => {
|
|
467
|
+
// When validation rules are added, this test should verify fix count reporting
|
|
468
|
+
const script = await writeFile('fix-count.rill', '"hello"');
|
|
469
|
+
const applied = applyFixesToFile(script);
|
|
470
|
+
|
|
471
|
+
// Should not apply fixes when none needed
|
|
472
|
+
expect(applied).toBe(0);
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// ============================================================
|
|
477
|
+
// VERBOSE MODE
|
|
478
|
+
// ============================================================
|
|
479
|
+
|
|
480
|
+
describe('verbose mode [AC-S4]', () => {
|
|
481
|
+
it('includes category in JSON output when verbose', () => {
|
|
482
|
+
// Test the formatDiagnostics function directly with verbose flag
|
|
483
|
+
const diagnostics: Diagnostic[] = [
|
|
484
|
+
{
|
|
485
|
+
location: { line: 1, column: 1, offset: 0 },
|
|
486
|
+
severity: 'warning',
|
|
487
|
+
code: 'TEST_WARN',
|
|
488
|
+
message: 'Test warning',
|
|
489
|
+
context: 'test',
|
|
490
|
+
fix: null,
|
|
491
|
+
},
|
|
492
|
+
];
|
|
493
|
+
|
|
494
|
+
const output = formatDiagnostics('test.rill', diagnostics, 'json', true);
|
|
495
|
+
const parsed = JSON.parse(output);
|
|
496
|
+
|
|
497
|
+
// Verbose mode adds category field (when rule exists in VALIDATION_RULES)
|
|
498
|
+
expect(parsed.errors[0]).toHaveProperty('severity');
|
|
499
|
+
expect(parsed.errors[0]).toHaveProperty('code');
|
|
500
|
+
expect(parsed.errors[0]).toHaveProperty('message');
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('CLI accepts --verbose flag without error', () => {
|
|
504
|
+
const args = parseCheckArgs(['test.rill', '--verbose']);
|
|
505
|
+
expect(args.mode).toBe('check');
|
|
506
|
+
if (args.mode === 'check') {
|
|
507
|
+
expect(args.verbose).toBe(true);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// ============================================================
|
|
513
|
+
// OUTPUT FORMAT
|
|
514
|
+
// ============================================================
|
|
515
|
+
|
|
516
|
+
describe('output format', () => {
|
|
517
|
+
it('outputs text format by default', async () => {
|
|
518
|
+
const script = await writeFile('format-default.rill', '"hello"');
|
|
519
|
+
const diagnostics = validateFile(script);
|
|
520
|
+
|
|
521
|
+
const output = formatDiagnostics(script, diagnostics, 'text', false);
|
|
522
|
+
expect(output).toBe('');
|
|
523
|
+
|
|
524
|
+
// Text format outputs empty string when no diagnostics
|
|
525
|
+
expect(diagnostics).toEqual([]);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('outputs JSON when --format json specified [AC-S3]', async () => {
|
|
529
|
+
const script = await writeFile('format-json.rill', '"hello"');
|
|
530
|
+
const diagnostics = validateFile(script);
|
|
531
|
+
|
|
532
|
+
const output = formatDiagnostics(script, diagnostics, 'json', false);
|
|
533
|
+
// Should be valid JSON
|
|
534
|
+
const parsed = JSON.parse(output);
|
|
535
|
+
expect(parsed).toHaveProperty('file');
|
|
536
|
+
expect(parsed).toHaveProperty('errors');
|
|
537
|
+
expect(parsed).toHaveProperty('summary');
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// ============================================================
|
|
542
|
+
// BOUNDARY TESTS
|
|
543
|
+
// ============================================================
|
|
544
|
+
|
|
545
|
+
describe('boundary tests', () => {
|
|
546
|
+
it('fix idempotency: second run applies zero fixes [AC-B3]', async () => {
|
|
547
|
+
// Create file with multiple naming violations
|
|
548
|
+
const content = `
|
|
549
|
+
"userName" => $userName
|
|
550
|
+
"itemList" => $itemList
|
|
551
|
+
$userName -> .len
|
|
552
|
+
$itemList -> .len
|
|
553
|
+
`;
|
|
554
|
+
const script = await writeFile('idempotent.rill', content);
|
|
555
|
+
|
|
556
|
+
// Get initial diagnostics
|
|
557
|
+
const firstDiagnostics = validateFile(script);
|
|
558
|
+
const hasViolations = firstDiagnostics.length > 0;
|
|
559
|
+
|
|
560
|
+
if (hasViolations) {
|
|
561
|
+
// Apply fixes first time
|
|
562
|
+
const firstApplied = applyFixesToFile(script);
|
|
563
|
+
expect(firstApplied).toBeGreaterThan(0);
|
|
564
|
+
|
|
565
|
+
// Apply fixes second time (should be no-op because file was modified)
|
|
566
|
+
const secondApplied = applyFixesToFile(script);
|
|
567
|
+
expect(secondApplied).toBe(0);
|
|
568
|
+
|
|
569
|
+
const finalDiagnostics = validateFile(script);
|
|
570
|
+
expect(finalDiagnostics).toEqual([]);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('1000-line validation completes in reasonable time [AC-B4]', async () => {
|
|
575
|
+
// Generate 1000 lines of valid rill code
|
|
576
|
+
const lines: string[] = [];
|
|
577
|
+
for (let i = 0; i < 1000; i++) {
|
|
578
|
+
lines.push(`"line_${i}" => $line_${i}`);
|
|
579
|
+
}
|
|
580
|
+
const content = lines.join('\n');
|
|
581
|
+
const script = await writeFile('perf-1000.rill', content);
|
|
582
|
+
|
|
583
|
+
const startTime = Date.now();
|
|
584
|
+
const diagnostics = validateFile(script);
|
|
585
|
+
const duration = Date.now() - startTime;
|
|
586
|
+
|
|
587
|
+
expect(diagnostics).toEqual([]);
|
|
588
|
+
expect(duration).toBeLessThan(2000);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('all rules enabled by default [AC-B6]', async () => {
|
|
592
|
+
// Import config and rules modules to verify defaults
|
|
593
|
+
const { createDefaultConfig } = await import('../../src/check/config.js');
|
|
594
|
+
const { VALIDATION_RULES } =
|
|
595
|
+
await import('../../src/check/rules/index.js');
|
|
596
|
+
|
|
597
|
+
const config = createDefaultConfig();
|
|
598
|
+
|
|
599
|
+
// Verify all rules in VALIDATION_RULES are enabled by default
|
|
600
|
+
const totalRules = VALIDATION_RULES.length;
|
|
601
|
+
const enabledCount = Object.values(config.rules).filter(
|
|
602
|
+
(state) => state === 'on'
|
|
603
|
+
).length;
|
|
604
|
+
|
|
605
|
+
expect(enabledCount).toBe(totalRules);
|
|
606
|
+
expect(totalRules).toBeGreaterThanOrEqual(20);
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// ============================================================
|
|
611
|
+
// ERROR HANDLING
|
|
612
|
+
// ============================================================
|
|
613
|
+
|
|
614
|
+
describe('error handling', () => {
|
|
615
|
+
it('applies fixes for multiple violations [AC-E6]', async () => {
|
|
616
|
+
// Note: Fix collision handling (EC-5) is tested in tests/check/fixer.test.ts
|
|
617
|
+
// This test verifies that non-colliding fixes are successfully applied
|
|
618
|
+
const content = `
|
|
619
|
+
[userName: "test"] => $data1
|
|
620
|
+
[itemList: [1, 2, 3]] => $data2
|
|
621
|
+
`;
|
|
622
|
+
const script = await writeFile('collision.rill', content);
|
|
623
|
+
|
|
624
|
+
// Run check to get diagnostics
|
|
625
|
+
const initialDiagnostics = validateFile(script);
|
|
626
|
+
|
|
627
|
+
// Should have violations
|
|
628
|
+
const hasUserName = initialDiagnostics.some((d) =>
|
|
629
|
+
d.message.includes('userName')
|
|
630
|
+
);
|
|
631
|
+
const hasItemList = initialDiagnostics.some((d) =>
|
|
632
|
+
d.message.includes('itemList')
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
if (hasUserName || hasItemList) {
|
|
636
|
+
// Apply fixes
|
|
637
|
+
const applied = applyFixesToFile(script);
|
|
638
|
+
expect(applied).toBeGreaterThan(0);
|
|
639
|
+
|
|
640
|
+
// Verify fix was applied (run check again on modified file)
|
|
641
|
+
const finalDiagnostics = validateFile(script);
|
|
642
|
+
|
|
643
|
+
// After fix, violations should be eliminated
|
|
644
|
+
expect(finalDiagnostics).toEqual([]);
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// ============================================================
|
|
650
|
+
// ERROR CONTRACTS
|
|
651
|
+
// ============================================================
|
|
652
|
+
|
|
653
|
+
describe('error contracts', () => {
|
|
654
|
+
describe('EC-1: parseCheckArgs - unknown flag', () => {
|
|
655
|
+
it('throws error for unknown long flag', () => {
|
|
656
|
+
expect(() => parseCheckArgs(['--unknown'])).toThrow(
|
|
657
|
+
'Unknown option: --unknown'
|
|
658
|
+
);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('throws error for unknown short flag', () => {
|
|
662
|
+
expect(() => parseCheckArgs(['-x'])).toThrow('Unknown option: -x');
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('throws error for invalid --format value', () => {
|
|
666
|
+
expect(() => parseCheckArgs(['test.rill', '--format', 'xml'])).toThrow(
|
|
667
|
+
'Invalid format: xml'
|
|
668
|
+
);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it('CLI exits with code 1 for unknown flag', async () => {
|
|
672
|
+
const result = await execCheck(['--unknown']);
|
|
673
|
+
expect(result.exitCode).toBe(1);
|
|
674
|
+
expect(result.stderr).toContain('Unknown option');
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
describe('EC-2: parseCheckArgs - missing file', () => {
|
|
679
|
+
it('throws error when no arguments provided', () => {
|
|
680
|
+
expect(() => parseCheckArgs([])).toThrow('Missing file argument');
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('throws error when only flags provided', () => {
|
|
684
|
+
expect(() => parseCheckArgs(['--fix', '--verbose'])).toThrow(
|
|
685
|
+
'Missing file argument'
|
|
686
|
+
);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it('CLI exits with code 1 for missing file', async () => {
|
|
690
|
+
const result = await execCheck(['--fix']);
|
|
691
|
+
expect(result.exitCode).toBe(1);
|
|
692
|
+
expect(result.stderr).toContain('Missing file argument');
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
describe('EC-3: loadConfig - invalid JSON', () => {
|
|
697
|
+
it('throws error for malformed JSON', async () => {
|
|
698
|
+
await writeFile('.rill-check.json', '{ invalid json }');
|
|
699
|
+
|
|
700
|
+
expect(() => loadConfig(tempDir)).toThrow(/invalid JSON/i);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('throws error for non-object JSON', async () => {
|
|
704
|
+
await writeFile('.rill-check.json', '"string value"');
|
|
705
|
+
|
|
706
|
+
expect(() => loadConfig(tempDir)).toThrow(/must be an object/i);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it('throws error for invalid rule state', async () => {
|
|
710
|
+
await writeFile(
|
|
711
|
+
'.rill-check.json',
|
|
712
|
+
JSON.stringify({ rules: { SOME_RULE: 'invalid_state' } })
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
expect(() => loadConfig(tempDir)).toThrow(/Invalid configuration/i);
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
describe('EC-4: loadConfig - unknown rule', () => {
|
|
720
|
+
it('throws error for unknown rule in rules field', async () => {
|
|
721
|
+
await writeFile(
|
|
722
|
+
'.rill-check.json',
|
|
723
|
+
JSON.stringify({ rules: { UNKNOWN_RULE: 'on' } })
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
expect(() => loadConfig(tempDir)).toThrow(/unknown rule UNKNOWN_RULE/i);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it('throws error for unknown rule in severity field', async () => {
|
|
730
|
+
await writeFile(
|
|
731
|
+
'.rill-check.json',
|
|
732
|
+
JSON.stringify({ severity: { UNKNOWN_RULE: 'error' } })
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
expect(() => loadConfig(tempDir)).toThrow(/unknown rule UNKNOWN_RULE/i);
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
describe('EC-5: applyFixes - fix collision (tested via unit tests)', () => {
|
|
740
|
+
it('reference: collision detection in fixer.test.ts', () => {
|
|
741
|
+
// EC-5 is tested in tests/check/fixer.test.ts
|
|
742
|
+
// The applyFixes function skips overlapping fixes with reason
|
|
743
|
+
// See fixer.test.ts "collision detection [EC-5]" describe block
|
|
744
|
+
expect(true).toBe(true);
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
describe('EC-6: applyFixes - parse failure (tested via unit tests)', () => {
|
|
749
|
+
it('reference: parse verification in fixer.test.ts', () => {
|
|
750
|
+
// EC-6 is tested in tests/check/fixer.test.ts
|
|
751
|
+
// The applyFixes function throws when fix creates invalid syntax
|
|
752
|
+
// See fixer.test.ts "parse verification [EC-6]" describe block
|
|
753
|
+
expect(true).toBe(true);
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// ============================================================
|
|
759
|
+
// EDGE CASES
|
|
760
|
+
// ============================================================
|
|
761
|
+
|
|
762
|
+
describe('edge cases', () => {
|
|
763
|
+
it('handles file with only whitespace', async () => {
|
|
764
|
+
const script = await writeFile('whitespace.rill', ' \n\n \t ');
|
|
765
|
+
const diagnostics = validateFile(script);
|
|
766
|
+
|
|
767
|
+
expect(diagnostics).toEqual([]);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
it('handles file with only comments', async () => {
|
|
771
|
+
const script = await writeFile('comments.rill', '# comment\n# another');
|
|
772
|
+
const diagnostics = validateFile(script);
|
|
773
|
+
|
|
774
|
+
expect(diagnostics).toEqual([]);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it('handles multiple flags in different order', async () => {
|
|
778
|
+
const script = await writeFile('multi-flags.rill', '"hello"');
|
|
779
|
+
const args = parseCheckArgs([
|
|
780
|
+
'--verbose',
|
|
781
|
+
script,
|
|
782
|
+
'--format',
|
|
783
|
+
'json',
|
|
784
|
+
'--fix',
|
|
785
|
+
]);
|
|
786
|
+
|
|
787
|
+
expect(args.mode).toBe('check');
|
|
788
|
+
if (args.mode === 'check') {
|
|
789
|
+
expect(args.verbose).toBe(true);
|
|
790
|
+
expect(args.format).toBe('json');
|
|
791
|
+
expect(args.fix).toBe(true);
|
|
792
|
+
expect(args.file).toBe(script);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const diagnostics = validateFile(script);
|
|
796
|
+
const output = formatDiagnostics(script, diagnostics, 'json', true);
|
|
797
|
+
const parsed = JSON.parse(output);
|
|
798
|
+
expect(parsed).toHaveProperty('file');
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
});
|