@sarj/eslint-plugin 4.2.0 → 5.0.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/dist/index.d.cts CHANGED
@@ -86,8 +86,10 @@ interface RuleOptions$2 {
86
86
  * "allow": ["[\\\\/]lib[\\\\/]api[\\\\/]", "-gateway\\\\.ts$"]
87
87
  * }]
88
88
  *
89
- * Supplying `allow` REPLACES the defaults, so include the test patterns if you
90
- * still want test files exempt.
89
+ * Supplying `allow` REPLACES the default CLIENT-LAYER patterns. Test files are
90
+ * no longer part of that list — they are recognised unconditionally by the
91
+ * shared `isTestFile` predicate — so a repo that overrides `allow` keeps its
92
+ * test tree exempt without having to remember the test patterns.
91
93
  *
92
94
  * CORPUS SWEEP (2220 files, zod / TanStack Query / react-router / swr /
93
95
  * zustand, 2026-07): 96 raw hits. The defaults missed three path conventions
@@ -103,6 +105,44 @@ interface RuleOptions$2 {
103
105
  * - `__testfixtures__/` — 8 hits from jscodeshift fixtures such as
104
106
  * `query/packages/query-codemods/src/v5/remove-overloads/__testfixtures__/bug-reports.input.tsx`,
105
107
  * which are input/output text for a codemod, not code that runs.
108
+ *
109
+ * SECOND SWEEP (25,508 deduped TS/TSX files across zod / trpc / dub /
110
+ * openstatus / formbricks / documenso / unkey / midday / papermark / cal.com /
111
+ * hono plus six first-party repos, 2026-07): 1,019 hits, 50 read in a seeded
112
+ * random sample — 24 true positives, 21 false positives, 5 arguable. At a 42%
113
+ * false-positive rate this was the loudest rule of the five audited, and the
114
+ * cause was the same one the first sweep found, just further along: the reports
115
+ * were landing ON the client layer, under names the `allow` list did not know.
116
+ *
117
+ * - **Vendor wrapper modules** — 147 findings. `*Service.ts`, `services/`,
118
+ * `connectors/` (16), `providers/` (12), `integrations/` (16),
119
+ * `notifications/<vendor>/` (29), `adapters/`, `*-fetcher.ts`. Every fetch
120
+ * in them is an absolute-URL call to ONE third-party origin — they ARE the
121
+ * module the rule wants the fetch to live in. Citable:
122
+ * `cal.com/packages/app-store/office365calendar/lib/CalendarService.ts:265`,
123
+ * `openstatus/packages/notifications/telegram/src/index.ts:81`,
124
+ * `formbricks/apps/web/lib/googleSheet/service.ts:164`.
125
+ * Recall cost: 0 of the 50 sampled true positives lived in such a path —
126
+ * every one was a `.tsx` component/page/modal or a React hook.
127
+ * - **Test-path drift** — 36 findings. The rule hand-rolled its own test-path
128
+ * list instead of using the shared `isTestFile`, so it missed `playwright/`
129
+ * (19) and the `.e2e.ts` basename. The single loudest file corpus-wide was
130
+ * `cal.com/apps/web/playwright/oauth-provider.e2e.ts` (16 findings). The
131
+ * sibling rule `require-fetch-timeout` had always used `isTestFile`; this
132
+ * was pure drift between two rules that see the same call sites.
133
+ * - **Asset / passthrough handoff** — see `isConstructedArgumentHandoff`.
134
+ *
135
+ * DELIBERATELY NOT GUARDED: restricting the rule to component/hook files. It
136
+ * would cut the residual false-positive rate from ~25% to ~4%, but costs ~80
137
+ * real positives in `.ts` background jobs (e.g.
138
+ * `papermark/lib/trigger/convert-pdf-direct.ts:332`).
139
+ *
140
+ * DELIBERATELY NOT ALLOWED: a `-provider.*` BASENAME, even though the
141
+ * `providers/` DIRECTORY is. In the corpus that basename is usually a React
142
+ * context or modal provider calling the app's own API —
143
+ * `dub/apps/web/ui/modals/modal-provider.tsx:134` is exactly the true-positive
144
+ * shape — and the two vendor files that carry it are already covered by
145
+ * `integrations/`.
106
146
  */
107
147
 
108
148
  interface RuleOptions$1 {
@@ -197,9 +237,6 @@ interface LoggingOptions {
197
237
  }
198
238
 
199
239
  declare const rules: {
200
- "ban-loose-type-guards-in-tests": _typescript_eslint_utils_ts_eslint.RuleModule<"banLooseTypeGuardsInTests", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
201
- name: string;
202
- };
203
240
  "enforce-file-structure": _typescript_eslint_utils_ts_eslint.RuleModule<"importsFirst" | "useServerDirective", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
204
241
  name: string;
205
242
  };
@@ -358,19 +395,15 @@ declare const rules: {
358
395
  "require-interface-for-injected-service": _typescript_eslint_utils_ts_eslint.RuleModule<"requireInterface", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
359
396
  name: string;
360
397
  };
361
- "strict-test-assertions": _typescript_eslint_utils_ts_eslint.RuleModule<"combineAssertions", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
362
- "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
398
+ "strict-test-assertions": _typescript_eslint_utils_ts_eslint.RuleModule<"combineAssertions" | "assertArrayOnce", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
363
399
  name: string;
364
400
  };
365
- "no-implicit-attribute-access": _typescript_eslint_utils_ts_eslint.RuleModule<"noImplicitAttributeAccess", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
401
+ "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
366
402
  name: string;
367
403
  };
368
404
  "no-async-callback-in-waitfor": _typescript_eslint_utils_ts_eslint.RuleModule<"noAsyncCallbackInWaitFor", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
369
405
  name: string;
370
406
  };
371
- "prefer-setup-file-mocks": _typescript_eslint_utils_ts_eslint.RuleModule<"preferSetupFileMocks", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
372
- name: string;
373
- };
374
407
  };
375
408
  declare const plugin: {
376
409
  meta: {
@@ -378,9 +411,6 @@ declare const plugin: {
378
411
  version: string;
379
412
  };
380
413
  rules: {
381
- "ban-loose-type-guards-in-tests": _typescript_eslint_utils_ts_eslint.RuleModule<"banLooseTypeGuardsInTests", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
382
- name: string;
383
- };
384
414
  "enforce-file-structure": _typescript_eslint_utils_ts_eslint.RuleModule<"importsFirst" | "useServerDirective", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
385
415
  name: string;
386
416
  };
@@ -539,19 +569,15 @@ declare const plugin: {
539
569
  "require-interface-for-injected-service": _typescript_eslint_utils_ts_eslint.RuleModule<"requireInterface", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
540
570
  name: string;
541
571
  };
542
- "strict-test-assertions": _typescript_eslint_utils_ts_eslint.RuleModule<"combineAssertions", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
543
- "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
572
+ "strict-test-assertions": _typescript_eslint_utils_ts_eslint.RuleModule<"combineAssertions" | "assertArrayOnce", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
544
573
  name: string;
545
574
  };
546
- "no-implicit-attribute-access": _typescript_eslint_utils_ts_eslint.RuleModule<"noImplicitAttributeAccess", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
575
+ "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
547
576
  name: string;
548
577
  };
549
578
  "no-async-callback-in-waitfor": _typescript_eslint_utils_ts_eslint.RuleModule<"noAsyncCallbackInWaitFor", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
550
579
  name: string;
551
580
  };
552
- "prefer-setup-file-mocks": _typescript_eslint_utils_ts_eslint.RuleModule<"preferSetupFileMocks", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
553
- name: string;
554
- };
555
581
  };
556
582
  configs: {
557
583
  recommended: {
@@ -560,7 +586,6 @@ declare const plugin: {
560
586
  "@sarj/zod-naming-convention": string;
561
587
  "@sarj/require-assert-never": string;
562
588
  "@sarj/require-zod-form-validation": string;
563
- "@sarj/ban-loose-type-guards-in-tests": string;
564
589
  "@sarj/enforce-file-structure": string;
565
590
  "@sarj/no-client-side-data-fetching": string;
566
591
  "@sarj/prefer-server-actions": string;
@@ -603,9 +628,7 @@ declare const plugin: {
603
628
  "@sarj/no-tautological-expect": string;
604
629
  "@sarj/require-interface-for-injected-service": string;
605
630
  "@sarj/prefer-non-nullable-collection": string;
606
- "@sarj/no-implicit-attribute-access": string;
607
631
  "@sarj/no-async-callback-in-waitfor": string;
608
- "@sarj/prefer-setup-file-mocks": string;
609
632
  };
610
633
  };
611
634
  strict: {
@@ -614,7 +637,6 @@ declare const plugin: {
614
637
  "@sarj/zod-naming-convention": string;
615
638
  "@sarj/require-assert-never": string;
616
639
  "@sarj/require-zod-form-validation": string;
617
- "@sarj/ban-loose-type-guards-in-tests": string;
618
640
  "@sarj/enforce-file-structure": string;
619
641
  "@sarj/no-raw-env": string;
620
642
  "@sarj/no-enum": string;
@@ -661,9 +683,7 @@ declare const plugin: {
661
683
  "@sarj/no-tautological-expect": string;
662
684
  "@sarj/require-interface-for-injected-service": string;
663
685
  "@sarj/prefer-non-nullable-collection": string;
664
- "@sarj/no-implicit-attribute-access": string;
665
686
  "@sarj/no-async-callback-in-waitfor": string;
666
- "@sarj/prefer-setup-file-mocks": string;
667
687
  };
668
688
  };
669
689
  };
package/dist/index.d.ts CHANGED
@@ -86,8 +86,10 @@ interface RuleOptions$2 {
86
86
  * "allow": ["[\\\\/]lib[\\\\/]api[\\\\/]", "-gateway\\\\.ts$"]
87
87
  * }]
88
88
  *
89
- * Supplying `allow` REPLACES the defaults, so include the test patterns if you
90
- * still want test files exempt.
89
+ * Supplying `allow` REPLACES the default CLIENT-LAYER patterns. Test files are
90
+ * no longer part of that list — they are recognised unconditionally by the
91
+ * shared `isTestFile` predicate — so a repo that overrides `allow` keeps its
92
+ * test tree exempt without having to remember the test patterns.
91
93
  *
92
94
  * CORPUS SWEEP (2220 files, zod / TanStack Query / react-router / swr /
93
95
  * zustand, 2026-07): 96 raw hits. The defaults missed three path conventions
@@ -103,6 +105,44 @@ interface RuleOptions$2 {
103
105
  * - `__testfixtures__/` — 8 hits from jscodeshift fixtures such as
104
106
  * `query/packages/query-codemods/src/v5/remove-overloads/__testfixtures__/bug-reports.input.tsx`,
105
107
  * which are input/output text for a codemod, not code that runs.
108
+ *
109
+ * SECOND SWEEP (25,508 deduped TS/TSX files across zod / trpc / dub /
110
+ * openstatus / formbricks / documenso / unkey / midday / papermark / cal.com /
111
+ * hono plus six first-party repos, 2026-07): 1,019 hits, 50 read in a seeded
112
+ * random sample — 24 true positives, 21 false positives, 5 arguable. At a 42%
113
+ * false-positive rate this was the loudest rule of the five audited, and the
114
+ * cause was the same one the first sweep found, just further along: the reports
115
+ * were landing ON the client layer, under names the `allow` list did not know.
116
+ *
117
+ * - **Vendor wrapper modules** — 147 findings. `*Service.ts`, `services/`,
118
+ * `connectors/` (16), `providers/` (12), `integrations/` (16),
119
+ * `notifications/<vendor>/` (29), `adapters/`, `*-fetcher.ts`. Every fetch
120
+ * in them is an absolute-URL call to ONE third-party origin — they ARE the
121
+ * module the rule wants the fetch to live in. Citable:
122
+ * `cal.com/packages/app-store/office365calendar/lib/CalendarService.ts:265`,
123
+ * `openstatus/packages/notifications/telegram/src/index.ts:81`,
124
+ * `formbricks/apps/web/lib/googleSheet/service.ts:164`.
125
+ * Recall cost: 0 of the 50 sampled true positives lived in such a path —
126
+ * every one was a `.tsx` component/page/modal or a React hook.
127
+ * - **Test-path drift** — 36 findings. The rule hand-rolled its own test-path
128
+ * list instead of using the shared `isTestFile`, so it missed `playwright/`
129
+ * (19) and the `.e2e.ts` basename. The single loudest file corpus-wide was
130
+ * `cal.com/apps/web/playwright/oauth-provider.e2e.ts` (16 findings). The
131
+ * sibling rule `require-fetch-timeout` had always used `isTestFile`; this
132
+ * was pure drift between two rules that see the same call sites.
133
+ * - **Asset / passthrough handoff** — see `isConstructedArgumentHandoff`.
134
+ *
135
+ * DELIBERATELY NOT GUARDED: restricting the rule to component/hook files. It
136
+ * would cut the residual false-positive rate from ~25% to ~4%, but costs ~80
137
+ * real positives in `.ts` background jobs (e.g.
138
+ * `papermark/lib/trigger/convert-pdf-direct.ts:332`).
139
+ *
140
+ * DELIBERATELY NOT ALLOWED: a `-provider.*` BASENAME, even though the
141
+ * `providers/` DIRECTORY is. In the corpus that basename is usually a React
142
+ * context or modal provider calling the app's own API —
143
+ * `dub/apps/web/ui/modals/modal-provider.tsx:134` is exactly the true-positive
144
+ * shape — and the two vendor files that carry it are already covered by
145
+ * `integrations/`.
106
146
  */
107
147
 
108
148
  interface RuleOptions$1 {
@@ -197,9 +237,6 @@ interface LoggingOptions {
197
237
  }
198
238
 
199
239
  declare const rules: {
200
- "ban-loose-type-guards-in-tests": _typescript_eslint_utils_ts_eslint.RuleModule<"banLooseTypeGuardsInTests", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
201
- name: string;
202
- };
203
240
  "enforce-file-structure": _typescript_eslint_utils_ts_eslint.RuleModule<"importsFirst" | "useServerDirective", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
204
241
  name: string;
205
242
  };
@@ -358,19 +395,15 @@ declare const rules: {
358
395
  "require-interface-for-injected-service": _typescript_eslint_utils_ts_eslint.RuleModule<"requireInterface", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
359
396
  name: string;
360
397
  };
361
- "strict-test-assertions": _typescript_eslint_utils_ts_eslint.RuleModule<"combineAssertions", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
362
- "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
398
+ "strict-test-assertions": _typescript_eslint_utils_ts_eslint.RuleModule<"combineAssertions" | "assertArrayOnce", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
363
399
  name: string;
364
400
  };
365
- "no-implicit-attribute-access": _typescript_eslint_utils_ts_eslint.RuleModule<"noImplicitAttributeAccess", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
401
+ "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
366
402
  name: string;
367
403
  };
368
404
  "no-async-callback-in-waitfor": _typescript_eslint_utils_ts_eslint.RuleModule<"noAsyncCallbackInWaitFor", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
369
405
  name: string;
370
406
  };
371
- "prefer-setup-file-mocks": _typescript_eslint_utils_ts_eslint.RuleModule<"preferSetupFileMocks", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
372
- name: string;
373
- };
374
407
  };
375
408
  declare const plugin: {
376
409
  meta: {
@@ -378,9 +411,6 @@ declare const plugin: {
378
411
  version: string;
379
412
  };
380
413
  rules: {
381
- "ban-loose-type-guards-in-tests": _typescript_eslint_utils_ts_eslint.RuleModule<"banLooseTypeGuardsInTests", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
382
- name: string;
383
- };
384
414
  "enforce-file-structure": _typescript_eslint_utils_ts_eslint.RuleModule<"importsFirst" | "useServerDirective", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
385
415
  name: string;
386
416
  };
@@ -539,19 +569,15 @@ declare const plugin: {
539
569
  "require-interface-for-injected-service": _typescript_eslint_utils_ts_eslint.RuleModule<"requireInterface", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
540
570
  name: string;
541
571
  };
542
- "strict-test-assertions": _typescript_eslint_utils_ts_eslint.RuleModule<"combineAssertions", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
543
- "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
572
+ "strict-test-assertions": _typescript_eslint_utils_ts_eslint.RuleModule<"combineAssertions" | "assertArrayOnce", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
544
573
  name: string;
545
574
  };
546
- "no-implicit-attribute-access": _typescript_eslint_utils_ts_eslint.RuleModule<"noImplicitAttributeAccess", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
575
+ "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
547
576
  name: string;
548
577
  };
549
578
  "no-async-callback-in-waitfor": _typescript_eslint_utils_ts_eslint.RuleModule<"noAsyncCallbackInWaitFor", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
550
579
  name: string;
551
580
  };
552
- "prefer-setup-file-mocks": _typescript_eslint_utils_ts_eslint.RuleModule<"preferSetupFileMocks", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
553
- name: string;
554
- };
555
581
  };
556
582
  configs: {
557
583
  recommended: {
@@ -560,7 +586,6 @@ declare const plugin: {
560
586
  "@sarj/zod-naming-convention": string;
561
587
  "@sarj/require-assert-never": string;
562
588
  "@sarj/require-zod-form-validation": string;
563
- "@sarj/ban-loose-type-guards-in-tests": string;
564
589
  "@sarj/enforce-file-structure": string;
565
590
  "@sarj/no-client-side-data-fetching": string;
566
591
  "@sarj/prefer-server-actions": string;
@@ -603,9 +628,7 @@ declare const plugin: {
603
628
  "@sarj/no-tautological-expect": string;
604
629
  "@sarj/require-interface-for-injected-service": string;
605
630
  "@sarj/prefer-non-nullable-collection": string;
606
- "@sarj/no-implicit-attribute-access": string;
607
631
  "@sarj/no-async-callback-in-waitfor": string;
608
- "@sarj/prefer-setup-file-mocks": string;
609
632
  };
610
633
  };
611
634
  strict: {
@@ -614,7 +637,6 @@ declare const plugin: {
614
637
  "@sarj/zod-naming-convention": string;
615
638
  "@sarj/require-assert-never": string;
616
639
  "@sarj/require-zod-form-validation": string;
617
- "@sarj/ban-loose-type-guards-in-tests": string;
618
640
  "@sarj/enforce-file-structure": string;
619
641
  "@sarj/no-raw-env": string;
620
642
  "@sarj/no-enum": string;
@@ -661,9 +683,7 @@ declare const plugin: {
661
683
  "@sarj/no-tautological-expect": string;
662
684
  "@sarj/require-interface-for-injected-service": string;
663
685
  "@sarj/prefer-non-nullable-collection": string;
664
- "@sarj/no-implicit-attribute-access": string;
665
686
  "@sarj/no-async-callback-in-waitfor": string;
666
- "@sarj/prefer-setup-file-mocks": string;
667
687
  };
668
688
  };
669
689
  };