invar-tools 1.17.2__py3-none-any.whl → 1.17.3__py3-none-any.whl

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 (58) hide show
  1. invar/node_tools/.gitignore +10 -6
  2. invar/node_tools/eslint-plugin/rules/__tests__/behavior.test.js +1321 -0
  3. invar/node_tools/eslint-plugin/rules/__tests__/behavior.test.js.map +1 -0
  4. invar/node_tools/eslint-plugin/rules/__tests__/e2e-scenarios.test.js +414 -0
  5. invar/node_tools/eslint-plugin/rules/__tests__/e2e-scenarios.test.js.map +1 -0
  6. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/function-lengths.js +142 -0
  7. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/function-lengths.js.map +1 -0
  8. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/has-io-imports.js +15 -0
  9. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/has-io-imports.js.map +1 -0
  10. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/valid-small.js +27 -0
  11. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/valid-small.js.map +1 -0
  12. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/exported-functions.js +43 -0
  13. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/exported-functions.js.map +1 -0
  14. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/shell/with-io.js +27 -0
  15. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/shell/with-io.js.map +1 -0
  16. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/tests/large.test.js +260 -0
  17. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/tests/large.test.js.map +1 -0
  18. invar/node_tools/eslint-plugin/rules/max-file-lines.js +105 -0
  19. invar/node_tools/eslint-plugin/rules/max-file-lines.js.map +1 -0
  20. invar/node_tools/eslint-plugin/rules/max-function-lines.js +133 -0
  21. invar/node_tools/eslint-plugin/rules/max-function-lines.js.map +1 -0
  22. invar/node_tools/eslint-plugin/rules/no-any-in-schema.js +39 -0
  23. invar/node_tools/eslint-plugin/rules/no-any-in-schema.js.map +1 -0
  24. invar/node_tools/eslint-plugin/rules/no-empty-schema.js +69 -0
  25. invar/node_tools/eslint-plugin/rules/no-empty-schema.js.map +1 -0
  26. invar/node_tools/eslint-plugin/rules/no-impure-calls-in-core.js +52 -0
  27. invar/node_tools/eslint-plugin/rules/no-impure-calls-in-core.js.map +1 -0
  28. invar/node_tools/eslint-plugin/rules/no-io-in-core.js +99 -0
  29. invar/node_tools/eslint-plugin/rules/no-io-in-core.js.map +1 -0
  30. invar/node_tools/eslint-plugin/rules/no-pure-logic-in-shell.js +197 -0
  31. invar/node_tools/eslint-plugin/rules/no-pure-logic-in-shell.js.map +1 -0
  32. invar/node_tools/eslint-plugin/rules/no-redundant-type-schema.js +99 -0
  33. invar/node_tools/eslint-plugin/rules/no-redundant-type-schema.js.map +1 -0
  34. invar/node_tools/eslint-plugin/rules/no-runtime-imports.js +66 -0
  35. invar/node_tools/eslint-plugin/rules/no-runtime-imports.js.map +1 -0
  36. invar/node_tools/eslint-plugin/rules/require-complete-validation.js +104 -0
  37. invar/node_tools/eslint-plugin/rules/require-complete-validation.js.map +1 -0
  38. invar/node_tools/eslint-plugin/rules/require-jsdoc-example.js +81 -0
  39. invar/node_tools/eslint-plugin/rules/require-jsdoc-example.js.map +1 -0
  40. invar/node_tools/eslint-plugin/rules/require-schema-validation.js +308 -0
  41. invar/node_tools/eslint-plugin/rules/require-schema-validation.js.map +1 -0
  42. invar/node_tools/eslint-plugin/rules/shell-complexity.js +273 -0
  43. invar/node_tools/eslint-plugin/rules/shell-complexity.js.map +1 -0
  44. invar/node_tools/eslint-plugin/rules/shell-result-type.js +138 -0
  45. invar/node_tools/eslint-plugin/rules/shell-result-type.js.map +1 -0
  46. invar/node_tools/eslint-plugin/rules/thin-entry-points.js +174 -0
  47. invar/node_tools/eslint-plugin/rules/thin-entry-points.js.map +1 -0
  48. invar/node_tools/eslint-plugin/utils/layer-detection.js +91 -0
  49. invar/node_tools/eslint-plugin/utils/layer-detection.js.map +1 -0
  50. invar/node_tools/eslint-plugin/utils/math-example.js +31 -0
  51. invar/node_tools/eslint-plugin/utils/math-example.js.map +1 -0
  52. {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/METADATA +1 -1
  53. {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/RECORD +58 -8
  54. {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/WHEEL +0 -0
  55. {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/entry_points.txt +0 -0
  56. {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/licenses/LICENSE +0 -0
  57. {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/licenses/LICENSE-GPL +0 -0
  58. {invar_tools-1.17.2.dist-info → invar_tools-1.17.3.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,1321 @@
1
+ /**
2
+ * Behavior tests for Invar ESLint rules
3
+ *
4
+ * Tests rules with inline code examples (JavaScript syntax for compatibility)
5
+ */
6
+ import { describe, it, expect } from 'vitest';
7
+ import { RuleTester } from 'eslint';
8
+ // Import rules
9
+ import { maxFileLines } from '../max-file-lines.js';
10
+ import { maxFunctionLines } from '../max-function-lines.js';
11
+ import { requireJsdocExample } from '../require-jsdoc-example.js';
12
+ import { noIoInCore } from '../no-io-in-core.js';
13
+ import { noEmptySchema } from '../no-empty-schema.js';
14
+ import { noRedundantTypeSchema } from '../no-redundant-type-schema.js';
15
+ import { requireCompleteValidation } from '../require-complete-validation.js';
16
+ import { requireSchemaValidation } from '../require-schema-validation.js';
17
+ import { noRuntimeImports } from '../no-runtime-imports.js';
18
+ import { noImpureCallsInCore } from '../no-impure-calls-in-core.js';
19
+ import { noPureLogicInShell } from '../no-pure-logic-in-shell.js';
20
+ import { shellComplexity } from '../shell-complexity.js';
21
+ import { thinEntryPoints } from '../thin-entry-points.js';
22
+ import { getLayer, getLimits } from '../../utils/layer-detection.js';
23
+ // Create RuleTester with modern JS configuration
24
+ const ruleTester = new RuleTester({
25
+ languageOptions: {
26
+ ecmaVersion: 2022,
27
+ sourceType: 'module',
28
+ },
29
+ });
30
+ describe('layer-detection', () => {
31
+ it('should detect core layer from /core/ path', () => {
32
+ expect(getLayer('/project/src/core/parser.ts')).toBe('core');
33
+ });
34
+ it('should detect shell layer from /shell/ path', () => {
35
+ expect(getLayer('/project/src/shell/io.ts')).toBe('shell');
36
+ });
37
+ it('should detect tests layer from .test.ts extension', () => {
38
+ expect(getLayer('/project/src/utils.test.ts')).toBe('tests');
39
+ });
40
+ it('should detect tests layer from .spec.ts extension', () => {
41
+ expect(getLayer('/project/src/utils.spec.ts')).toBe('tests');
42
+ });
43
+ it('should detect tests layer from /tests/ directory', () => {
44
+ expect(getLayer('/project/tests/unit.ts')).toBe('tests');
45
+ });
46
+ it('should prioritize tests over core', () => {
47
+ expect(getLayer('/project/src/core/parser.test.ts')).toBe('tests');
48
+ });
49
+ it('should detect core from relative path', () => {
50
+ expect(getLayer('core/parser.ts')).toBe('core');
51
+ });
52
+ it('should detect shell from relative path', () => {
53
+ expect(getLayer('shell/io.ts')).toBe('shell');
54
+ });
55
+ it('should handle Windows paths', () => {
56
+ expect(getLayer('C:\\Project\\core\\parser.ts')).toBe('core');
57
+ expect(getLayer('C:\\Project\\shell\\io.ts')).toBe('shell');
58
+ });
59
+ it('should not match /hardcore/ as core', () => {
60
+ expect(getLayer('/project/hardcore/file.ts')).toBe('default');
61
+ });
62
+ it('should not match /eggshell/ as shell', () => {
63
+ expect(getLayer('/project/eggshell/file.ts')).toBe('default');
64
+ });
65
+ it('should return correct limits for each layer', () => {
66
+ expect(getLimits('/core/file.ts')).toEqual({
67
+ maxFileLines: 650,
68
+ maxFunctionLines: 65,
69
+ });
70
+ expect(getLimits('/shell/file.ts')).toEqual({
71
+ maxFileLines: 910,
72
+ maxFunctionLines: 130,
73
+ });
74
+ expect(getLimits('/file.test.ts')).toEqual({
75
+ maxFileLines: 1300,
76
+ maxFunctionLines: 260,
77
+ });
78
+ expect(getLimits('/file.ts')).toEqual({
79
+ maxFileLines: 780,
80
+ maxFunctionLines: 104,
81
+ });
82
+ });
83
+ });
84
+ describe('max-file-lines', () => {
85
+ it('should detect files exceeding core limit (650 lines)', () => {
86
+ ruleTester.run('max-file-lines', maxFileLines, {
87
+ valid: [
88
+ {
89
+ code: '// Valid core file\n' + 'const x = 1;\n'.repeat(648), // 649 lines total
90
+ filename: '/project/core/valid.js',
91
+ },
92
+ ],
93
+ invalid: [
94
+ {
95
+ code: '// Invalid core file\n' + 'const x = 1;\n'.repeat(650), // 651 lines total
96
+ filename: '/project/core/invalid.js',
97
+ errors: [{ messageId: 'tooManyLines' }],
98
+ },
99
+ ],
100
+ });
101
+ });
102
+ it('should use different limits for shell (910 lines)', () => {
103
+ ruleTester.run('max-file-lines', maxFileLines, {
104
+ valid: [
105
+ {
106
+ code: '// Valid shell file\n' + 'const x = 1;\n'.repeat(908), // 909 lines total
107
+ filename: '/project/shell/valid.js',
108
+ },
109
+ ],
110
+ invalid: [
111
+ {
112
+ code: '// Invalid shell file\n' + 'const x = 1;\n'.repeat(910), // 911 lines total
113
+ filename: '/project/shell/invalid.js',
114
+ errors: [{ messageId: 'tooManyLines' }],
115
+ },
116
+ ],
117
+ });
118
+ });
119
+ it('should use different limits for tests (1300 lines)', () => {
120
+ ruleTester.run('max-file-lines', maxFileLines, {
121
+ valid: [
122
+ {
123
+ code: '// Valid test file\n' + 'const x = 1;\n'.repeat(1298), // 1299 lines total
124
+ filename: '/project/tests/valid.test.js',
125
+ },
126
+ ],
127
+ invalid: [
128
+ {
129
+ code: '// Invalid test file\n' + 'const x = 1;\n'.repeat(1300), // 1301 lines total
130
+ filename: '/project/tests/invalid.test.js',
131
+ errors: [{ messageId: 'tooManyLines' }],
132
+ },
133
+ ],
134
+ });
135
+ });
136
+ });
137
+ describe('max-function-lines', () => {
138
+ it('should detect functions exceeding core limit (65 lines)', () => {
139
+ ruleTester.run('max-function-lines', maxFunctionLines, {
140
+ valid: [
141
+ {
142
+ code: `function validCoreFunction() {\n${' const x = 1;\n'.repeat(63)}}`, // 65 lines total
143
+ filename: '/project/core/valid.js',
144
+ },
145
+ ],
146
+ invalid: [
147
+ {
148
+ code: `function invalidCoreFunction() {\n${' const x = 1;\n'.repeat(65)}}`, // 67 lines total
149
+ filename: '/project/core/invalid.js',
150
+ errors: [{ messageId: 'tooManyLines' }],
151
+ },
152
+ ],
153
+ });
154
+ });
155
+ it('should use different limits for shell (130 lines)', () => {
156
+ ruleTester.run('max-function-lines', maxFunctionLines, {
157
+ valid: [
158
+ {
159
+ code: `function validShellFunction() {\n${' const x = 1;\n'.repeat(128)}}`, // 130 lines total
160
+ filename: '/project/shell/valid.js',
161
+ },
162
+ ],
163
+ invalid: [
164
+ {
165
+ code: `function invalidShellFunction() {\n${' const x = 1;\n'.repeat(130)}}`, // 132 lines total
166
+ filename: '/project/shell/invalid.js',
167
+ errors: [{ messageId: 'tooManyLines' }],
168
+ },
169
+ ],
170
+ });
171
+ });
172
+ it('should use different limits for tests (260 lines)', () => {
173
+ ruleTester.run('max-function-lines', maxFunctionLines, {
174
+ valid: [
175
+ {
176
+ code: `function validTestFunction() {\n${' const x = 1;\n'.repeat(258)}}`, // 260 lines total
177
+ filename: '/project/tests/valid.test.js',
178
+ },
179
+ ],
180
+ invalid: [
181
+ {
182
+ code: `function invalidTestFunction() {\n${' const x = 1;\n'.repeat(260)}}`, // 262 lines total
183
+ filename: '/project/tests/invalid.test.js',
184
+ errors: [{ messageId: 'tooManyLines' }],
185
+ },
186
+ ],
187
+ });
188
+ });
189
+ });
190
+ describe('require-jsdoc-example', () => {
191
+ it('should require @example for exported functions', () => {
192
+ ruleTester.run('require-jsdoc-example', requireJsdocExample, {
193
+ valid: [
194
+ {
195
+ code: `
196
+ /**
197
+ * Valid function with example
198
+ * @example
199
+ * foo() // => 'bar'
200
+ */
201
+ export function foo() { return 'bar'; }
202
+ `,
203
+ filename: '/project/test.js',
204
+ },
205
+ {
206
+ code: `
207
+ // Non-exported function - no @example required
208
+ function privateHelper() { return 'private'; }
209
+ `,
210
+ filename: '/project/test.js',
211
+ },
212
+ ],
213
+ invalid: [
214
+ {
215
+ code: `
216
+ /**
217
+ * Missing @example
218
+ */
219
+ export function foo() { return 'bar'; }
220
+ `,
221
+ filename: '/project/test.js',
222
+ errors: [{ messageId: 'missingExample' }],
223
+ },
224
+ ],
225
+ });
226
+ });
227
+ it('should require @example for exported arrow functions', () => {
228
+ ruleTester.run('require-jsdoc-example', requireJsdocExample, {
229
+ valid: [
230
+ {
231
+ code: `
232
+ /**
233
+ * Valid arrow function with example
234
+ * @example
235
+ * foo() // => 'bar'
236
+ */
237
+ export const foo = () => 'bar';
238
+ `,
239
+ filename: '/project/test.js',
240
+ },
241
+ ],
242
+ invalid: [
243
+ {
244
+ code: `
245
+ /**
246
+ * Missing @example
247
+ */
248
+ export const foo = () => 'bar';
249
+ `,
250
+ filename: '/project/test.js',
251
+ errors: [{ messageId: 'missingExample' }],
252
+ },
253
+ ],
254
+ });
255
+ });
256
+ });
257
+ describe('no-io-in-core', () => {
258
+ it('should forbid I/O imports in /core/ directories', () => {
259
+ ruleTester.run('no-io-in-core', noIoInCore, {
260
+ valid: [
261
+ {
262
+ code: `import { something } from 'lodash';`,
263
+ filename: '/project/core/valid.js',
264
+ },
265
+ {
266
+ code: `import * as fs from 'fs';`,
267
+ filename: '/project/shell/valid.js', // Allowed in shell
268
+ },
269
+ ],
270
+ invalid: [
271
+ {
272
+ code: `import * as fs from 'fs';`,
273
+ filename: '/project/core/invalid.js',
274
+ errors: [{ messageId: 'ioInCore' }],
275
+ },
276
+ {
277
+ code: `import { readFile } from 'node:fs/promises';`,
278
+ filename: '/project/core/invalid.js',
279
+ errors: [{ messageId: 'ioInCore' }],
280
+ },
281
+ {
282
+ code: `import axios from 'axios';`,
283
+ filename: '/project/core/invalid.js',
284
+ errors: [{ messageId: 'ioInCore' }],
285
+ },
286
+ {
287
+ code: `import { S3Client } from '@aws-sdk/client-s3';`,
288
+ filename: '/project/core/invalid.js',
289
+ errors: [{ messageId: 'ioInCore' }],
290
+ },
291
+ {
292
+ code: `import { deploy } from '@vercel/node';`,
293
+ filename: '/project/core/invalid.js',
294
+ errors: [{ messageId: 'ioInCore' }],
295
+ },
296
+ ],
297
+ });
298
+ });
299
+ it('should handle Windows-style core paths', () => {
300
+ ruleTester.run('no-io-in-core', noIoInCore, {
301
+ valid: [],
302
+ invalid: [
303
+ {
304
+ code: `import * as fs from 'fs';`,
305
+ filename: 'C:\\\\Project\\\\core\\\\test.js',
306
+ errors: [{ messageId: 'ioInCore' }],
307
+ },
308
+ ],
309
+ });
310
+ });
311
+ it('should detect require() calls', () => {
312
+ ruleTester.run('no-io-in-core', noIoInCore, {
313
+ valid: [],
314
+ invalid: [
315
+ {
316
+ code: `const fs = require('fs');`,
317
+ filename: '/project/core/test.js',
318
+ errors: [{ messageId: 'ioInCore' }],
319
+ },
320
+ ],
321
+ });
322
+ });
323
+ });
324
+ describe('Integration: Cross-platform path normalization', () => {
325
+ it('should handle Unix paths correctly', () => {
326
+ expect(getLayer('/Users/project/core/parser.ts')).toBe('core');
327
+ expect(getLayer('/home/user/project/shell/io.ts')).toBe('shell');
328
+ });
329
+ it('should handle Windows paths correctly', () => {
330
+ expect(getLayer('C:\\Users\\project\\core\\parser.ts')).toBe('core');
331
+ expect(getLayer('D:\\Projects\\shell\\io.ts')).toBe('shell');
332
+ });
333
+ it('should handle relative paths correctly', () => {
334
+ expect(getLayer('core/parser.ts')).toBe('core');
335
+ expect(getLayer('shell/io.ts')).toBe('shell');
336
+ expect(getLayer('src/core/parser.ts')).toBe('core');
337
+ });
338
+ });
339
+ describe('no-empty-schema', () => {
340
+ it('should detect empty z.object({})', () => {
341
+ ruleTester.run('no-empty-schema', noEmptySchema, {
342
+ valid: [
343
+ { code: `const Schema = z.object({ id: z.string() });` },
344
+ { code: `const Schema = z.object({ name: z.string(), age: z.number() });` },
345
+ ],
346
+ invalid: [
347
+ {
348
+ code: `const Schema = z.object({});`,
349
+ errors: [{ messageId: 'emptyObject' }],
350
+ },
351
+ ],
352
+ });
353
+ });
354
+ it('should detect .passthrough() calls', () => {
355
+ ruleTester.run('no-empty-schema', noEmptySchema, {
356
+ valid: [
357
+ { code: `const Schema = z.object({ id: z.string() }).strict();` },
358
+ ],
359
+ invalid: [
360
+ {
361
+ code: `const Schema = z.object({ id: z.string() }).passthrough();`,
362
+ errors: [{ messageId: 'passthrough' }],
363
+ },
364
+ ],
365
+ });
366
+ });
367
+ it('should detect .loose() calls', () => {
368
+ ruleTester.run('no-empty-schema', noEmptySchema, {
369
+ valid: [
370
+ { code: `const Schema = z.object({ id: z.string() });` },
371
+ ],
372
+ invalid: [
373
+ {
374
+ code: `const Schema = z.object({ id: z.string() }).loose();`,
375
+ errors: [{ messageId: 'loose' }],
376
+ },
377
+ ],
378
+ });
379
+ });
380
+ });
381
+ describe('no-redundant-type-schema', () => {
382
+ it('should detect z.string() without constraints', () => {
383
+ ruleTester.run('no-redundant-type-schema', noRedundantTypeSchema, {
384
+ valid: [
385
+ { code: `const Schema = z.string().min(1);` },
386
+ { code: `const Schema = z.string().max(100);` },
387
+ { code: `const Schema = z.string().email();` },
388
+ { code: `const Schema = z.string().regex(/^[a-z]+$/);` },
389
+ ],
390
+ invalid: [
391
+ {
392
+ code: `const Schema = z.string();`,
393
+ errors: [{ messageId: 'redundantString' }],
394
+ },
395
+ ],
396
+ });
397
+ });
398
+ it('should detect z.number() without constraints', () => {
399
+ ruleTester.run('no-redundant-type-schema', noRedundantTypeSchema, {
400
+ valid: [
401
+ { code: `const Schema = z.number().min(0);` },
402
+ { code: `const Schema = z.number().max(100);` },
403
+ { code: `const Schema = z.number().int();` },
404
+ { code: `const Schema = z.number().positive();` },
405
+ ],
406
+ invalid: [
407
+ {
408
+ code: `const Schema = z.number();`,
409
+ errors: [{ messageId: 'redundantNumber' }],
410
+ },
411
+ ],
412
+ });
413
+ });
414
+ it('should detect z.boolean() (always redundant)', () => {
415
+ ruleTester.run('no-redundant-type-schema', noRedundantTypeSchema, {
416
+ valid: [],
417
+ invalid: [
418
+ {
419
+ code: `const Schema = z.boolean();`,
420
+ errors: [{ messageId: 'redundantBoolean' }],
421
+ },
422
+ ],
423
+ });
424
+ });
425
+ });
426
+ describe('require-complete-validation', () => {
427
+ // Create RuleTester with TypeScript parser for this suite
428
+ const tsRuleTester = new RuleTester({
429
+ languageOptions: {
430
+ ecmaVersion: 2022,
431
+ sourceType: 'module',
432
+ parser: require('@typescript-eslint/parser'),
433
+ },
434
+ });
435
+ it('should detect mixed validated/unvalidated parameters', () => {
436
+ tsRuleTester.run('require-complete-validation', requireCompleteValidation, {
437
+ valid: [
438
+ {
439
+ code: `function transfer(
440
+ user: z.infer<typeof UserSchema>,
441
+ amount: z.infer<typeof AmountSchema>
442
+ ) {}`,
443
+ },
444
+ {
445
+ code: `function calculate(x: number, y: number) {}`,
446
+ },
447
+ {
448
+ code: `function greet() {}`,
449
+ },
450
+ ],
451
+ invalid: [
452
+ {
453
+ code: `function transfer(
454
+ user: z.infer<typeof UserSchema>,
455
+ amount: number
456
+ ) {}`,
457
+ errors: [{ messageId: 'partialValidation' }],
458
+ },
459
+ {
460
+ code: `function process(
461
+ data: z.infer<typeof DataSchema>,
462
+ count: number,
463
+ name: string
464
+ ) {}`,
465
+ errors: [{ messageId: 'partialValidation' }],
466
+ },
467
+ ],
468
+ });
469
+ });
470
+ it('should handle arrow functions', () => {
471
+ tsRuleTester.run('require-complete-validation', requireCompleteValidation, {
472
+ valid: [
473
+ {
474
+ code: `const fn = (user: z.infer<typeof UserSchema>, id: z.infer<typeof IdSchema>) => {};`,
475
+ },
476
+ ],
477
+ invalid: [
478
+ {
479
+ code: `const fn = (user: z.infer<typeof UserSchema>, id: number) => {};`,
480
+ errors: [{ messageId: 'partialValidation' }],
481
+ },
482
+ ],
483
+ });
484
+ });
485
+ });
486
+ describe('require-schema-validation modes', () => {
487
+ const tsRuleTester = new RuleTester({
488
+ languageOptions: {
489
+ ecmaVersion: 2022,
490
+ sourceType: 'module',
491
+ parser: require('@typescript-eslint/parser'),
492
+ },
493
+ });
494
+ it('should check all functions in recommended mode (default)', () => {
495
+ tsRuleTester.run('require-schema-validation', requireSchemaValidation, {
496
+ valid: [
497
+ {
498
+ code: `
499
+ function process(user: z.infer<typeof UserSchema>) {
500
+ const validated = UserSchema.parse(user);
501
+ }
502
+ `,
503
+ },
504
+ ],
505
+ invalid: [
506
+ {
507
+ code: `
508
+ function process(user: z.infer<typeof UserSchema>) {
509
+ console.log(user);
510
+ }
511
+ `,
512
+ errors: [{ messageId: 'missingValidation' }],
513
+ },
514
+ ],
515
+ });
516
+ });
517
+ it('should check all functions in strict mode', () => {
518
+ tsRuleTester.run('require-schema-validation', requireSchemaValidation, {
519
+ valid: [
520
+ {
521
+ code: `
522
+ function process(user: z.infer<typeof UserSchema>) {
523
+ const validated = UserSchema.parse(user);
524
+ }
525
+ `,
526
+ options: [{ mode: 'strict' }],
527
+ },
528
+ ],
529
+ invalid: [
530
+ {
531
+ code: `
532
+ function process(user: z.infer<typeof UserSchema>) {
533
+ console.log(user);
534
+ }
535
+ `,
536
+ options: [{ mode: 'strict' }],
537
+ errors: [{ messageId: 'missingValidation' }],
538
+ },
539
+ ],
540
+ });
541
+ });
542
+ it('should only check high-risk functions in risk-based mode', () => {
543
+ tsRuleTester.run('require-schema-validation', requireSchemaValidation, {
544
+ valid: [
545
+ {
546
+ // Non-risk function should pass even without validation
547
+ code: `
548
+ function getData(user: z.infer<typeof UserSchema>) {
549
+ console.log(user);
550
+ }
551
+ `,
552
+ options: [{ mode: 'risk-based' }],
553
+ },
554
+ {
555
+ // Risk function with validation should pass
556
+ code: `
557
+ function processPayment(user: z.infer<typeof UserSchema>) {
558
+ const validated = UserSchema.parse(user);
559
+ }
560
+ `,
561
+ options: [{ mode: 'risk-based' }],
562
+ },
563
+ ],
564
+ invalid: [
565
+ {
566
+ // Risk function without validation should fail
567
+ code: `
568
+ function processPayment(user: z.infer<typeof UserSchema>) {
569
+ console.log(user);
570
+ }
571
+ `,
572
+ options: [{ mode: 'risk-based' }],
573
+ errors: [{ messageId: 'missingValidationRisk' }],
574
+ },
575
+ {
576
+ code: `
577
+ function authenticateUser(token: z.infer<typeof TokenSchema>) {
578
+ console.log(token);
579
+ }
580
+ `,
581
+ options: [{ mode: 'risk-based' }],
582
+ errors: [{ messageId: 'missingValidationRisk' }],
583
+ },
584
+ ],
585
+ });
586
+ });
587
+ it('should enforce for specific paths when enforceFor is set', () => {
588
+ tsRuleTester.run('require-schema-validation', requireSchemaValidation, {
589
+ valid: [
590
+ {
591
+ // File outside enforceFor paths should pass
592
+ code: `
593
+ function process(user: z.infer<typeof UserSchema>) {
594
+ console.log(user);
595
+ }
596
+ `,
597
+ filename: '/project/src/utils/helper.ts',
598
+ options: [{ mode: 'risk-based', enforceFor: ['**/payment/**', '**/auth/**'] }],
599
+ },
600
+ ],
601
+ invalid: [
602
+ {
603
+ // File in payment path should fail
604
+ code: `
605
+ function process(user: z.infer<typeof UserSchema>) {
606
+ console.log(user);
607
+ }
608
+ `,
609
+ filename: '/project/src/payment/processor.ts',
610
+ options: [{ mode: 'risk-based', enforceFor: ['**/payment/**', '**/auth/**'] }],
611
+ errors: [{ messageId: 'missingValidation' }],
612
+ },
613
+ {
614
+ // File in auth path should fail
615
+ code: `
616
+ function process(user: z.infer<typeof UserSchema>) {
617
+ console.log(user);
618
+ }
619
+ `,
620
+ filename: '/project/src/auth/login.ts',
621
+ options: [{ mode: 'risk-based', enforceFor: ['**/payment/**', '**/auth/**'] }],
622
+ errors: [{ messageId: 'missingValidation' }],
623
+ },
624
+ ],
625
+ });
626
+ });
627
+ });
628
+ describe('no-runtime-imports', () => {
629
+ it('should detect require() inside functions', () => {
630
+ ruleTester.run('no-runtime-imports', noRuntimeImports, {
631
+ valid: [
632
+ {
633
+ code: `const fs = require('fs');`, // Top-level require is OK
634
+ },
635
+ {
636
+ code: `import fs from 'fs';`, // Top-level import is OK
637
+ },
638
+ ],
639
+ invalid: [
640
+ {
641
+ code: `
642
+ function loadModule() {
643
+ const fs = require('fs');
644
+ }
645
+ `,
646
+ errors: [{ messageId: 'runtimeRequire' }],
647
+ },
648
+ {
649
+ code: `
650
+ const handler = () => {
651
+ const path = require('path');
652
+ };
653
+ `,
654
+ errors: [{ messageId: 'runtimeRequire' }],
655
+ },
656
+ ],
657
+ });
658
+ });
659
+ it('should detect dynamic import() inside functions', () => {
660
+ ruleTester.run('no-runtime-imports', noRuntimeImports, {
661
+ valid: [],
662
+ invalid: [
663
+ {
664
+ code: `
665
+ async function loadModule() {
666
+ const mod = await import('./module');
667
+ }
668
+ `,
669
+ errors: [{ messageId: 'runtimeImport' }],
670
+ },
671
+ {
672
+ code: `
673
+ const handler = async () => {
674
+ const { foo } = await import('./foo');
675
+ };
676
+ `,
677
+ errors: [{ messageId: 'runtimeImport' }],
678
+ },
679
+ ],
680
+ });
681
+ });
682
+ });
683
+ describe('no-impure-calls-in-core', () => {
684
+ it('should detect Core importing from Shell', () => {
685
+ ruleTester.run('no-impure-calls-in-core', noImpureCallsInCore, {
686
+ valid: [
687
+ {
688
+ code: `import { helper } from '../utils';`,
689
+ filename: '/project/core/logic.js',
690
+ },
691
+ {
692
+ code: `import { ioFunc } from '../shell/io';`,
693
+ filename: '/project/shell/handler.js', // OK in shell
694
+ },
695
+ ],
696
+ invalid: [
697
+ {
698
+ code: `import { ioFunc } from '../shell/io';`,
699
+ filename: '/project/core/logic.js',
700
+ errors: [{ messageId: 'shellImportInCore' }],
701
+ },
702
+ {
703
+ code: `import { readData } from '../../shell/data';`,
704
+ filename: '/project/src/core/parser.js',
705
+ errors: [{ messageId: 'shellImportInCore' }],
706
+ },
707
+ {
708
+ code: `import { handler } from 'shell/handler';`,
709
+ filename: '/project/core/logic.js',
710
+ errors: [{ messageId: 'shellImportInCore' }],
711
+ },
712
+ ],
713
+ });
714
+ });
715
+ it('should handle Windows-style paths', () => {
716
+ ruleTester.run('no-impure-calls-in-core', noImpureCallsInCore, {
717
+ valid: [],
718
+ invalid: [
719
+ {
720
+ code: `import { ioFunc } from '..\\\\shell\\\\io';`,
721
+ filename: 'C:\\\\Project\\\\core\\\\logic.js',
722
+ errors: [{ messageId: 'shellImportInCore' }],
723
+ },
724
+ ],
725
+ });
726
+ });
727
+ });
728
+ describe('no-pure-logic-in-shell', () => {
729
+ it('should warn when Shell function has no I/O indicators', () => {
730
+ ruleTester.run('no-pure-logic-in-shell', noPureLogicInShell, {
731
+ valid: [
732
+ {
733
+ // Async function - has I/O indicator
734
+ code: `
735
+ async function fetchData() {
736
+ const x = 1;
737
+ const y = 2;
738
+ const z = 3;
739
+ const w = 4;
740
+ return x + y + z + w;
741
+ }
742
+ `,
743
+ filename: '/project/shell/data.js',
744
+ },
745
+ {
746
+ // Uses fs - has I/O indicator
747
+ code: `
748
+ function readConfig() {
749
+ const x = 1;
750
+ const y = 2;
751
+ const z = 3;
752
+ const w = 4;
753
+ return fs.readFileSync('config.json');
754
+ }
755
+ `,
756
+ filename: '/project/shell/config.js',
757
+ },
758
+ {
759
+ // Returns Result - has I/O indicator
760
+ code: `
761
+ function loadData() {
762
+ const x = 1;
763
+ const y = 2;
764
+ const z = 3;
765
+ const w = 4;
766
+ return Success(data);
767
+ }
768
+ `,
769
+ filename: '/project/shell/loader.js',
770
+ },
771
+ {
772
+ // Small function - not substantial logic
773
+ code: `
774
+ function helper() {
775
+ const x = 1;
776
+ return x;
777
+ }
778
+ `,
779
+ filename: '/project/shell/utils.js',
780
+ },
781
+ ],
782
+ invalid: [
783
+ {
784
+ // Pure logic in Shell - no I/O, substantial statements
785
+ code: `
786
+ function calculateTotal() {
787
+ const a = 1;
788
+ const b = 2;
789
+ const c = 3;
790
+ const d = 4;
791
+ return a + b + c + d;
792
+ }
793
+ `,
794
+ filename: '/project/shell/calculator.js',
795
+ errors: [{ messageId: 'pureLogicInShell' }],
796
+ },
797
+ ],
798
+ });
799
+ });
800
+ it('should not check non-shell files', () => {
801
+ ruleTester.run('no-pure-logic-in-shell', noPureLogicInShell, {
802
+ valid: [
803
+ {
804
+ // Core file - rule should skip it
805
+ code: `
806
+ function pureCalculation() {
807
+ const a = 1;
808
+ const b = 2;
809
+ const c = 3;
810
+ const d = 4;
811
+ return a + b + c + d;
812
+ }
813
+ `,
814
+ filename: '/project/core/logic.js',
815
+ },
816
+ ],
817
+ invalid: [],
818
+ });
819
+ });
820
+ it('should extract function name from FunctionExpression with id', () => {
821
+ ruleTester.run('no-pure-logic-in-shell', noPureLogicInShell, {
822
+ valid: [],
823
+ invalid: [
824
+ {
825
+ // Named FunctionExpression should use its own name
826
+ code: `
827
+ const foo = function namedFunc() {
828
+ const a = 1;
829
+ const b = 2;
830
+ const c = 3;
831
+ const d = 4;
832
+ return a + b + c + d;
833
+ };
834
+ `,
835
+ filename: '/project/shell/calculator.js',
836
+ errors: [{
837
+ messageId: 'pureLogicInShell',
838
+ data: { name: 'namedFunc' }, // Should use function's own name
839
+ }],
840
+ },
841
+ ],
842
+ });
843
+ });
844
+ it('should extract function name from parent VariableDeclarator for anonymous FunctionExpression', () => {
845
+ ruleTester.run('no-pure-logic-in-shell', noPureLogicInShell, {
846
+ valid: [],
847
+ invalid: [
848
+ {
849
+ // Anonymous FunctionExpression should use variable name
850
+ code: `
851
+ const calculateTotal = function() {
852
+ const a = 1;
853
+ const b = 2;
854
+ const c = 3;
855
+ const d = 4;
856
+ return a + b + c + d;
857
+ };
858
+ `,
859
+ filename: '/project/shell/calculator.js',
860
+ errors: [{
861
+ messageId: 'pureLogicInShell',
862
+ data: { name: 'calculateTotal' }, // Should use variable name
863
+ }],
864
+ },
865
+ ],
866
+ });
867
+ });
868
+ it('should extract function name from parent VariableDeclarator for ArrowFunctionExpression', () => {
869
+ ruleTester.run('no-pure-logic-in-shell', noPureLogicInShell, {
870
+ valid: [],
871
+ invalid: [
872
+ {
873
+ // Arrow function should use variable name
874
+ code: `
875
+ const processData = () => {
876
+ const a = 1;
877
+ const b = 2;
878
+ const c = 3;
879
+ const d = 4;
880
+ return a + b + c + d;
881
+ };
882
+ `,
883
+ filename: '/project/shell/processor.js',
884
+ errors: [{
885
+ messageId: 'pureLogicInShell',
886
+ data: { name: 'processData' }, // Should use variable name
887
+ }],
888
+ },
889
+ ],
890
+ });
891
+ });
892
+ });
893
+ describe('shell-complexity', () => {
894
+ it('should detect functions with too many statements', () => {
895
+ ruleTester.run('shell-complexity', shellComplexity, {
896
+ valid: [
897
+ {
898
+ code: `
899
+ function simpleHandler() {
900
+ ${'const x = 1;\n'.repeat(19)}
901
+ }
902
+ `,
903
+ filename: '/project/shell/handler.js',
904
+ },
905
+ ],
906
+ invalid: [
907
+ {
908
+ code: `
909
+ function complexHandler() {
910
+ ${'const x = 1;\n'.repeat(21)}
911
+ }
912
+ `,
913
+ filename: '/project/shell/handler.js',
914
+ errors: [{ messageId: 'tooManyStatements' }],
915
+ },
916
+ ],
917
+ });
918
+ });
919
+ it('should detect functions with high cyclomatic complexity', () => {
920
+ ruleTester.run('shell-complexity', shellComplexity, {
921
+ valid: [
922
+ {
923
+ code: `
924
+ function lowComplexity() {
925
+ if (a) return 1;
926
+ if (b) return 2;
927
+ if (c) return 3;
928
+ return 0;
929
+ }
930
+ `,
931
+ filename: '/project/shell/handler.js',
932
+ },
933
+ ],
934
+ invalid: [
935
+ {
936
+ code: `
937
+ function highComplexity() {
938
+ if (a) return 1;
939
+ else if (b) return 2;
940
+ else if (c) return 3;
941
+ else if (d) return 4;
942
+ else if (e) return 5;
943
+ else if (f) return 6;
944
+ else if (g) return 7;
945
+ else if (h) return 8;
946
+ else if (i) return 9;
947
+ else if (j) return 10;
948
+ else return 0;
949
+ }
950
+ `,
951
+ filename: '/project/shell/handler.js',
952
+ errors: [{ messageId: 'tooComplex' }],
953
+ },
954
+ ],
955
+ });
956
+ });
957
+ it('should use custom thresholds', () => {
958
+ ruleTester.run('shell-complexity', shellComplexity, {
959
+ valid: [
960
+ {
961
+ code: `
962
+ function handler() {
963
+ ${'const x = 1;\n'.repeat(6)}
964
+ }
965
+ `,
966
+ filename: '/project/shell/handler.js',
967
+ options: [{ maxStatements: 5 }],
968
+ },
969
+ ],
970
+ invalid: [
971
+ {
972
+ code: `
973
+ function handler() {
974
+ ${'const x = 1;\n'.repeat(6)}
975
+ }
976
+ `,
977
+ filename: '/project/shell/handler.js',
978
+ options: [{ maxStatements: 5 }],
979
+ errors: [{ messageId: 'tooManyStatements' }],
980
+ },
981
+ ],
982
+ });
983
+ });
984
+ it('should not count default case in complexity', () => {
985
+ ruleTester.run('shell-complexity', shellComplexity, {
986
+ valid: [
987
+ {
988
+ // Switch with default should not add to complexity
989
+ code: `
990
+ function handler(type) {
991
+ switch (type) {
992
+ case 'a': return 1;
993
+ case 'b': return 2;
994
+ case 'c': return 3;
995
+ default: return 0;
996
+ }
997
+ }
998
+ `,
999
+ filename: '/project/shell/handler.js',
1000
+ options: [{ maxComplexity: 3 }],
1001
+ },
1002
+ ],
1003
+ invalid: [],
1004
+ });
1005
+ });
1006
+ it('should count nullish coalescing operator', () => {
1007
+ ruleTester.run('shell-complexity', shellComplexity, {
1008
+ valid: [],
1009
+ invalid: [
1010
+ {
1011
+ // Nullish coalescing should add to complexity
1012
+ code: `
1013
+ function handler() {
1014
+ const a = x ?? 1;
1015
+ const b = y ?? 2;
1016
+ const c = z ?? 3;
1017
+ const d = w ?? 4;
1018
+ const e = v ?? 5;
1019
+ if (a) return a;
1020
+ if (b) return b;
1021
+ if (c) return c;
1022
+ if (d) return d;
1023
+ if (e) return e;
1024
+ return 0;
1025
+ }
1026
+ `,
1027
+ filename: '/project/shell/handler.js',
1028
+ options: [{ maxComplexity: 10 }],
1029
+ errors: [{ messageId: 'tooComplex' }],
1030
+ },
1031
+ ],
1032
+ });
1033
+ });
1034
+ it('should not check non-shell files', () => {
1035
+ ruleTester.run('shell-complexity', shellComplexity, {
1036
+ valid: [
1037
+ {
1038
+ code: `
1039
+ function complexCore() {
1040
+ ${'const x = 1;\n'.repeat(30)}
1041
+ }
1042
+ `,
1043
+ filename: '/project/core/logic.js',
1044
+ },
1045
+ ],
1046
+ invalid: [],
1047
+ });
1048
+ });
1049
+ it('should extract function name from FunctionExpression with id', () => {
1050
+ ruleTester.run('shell-complexity', shellComplexity, {
1051
+ valid: [],
1052
+ invalid: [
1053
+ {
1054
+ // Named FunctionExpression should use its own name
1055
+ code: `
1056
+ const foo = function namedHandler() {
1057
+ ${'const x = 1;\n'.repeat(21)}
1058
+ };
1059
+ `,
1060
+ filename: '/project/shell/handler.js',
1061
+ errors: [{
1062
+ messageId: 'tooManyStatements',
1063
+ data: { name: 'namedHandler' }, // Should use function's own name
1064
+ }],
1065
+ },
1066
+ ],
1067
+ });
1068
+ });
1069
+ it('should extract function name from parent VariableDeclarator for anonymous FunctionExpression', () => {
1070
+ ruleTester.run('shell-complexity', shellComplexity, {
1071
+ valid: [],
1072
+ invalid: [
1073
+ {
1074
+ // Anonymous FunctionExpression should use variable name
1075
+ code: `
1076
+ const processOrder = function() {
1077
+ ${'const x = 1;\n'.repeat(21)}
1078
+ };
1079
+ `,
1080
+ filename: '/project/shell/orders.js',
1081
+ errors: [{
1082
+ messageId: 'tooManyStatements',
1083
+ data: { name: 'processOrder' }, // Should use variable name
1084
+ }],
1085
+ },
1086
+ ],
1087
+ });
1088
+ });
1089
+ it('should extract function name from parent VariableDeclarator for ArrowFunctionExpression', () => {
1090
+ ruleTester.run('shell-complexity', shellComplexity, {
1091
+ valid: [],
1092
+ invalid: [
1093
+ {
1094
+ // Arrow function should use variable name
1095
+ code: `
1096
+ const handleRequest = () => {
1097
+ ${'const x = 1;\n'.repeat(21)}
1098
+ };
1099
+ `,
1100
+ filename: '/project/shell/api.js',
1101
+ errors: [{
1102
+ messageId: 'tooManyStatements',
1103
+ data: { name: 'handleRequest' }, // Should use variable name
1104
+ }],
1105
+ },
1106
+ ],
1107
+ });
1108
+ });
1109
+ });
1110
+ describe('thin-entry-points', () => {
1111
+ it('should detect entry point files with too much logic', () => {
1112
+ ruleTester.run('thin-entry-points', thinEntryPoints, {
1113
+ valid: [
1114
+ {
1115
+ // Simple index.ts with imports and exports
1116
+ code: `
1117
+ import { foo } from './foo';
1118
+ import { bar } from './bar';
1119
+ export { foo, bar };
1120
+ export default foo;
1121
+ `,
1122
+ filename: '/project/index.ts',
1123
+ },
1124
+ {
1125
+ // Few config statements are OK
1126
+ code: `
1127
+ import express from 'express';
1128
+ const app = express();
1129
+ const PORT = 3000;
1130
+ export { app, PORT };
1131
+ `,
1132
+ filename: '/project/main.ts',
1133
+ },
1134
+ ],
1135
+ invalid: [
1136
+ {
1137
+ // Too many statements
1138
+ code: `
1139
+ import express from 'express';
1140
+ const app = express();
1141
+ const x1 = 1;
1142
+ const x2 = 2;
1143
+ const x3 = 3;
1144
+ const x4 = 4;
1145
+ const x5 = 5;
1146
+ const x6 = 6;
1147
+ const x7 = 7;
1148
+ const x8 = 8;
1149
+ const x9 = 9;
1150
+ const x10 = 10;
1151
+ const x11 = 11;
1152
+ export { app };
1153
+ `,
1154
+ filename: '/project/index.ts',
1155
+ errors: [{ messageId: 'tooMuchLogic' }],
1156
+ },
1157
+ ],
1158
+ });
1159
+ });
1160
+ it('should detect complex logic in entry points', () => {
1161
+ ruleTester.run('thin-entry-points', thinEntryPoints, {
1162
+ valid: [
1163
+ {
1164
+ code: `
1165
+ import { handler } from './handler';
1166
+ export { handler };
1167
+ `,
1168
+ filename: '/project/cli.ts',
1169
+ },
1170
+ ],
1171
+ invalid: [
1172
+ {
1173
+ // Function definition in entry point
1174
+ code: `
1175
+ function processData() {
1176
+ return 42;
1177
+ }
1178
+ export { processData };
1179
+ `,
1180
+ filename: '/project/index.ts',
1181
+ errors: [{ messageId: 'hasComplexLogic' }],
1182
+ },
1183
+ {
1184
+ // Class definition in entry point
1185
+ code: `
1186
+ class App {
1187
+ run() { return 'running'; }
1188
+ }
1189
+ export { App };
1190
+ `,
1191
+ filename: '/project/main.ts',
1192
+ errors: [{ messageId: 'hasComplexLogic' }],
1193
+ },
1194
+ {
1195
+ // Control flow in entry point
1196
+ code: `
1197
+ import { config } from './config';
1198
+ if (config.enabled) {
1199
+ console.log('enabled');
1200
+ }
1201
+ export { config };
1202
+ `,
1203
+ filename: '/project/app.ts',
1204
+ errors: [{ messageId: 'hasComplexLogic' }],
1205
+ },
1206
+ ],
1207
+ });
1208
+ });
1209
+ it('should only check entry point files', () => {
1210
+ ruleTester.run('thin-entry-points', thinEntryPoints, {
1211
+ valid: [
1212
+ {
1213
+ // Non-entry point file can have complex logic
1214
+ code: `
1215
+ function complexFunction() {
1216
+ if (a) return 1;
1217
+ if (b) return 2;
1218
+ if (c) return 3;
1219
+ return 0;
1220
+ }
1221
+ export { complexFunction };
1222
+ `,
1223
+ filename: '/project/utils/helper.ts',
1224
+ },
1225
+ ],
1226
+ invalid: [],
1227
+ });
1228
+ });
1229
+ it('should detect all entry point patterns', () => {
1230
+ const patterns = ['index.ts', 'main.ts', 'cli.ts', 'app.ts', 'server.ts'];
1231
+ for (const pattern of patterns) {
1232
+ ruleTester.run('thin-entry-points', thinEntryPoints, {
1233
+ valid: [],
1234
+ invalid: [
1235
+ {
1236
+ code: `
1237
+ function logic() { return 42; }
1238
+ export { logic };
1239
+ `,
1240
+ filename: `/project/${pattern}`,
1241
+ errors: [{ messageId: 'hasComplexLogic' }],
1242
+ },
1243
+ ],
1244
+ });
1245
+ }
1246
+ });
1247
+ it('should handle Windows paths correctly in error messages', () => {
1248
+ ruleTester.run('thin-entry-points', thinEntryPoints, {
1249
+ valid: [],
1250
+ invalid: [
1251
+ {
1252
+ // Windows path should be normalized to show just filename
1253
+ code: `
1254
+ function logic() { return 42; }
1255
+ export { logic };
1256
+ `,
1257
+ filename: 'C:\\\\Project\\\\src\\\\index.ts',
1258
+ errors: [{
1259
+ messageId: 'hasComplexLogic',
1260
+ // Error message should show 'index.ts' not full path
1261
+ }],
1262
+ },
1263
+ ],
1264
+ });
1265
+ });
1266
+ it('should treat export declarations with bodies as complex logic', () => {
1267
+ ruleTester.run('thin-entry-points', thinEntryPoints, {
1268
+ valid: [
1269
+ {
1270
+ // Pure re-export is OK
1271
+ code: `
1272
+ import { foo } from './foo';
1273
+ export { foo };
1274
+ `,
1275
+ filename: '/project/index.ts',
1276
+ },
1277
+ {
1278
+ // Type-only exports are OK
1279
+ code: `
1280
+ export type { User } from './types';
1281
+ export interface Config { port: number; }
1282
+ `,
1283
+ filename: '/project/index.ts',
1284
+ },
1285
+ ],
1286
+ invalid: [
1287
+ {
1288
+ // Export with function definition is complex logic
1289
+ code: `
1290
+ export function processData() {
1291
+ return 42;
1292
+ }
1293
+ `,
1294
+ filename: '/project/index.ts',
1295
+ errors: [{ messageId: 'hasComplexLogic' }],
1296
+ },
1297
+ {
1298
+ // Export with class definition is complex logic
1299
+ code: `
1300
+ export class Handler {
1301
+ handle() { return 'handled'; }
1302
+ }
1303
+ `,
1304
+ filename: '/project/index.ts',
1305
+ errors: [{ messageId: 'hasComplexLogic' }],
1306
+ },
1307
+ {
1308
+ // Export default with function is complex logic
1309
+ code: `
1310
+ export default function main() {
1311
+ console.log('running');
1312
+ }
1313
+ `,
1314
+ filename: '/project/main.ts',
1315
+ errors: [{ messageId: 'hasComplexLogic' }],
1316
+ },
1317
+ ],
1318
+ });
1319
+ });
1320
+ });
1321
+ //# sourceMappingURL=behavior.test.js.map