@sarj/eslint-plugin 16.4.4 → 16.4.6

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.
@@ -15,6 +15,11 @@ interface ExampleFile {
15
15
  }
16
16
  /** A reviewed example. It remains private unless `public: true` is explicit. */
17
17
  interface RuleExample {
18
+ readonly ruleOptions?: readonly unknown[];
19
+ readonly installedDependencies?: readonly {
20
+ readonly module: string;
21
+ readonly version: string;
22
+ }[];
18
23
  readonly id: string;
19
24
  readonly scenarioId?: string;
20
25
  readonly title: string;
@@ -15,6 +15,11 @@ interface ExampleFile {
15
15
  }
16
16
  /** A reviewed example. It remains private unless `public: true` is explicit. */
17
17
  interface RuleExample {
18
+ readonly ruleOptions?: readonly unknown[];
19
+ readonly installedDependencies?: readonly {
20
+ readonly module: string;
21
+ readonly version: string;
22
+ }[];
18
23
  readonly id: string;
19
24
  readonly scenarioId?: string;
20
25
  readonly title: string;
package/dist/index.cjs CHANGED
@@ -112,7 +112,7 @@ ${missing.join("\n")}`);
112
112
  });
113
113
  }
114
114
  function publicExample(example) {
115
- return {
115
+ const copy = {
116
116
  id: example.id,
117
117
  scenarioId: example.scenarioId ?? "primary",
118
118
  title: example.title,
@@ -122,6 +122,9 @@ function publicExample(example) {
122
122
  expectedCount: example.expectedCount,
123
123
  fixedFiles: (example.fixedFiles ?? []).map(publicFile)
124
124
  };
125
+ if (example.ruleOptions !== void 0) copy.ruleOptions = example.ruleOptions;
126
+ if (example.installedDependencies !== void 0) copy.installedDependencies = example.installedDependencies.map(({ module: module2, version }) => ({ module: module2, version }));
127
+ return copy;
125
128
  }
126
129
  function publicFile(file) {
127
130
  return { path: file.path, source: file.source };
@@ -7193,7 +7196,7 @@ var import_utils43 = require("@typescript-eslint/utils");
7193
7196
  // src/rules/_zod.ts
7194
7197
  var ZOD_SCHEMA_NAME_RE = /Schema$|^Z[A-Z]/;
7195
7198
  function isZodModule(source) {
7196
- return /(^|[/@-])zod([/-]|$)/.test(source);
7199
+ return source === "zod" || source.startsWith("zod/");
7197
7200
  }
7198
7201
 
7199
7202
  // src/rules/no-impossible-zod-literal-bounds.ts
@@ -7580,6 +7583,7 @@ var NO_REDUNDANT_OPTIONAL_ARRAY_DEFAULT_DOCUMENTATION = {
7580
7583
  autofix: "safe",
7581
7584
  limitations: [
7582
7585
  "Only syntactically local Zod array chains with adjacent `.optional().default(...)` calls are checked.",
7586
+ "Imports must name the official zod package or its subpaths; relative modules and unproven npm aliases are excluded.",
7583
7587
  "String, scalar, tuple, set, record, aliased, composed, and dynamically constructed schemas are excluded.",
7584
7588
  "The reversed `.default(...).optional()` order is preserved because its outer optional can return undefined instead of the default.",
7585
7589
  "Defaults must be literal arrays or synchronous functions returning one literal array; dynamic or undefined fallbacks are excluded because Zod 3 reparses the fallback.",
@@ -7620,6 +7624,30 @@ var NO_REDUNDANT_OPTIONAL_ARRAY_DEFAULT_DOCUMENTATION = {
7620
7624
  focusPath: "src/schema.ts",
7621
7625
  expectedCount: 1,
7622
7626
  public: true
7627
+ },
7628
+ {
7629
+ id: "unrelated-relative-builder",
7630
+ title: "Preserve an unrelated builder in a directory named zod",
7631
+ outcome: "no-match",
7632
+ files: [
7633
+ { path: "src/my-zod/index.ts", source: 'export const z = { string: () => "string", array: (_element: string) => ({ optional: () => ({ default: (value: readonly string[]) => ({ value, optional: true }) }), default: (value: readonly string[]) => ({ value, optional: false }) }) };' },
7634
+ { path: "src/schema.ts", source: 'import { z } from "./my-zod/index.js"; export const Items = z.array(z.string()).optional().default([]);' }
7635
+ ],
7636
+ focusPath: "src/schema.ts",
7637
+ expectedCount: 0,
7638
+ public: true,
7639
+ scenarioId: "module-identity"
7640
+ },
7641
+ {
7642
+ id: "official-zod-subpath-array",
7643
+ title: "Official Zod subpath constructors retain the safe fix",
7644
+ outcome: "match",
7645
+ files: [{ path: "src/schema.ts", source: 'import { array, string } from "zod/v4"; const Items = array(string()).optional().default([]);' }],
7646
+ fixedFiles: [{ path: "src/schema.ts", source: 'import { array, string } from "zod/v4"; const Items = array(string()).default([]);' }],
7647
+ focusPath: "src/schema.ts",
7648
+ expectedCount: 1,
7649
+ public: true,
7650
+ scenarioId: "module-identity"
7623
7651
  }
7624
7652
  ]
7625
7653
  };
@@ -9687,8 +9715,8 @@ var NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION = {
9687
9715
  category: "architecture",
9688
9716
  limitations: ["Only literal dynamic imports, unshadowed CommonJS loads/resolution calls, and runtime TypeScript import-equals declarations are checked; erased type imports are excluded. A configured restriction list is required."],
9689
9717
  examples: [
9690
- { id: "static-import", title: "Static imports remain the static-import rule's responsibility", outcome: "no-match", files: [{ path: "src/client.ts", source: "import axios from 'axios';" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
9691
- { id: "runtime-load", title: "Do not load a restricted library at runtime", outcome: "match", files: [{ path: "src/client.ts", source: "const client = require('axios');" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
9718
+ { id: "static-import", title: "Static imports remain the static-import rule's responsibility", outcome: "no-match", files: [{ path: "src/client.ts", source: "import axios from 'axios';" }], focusPath: "src/client.ts", expectedCount: 0, public: true, ruleOptions: [{ libraries: [{ id: "LIB101", module: "axios", replacement: "Ky" }] }] },
9719
+ { id: "runtime-load", title: "Do not load a restricted library at runtime", outcome: "match", files: [{ path: "src/client.ts", source: "const client = require('axios');" }], focusPath: "src/client.ts", expectedCount: 1, public: true, ruleOptions: [{ libraries: [{ id: "LIB101", module: "axios", replacement: "Ky" }] }] }
9692
9720
  ]
9693
9721
  };
9694
9722
  function staticModule(node) {
@@ -11832,8 +11860,8 @@ var NO_STORAGE_IN_STATELESS_MODULES_DOCUMENTATION = {
11832
11860
  category: "architecture",
11833
11861
  limitations: ["This opt-in architectural policy requires configured module paths and storage method names. Overloaded `put` requires storage-like receiver evidence; `prepare` requires SQL-shaped literal text or a conventional database receiver for dynamic text. These syntax heuristics do not prove database provenance or identify the system of record."],
11834
11862
  examples: [
11835
- { id: "system-of-record", title: "Read from the system of record", outcome: "no-match", files: [{ path: "src/engineer-digest/post.ts", source: "const issues = await linear.listIssues();" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 0, public: true },
11836
- { id: "private-storage", title: "Do not write private state in a stateless module", outcome: "match", files: [{ path: "src/engineer-digest/post.ts", source: "export {}; await kv.put('digest:last', timestamp);" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 1, public: true }
11863
+ { id: "system-of-record", title: "Read from the system of record", outcome: "no-match", files: [{ path: "src/engineer-digest/post.ts", source: "const issues = await linear.listIssues();" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 0, public: true, ruleOptions: [{ modules: ["[\\\\/]engineer-digest[\\\\/]"] }] },
11864
+ { id: "private-storage", title: "Do not write private state in a stateless module", outcome: "match", files: [{ path: "src/engineer-digest/post.ts", source: "export {}; await kv.put('digest:last', timestamp);" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 1, public: true, ruleOptions: [{ modules: ["[\\\\/]engineer-digest[\\\\/]"] }] }
11837
11865
  ]
11838
11866
  };
11839
11867
  function compile2(patterns) {
@@ -20993,7 +21021,8 @@ var PREFER_ZOD_PARSE_OUTPUT_TYPE_DOCUMENTATION = {
20993
21021
  ],
20994
21022
  focusPath: "src/row.ts",
20995
21023
  expectedCount: 0,
20996
- public: true
21024
+ public: true,
21025
+ installedDependencies: [{ module: "zod", version: "4.6.5" }]
20997
21026
  },
20998
21027
  {
20999
21028
  id: "hand-written-parsed-return",
@@ -21011,7 +21040,8 @@ var PREFER_ZOD_PARSE_OUTPUT_TYPE_DOCUMENTATION = {
21011
21040
  ],
21012
21041
  focusPath: "src/row.ts",
21013
21042
  expectedCount: 1,
21014
- public: true
21043
+ public: true,
21044
+ installedDependencies: [{ module: "zod", version: "4.6.5" }]
21015
21045
  }
21016
21046
  ]
21017
21047
  };
@@ -24284,7 +24314,7 @@ var RULES = {
24284
24314
  };
24285
24315
  var meta = {
24286
24316
  name: "@sarj/eslint-plugin",
24287
- version: "16.4.4"
24317
+ version: "16.4.6"
24288
24318
  };
24289
24319
  var APPLICATION_ONLY_RULES = [];
24290
24320
  var LIBRARY_IMPORT_POLICY = ["error", {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DocumentedRule } from './_docs-XCw_6XV9.cjs';
2
- export { p as publicDocumentation } from './_docs-XCw_6XV9.cjs';
1
+ import { D as DocumentedRule } from './_docs-B9LZ5Out.cjs';
2
+ export { p as publicDocumentation } from './_docs-B9LZ5Out.cjs';
3
3
  import '@typescript-eslint/utils';
4
4
 
5
5
  /** @fileoverview _retired-rules — burned rule names and their migration action. */
@@ -314,7 +314,7 @@ declare const RECOMMENDED_RULES: {
314
314
  }];
315
315
  readonly "no-restricted-imports": readonly ["error", {
316
316
  readonly paths: {
317
- name: "request" | "node-fetch" | "cross-fetch" | "isomorphic-fetch" | "axios" | "moment" | "dayjs" | "lodash" | "lodash-es" | "underscore" | "classnames" | "joi" | "yup" | "superstruct" | "io-ts" | "runtypes" | "jsonwebtoken" | "express" | "koa" | "jest" | "mocha" | "sinon" | "commander" | "yargs" | "bluebird" | "rimraf" | "fs-extra" | "abort-controller" | "querystring" | "dotenv" | "chalk" | "faker" | "node-sass" | "tslint";
317
+ name: "axios" | "request" | "node-fetch" | "cross-fetch" | "isomorphic-fetch" | "moment" | "dayjs" | "lodash" | "lodash-es" | "underscore" | "classnames" | "joi" | "yup" | "superstruct" | "io-ts" | "runtypes" | "jsonwebtoken" | "express" | "koa" | "jest" | "mocha" | "sinon" | "commander" | "yargs" | "bluebird" | "rimraf" | "fs-extra" | "abort-controller" | "querystring" | "dotenv" | "chalk" | "faker" | "node-sass" | "tslint";
318
318
  message: "Use the platform-native fetch API; review errors, timeouts, retries, and response parsing during migration." | "Standards standardizes date utilities on date-fns; migration is not API-compatible." | "Standards standardizes collection utilities on Remeda and native APIs." | "Use clsx for conditional class-name composition." | "Standards standardizes runtime validation on Zod; schemas are not drop-in compatible." | "Use jose; review key formats and async signing and verification APIs." | "Standards standardizes servers on Hono; Node deployments also need @hono/node-server." | "Standards standardizes tests on Vitest; review globals, timers, mocks, and environment setup." | "Use Vitest spies, mocks, and fake timers instead of Sinon." | "Standards standardizes command-line interfaces on citty." | "Use native Promise, adding p-limit or p-map only for the extensions actually needed." | "Prefer node:fs/promises; verify recursive removal, copy, path, and error semantics." | "Node 22 provides global AbortController." | "Use URLSearchParams and explicitly review repeated keys, escaping, arrays, and object coercion." | "Standards standardizes environment loading on @dotenvx/dotenvx." | "Use picocolors for terminal colors." | "The original faker package is abandoned; use @faker-js/faker." | "node-sass is end-of-life; use Dart Sass." | "TSLint is deprecated; use ESLint with typescript-eslint.";
319
319
  }[];
320
320
  readonly patterns: {
@@ -626,7 +626,7 @@ declare const STRICT_RULES: {
626
626
  }];
627
627
  readonly "no-restricted-imports": readonly ["error", {
628
628
  readonly paths: {
629
- name: "request" | "node-fetch" | "cross-fetch" | "isomorphic-fetch" | "axios" | "moment" | "dayjs" | "lodash" | "lodash-es" | "underscore" | "classnames" | "joi" | "yup" | "superstruct" | "io-ts" | "runtypes" | "jsonwebtoken" | "express" | "koa" | "jest" | "mocha" | "sinon" | "commander" | "yargs" | "bluebird" | "rimraf" | "fs-extra" | "abort-controller" | "querystring" | "dotenv" | "chalk" | "faker" | "node-sass" | "tslint";
629
+ name: "axios" | "request" | "node-fetch" | "cross-fetch" | "isomorphic-fetch" | "moment" | "dayjs" | "lodash" | "lodash-es" | "underscore" | "classnames" | "joi" | "yup" | "superstruct" | "io-ts" | "runtypes" | "jsonwebtoken" | "express" | "koa" | "jest" | "mocha" | "sinon" | "commander" | "yargs" | "bluebird" | "rimraf" | "fs-extra" | "abort-controller" | "querystring" | "dotenv" | "chalk" | "faker" | "node-sass" | "tslint";
630
630
  message: "Use the platform-native fetch API; review errors, timeouts, retries, and response parsing during migration." | "Standards standardizes date utilities on date-fns; migration is not API-compatible." | "Standards standardizes collection utilities on Remeda and native APIs." | "Use clsx for conditional class-name composition." | "Standards standardizes runtime validation on Zod; schemas are not drop-in compatible." | "Use jose; review key formats and async signing and verification APIs." | "Standards standardizes servers on Hono; Node deployments also need @hono/node-server." | "Standards standardizes tests on Vitest; review globals, timers, mocks, and environment setup." | "Use Vitest spies, mocks, and fake timers instead of Sinon." | "Standards standardizes command-line interfaces on citty." | "Use native Promise, adding p-limit or p-map only for the extensions actually needed." | "Prefer node:fs/promises; verify recursive removal, copy, path, and error semantics." | "Node 22 provides global AbortController." | "Use URLSearchParams and explicitly review repeated keys, escaping, arrays, and object coercion." | "Standards standardizes environment loading on @dotenvx/dotenvx." | "Use picocolors for terminal colors." | "The original faker package is abandoned; use @faker-js/faker." | "node-sass is end-of-life; use Dart Sass." | "TSLint is deprecated; use ESLint with typescript-eslint.";
631
631
  }[];
632
632
  readonly patterns: {
@@ -927,7 +927,7 @@ type FlatPreset = {
927
927
  declare const PLUGIN: {
928
928
  readonly meta: {
929
929
  readonly name: "@sarj/eslint-plugin";
930
- readonly version: "16.4.4";
930
+ readonly version: "16.4.6";
931
931
  };
932
932
  readonly rules: {
933
933
  readonly "require-explicit-contract-implementation": DocumentedRule<readonly [], "declareActualContract">;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DocumentedRule } from './_docs-XCw_6XV9.js';
2
- export { p as publicDocumentation } from './_docs-XCw_6XV9.js';
1
+ import { D as DocumentedRule } from './_docs-B9LZ5Out.js';
2
+ export { p as publicDocumentation } from './_docs-B9LZ5Out.js';
3
3
  import '@typescript-eslint/utils';
4
4
 
5
5
  /** @fileoverview _retired-rules — burned rule names and their migration action. */
@@ -314,7 +314,7 @@ declare const RECOMMENDED_RULES: {
314
314
  }];
315
315
  readonly "no-restricted-imports": readonly ["error", {
316
316
  readonly paths: {
317
- name: "request" | "node-fetch" | "cross-fetch" | "isomorphic-fetch" | "axios" | "moment" | "dayjs" | "lodash" | "lodash-es" | "underscore" | "classnames" | "joi" | "yup" | "superstruct" | "io-ts" | "runtypes" | "jsonwebtoken" | "express" | "koa" | "jest" | "mocha" | "sinon" | "commander" | "yargs" | "bluebird" | "rimraf" | "fs-extra" | "abort-controller" | "querystring" | "dotenv" | "chalk" | "faker" | "node-sass" | "tslint";
317
+ name: "axios" | "request" | "node-fetch" | "cross-fetch" | "isomorphic-fetch" | "moment" | "dayjs" | "lodash" | "lodash-es" | "underscore" | "classnames" | "joi" | "yup" | "superstruct" | "io-ts" | "runtypes" | "jsonwebtoken" | "express" | "koa" | "jest" | "mocha" | "sinon" | "commander" | "yargs" | "bluebird" | "rimraf" | "fs-extra" | "abort-controller" | "querystring" | "dotenv" | "chalk" | "faker" | "node-sass" | "tslint";
318
318
  message: "Use the platform-native fetch API; review errors, timeouts, retries, and response parsing during migration." | "Standards standardizes date utilities on date-fns; migration is not API-compatible." | "Standards standardizes collection utilities on Remeda and native APIs." | "Use clsx for conditional class-name composition." | "Standards standardizes runtime validation on Zod; schemas are not drop-in compatible." | "Use jose; review key formats and async signing and verification APIs." | "Standards standardizes servers on Hono; Node deployments also need @hono/node-server." | "Standards standardizes tests on Vitest; review globals, timers, mocks, and environment setup." | "Use Vitest spies, mocks, and fake timers instead of Sinon." | "Standards standardizes command-line interfaces on citty." | "Use native Promise, adding p-limit or p-map only for the extensions actually needed." | "Prefer node:fs/promises; verify recursive removal, copy, path, and error semantics." | "Node 22 provides global AbortController." | "Use URLSearchParams and explicitly review repeated keys, escaping, arrays, and object coercion." | "Standards standardizes environment loading on @dotenvx/dotenvx." | "Use picocolors for terminal colors." | "The original faker package is abandoned; use @faker-js/faker." | "node-sass is end-of-life; use Dart Sass." | "TSLint is deprecated; use ESLint with typescript-eslint.";
319
319
  }[];
320
320
  readonly patterns: {
@@ -626,7 +626,7 @@ declare const STRICT_RULES: {
626
626
  }];
627
627
  readonly "no-restricted-imports": readonly ["error", {
628
628
  readonly paths: {
629
- name: "request" | "node-fetch" | "cross-fetch" | "isomorphic-fetch" | "axios" | "moment" | "dayjs" | "lodash" | "lodash-es" | "underscore" | "classnames" | "joi" | "yup" | "superstruct" | "io-ts" | "runtypes" | "jsonwebtoken" | "express" | "koa" | "jest" | "mocha" | "sinon" | "commander" | "yargs" | "bluebird" | "rimraf" | "fs-extra" | "abort-controller" | "querystring" | "dotenv" | "chalk" | "faker" | "node-sass" | "tslint";
629
+ name: "axios" | "request" | "node-fetch" | "cross-fetch" | "isomorphic-fetch" | "moment" | "dayjs" | "lodash" | "lodash-es" | "underscore" | "classnames" | "joi" | "yup" | "superstruct" | "io-ts" | "runtypes" | "jsonwebtoken" | "express" | "koa" | "jest" | "mocha" | "sinon" | "commander" | "yargs" | "bluebird" | "rimraf" | "fs-extra" | "abort-controller" | "querystring" | "dotenv" | "chalk" | "faker" | "node-sass" | "tslint";
630
630
  message: "Use the platform-native fetch API; review errors, timeouts, retries, and response parsing during migration." | "Standards standardizes date utilities on date-fns; migration is not API-compatible." | "Standards standardizes collection utilities on Remeda and native APIs." | "Use clsx for conditional class-name composition." | "Standards standardizes runtime validation on Zod; schemas are not drop-in compatible." | "Use jose; review key formats and async signing and verification APIs." | "Standards standardizes servers on Hono; Node deployments also need @hono/node-server." | "Standards standardizes tests on Vitest; review globals, timers, mocks, and environment setup." | "Use Vitest spies, mocks, and fake timers instead of Sinon." | "Standards standardizes command-line interfaces on citty." | "Use native Promise, adding p-limit or p-map only for the extensions actually needed." | "Prefer node:fs/promises; verify recursive removal, copy, path, and error semantics." | "Node 22 provides global AbortController." | "Use URLSearchParams and explicitly review repeated keys, escaping, arrays, and object coercion." | "Standards standardizes environment loading on @dotenvx/dotenvx." | "Use picocolors for terminal colors." | "The original faker package is abandoned; use @faker-js/faker." | "node-sass is end-of-life; use Dart Sass." | "TSLint is deprecated; use ESLint with typescript-eslint.";
631
631
  }[];
632
632
  readonly patterns: {
@@ -927,7 +927,7 @@ type FlatPreset = {
927
927
  declare const PLUGIN: {
928
928
  readonly meta: {
929
929
  readonly name: "@sarj/eslint-plugin";
930
- readonly version: "16.4.4";
930
+ readonly version: "16.4.6";
931
931
  };
932
932
  readonly rules: {
933
933
  readonly "require-explicit-contract-implementation": DocumentedRule<readonly [], "declareActualContract">;
package/dist/index.js CHANGED
@@ -61,7 +61,7 @@ ${missing.join("\n")}`);
61
61
  });
62
62
  }
63
63
  function publicExample(example) {
64
- return {
64
+ const copy = {
65
65
  id: example.id,
66
66
  scenarioId: example.scenarioId ?? "primary",
67
67
  title: example.title,
@@ -71,6 +71,9 @@ function publicExample(example) {
71
71
  expectedCount: example.expectedCount,
72
72
  fixedFiles: (example.fixedFiles ?? []).map(publicFile)
73
73
  };
74
+ if (example.ruleOptions !== void 0) copy.ruleOptions = example.ruleOptions;
75
+ if (example.installedDependencies !== void 0) copy.installedDependencies = example.installedDependencies.map(({ module, version }) => ({ module, version }));
76
+ return copy;
74
77
  }
75
78
  function publicFile(file) {
76
79
  return { path: file.path, source: file.source };
@@ -7167,7 +7170,7 @@ import {
7167
7170
  // src/rules/_zod.ts
7168
7171
  var ZOD_SCHEMA_NAME_RE = /Schema$|^Z[A-Z]/;
7169
7172
  function isZodModule(source) {
7170
- return /(^|[/@-])zod([/-]|$)/.test(source);
7173
+ return source === "zod" || source.startsWith("zod/");
7171
7174
  }
7172
7175
 
7173
7176
  // src/rules/no-impossible-zod-literal-bounds.ts
@@ -7559,6 +7562,7 @@ var NO_REDUNDANT_OPTIONAL_ARRAY_DEFAULT_DOCUMENTATION = {
7559
7562
  autofix: "safe",
7560
7563
  limitations: [
7561
7564
  "Only syntactically local Zod array chains with adjacent `.optional().default(...)` calls are checked.",
7565
+ "Imports must name the official zod package or its subpaths; relative modules and unproven npm aliases are excluded.",
7562
7566
  "String, scalar, tuple, set, record, aliased, composed, and dynamically constructed schemas are excluded.",
7563
7567
  "The reversed `.default(...).optional()` order is preserved because its outer optional can return undefined instead of the default.",
7564
7568
  "Defaults must be literal arrays or synchronous functions returning one literal array; dynamic or undefined fallbacks are excluded because Zod 3 reparses the fallback.",
@@ -7599,6 +7603,30 @@ var NO_REDUNDANT_OPTIONAL_ARRAY_DEFAULT_DOCUMENTATION = {
7599
7603
  focusPath: "src/schema.ts",
7600
7604
  expectedCount: 1,
7601
7605
  public: true
7606
+ },
7607
+ {
7608
+ id: "unrelated-relative-builder",
7609
+ title: "Preserve an unrelated builder in a directory named zod",
7610
+ outcome: "no-match",
7611
+ files: [
7612
+ { path: "src/my-zod/index.ts", source: 'export const z = { string: () => "string", array: (_element: string) => ({ optional: () => ({ default: (value: readonly string[]) => ({ value, optional: true }) }), default: (value: readonly string[]) => ({ value, optional: false }) }) };' },
7613
+ { path: "src/schema.ts", source: 'import { z } from "./my-zod/index.js"; export const Items = z.array(z.string()).optional().default([]);' }
7614
+ ],
7615
+ focusPath: "src/schema.ts",
7616
+ expectedCount: 0,
7617
+ public: true,
7618
+ scenarioId: "module-identity"
7619
+ },
7620
+ {
7621
+ id: "official-zod-subpath-array",
7622
+ title: "Official Zod subpath constructors retain the safe fix",
7623
+ outcome: "match",
7624
+ files: [{ path: "src/schema.ts", source: 'import { array, string } from "zod/v4"; const Items = array(string()).optional().default([]);' }],
7625
+ fixedFiles: [{ path: "src/schema.ts", source: 'import { array, string } from "zod/v4"; const Items = array(string()).default([]);' }],
7626
+ focusPath: "src/schema.ts",
7627
+ expectedCount: 1,
7628
+ public: true,
7629
+ scenarioId: "module-identity"
7602
7630
  }
7603
7631
  ]
7604
7632
  };
@@ -9671,8 +9699,8 @@ var NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION = {
9671
9699
  category: "architecture",
9672
9700
  limitations: ["Only literal dynamic imports, unshadowed CommonJS loads/resolution calls, and runtime TypeScript import-equals declarations are checked; erased type imports are excluded. A configured restriction list is required."],
9673
9701
  examples: [
9674
- { id: "static-import", title: "Static imports remain the static-import rule's responsibility", outcome: "no-match", files: [{ path: "src/client.ts", source: "import axios from 'axios';" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
9675
- { id: "runtime-load", title: "Do not load a restricted library at runtime", outcome: "match", files: [{ path: "src/client.ts", source: "const client = require('axios');" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
9702
+ { id: "static-import", title: "Static imports remain the static-import rule's responsibility", outcome: "no-match", files: [{ path: "src/client.ts", source: "import axios from 'axios';" }], focusPath: "src/client.ts", expectedCount: 0, public: true, ruleOptions: [{ libraries: [{ id: "LIB101", module: "axios", replacement: "Ky" }] }] },
9703
+ { id: "runtime-load", title: "Do not load a restricted library at runtime", outcome: "match", files: [{ path: "src/client.ts", source: "const client = require('axios');" }], focusPath: "src/client.ts", expectedCount: 1, public: true, ruleOptions: [{ libraries: [{ id: "LIB101", module: "axios", replacement: "Ky" }] }] }
9676
9704
  ]
9677
9705
  };
9678
9706
  function staticModule(node) {
@@ -11816,8 +11844,8 @@ var NO_STORAGE_IN_STATELESS_MODULES_DOCUMENTATION = {
11816
11844
  category: "architecture",
11817
11845
  limitations: ["This opt-in architectural policy requires configured module paths and storage method names. Overloaded `put` requires storage-like receiver evidence; `prepare` requires SQL-shaped literal text or a conventional database receiver for dynamic text. These syntax heuristics do not prove database provenance or identify the system of record."],
11818
11846
  examples: [
11819
- { id: "system-of-record", title: "Read from the system of record", outcome: "no-match", files: [{ path: "src/engineer-digest/post.ts", source: "const issues = await linear.listIssues();" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 0, public: true },
11820
- { id: "private-storage", title: "Do not write private state in a stateless module", outcome: "match", files: [{ path: "src/engineer-digest/post.ts", source: "export {}; await kv.put('digest:last', timestamp);" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 1, public: true }
11847
+ { id: "system-of-record", title: "Read from the system of record", outcome: "no-match", files: [{ path: "src/engineer-digest/post.ts", source: "const issues = await linear.listIssues();" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 0, public: true, ruleOptions: [{ modules: ["[\\\\/]engineer-digest[\\\\/]"] }] },
11848
+ { id: "private-storage", title: "Do not write private state in a stateless module", outcome: "match", files: [{ path: "src/engineer-digest/post.ts", source: "export {}; await kv.put('digest:last', timestamp);" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 1, public: true, ruleOptions: [{ modules: ["[\\\\/]engineer-digest[\\\\/]"] }] }
11821
11849
  ]
11822
11850
  };
11823
11851
  function compile2(patterns) {
@@ -21008,7 +21036,8 @@ var PREFER_ZOD_PARSE_OUTPUT_TYPE_DOCUMENTATION = {
21008
21036
  ],
21009
21037
  focusPath: "src/row.ts",
21010
21038
  expectedCount: 0,
21011
- public: true
21039
+ public: true,
21040
+ installedDependencies: [{ module: "zod", version: "4.6.5" }]
21012
21041
  },
21013
21042
  {
21014
21043
  id: "hand-written-parsed-return",
@@ -21026,7 +21055,8 @@ var PREFER_ZOD_PARSE_OUTPUT_TYPE_DOCUMENTATION = {
21026
21055
  ],
21027
21056
  focusPath: "src/row.ts",
21028
21057
  expectedCount: 1,
21029
- public: true
21058
+ public: true,
21059
+ installedDependencies: [{ module: "zod", version: "4.6.5" }]
21030
21060
  }
21031
21061
  ]
21032
21062
  };
@@ -24308,7 +24338,7 @@ var RULES = {
24308
24338
  };
24309
24339
  var meta = {
24310
24340
  name: "@sarj/eslint-plugin",
24311
- version: "16.4.4"
24341
+ version: "16.4.6"
24312
24342
  };
24313
24343
  var APPLICATION_ONLY_RULES = [];
24314
24344
  var LIBRARY_IMPORT_POLICY = ["error", {
@@ -38,7 +38,7 @@ var import_node_os = require("os");
38
38
  var import_node_path = require("path");
39
39
  var parser = __toESM(require("@typescript-eslint/parser"), 1);
40
40
  var import_eslint = require("eslint");
41
- async function verifyRuleExamples(rule) {
41
+ async function verifyRuleExamples(rule, setup = {}) {
42
42
  const spec = rule.documentation;
43
43
  if (spec === void 0 || !spec.publicExamples.some((item) => item.outcome === "match") || !spec.publicExamples.some((item) => item.outcome === "no-match")) {
44
44
  throw new Error("rule requires documented public matching and non-matching examples");
@@ -51,6 +51,15 @@ async function verifyRuleExamples(rule) {
51
51
  await (0, import_promises.mkdir)((0, import_node_path.dirname)(path), { recursive: true });
52
52
  await (0, import_promises.writeFile)(path, file.source);
53
53
  }
54
+ for (const dependency of example.installedDependencies ?? []) {
55
+ if (setup.installedDependencyRoot === void 0) throw new Error(`${example.id}: explicit installed dependency root is required`);
56
+ const packageFile = fixturePath((0, import_node_path.resolve)(setup.installedDependencyRoot), `node_modules/${dependency.module}/package.json`);
57
+ const metadata = JSON.parse(await (0, import_promises.readFile)(packageFile, "utf8"));
58
+ if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || !("version" in metadata) || metadata.name !== dependency.module || metadata.version !== dependency.version) throw new Error(`${example.id}: installed dependency identity/version mismatch for ${dependency.module}`);
59
+ const destination = fixturePath(root, `node_modules/${dependency.module}`);
60
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(destination), { recursive: true });
61
+ await (0, import_promises.symlink)((0, import_node_path.dirname)(packageFile), destination, process.platform === "win32" ? "junction" : "dir");
62
+ }
54
63
  const tsconfig = (0, import_node_path.join)(root, "tsconfig.json");
55
64
  if (!example.files.some((file) => file.path === "tsconfig.json")) {
56
65
  await (0, import_promises.writeFile)(tsconfig, JSON.stringify({ compilerOptions: {
@@ -68,10 +77,11 @@ async function verifyRuleExamples(rule) {
68
77
  files: ["**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"],
69
78
  languageOptions: { parser, parserOptions: { project: tsconfig, tsconfigRootDir: root } },
70
79
  plugins: { "@sarj": { rules: { [spec.ruleId]: rule } } },
71
- rules: { [ruleId]: ["error", ...rule.defaultOptions ?? []] }
80
+ rules: { [ruleId]: ["error", ...example.ruleOptions ?? rule.defaultOptions ?? []] }
72
81
  };
73
82
  await verifyExample(new import_eslint.Linter({ cwd: root }), config, example, root, ruleId);
74
83
  } finally {
84
+ parser.clearCaches();
75
85
  await (0, import_promises.rm)(root, { recursive: true, force: true });
76
86
  }
77
87
  }
@@ -1,7 +1,9 @@
1
- import { D as DocumentedRule } from './_docs-XCw_6XV9.cjs';
1
+ import { D as DocumentedRule } from './_docs-B9LZ5Out.cjs';
2
2
  import '@typescript-eslint/utils';
3
3
 
4
4
  /** Execute all authored examples, including private regressions, in isolated projects. */
5
- declare function verifyRuleExamples<Options extends readonly unknown[], MessageIds extends string>(rule: DocumentedRule<Options, MessageIds>): Promise<number>;
5
+ declare function verifyRuleExamples<Options extends readonly unknown[], MessageIds extends string>(rule: DocumentedRule<Options, MessageIds>, setup?: {
6
+ readonly installedDependencyRoot?: string;
7
+ }): Promise<number>;
6
8
 
7
9
  export { verifyRuleExamples };
@@ -1,7 +1,9 @@
1
- import { D as DocumentedRule } from './_docs-XCw_6XV9.js';
1
+ import { D as DocumentedRule } from './_docs-B9LZ5Out.js';
2
2
  import '@typescript-eslint/utils';
3
3
 
4
4
  /** Execute all authored examples, including private regressions, in isolated projects. */
5
- declare function verifyRuleExamples<Options extends readonly unknown[], MessageIds extends string>(rule: DocumentedRule<Options, MessageIds>): Promise<number>;
5
+ declare function verifyRuleExamples<Options extends readonly unknown[], MessageIds extends string>(rule: DocumentedRule<Options, MessageIds>, setup?: {
6
+ readonly installedDependencyRoot?: string;
7
+ }): Promise<number>;
6
8
 
7
9
  export { verifyRuleExamples };
@@ -1,10 +1,10 @@
1
1
  // src/verify-rule-examples.ts
2
- import { mkdtemp, mkdir, rm, writeFile } from "fs/promises";
2
+ import { mkdtemp, mkdir, rm, writeFile, readFile, symlink } from "fs/promises";
3
3
  import { tmpdir } from "os";
4
4
  import { dirname, join, resolve, sep } from "path";
5
5
  import * as parser from "@typescript-eslint/parser";
6
6
  import { Linter } from "eslint";
7
- async function verifyRuleExamples(rule) {
7
+ async function verifyRuleExamples(rule, setup = {}) {
8
8
  const spec = rule.documentation;
9
9
  if (spec === void 0 || !spec.publicExamples.some((item) => item.outcome === "match") || !spec.publicExamples.some((item) => item.outcome === "no-match")) {
10
10
  throw new Error("rule requires documented public matching and non-matching examples");
@@ -17,6 +17,15 @@ async function verifyRuleExamples(rule) {
17
17
  await mkdir(dirname(path), { recursive: true });
18
18
  await writeFile(path, file.source);
19
19
  }
20
+ for (const dependency of example.installedDependencies ?? []) {
21
+ if (setup.installedDependencyRoot === void 0) throw new Error(`${example.id}: explicit installed dependency root is required`);
22
+ const packageFile = fixturePath(resolve(setup.installedDependencyRoot), `node_modules/${dependency.module}/package.json`);
23
+ const metadata = JSON.parse(await readFile(packageFile, "utf8"));
24
+ if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || !("version" in metadata) || metadata.name !== dependency.module || metadata.version !== dependency.version) throw new Error(`${example.id}: installed dependency identity/version mismatch for ${dependency.module}`);
25
+ const destination = fixturePath(root, `node_modules/${dependency.module}`);
26
+ await mkdir(dirname(destination), { recursive: true });
27
+ await symlink(dirname(packageFile), destination, process.platform === "win32" ? "junction" : "dir");
28
+ }
20
29
  const tsconfig = join(root, "tsconfig.json");
21
30
  if (!example.files.some((file) => file.path === "tsconfig.json")) {
22
31
  await writeFile(tsconfig, JSON.stringify({ compilerOptions: {
@@ -34,10 +43,11 @@ async function verifyRuleExamples(rule) {
34
43
  files: ["**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"],
35
44
  languageOptions: { parser, parserOptions: { project: tsconfig, tsconfigRootDir: root } },
36
45
  plugins: { "@sarj": { rules: { [spec.ruleId]: rule } } },
37
- rules: { [ruleId]: ["error", ...rule.defaultOptions ?? []] }
46
+ rules: { [ruleId]: ["error", ...example.ruleOptions ?? rule.defaultOptions ?? []] }
38
47
  };
39
48
  await verifyExample(new Linter({ cwd: root }), config, example, root, ruleId);
40
49
  } finally {
50
+ parser.clearCaches();
41
51
  await rm(root, { recursive: true, force: true });
42
52
  }
43
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "16.4.4",
3
+ "version": "16.4.6",
4
4
  "packageManager": "npm@12.1.0",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",