@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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/dist/check/config.d.ts +20 -0
  3. package/dist/check/config.d.ts.map +1 -0
  4. package/dist/check/config.js +151 -0
  5. package/dist/check/config.js.map +1 -0
  6. package/dist/check/fixer.d.ts +39 -0
  7. package/dist/check/fixer.d.ts.map +1 -0
  8. package/dist/check/fixer.js +119 -0
  9. package/dist/check/fixer.js.map +1 -0
  10. package/dist/check/index.d.ts +10 -0
  11. package/dist/check/index.d.ts.map +1 -0
  12. package/dist/check/index.js +21 -0
  13. package/dist/check/index.js.map +1 -0
  14. package/dist/check/rules/anti-patterns.d.ts +65 -0
  15. package/dist/check/rules/anti-patterns.d.ts.map +1 -0
  16. package/dist/check/rules/anti-patterns.js +481 -0
  17. package/dist/check/rules/anti-patterns.js.map +1 -0
  18. package/dist/check/rules/closures.d.ts +66 -0
  19. package/dist/check/rules/closures.d.ts.map +1 -0
  20. package/dist/check/rules/closures.js +370 -0
  21. package/dist/check/rules/closures.js.map +1 -0
  22. package/dist/check/rules/collections.d.ts +90 -0
  23. package/dist/check/rules/collections.d.ts.map +1 -0
  24. package/dist/check/rules/collections.js +373 -0
  25. package/dist/check/rules/collections.js.map +1 -0
  26. package/dist/check/rules/conditionals.d.ts +41 -0
  27. package/dist/check/rules/conditionals.d.ts.map +1 -0
  28. package/dist/check/rules/conditionals.js +134 -0
  29. package/dist/check/rules/conditionals.js.map +1 -0
  30. package/dist/check/rules/flow.d.ts +46 -0
  31. package/dist/check/rules/flow.d.ts.map +1 -0
  32. package/dist/check/rules/flow.js +206 -0
  33. package/dist/check/rules/flow.js.map +1 -0
  34. package/dist/check/rules/formatting.d.ts +143 -0
  35. package/dist/check/rules/formatting.d.ts.map +1 -0
  36. package/dist/check/rules/formatting.js +656 -0
  37. package/dist/check/rules/formatting.js.map +1 -0
  38. package/dist/check/rules/helpers.d.ts +26 -0
  39. package/dist/check/rules/helpers.d.ts.map +1 -0
  40. package/dist/check/rules/helpers.js +66 -0
  41. package/dist/check/rules/helpers.js.map +1 -0
  42. package/dist/check/rules/index.d.ts +21 -0
  43. package/dist/check/rules/index.d.ts.map +1 -0
  44. package/dist/check/rules/index.js +78 -0
  45. package/dist/check/rules/index.js.map +1 -0
  46. package/dist/check/rules/loops.d.ts +77 -0
  47. package/dist/check/rules/loops.d.ts.map +1 -0
  48. package/dist/check/rules/loops.js +310 -0
  49. package/dist/check/rules/loops.js.map +1 -0
  50. package/dist/check/rules/naming.d.ts +21 -0
  51. package/dist/check/rules/naming.d.ts.map +1 -0
  52. package/dist/check/rules/naming.js +174 -0
  53. package/dist/check/rules/naming.js.map +1 -0
  54. package/dist/check/rules/strings.d.ts +28 -0
  55. package/dist/check/rules/strings.d.ts.map +1 -0
  56. package/dist/check/rules/strings.js +79 -0
  57. package/dist/check/rules/strings.js.map +1 -0
  58. package/dist/check/rules/types.d.ts +41 -0
  59. package/dist/check/rules/types.d.ts.map +1 -0
  60. package/dist/check/rules/types.js +167 -0
  61. package/dist/check/rules/types.js.map +1 -0
  62. package/dist/check/types.d.ts +112 -0
  63. package/dist/check/types.d.ts.map +1 -0
  64. package/dist/check/types.js +6 -0
  65. package/dist/check/types.js.map +1 -0
  66. package/dist/check/validator.d.ts +18 -0
  67. package/dist/check/validator.d.ts.map +1 -0
  68. package/dist/check/validator.js +110 -0
  69. package/dist/check/validator.js.map +1 -0
  70. package/dist/check/visitor.d.ts +33 -0
  71. package/dist/check/visitor.d.ts.map +1 -0
  72. package/dist/check/visitor.js +259 -0
  73. package/dist/check/visitor.js.map +1 -0
  74. package/dist/cli-check.d.ts +43 -0
  75. package/dist/cli-check.d.ts.map +1 -0
  76. package/dist/cli-check.js +366 -0
  77. package/dist/cli-check.js.map +1 -0
  78. package/dist/cli-error-enrichment.d.ts +73 -0
  79. package/dist/cli-error-enrichment.d.ts.map +1 -0
  80. package/dist/cli-error-enrichment.js +205 -0
  81. package/dist/cli-error-enrichment.js.map +1 -0
  82. package/dist/cli-error-formatter.d.ts +45 -0
  83. package/dist/cli-error-formatter.d.ts.map +1 -0
  84. package/dist/cli-error-formatter.js +218 -0
  85. package/dist/cli-error-formatter.js.map +1 -0
  86. package/dist/cli-eval.d.ts +15 -0
  87. package/dist/cli-eval.d.ts.map +1 -0
  88. package/dist/cli-eval.js +116 -0
  89. package/dist/cli-eval.js.map +1 -0
  90. package/dist/cli-exec.d.ts +58 -0
  91. package/dist/cli-exec.d.ts.map +1 -0
  92. package/dist/cli-exec.js +326 -0
  93. package/dist/cli-exec.js.map +1 -0
  94. package/dist/cli-explain.d.ts +24 -0
  95. package/dist/cli-explain.d.ts.map +1 -0
  96. package/dist/cli-explain.js +68 -0
  97. package/dist/cli-explain.js.map +1 -0
  98. package/dist/cli-lsp-diagnostic.d.ts +35 -0
  99. package/dist/cli-lsp-diagnostic.d.ts.map +1 -0
  100. package/dist/cli-lsp-diagnostic.js +98 -0
  101. package/dist/cli-lsp-diagnostic.js.map +1 -0
  102. package/dist/cli-module-loader.d.ts +19 -0
  103. package/dist/cli-module-loader.d.ts.map +1 -0
  104. package/dist/cli-module-loader.js +83 -0
  105. package/dist/cli-module-loader.js.map +1 -0
  106. package/dist/cli-shared.d.ts +62 -0
  107. package/dist/cli-shared.d.ts.map +1 -0
  108. package/dist/cli-shared.js +158 -0
  109. package/dist/cli-shared.js.map +1 -0
  110. package/dist/cli.d.ts +13 -0
  111. package/dist/cli.d.ts.map +1 -0
  112. package/dist/cli.js +62 -0
  113. package/dist/cli.js.map +1 -0
  114. package/dist/test-internal-import.d.ts +2 -0
  115. package/dist/test-internal-import.d.ts.map +1 -0
  116. package/dist/test-internal-import.js +7 -0
  117. package/dist/test-internal-import.js.map +1 -0
  118. package/package.json +24 -0
  119. package/src/check/config.ts +202 -0
  120. package/src/check/fixer.ts +174 -0
  121. package/src/check/index.ts +39 -0
  122. package/src/check/rules/anti-patterns.ts +585 -0
  123. package/src/check/rules/closures.ts +445 -0
  124. package/src/check/rules/collections.ts +437 -0
  125. package/src/check/rules/conditionals.ts +155 -0
  126. package/src/check/rules/flow.ts +262 -0
  127. package/src/check/rules/formatting.ts +811 -0
  128. package/src/check/rules/helpers.ts +89 -0
  129. package/src/check/rules/index.ts +140 -0
  130. package/src/check/rules/loops.ts +372 -0
  131. package/src/check/rules/naming.ts +242 -0
  132. package/src/check/rules/strings.ts +104 -0
  133. package/src/check/rules/types.ts +214 -0
  134. package/src/check/types.ts +163 -0
  135. package/src/check/validator.ts +136 -0
  136. package/src/check/visitor.ts +338 -0
  137. package/src/cli-check.ts +456 -0
  138. package/src/cli-error-enrichment.ts +274 -0
  139. package/src/cli-error-formatter.ts +313 -0
  140. package/src/cli-eval.ts +145 -0
  141. package/src/cli-exec.ts +408 -0
  142. package/src/cli-explain.ts +76 -0
  143. package/src/cli-lsp-diagnostic.ts +132 -0
  144. package/src/cli-module-loader.ts +101 -0
  145. package/src/cli-shared.ts +187 -0
  146. package/tests/check/cli-check.test.ts +189 -0
  147. package/tests/check/config.test.ts +350 -0
  148. package/tests/check/fixer.test.ts +373 -0
  149. package/tests/check/format-diagnostics.test.ts +327 -0
  150. package/tests/check/rules/anti-patterns.test.ts +467 -0
  151. package/tests/check/rules/closures.test.ts +192 -0
  152. package/tests/check/rules/collections.test.ts +380 -0
  153. package/tests/check/rules/conditionals.test.ts +185 -0
  154. package/tests/check/rules/flow.test.ts +250 -0
  155. package/tests/check/rules/formatting.test.ts +755 -0
  156. package/tests/check/rules/loops.test.ts +334 -0
  157. package/tests/check/rules/naming.test.ts +336 -0
  158. package/tests/check/rules/strings.test.ts +129 -0
  159. package/tests/check/rules/types.test.ts +257 -0
  160. package/tests/check/validator.test.ts +444 -0
  161. package/tests/check/visitor.test.ts +171 -0
  162. package/tests/cli/check.test.ts +801 -0
  163. package/tests/cli/error-enrichment.test.ts +510 -0
  164. package/tests/cli/error-formatter.test.ts +631 -0
  165. package/tests/cli/eval.test.ts +85 -0
  166. package/tests/cli/exec.test.ts +537 -0
  167. package/tests/cli-explain.test.ts +249 -0
  168. package/tests/cli-lsp-diagnostic.test.ts +202 -0
  169. package/tests/cli-shared.test.ts +439 -0
  170. package/tsconfig.json +9 -0
  171. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,129 @@
1
+ /**
2
+ * String Handling Convention Rules Tests
3
+ * Verify string handling convention enforcement.
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+ import { parse } from '@rcrsr/rill';
8
+ import { validateScript } from '../../../src/check/validator.js';
9
+ import type { CheckConfig } from '../../../src/check/types.js';
10
+
11
+ // ============================================================
12
+ // TEST HELPERS
13
+ // ============================================================
14
+
15
+ /**
16
+ * Create a config with string rules enabled.
17
+ */
18
+ function createConfig(rules: Record<string, 'on' | 'off'> = {}): CheckConfig {
19
+ return {
20
+ rules: {
21
+ USE_EMPTY_METHOD: 'on',
22
+ ...rules,
23
+ },
24
+ severity: {},
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Validate source and extract diagnostic messages.
30
+ */
31
+ function getDiagnostics(source: string, config?: CheckConfig): string[] {
32
+ const ast = parse(source);
33
+ const diagnostics = validateScript(ast, source, config ?? createConfig());
34
+ return diagnostics.map((d) => d.message);
35
+ }
36
+
37
+ /**
38
+ * Validate source and check for violations.
39
+ */
40
+ function hasViolations(source: string, config?: CheckConfig): boolean {
41
+ const ast = parse(source);
42
+ const diagnostics = validateScript(ast, source, config ?? createConfig());
43
+ return diagnostics.length > 0;
44
+ }
45
+
46
+ /**
47
+ * Validate source and get diagnostic codes.
48
+ */
49
+ function getCodes(source: string, config?: CheckConfig): string[] {
50
+ const ast = parse(source);
51
+ const diagnostics = validateScript(ast, source, config ?? createConfig());
52
+ return diagnostics.map((d) => d.code);
53
+ }
54
+
55
+ // ============================================================
56
+ // USE_EMPTY_METHOD TESTS
57
+ // ============================================================
58
+
59
+ describe('USE_EMPTY_METHOD', () => {
60
+ const config = createConfig();
61
+
62
+ it('accepts .empty method usage', () => {
63
+ expect(hasViolations('$str -> .empty', config)).toBe(false);
64
+ });
65
+
66
+ it('accepts comparisons not involving empty strings', () => {
67
+ expect(hasViolations('$str == "hello"', config)).toBe(false);
68
+ });
69
+
70
+ it('warns on equality comparison with empty string', () => {
71
+ const source = '$str == ""';
72
+
73
+ const messages = getDiagnostics(source, config);
74
+ expect(messages.length).toBeGreaterThan(0);
75
+ expect(messages[0]).toContain('Use .empty');
76
+ expect(messages[0]).toContain('comparing with ""');
77
+ });
78
+
79
+ it('warns on inequality comparison with empty string', () => {
80
+ const source = '$str != ""';
81
+
82
+ const messages = getDiagnostics(source, config);
83
+ expect(messages.length).toBeGreaterThan(0);
84
+ expect(messages[0]).toContain('Use .empty');
85
+ });
86
+
87
+ it('suggests correct method for equality', () => {
88
+ const source = '$str == ""';
89
+
90
+ const messages = getDiagnostics(source, config);
91
+ expect(messages.length).toBeGreaterThan(0);
92
+ expect(messages[0]).toContain('.empty');
93
+ });
94
+
95
+ it('suggests correct method for inequality', () => {
96
+ const source = '$str != ""';
97
+
98
+ const messages = getDiagnostics(source, config);
99
+ expect(messages.length).toBeGreaterThan(0);
100
+ expect(messages[0]).toContain('.empty -> !');
101
+ });
102
+
103
+ it('detects empty string on left side', () => {
104
+ const source = '"" == $str';
105
+
106
+ const messages = getDiagnostics(source, config);
107
+ expect(messages.length).toBeGreaterThan(0);
108
+ expect(messages[0]).toContain('Use .empty');
109
+ });
110
+
111
+ it('has correct severity and code', () => {
112
+ const source = '$str == ""';
113
+ const ast = parse(source);
114
+ const diagnostics = validateScript(ast, source, config);
115
+
116
+ expect(diagnostics.length).toBeGreaterThan(0);
117
+ expect(diagnostics[0]?.code).toBe('USE_EMPTY_METHOD');
118
+ expect(diagnostics[0]?.severity).toBe('warning');
119
+ });
120
+
121
+ it('does not provide auto-fix', () => {
122
+ const source = '$str == ""';
123
+ const ast = parse(source);
124
+ const diagnostics = validateScript(ast, source, config);
125
+
126
+ expect(diagnostics.length).toBeGreaterThan(0);
127
+ expect(diagnostics[0]?.fix).toBeNull();
128
+ });
129
+ });
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Type Safety Convention Rules Tests
3
+ * Verify type safety convention enforcement.
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+ import { parse } from '@rcrsr/rill';
8
+ import { validateScript } from '../../../src/check/validator.js';
9
+ import type { CheckConfig, Fix } from '../../../src/check/types.js';
10
+
11
+ /**
12
+ * Apply a single fix to source code.
13
+ */
14
+ function applyFix(source: string, fix: Fix): string {
15
+ const before = source.slice(0, fix.range.start.offset);
16
+ const after = source.slice(fix.range.end.offset);
17
+ return before + fix.replacement + after;
18
+ }
19
+
20
+ // ============================================================
21
+ // TEST HELPERS
22
+ // ============================================================
23
+
24
+ /**
25
+ * Create a config with type rules enabled.
26
+ */
27
+ function createConfig(rules: Record<string, 'on' | 'off'> = {}): CheckConfig {
28
+ return {
29
+ rules: {
30
+ UNNECESSARY_ASSERTION: 'on',
31
+ VALIDATE_EXTERNAL: 'on',
32
+ ...rules,
33
+ },
34
+ severity: {},
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Validate source and extract diagnostic messages.
40
+ */
41
+ function getDiagnostics(source: string, config?: CheckConfig): string[] {
42
+ const ast = parse(source);
43
+ const diagnostics = validateScript(ast, source, config ?? createConfig());
44
+ return diagnostics.map((d) => d.message);
45
+ }
46
+
47
+ /**
48
+ * Validate source and check for violations.
49
+ */
50
+ function hasViolations(source: string, config?: CheckConfig): boolean {
51
+ const ast = parse(source);
52
+ const diagnostics = validateScript(ast, source, config ?? createConfig());
53
+ return diagnostics.length > 0;
54
+ }
55
+
56
+ /**
57
+ * Validate source and get diagnostic codes.
58
+ */
59
+ function getCodes(source: string, config?: CheckConfig): string[] {
60
+ const ast = parse(source);
61
+ const diagnostics = validateScript(ast, source, config ?? createConfig());
62
+ return diagnostics.map((d) => d.code);
63
+ }
64
+
65
+ // ============================================================
66
+ // UNNECESSARY_ASSERTION TESTS
67
+ // ============================================================
68
+
69
+ describe('UNNECESSARY_ASSERTION', () => {
70
+ const config = createConfig({ VALIDATE_EXTERNAL: 'off' });
71
+
72
+ it('accepts assertions on variables', () => {
73
+ expect(hasViolations('$val:number', config)).toBe(false);
74
+ });
75
+
76
+ it('accepts assertions on function results', () => {
77
+ expect(hasViolations('getData():dict', config)).toBe(false);
78
+ });
79
+
80
+ it('accepts bare type assertions', () => {
81
+ expect(hasViolations('$ -> :string', config)).toBe(false);
82
+ });
83
+
84
+ it('detects unnecessary number assertion', () => {
85
+ const source = '5:number => $n';
86
+
87
+ const messages = getDiagnostics(source, config);
88
+ expect(messages.length).toBeGreaterThan(0);
89
+ expect(messages[0]).toContain('unnecessary');
90
+ expect(messages[0]).toContain('number literal');
91
+ });
92
+
93
+ it('detects unnecessary string assertion', () => {
94
+ const source = '"hello":string => $s';
95
+
96
+ const messages = getDiagnostics(source, config);
97
+ expect(messages.length).toBeGreaterThan(0);
98
+ expect(messages[0]).toContain('unnecessary');
99
+ expect(messages[0]).toContain('string literal');
100
+ });
101
+
102
+ it('detects unnecessary bool assertion', () => {
103
+ const source = 'true:bool => $b';
104
+
105
+ const messages = getDiagnostics(source, config);
106
+ expect(messages.length).toBeGreaterThan(0);
107
+ expect(messages[0]).toContain('unnecessary');
108
+ expect(messages[0]).toContain('bool literal');
109
+ });
110
+
111
+ it('has correct severity and code', () => {
112
+ const source = '42:number';
113
+ const ast = parse(source);
114
+ const diagnostics = validateScript(ast, source, config);
115
+
116
+ expect(diagnostics.length).toBeGreaterThan(0);
117
+ expect(diagnostics[0]?.code).toBe('UNNECESSARY_ASSERTION');
118
+ expect(diagnostics[0]?.severity).toBe('info');
119
+ });
120
+
121
+ it('provides fix to remove assertion', () => {
122
+ const source = '5:number => $n';
123
+ const ast = parse(source);
124
+ const diagnostics = validateScript(ast, source, config);
125
+
126
+ expect(diagnostics.length).toBeGreaterThan(0);
127
+ const diagnostic = diagnostics[0];
128
+ expect(diagnostic?.fix).toBeDefined();
129
+ expect(diagnostic?.fix?.applicable).toBe(true);
130
+ expect(diagnostic?.fix?.description).toContain('Remove unnecessary');
131
+ });
132
+
133
+ it('fix removes assertion correctly', () => {
134
+ const source = '5:number => $n';
135
+ const ast = parse(source);
136
+ const diagnostics = validateScript(ast, source, config);
137
+
138
+ expect(diagnostics.length).toBeGreaterThan(0);
139
+ const diagnostic = diagnostics[0];
140
+ if (diagnostic?.fix) {
141
+ const fixed = applyFix(source, diagnostic.fix);
142
+ expect(fixed).toBe('5 => $n');
143
+ }
144
+ });
145
+
146
+ it('fix handles string assertions', () => {
147
+ const source = '"test":string => $s';
148
+ const ast = parse(source);
149
+ const diagnostics = validateScript(ast, source, config);
150
+
151
+ expect(diagnostics.length).toBeGreaterThan(0);
152
+ const diagnostic = diagnostics[0];
153
+ if (diagnostic?.fix) {
154
+ const fixed = applyFix(source, diagnostic.fix);
155
+ expect(fixed).toBe('"test" => $s');
156
+ }
157
+ });
158
+ });
159
+
160
+ // ============================================================
161
+ // VALIDATE_EXTERNAL TESTS
162
+ // ============================================================
163
+
164
+ describe('VALIDATE_EXTERNAL', () => {
165
+ const config = createConfig({ UNNECESSARY_ASSERTION: 'off' });
166
+
167
+ it('recommends validation for parse functions', () => {
168
+ const source = 'parse_json($input)';
169
+
170
+ const messages = getDiagnostics(source, config);
171
+ expect(messages.length).toBeGreaterThan(0);
172
+ expect(messages[0]).toContain('Consider validating external input');
173
+ expect(messages[0]).toContain('parse_json');
174
+ });
175
+
176
+ it('recommends validation for fetch functions', () => {
177
+ const source = 'fetch_data($url)';
178
+
179
+ const messages = getDiagnostics(source, config);
180
+ expect(messages.length).toBeGreaterThan(0);
181
+ expect(messages[0]).toContain('external input');
182
+ });
183
+
184
+ it('recommends validation for read functions', () => {
185
+ const source = 'read_file($path)';
186
+
187
+ const messages = getDiagnostics(source, config);
188
+ expect(messages.length).toBeGreaterThan(0);
189
+ expect(messages[0]).toContain('external input');
190
+ });
191
+
192
+ it('does not warn on non-external functions', () => {
193
+ expect(hasViolations('compute($x)', config)).toBe(false);
194
+ });
195
+
196
+ it('has correct severity and code', () => {
197
+ const source = 'parse_json($input)';
198
+ const ast = parse(source);
199
+ const diagnostics = validateScript(ast, source, config);
200
+
201
+ expect(diagnostics.length).toBeGreaterThan(0);
202
+ expect(diagnostics[0]?.code).toBe('VALIDATE_EXTERNAL');
203
+ expect(diagnostics[0]?.severity).toBe('info');
204
+ });
205
+
206
+ it('does not provide auto-fix', () => {
207
+ const source = 'parse_json($input)';
208
+ const ast = parse(source);
209
+ const diagnostics = validateScript(ast, source, config);
210
+
211
+ expect(diagnostics.length).toBeGreaterThan(0);
212
+ expect(diagnostics[0]?.fix).toBeNull();
213
+ });
214
+
215
+ it('does not warn when already type-asserted (simple case)', () => {
216
+ const source = 'parse_json($input):dict';
217
+
218
+ const violations = hasViolations(source, config);
219
+ expect(violations).toBe(false);
220
+ });
221
+
222
+ it('does not warn when already type-asserted (with property access)', () => {
223
+ const source = 'ccr::read_frontmatter($path, [status: ""]):dict.status';
224
+
225
+ const violations = hasViolations(source, config);
226
+ expect(violations).toBe(false);
227
+ });
228
+
229
+ it('does not warn when already type-asserted (namespaced function)', () => {
230
+ const source = 'io::read_file($path):string';
231
+
232
+ const violations = hasViolations(source, config);
233
+ expect(violations).toBe(false);
234
+ });
235
+
236
+ it('does not warn for namespaced functions (trusted host APIs)', () => {
237
+ // Namespaced functions like ccr::read_frontmatter are trusted host APIs
238
+ const source = 'ccr::read_frontmatter($path)';
239
+
240
+ const violations = hasViolations(source, config);
241
+ expect(violations).toBe(false);
242
+ });
243
+
244
+ it('does not warn when parse_ function is type-asserted', () => {
245
+ const source = 'parse_auto($text):dict';
246
+
247
+ const violations = hasViolations(source, config);
248
+ expect(violations).toBe(false);
249
+ });
250
+
251
+ it('warns when parse_ function is not type-asserted', () => {
252
+ const source = 'parse_auto($text)';
253
+
254
+ const violations = hasViolations(source, config);
255
+ expect(violations).toBe(true);
256
+ });
257
+ });