@sarj/eslint-plugin 16.4.6 → 16.4.8

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.cjs CHANGED
@@ -49,6 +49,125 @@ __export(src_exports, {
49
49
  });
50
50
  module.exports = __toCommonJS(src_exports);
51
51
 
52
+ // package.json
53
+ var package_default = {
54
+ name: "@sarj/eslint-plugin",
55
+ version: "16.4.8",
56
+ packageManager: "npm@12.1.0",
57
+ description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
58
+ type: "module",
59
+ main: "./dist/index.cjs",
60
+ module: "./dist/index.js",
61
+ types: "./dist/index.d.ts",
62
+ exports: {
63
+ ".": {
64
+ types: "./dist/index.d.ts",
65
+ import: "./dist/index.js",
66
+ require: "./dist/index.cjs"
67
+ }
68
+ },
69
+ files: [
70
+ "dist",
71
+ "README.md",
72
+ "LICENSE"
73
+ ],
74
+ publishConfig: {
75
+ access: "public"
76
+ },
77
+ sideEffects: false,
78
+ repository: {
79
+ type: "git",
80
+ url: "git+https://github.com/sarj-ai/code-standards.git",
81
+ directory: "packages/typescript"
82
+ },
83
+ homepage: "https://code-standards.sarj.ai/rules/eslint/",
84
+ bugs: {
85
+ url: "https://github.com/sarj-ai/code-standards/issues"
86
+ },
87
+ scripts: {
88
+ build: "tsup",
89
+ dogfood: "npm run build && npm run dogfood:built",
90
+ lint: "eslint . ../../.github/scripts ../standards/src ../standards/eslint.config.mjs --max-warnings 0",
91
+ prepack: "npm run build && npm run verify-package",
92
+ test: "vitest run",
93
+ typecheck: "tsc --noEmit",
94
+ "verify-package": `node -e "const fs=require('node:fs'),p=require('./package.json'),walk=x=>typeof x==='string'?[x]:x&&typeof x==='object'?Object.values(x).flatMap(walk):[];for(const file of new Set([p.main,p.module,p.types,...walk(p.exports)].filter(Boolean).map(x=>x.startsWith('./')?x.slice(2):x))){if(!file.startsWith('dist/'))throw new Error('export must live under dist: '+file);const stat=fs.statSync(file);if(!stat.isFile()||stat.size===0)throw new Error('missing or empty export: '+file)}"`,
95
+ "dogfood:built": `eslint --config eslint.dogfood.config.mjs src tests eslint.config.mjs eslint.dogfood.config.mjs module-constant-naming-options.mjs tsup.config.ts vitest.config.ts --max-warnings 0 && node -e "import('./dist/index.js').then(({default:p}) => console.log('dogfood: '+Object.keys(p.rules).length+' TypeScript rules, 0 diagnostics'))"`
96
+ },
97
+ keywords: [
98
+ "eslint",
99
+ "eslintplugin",
100
+ "eslint-plugin",
101
+ "sarj",
102
+ "react",
103
+ "nextjs",
104
+ "zod"
105
+ ],
106
+ author: "sarj-ai",
107
+ license: "MIT",
108
+ dependencies: {
109
+ "@typescript-eslint/typescript-estree": "^8.70.1",
110
+ "@typescript-eslint/utils": "^8.70.1",
111
+ "jsonc-parser": "^3.3.1"
112
+ },
113
+ devDependencies: {
114
+ "@eslint-community/eslint-plugin-eslint-comments": "^4.8.1",
115
+ "@eslint/compat": "^2.1.1",
116
+ "@eslint/js": "10.0.1",
117
+ "@shadcn/lint": "0.2.0",
118
+ "@types/node": "26.6.2",
119
+ "@typescript-eslint/parser": "8.70.1",
120
+ "@typescript-eslint/rule-tester": "8.70.1",
121
+ "@typescript-eslint/utils": "8.70.1",
122
+ "@typescript/native": "npm:typescript@7.0.2",
123
+ "@vitest/eslint-plugin": "1.6.27",
124
+ eslint: "10.11.0",
125
+ "eslint-node-test": "0.4.0",
126
+ "eslint-plugin-better-tailwindcss": "^4.7.0",
127
+ "eslint-plugin-jest": "29.16.6",
128
+ "eslint-plugin-jsdoc": "64.5.4",
129
+ "eslint-plugin-n": "^18.3.0",
130
+ "eslint-plugin-perfectionist": "5.12.1",
131
+ "eslint-plugin-playwright": "2.12.0",
132
+ "eslint-plugin-promise": "^7.3.0",
133
+ "eslint-plugin-react": "^7.37.5",
134
+ "eslint-plugin-react-hooks": "^7.1.1",
135
+ "eslint-plugin-simple-import-sort": "^14.0.0",
136
+ "eslint-plugin-testing-library": "7.16.2",
137
+ "eslint-plugin-unicorn": "^76.0.0",
138
+ "eslint-plugin-zod": "^4.14.2",
139
+ globals: "15.15.0",
140
+ tsup: "latest",
141
+ typescript: "npm:@typescript/typescript6@6.0.2",
142
+ "typescript-eslint": "^8.70.1",
143
+ vitest: "5.0.1"
144
+ },
145
+ peerDependencies: {
146
+ eslint: ">=9.39.4",
147
+ typescript: ">=5.0.0"
148
+ },
149
+ engines: {
150
+ node: ">=22.22.2"
151
+ },
152
+ allowScripts: {
153
+ "esbuild@0.28.2": true,
154
+ "fsevents@2.3.3": true
155
+ },
156
+ peerDependenciesMeta: {
157
+ typescript: {
158
+ optional: true
159
+ }
160
+ },
161
+ overrides: {
162
+ "brace-expansion@^5.0.0": "5.0.9",
163
+ esbuild: "0.28.2",
164
+ "nanoid@<3.3.18": "3.3.18",
165
+ "eslint-plugin-react": {
166
+ eslint: "$eslint"
167
+ }
168
+ }
169
+ };
170
+
52
171
  // src/rules/require-explicit-contract-implementation.ts
53
172
  var import_utils2 = require("@typescript-eslint/utils");
54
173
  var ts = __toESM(require("typescript"), 1);
@@ -262,6 +381,7 @@ function deepFreeze(value) {
262
381
  }
263
382
 
264
383
  // src/rules/_paths.ts
384
+ var LINE_TERMINATOR_RE = /[\n\r\u2028\u2029]/u;
265
385
  var SCRIPT_FILE_RE = /(?:^|[\\/])scripts[\\/]|\.mjs$/;
266
386
  var STORY_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
267
387
  var STORY_TREE_RE = /(^|\/)stories(?:[_-][^/]*)?\//i;
@@ -311,8 +431,8 @@ function leadingCommentBodies(sourceText) {
311
431
  const bodies = [];
312
432
  let index = 0;
313
433
  if (sourceText.startsWith("#!")) {
314
- const shebangEnd = sourceText.indexOf("\n");
315
- index = shebangEnd === -1 ? sourceText.length : shebangEnd + 1;
434
+ const shebangEnd = commentLineEnd(sourceText, 2);
435
+ index = shebangEnd === sourceText.length ? sourceText.length : shebangEnd + 1;
316
436
  }
317
437
  while (index < sourceText.length) {
318
438
  while (/\s/u.test(sourceText[index] ?? "")) index++;
@@ -326,7 +446,7 @@ function leadingCommentBodies(sourceText) {
326
446
  const end = sourceText.indexOf("*/", index + 2);
327
447
  if (end === -1) break;
328
448
  bodies.push(
329
- ...sourceText.slice(index + 2, end).split("\n").map((line) => line.replace(/^\s*\*?\s?/u, "").trim()).filter((line) => line.length > 0)
449
+ ...sourceText.slice(index + 2, end).split(LINE_TERMINATOR_RE).map((line) => line.replace(/^\s*\*?\s?/u, "").trim()).filter((line) => line.length > 0)
330
450
  );
331
451
  index = end + 2;
332
452
  continue;
@@ -345,8 +465,8 @@ function isScriptFile(filename) {
345
465
  return SCRIPT_FILE_RE.test(filename);
346
466
  }
347
467
  function commentLineEnd(sourceText, start) {
348
- const end = sourceText.indexOf("\n", start);
349
- return end === -1 ? sourceText.length : end;
468
+ const end = sourceText.slice(start).search(LINE_TERMINATOR_RE);
469
+ return end === -1 ? sourceText.length : start + end;
350
470
  }
351
471
 
352
472
  // src/rules/require-explicit-contract-implementation.ts
@@ -4793,6 +4913,41 @@ function staticString(node) {
4793
4913
  if (value.type === import_utils29.AST_NODE_TYPES.Literal && typeof value.value === "string") return value.value;
4794
4914
  return value.type === import_utils29.AST_NODE_TYPES.TemplateLiteral && value.expressions.length === 0 ? value.quasis[0]?.value.cooked ?? null : null;
4795
4915
  }
4916
+ var SCOPED_STRING_VALUES = /* @__PURE__ */ new WeakMap();
4917
+ function scopedStaticString(node, sourceCode) {
4918
+ let value = unwrapExpression(node);
4919
+ if (value.type !== import_utils29.AST_NODE_TYPES.Identifier) return staticString(value);
4920
+ let values = SCOPED_STRING_VALUES.get(sourceCode);
4921
+ if (values === void 0) {
4922
+ values = /* @__PURE__ */ new WeakMap();
4923
+ SCOPED_STRING_VALUES.set(sourceCode, values);
4924
+ }
4925
+ const seen = /* @__PURE__ */ new Set();
4926
+ const pending = [];
4927
+ let result = null;
4928
+ while (value.type === import_utils29.AST_NODE_TYPES.Identifier) {
4929
+ const binding = import_utils29.ASTUtils.findVariable(sourceCode.getScope(value), value.name);
4930
+ if (binding === null || seen.has(binding)) break;
4931
+ if (values.has(binding)) {
4932
+ result = values.get(binding) ?? null;
4933
+ break;
4934
+ }
4935
+ seen.add(binding);
4936
+ pending.push(binding);
4937
+ const definition = binding.defs.length === 1 ? binding.defs[0] : void 0;
4938
+ if (definition?.type !== "Variable" || definition.node.id.type !== import_utils29.AST_NODE_TYPES.Identifier || definition.node.init === null || binding.references.some((reference) => reference.isWrite() && reference.init !== true)) break;
4939
+ value = unwrapExpression(definition.node.init);
4940
+ if (value.type !== import_utils29.AST_NODE_TYPES.Identifier) result = staticString(value);
4941
+ }
4942
+ for (const binding of pending) values.set(binding, result);
4943
+ return result;
4944
+ }
4945
+ function staticPropertyName(node, sourceCode) {
4946
+ const direct = import_utils29.ASTUtils.getPropertyName(node);
4947
+ if (direct !== null || !node.computed) return direct;
4948
+ const key = node.type === import_utils29.AST_NODE_TYPES.MemberExpression ? node.property : node.key;
4949
+ return key.type === import_utils29.AST_NODE_TYPES.Identifier ? scopedStaticString(key, sourceCode) : null;
4950
+ }
4796
4951
 
4797
4952
  // src/rules/no-duplicate-lifecycle-refresh-listeners.ts
4798
4953
  var NO_DUPLICATE_LIFECYCLE_REFRESH_LISTENERS_DOCUMENTATION = {
@@ -5473,7 +5628,9 @@ var NO_ENUM_DOCUMENTATION = {
5473
5628
  focusPath: "src/status.ts",
5474
5629
  expectedCount: 1,
5475
5630
  public: true
5476
- }
5631
+ },
5632
+ { id: "authored-after-comment", scenarioId: "native-header-boundary", title: "Keep executable authored code in scope", outcome: "match", files: [{ path: "src/status.ts", source: '// ordinary header\u2028const marker="auto-generated file"; enum Mode {Start} console.log(Mode.Start, marker);' }], focusPath: "src/status.ts", expectedCount: 1, public: true },
5633
+ { id: "generated-header", scenarioId: "native-header-boundary", title: "Respect a native generated banner", outcome: "no-match", files: [{ path: "src/status.ts", source: '// auto-generated file\u2028const marker="auto-generated file"; enum Mode {Start} console.log(Mode.Start, marker);' }], focusPath: "src/status.ts", expectedCount: 0, public: true }
5477
5634
  ]
5478
5635
  };
5479
5636
  function matchesAnyPattern(filename, patterns) {
@@ -6830,29 +6987,26 @@ var NO_JSON_STRINGIFY_ERROR_DOCUMENTATION = {
6830
6987
  rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
6831
6988
  remediation: "Serialize explicit error fields or use an error-aware serializer.",
6832
6989
  category: "correctness",
6833
- limitations: ["The rule uses stable local catch bindings and unshadowed built-in constructors, not runtime type information. Custom replacers are left to their serializer contract; a catch value is not guaranteed to be an Error."],
6990
+ limitations: ["The rule uses stable local catch bindings and unshadowed built-in constructors, not runtime type information. Custom replacers and explicit toJSON hooks are left to their serializer contract; a catch value is not guaranteed to be an Error. Member values, including cause, do not inherit the base Error's provenance; even an Error-valued member requires explicit value/dataflow proof outside this rule. Known literal __proto__ initializer chains preserve native hook inheritance and own toJSON precedence; computed __proto__ keys remain data. Unknown prototype expressions, hook values/spreads and deferred hook writes conservatively lose default serializer proof. Explicit writes invalidate constructor provenance except a later, separate top-level statement; deferred functions, loops, and unknown property keys conservatively lose constructor proof. Statically owned global/property writes and native Reflect.set/Reflect.defineProperty/Object.defineProperty/Object.defineProperties calls are tracked. Mutator aliases, replaced Reflect/Object methods, external calls, and runtime-only property names are not resolved; uncertain ordering conservatively loses proof."],
6834
6991
  examples: [
6835
6992
  { id: "explicit-error-message", title: "Narrow an unknown catch value before selecting fields", outcome: "no-match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err instanceof Error ? err.message : String(err) }); }" }], focusPath: "src/report.ts", expectedCount: 0, public: true },
6836
- { id: "stringified-error", title: "Do not stringify an Error object", outcome: "match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err }); }" }], focusPath: "src/report.ts", expectedCount: 1, public: true }
6993
+ { id: "stringified-error", title: "Do not stringify an Error object", outcome: "match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err }); }" }], focusPath: "src/report.ts", expectedCount: 1, public: true },
6994
+ { id: "shadowed-error-binding", scenarioId: "binding-provenance", title: "Keep native binding provenance", outcome: "match", files: [{ path: "src/error.ts", source: 'function probe(){try{throw {id:1};}catch(err){if(err instanceof Error)return err.message;{const err=new Error("lost");return JSON.stringify(err);}}}\nconsole.log(probe());' }], focusPath: "src/error.ts", expectedCount: 1, public: true },
6995
+ { id: "escaped-fallback-binding", scenarioId: "binding-provenance", title: "Keep native binding provenance", outcome: "no-match", files: [{ path: "src/error.ts", source: "function probe(){try{throw {id:1};}catch(err){if(err instanceof Error)return err.message;return JSON.stringify(\\u0065rr);}}\nconsole.log(probe());" }], focusPath: "src/error.ts", expectedCount: 0, public: true },
6996
+ { id: "constructor-before-write", scenarioId: "explicit-constructor-write", title: "A later write does not change an already constructed native Error", outcome: "match", files: [{ path: "src/error.js", source: "const value=new Error('lost');Error=class Other{message='kept'};JSON.stringify(value);" }], focusPath: "src/error.js", expectedCount: 1, public: true },
6997
+ { id: "constructor-after-write", scenarioId: "explicit-constructor-write", title: "A replaced constructor does not prove a native Error", outcome: "no-match", files: [{ path: "src/error.js", source: "Error=class Other{message='kept'};const value=new Error();JSON.stringify(value);" }], focusPath: "src/error.js", expectedCount: 0, public: true },
6998
+ { "id": "native-serializer-before-write", "scenarioId": "explicit-serializer-write", "title": "A later write does not change an earlier native serialization", "outcome": "match", "files": [{ "path": "src/error.js", "source": "const result=JSON.stringify(new Error('lost'));JSON.stringify=value=>'kept:'+value.message;" }], "focusPath": "src/error.js", "expectedCount": 1, "public": true },
6999
+ { "id": "native-serializer-after-write", "scenarioId": "explicit-serializer-write", "title": "A replaced serializer owns its error-field contract", "outcome": "no-match", "files": [{ "path": "src/error.js", "source": "JSON.stringify=value=>'kept:'+value.message;JSON.stringify(new Error('lost'));" }], "focusPath": "src/error.js", "expectedCount": 0, "public": true },
7000
+ { "id": "static-serializer-key", "scenarioId": "static-property-provenance", "title": "A constant computed serializer still loses native Error details", "outcome": "match", "files": [{ "path": "src/error.js", "source": "const key='stringify';JSON[key](new Error('lost'));" }], "focusPath": "src/error.js", "expectedCount": 1, "public": true },
7001
+ { "id": "static-error-field", "scenarioId": "static-property-provenance", "title": "A constant message field selects the explicit string escape hatch", "outcome": "no-match", "files": [{ "path": "src/error.js", "source": "const key='message';const error=new Error('lost');JSON.stringify(error[key]);" }], "focusPath": "src/error.js", "expectedCount": 0, "public": true },
7002
+ { "id": "scalar-cause", "scenarioId": "member-value-provenance", "title": "Native Error cause can be a string value", "outcome": "no-match", "files": [{ "path": "src/error.js", "source": "const error=new Error('lost',{cause:'kept'});JSON.stringify(error.cause);" }], "focusPath": "src/error.js", "expectedCount": 0, "public": true },
7003
+ { "id": "whole-error-with-cause", "scenarioId": "member-value-provenance", "title": "Serializing the whole Error still loses its native details", "outcome": "match", "files": [{ "path": "src/error.js", "source": "const error=new Error('lost',{cause:'kept'});JSON.stringify(error);" }], "focusPath": "src/error.js", "expectedCount": 1, "public": true },
7004
+ { "id": "explicit-to-json-hook", "scenarioId": "custom-serializer-contract", "title": "An explicit callable toJSON owns its serialization contract", "outcome": "no-match", "files": [{ "path": "src/error.js", "source": "JSON.stringify({error:new Error('lost'),toJSON(){return {message:'kept'};}});" }], "focusPath": "src/error.js", "expectedCount": 0, "public": true },
7005
+ { "id": "noncallable-to-json", "scenarioId": "custom-serializer-contract", "title": "A noncallable toJSON does not replace native serialization", "outcome": "match", "files": [{ "path": "src/error.js", "source": "JSON.stringify({error:new Error('lost'),toJSON:42});" }], "focusPath": "src/error.js", "expectedCount": 1, "public": true },
7006
+ { "id": "literal-prototype-hook", "scenarioId": "native-prototype-serializer", "title": "A literal prototype hook owns the payload serializer contract", "outcome": "no-match", "files": [{ "path": "src/error.js", "source": "JSON.stringify({error:new Error('lost'),__proto__:{toJSON(){return {message:'kept'};}}});" }], "focusPath": "src/error.js", "expectedCount": 0, "public": true },
7007
+ { "id": "computed-prototype-data", "scenarioId": "native-prototype-serializer", "title": "A computed prototype name remains ordinary serialized data", "outcome": "match", "files": [{ "path": "src/error.js", "source": "JSON.stringify({error:new Error('lost'),['__proto__']:{toJSON(){return {message:'kept'};}}});" }], "focusPath": "src/error.js", "expectedCount": 1, "public": true }
6837
7008
  ]
6838
7009
  };
6839
- var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
6840
- var PAYLOAD_PROPS = /* @__PURE__ */ new Set([
6841
- "data",
6842
- "status",
6843
- "statuscode",
6844
- "statustext",
6845
- "code",
6846
- "issues",
6847
- "details",
6848
- "body",
6849
- "payload",
6850
- "response",
6851
- "info",
6852
- "meta",
6853
- "metadata",
6854
- "context"
6855
- ]);
6856
7010
  var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
6857
7011
  "AggregateError",
6858
7012
  "Error",
@@ -6863,25 +7017,211 @@ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
6863
7017
  "TypeError",
6864
7018
  "URIError"
6865
7019
  ]);
6866
- function identifierIsProvenError(identifier, scope) {
6867
- const variable = import_utils41.ASTUtils.findVariable(scope, identifier.name);
6868
- if (variable === null || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
6869
- const definition = variable.defs[0];
6870
- if (definition?.type === "CatchClause") return true;
6871
- if (definition?.type !== "Variable") return false;
6872
- const initializer = definition.node.init;
6873
- return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && isGlobalIdentifier(initializer.callee.name, scope);
7020
+ var TRACKED_BUILTINS = /* @__PURE__ */ new Set([...BUILTIN_ERROR_CONSTRUCTORS, "JSON", "JSON.stringify"]);
7021
+ function nativeObjectName(expression, sourceCode) {
7022
+ const value = unwrapExpression(expression);
7023
+ const scope = sourceCode.getScope(value);
7024
+ if (value.type === "Identifier" && (value.name === "globalThis" || value.name === "JSON") && isGlobalIdentifier(value.name, scope)) return value.name;
7025
+ if (value.type === "MemberExpression" && nativeObjectName(value.object, sourceCode) === "globalThis" && staticPropertyName(value, sourceCode) === "JSON") return "JSON";
7026
+ return null;
7027
+ }
7028
+ function globalBuiltinName(expression, sourceCode) {
7029
+ const value = unwrapExpression(expression);
7030
+ const scope = sourceCode.getScope(value);
7031
+ if (value.type === "Identifier") return TRACKED_BUILTINS.has(value.name) && isGlobalIdentifier(value.name, scope) ? value.name : null;
7032
+ if (value.type !== "MemberExpression") return null;
7033
+ const receiver = nativeObjectName(value.object, sourceCode);
7034
+ const name = staticPropertyName(value, sourceCode);
7035
+ if (receiver === "JSON") return name === "stringify" ? "JSON.stringify" : null;
7036
+ return receiver === "globalThis" && name !== null && name !== "JSON.stringify" && TRACKED_BUILTINS.has(name) ? name : null;
7037
+ }
7038
+ function writePreservesBuiltin(target, sourceCode) {
7039
+ const assignment = target.parent;
7040
+ if (assignment?.type !== "AssignmentExpression" || assignment.left !== target) return false;
7041
+ const name = globalBuiltinName(target, sourceCode);
7042
+ if (name === null) return false;
7043
+ if (assignment.operator === "||=" || assignment.operator === "??=") return true;
7044
+ return (assignment.operator === "=" || assignment.operator === "&&=") && globalBuiltinName(assignment.right, sourceCode) === name;
7045
+ }
7046
+ function mayBeCallable(value) {
7047
+ return !["Literal", "TemplateLiteral", "ObjectExpression", "ArrayExpression"].includes(value.type);
7048
+ }
7049
+ var BUILTIN_WRITES = /* @__PURE__ */ new WeakMap();
7050
+ function directProgramStatement(node) {
7051
+ let current = node;
7052
+ while (current.parent !== void 0) {
7053
+ if (current.parent.type === "Program") return current;
7054
+ if (["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression", "ClassBody", "StaticBlock", "ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement"].includes(current.parent.type)) return null;
7055
+ current = current.parent;
7056
+ }
7057
+ return null;
7058
+ }
7059
+ function hasUnchangedBuiltin(name, read, sourceCode) {
7060
+ const summary = builtinWrites(sourceCode).get(name);
7061
+ if (summary === void 0) return true;
7062
+ const statement = directProgramStatement(read);
7063
+ return !summary.unknownOrder && statement !== null && statement.range[1] <= summary.earliestStatementStart;
7064
+ }
7065
+ function builtinWrites(sourceCode) {
7066
+ const cached = BUILTIN_WRITES.get(sourceCode);
7067
+ if (cached !== void 0) return cached;
7068
+ const writes = /* @__PURE__ */ new Map();
7069
+ const record = (name, node) => {
7070
+ const statement = directProgramStatement(node);
7071
+ const existing = writes.get(name);
7072
+ writes.set(name, { earliestStatementStart: Math.min(existing?.earliestStatementStart ?? Infinity, statement?.range[0] ?? Infinity), unknownOrder: (existing?.unknownOrder ?? false) || statement === null });
7073
+ };
7074
+ for (const scope of sourceCode.scopeManager?.scopes ?? []) {
7075
+ for (const reference of scope.references) {
7076
+ const name = reference.identifier.name;
7077
+ if (reference.identifier.type === "Identifier" && reference.isWrite() && TRACKED_BUILTINS.has(name) && (reference.resolved === null || reference.resolved.defs.length === 0) && !writePreservesBuiltin(reference.identifier, sourceCode)) record(name, reference.identifier);
7078
+ }
7079
+ }
7080
+ const propertyReceiver = (expression) => {
7081
+ const native = nativeObjectName(expression, sourceCode);
7082
+ if (native !== null) return native;
7083
+ const value = unwrapExpression(expression);
7084
+ if (value.type === "Identifier") return import_utils41.ASTUtils.findVariable(sourceCode.getScope(value), value.name);
7085
+ if (value.type === "MemberExpression" && staticPropertyName(value, sourceCode) === "prototype") {
7086
+ const constructor = globalBuiltinName(value.object, sourceCode);
7087
+ if (constructor !== null && BUILTIN_ERROR_CONSTRUCTORS.has(constructor)) return constructor + ".prototype";
7088
+ }
7089
+ return null;
7090
+ };
7091
+ const propertyWrite = (receiver, name, node) => {
7092
+ if (receiver === "JSON") {
7093
+ if (name === null || name === "stringify") record("JSON.stringify", node);
7094
+ } else if (receiver === "globalThis") {
7095
+ if (name === null) {
7096
+ for (const builtin of TRACKED_BUILTINS) record(builtin, node);
7097
+ } else if (name !== "JSON.stringify" && TRACKED_BUILTINS.has(name)) record(name, node);
7098
+ } else if (name === null || name === "toJSON") record(receiver, node);
7099
+ };
7100
+ const visit = (node) => {
7101
+ if (node.type === "AssignmentExpression") propertyTarget(node.left);
7102
+ else if (node.type === "UpdateExpression") propertyTarget(node.argument);
7103
+ else if (node.type === "ForInStatement" || node.type === "ForOfStatement") propertyTarget(node.left);
7104
+ else if (node.type === "UnaryExpression" && node.operator === "delete") propertyTarget(node.argument);
7105
+ else if (node.type === "CallExpression") mutationCall(node);
7106
+ forEachAstChild(node, sourceCode.visitorKeys, visit);
7107
+ };
7108
+ function propertyTarget(target) {
7109
+ if (target.type === "MemberExpression") {
7110
+ memberPropertyWrite(target);
7111
+ return;
7112
+ }
7113
+ switch (target.type) {
7114
+ case "ObjectPattern":
7115
+ for (const property of target.properties) propertyTarget(property.type === "RestElement" ? property.argument : property.value);
7116
+ return;
7117
+ case "ArrayPattern":
7118
+ for (const element of target.elements) if (element !== null) propertyTarget(element);
7119
+ return;
7120
+ case "AssignmentPattern":
7121
+ propertyTarget(target.left);
7122
+ return;
7123
+ case "RestElement":
7124
+ propertyTarget(target.argument);
7125
+ }
7126
+ }
7127
+ function memberPropertyWrite(target) {
7128
+ const receiver = propertyReceiver(target.object);
7129
+ if (receiver === null || writePreservesBuiltin(target, sourceCode)) return;
7130
+ const name = staticPropertyName(target, sourceCode);
7131
+ if (name === "toJSON") {
7132
+ const assignment = target.parent;
7133
+ if (assignment?.type === "UnaryExpression" && assignment.operator === "delete") return;
7134
+ if (assignment?.type === "AssignmentExpression" && assignment.operator === "=" && !mayBeCallable(assignment.right)) return;
7135
+ }
7136
+ propertyWrite(receiver, name, target);
7137
+ }
7138
+ function mutationCall(node) {
7139
+ const callee = unwrapExpression(node.callee);
7140
+ if (callee.type !== "MemberExpression" || callee.object.type !== "Identifier") return;
7141
+ const owner = callee.object.name;
7142
+ if (!["Reflect", "Object"].includes(owner) || !isGlobalIdentifier(owner, sourceCode.getScope(callee))) return;
7143
+ const method = staticPropertyName(callee, sourceCode);
7144
+ const [target, key, value, receiver] = node.arguments;
7145
+ if (target === void 0 || target.type === "SpreadElement" || key === void 0) return;
7146
+ const object = propertyReceiver(target);
7147
+ if (object === null) return;
7148
+ switch (method) {
7149
+ case "set":
7150
+ if (owner === "Reflect") reflectSet(object, scopedStaticString(key, sourceCode), value, receiver, node);
7151
+ return;
7152
+ case "defineProperty":
7153
+ descriptorWrite(object, scopedStaticString(key, sourceCode), value, node);
7154
+ return;
7155
+ case "defineProperties":
7156
+ if (owner === "Object") descriptorBatch(object, key, node);
7157
+ }
7158
+ }
7159
+ function reflectSet(object, name, value, receiver, node) {
7160
+ if (receiver !== void 0 && receiver.type !== "SpreadElement" && ["ObjectExpression", "ArrayExpression", "Literal"].includes(unwrapExpression(receiver).type)) return;
7161
+ const builtin = builtinPropertyName(object, name);
7162
+ if (value !== void 0 && value.type !== "SpreadElement" && builtin !== null && globalBuiltinName(value, sourceCode) === builtin) return;
7163
+ if (name === "toJSON" && value !== void 0 && !mayBeCallable(value)) return;
7164
+ propertyWrite(object, name, node);
7165
+ }
7166
+ function descriptorBatch(object, descriptors, node) {
7167
+ if (descriptors.type !== "ObjectExpression") {
7168
+ propertyWrite(object, null, node);
7169
+ return;
7170
+ }
7171
+ for (const property of descriptors.properties) {
7172
+ if (property.type !== "Property") {
7173
+ propertyWrite(object, null, node);
7174
+ continue;
7175
+ }
7176
+ descriptorWrite(object, staticPropertyName(property, sourceCode), property.value, node);
7177
+ }
7178
+ }
7179
+ function descriptorWrite(object, name, descriptor, node) {
7180
+ if (descriptor !== void 0 && !descriptorChangesValue(descriptor, builtinPropertyName(object, name))) return;
7181
+ propertyWrite(object, name, node);
7182
+ }
7183
+ function descriptorChangesValue(descriptor, name) {
7184
+ if (descriptor.type !== "ObjectExpression") return true;
7185
+ return descriptor.properties.some((property) => {
7186
+ if (property.type !== "Property") return true;
7187
+ const key = staticPropertyName(property, sourceCode);
7188
+ if (key === "value" && name === "toJSON" && !mayBeCallable(property.value)) return false;
7189
+ if (key === "value" && property.value.type !== "AssignmentPattern" && property.value.type !== "ArrayPattern" && property.value.type !== "ObjectPattern" && property.value.type !== "TSEmptyBodyFunctionExpression") return name === null || globalBuiltinName(property.value, sourceCode) !== name;
7190
+ return key === null || key === "get" || key === "set";
7191
+ });
7192
+ }
7193
+ function builtinPropertyName(object, name) {
7194
+ return object === "JSON" && name === "stringify" ? "JSON.stringify" : name;
7195
+ }
7196
+ visit(sourceCode.ast);
7197
+ BUILTIN_WRITES.set(sourceCode, writes);
7198
+ return writes;
7199
+ }
7200
+ function hasDefaultErrorSerialization(expression, call, sourceCode) {
7201
+ let constructor = null;
7202
+ if (expression.type === "Identifier") {
7203
+ const variable = import_utils41.ASTUtils.findVariable(sourceCode.getScope(expression), expression.name);
7204
+ if (variable !== null && !hasUnchangedBuiltin(variable, call, sourceCode)) return false;
7205
+ const definition = variable?.defs[0];
7206
+ const initializer = definition?.type === "Variable" ? definition.node.init : null;
7207
+ if (initializer?.type === "NewExpression" && initializer.callee.type === "Identifier") constructor = initializer.callee.name;
7208
+ } else if (expression.type === "NewExpression" && expression.callee.type === "Identifier") constructor = expression.callee.name;
7209
+ const families = constructor === null ? BUILTIN_ERROR_CONSTRUCTORS : ["Error", constructor];
7210
+ return [...families].every((name) => hasUnchangedBuiltin(name + ".prototype", call, sourceCode));
7211
+ }
7212
+ function isBuiltinErrorConstructor(identifier, sourceCode) {
7213
+ return sourceCode.scopeManager !== null && BUILTIN_ERROR_CONSTRUCTORS.has(identifier.name) && isGlobalIdentifier(identifier.name, sourceCode.getScope(identifier)) && hasUnchangedBuiltin(identifier.name, identifier, sourceCode);
6874
7214
  }
6875
7215
  function isGlobalIdentifier(name, scope) {
6876
7216
  const binding = import_utils41.ASTUtils.findVariable(scope, name);
6877
7217
  return binding === null || binding.defs.length === 0;
6878
7218
  }
6879
- function positiveErrorSubject(test) {
6880
- return instanceofErrorSubject(test) ?? typeGuardSubject(test);
7219
+ function positiveErrorSubject(test, sourceCode) {
7220
+ return instanceofErrorSubject(test, sourceCode) ?? typeGuardSubject(test);
6881
7221
  }
6882
7222
  var TYPE_GUARD_PATTERN = /^(is|has)[A-Z]/;
6883
- function instanceofErrorSubject(test) {
6884
- if (test.type === "BinaryExpression" && test.operator === "instanceof" && test.right.type === "Identifier" && test.right.name === "Error") {
7223
+ function instanceofErrorSubject(test, sourceCode) {
7224
+ if (test.type === "BinaryExpression" && test.operator === "instanceof" && test.right.type === "Identifier" && test.right.name === "Error" && isBuiltinErrorConstructor(test.right, sourceCode)) {
6885
7225
  return test.left;
6886
7226
  }
6887
7227
  return null;
@@ -6895,36 +7235,34 @@ function typeGuardSubject(test) {
6895
7235
  return null;
6896
7236
  }
6897
7237
  function isNarrowedByEarlyReturn(node, argExpr, sourceCode) {
6898
- const argText = sourceCode.getText(argExpr);
6899
7238
  let current = node.parent;
6900
7239
  while (current) {
6901
7240
  if (current.type === "BlockStatement" || current.type === "Program") {
6902
- if (hasEarlierErrorGuard(current.body, node, argText, sourceCode)) return true;
7241
+ if (hasEarlierErrorGuard(current.body, node, argExpr, sourceCode)) return true;
6903
7242
  }
6904
7243
  current = current.parent;
6905
7244
  }
6906
7245
  return false;
6907
7246
  }
6908
7247
  function isGuardedByInstanceofError(node, argExpr, sourceCode) {
6909
- const argText = sourceCode.getText(argExpr);
6910
- const sameSubject = (subject) => sourceCode.getText(subject) === argText;
7248
+ const sameSubject = (subject) => sameSubjectBinding(subject, argExpr, sourceCode);
6911
7249
  let current = node.parent;
6912
7250
  while (current) {
6913
7251
  if (current.type === "ConditionalExpression") {
6914
- const subject = positiveErrorSubject(current.test);
7252
+ const subject = positiveErrorSubject(current.test, sourceCode);
6915
7253
  if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
6916
7254
  return true;
6917
7255
  }
6918
- const negated = negatedInstanceofErrorSubject(current.test);
7256
+ const negated = negatedInstanceofErrorSubject(current.test, sourceCode);
6919
7257
  if (negated && sameSubject(negated) && nodeWithin(node, current.consequent)) {
6920
7258
  return true;
6921
7259
  }
6922
7260
  } else if (current.type === "IfStatement") {
6923
- const subject = positiveErrorSubject(current.test);
7261
+ const subject = positiveErrorSubject(current.test, sourceCode);
6924
7262
  if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
6925
7263
  return true;
6926
7264
  }
6927
- const negated = negatedInstanceofErrorSubject(current.test);
7265
+ const negated = negatedInstanceofErrorSubject(current.test, sourceCode);
6928
7266
  if (negated && sameSubject(negated) && nodeWithin(node, current.consequent)) {
6929
7267
  return true;
6930
7268
  }
@@ -6933,25 +7271,35 @@ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
6933
7271
  }
6934
7272
  return false;
6935
7273
  }
6936
- function negatedInstanceofErrorSubject(test) {
7274
+ function negatedInstanceofErrorSubject(test, sourceCode) {
6937
7275
  if (test.type === "UnaryExpression" && test.operator === "!") {
6938
- return positiveErrorSubject(test.argument);
7276
+ return positiveErrorSubject(test.argument, sourceCode);
6939
7277
  }
6940
7278
  return null;
6941
7279
  }
7280
+ function sameSubjectBinding(subject, value, sourceCode) {
7281
+ const left = unwrapExpression(subject);
7282
+ const right = unwrapExpression(value);
7283
+ if (left.type === "Identifier" && right.type === "Identifier") {
7284
+ const binding = import_utils41.ASTUtils.findVariable(sourceCode.getScope(left), left.name);
7285
+ return binding !== null && binding === import_utils41.ASTUtils.findVariable(sourceCode.getScope(right), right.name);
7286
+ }
7287
+ return sourceCode.getText(subject) === sourceCode.getText(value);
7288
+ }
6942
7289
  function nodeWithin(node, container) {
6943
7290
  return container !== null && node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
6944
7291
  }
6945
- function isJsonStringify(callee) {
7292
+ function isJsonStringify(callee, sourceCode) {
6946
7293
  if (callee.type !== "MemberExpression") return false;
6947
7294
  const receiver = unwrapExpression(callee.object);
6948
- return callee.type === "MemberExpression" && receiver.type === "Identifier" && receiver.name === "JSON" && import_utils41.ASTUtils.getPropertyName(callee) === "stringify";
7295
+ return callee.type === "MemberExpression" && receiver.type === "Identifier" && receiver.name === "JSON" && staticPropertyName(callee, sourceCode) === "stringify";
6949
7296
  }
6950
- function directLiteralValues(argument) {
7297
+ function directLiteralValues(argument, sourceCode) {
6951
7298
  argument = unwrapExpression(argument);
6952
7299
  if (argument.type === "ObjectExpression") {
7300
+ if (literalHasSerializer(argument, sourceCode)) return [];
6953
7301
  return argument.properties.flatMap((property) => {
6954
- if (property.type !== "Property" || import_utils41.ASTUtils.getPropertyName(property) === null) return [];
7302
+ if (property.type !== "Property" || staticPropertyName(property, sourceCode) === null) return [];
6955
7303
  const value = property.value;
6956
7304
  if (value.type === "AssignmentPattern" || value.type === "ArrayPattern" || value.type === "ObjectPattern" || value.type === "TSEmptyBodyFunctionExpression") {
6957
7305
  return [];
@@ -6966,30 +7314,40 @@ function directLiteralValues(argument) {
6966
7314
  }
6967
7315
  return argument.type === "SpreadElement" ? [] : [argument];
6968
7316
  }
6969
- function expressionSuggestsError(expression, scope) {
7317
+ function literalHasSerializer(argument, sourceCode) {
7318
+ for (const property of argument.properties.toReversed()) {
7319
+ if (property.type !== "Property") return true;
7320
+ const key = staticPropertyName(property, sourceCode);
7321
+ if (key === null) return true;
7322
+ if (key === "toJSON") return mayBeCallable(property.value);
7323
+ }
7324
+ for (const property of argument.properties) {
7325
+ if (property.type !== "Property" || property.computed || property.method || property.shorthand || property.kind !== "init" || staticPropertyName(property, sourceCode) !== "__proto__") continue;
7326
+ const prototype = property.value;
7327
+ return prototype.type === "ObjectExpression" ? literalHasSerializer(prototype, sourceCode) : prototype.type !== "Literal" || "regex" in prototype && prototype.regex !== void 0;
7328
+ }
7329
+ return false;
7330
+ }
7331
+ function expressionSuggestsError(expression, sourceCode) {
6970
7332
  expression = unwrapExpression(expression);
6971
7333
  const unwrappedExpressionCallee = expression.type === "CallExpression" || expression.type === "NewExpression" ? unwrapExpression(expression.callee) : null;
6972
7334
  if (expression.type === "Identifier") {
6973
- return identifierIsProvenError(expression, scope);
7335
+ return identifierIsProvenError(expression, sourceCode);
6974
7336
  }
6975
7337
  if (expression.type === "NewExpression" && unwrappedExpressionCallee?.type === "Identifier") {
6976
- return BUILTIN_ERROR_CONSTRUCTORS.has(unwrappedExpressionCallee.name) && isGlobalIdentifier(unwrappedExpressionCallee.name, scope);
6977
- }
6978
- return expression.type === "MemberExpression" && memberSuggestsError(expression, scope);
6979
- }
6980
- function memberSuggestsError(member, scope) {
6981
- const propName2 = import_utils41.ASTUtils.getPropertyName(member);
6982
- const base = member.object;
6983
- const baseSuggestsError = base.type === "Identifier" && identifierIsProvenError(base, scope);
6984
- if (baseSuggestsError) {
6985
- if (propName2 === null) {
6986
- return true;
6987
- }
6988
- const lowered = propName2.toLowerCase();
6989
- return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
7338
+ return isBuiltinErrorConstructor(unwrappedExpressionCallee, sourceCode);
6990
7339
  }
6991
7340
  return false;
6992
7341
  }
7342
+ function identifierIsProvenError(identifier, sourceCode) {
7343
+ const variable = import_utils41.ASTUtils.findVariable(sourceCode.getScope(identifier), identifier.name);
7344
+ if (variable === null || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
7345
+ const definition = variable.defs[0];
7346
+ if (definition?.type === "CatchClause") return true;
7347
+ if (definition?.type !== "Variable") return false;
7348
+ const initializer = definition.node.init;
7349
+ return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && isBuiltinErrorConstructor(initializer.callee, sourceCode);
7350
+ }
6993
7351
  var no_json_stringify_error_default = createRule({
6994
7352
  name: "no-json-stringify-error",
6995
7353
  documentation: NO_JSON_STRINGIFY_ERROR_DOCUMENTATION,
@@ -7008,7 +7366,7 @@ var no_json_stringify_error_default = createRule({
7008
7366
  return {
7009
7367
  CallExpression(node) {
7010
7368
  const unwrappedNodeCallee = unwrapExpression(node.callee);
7011
- if (!isJsonStringify(unwrappedNodeCallee)) {
7369
+ if (!isJsonStringify(unwrappedNodeCallee, context.sourceCode)) {
7012
7370
  return;
7013
7371
  }
7014
7372
  const firstArg = node.arguments[0];
@@ -7016,11 +7374,11 @@ var no_json_stringify_error_default = createRule({
7016
7374
  return;
7017
7375
  }
7018
7376
  const scope = context.sourceCode.getScope(firstArg);
7019
- if (!isGlobalIdentifier("JSON", scope)) return;
7377
+ if (!isGlobalIdentifier("JSON", scope) || !hasUnchangedBuiltin("JSON", node, context.sourceCode) || !hasUnchangedBuiltin("JSON.stringify", node, context.sourceCode)) return;
7020
7378
  const replacer = node.arguments[1] === void 0 ? void 0 : unwrapExpression(node.arguments[1]);
7021
7379
  if (replacer !== void 0 && !(replacer.type === "Literal" && replacer.value === null) && !(replacer.type === "Identifier" && replacer.name === "undefined" && isGlobalIdentifier("undefined", scope))) return;
7022
- const unsafeValue = directLiteralValues(firstArg).find(
7023
- (value) => expressionSuggestsError(value, scope) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
7380
+ const unsafeValue = directLiteralValues(firstArg, context.sourceCode).find(
7381
+ (value) => expressionSuggestsError(value, context.sourceCode) && hasDefaultErrorSerialization(unwrapExpression(value), node, context.sourceCode) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
7024
7382
  );
7025
7383
  if (unsafeValue === void 0) {
7026
7384
  return;
@@ -7033,14 +7391,14 @@ var no_json_stringify_error_default = createRule({
7033
7391
  };
7034
7392
  }
7035
7393
  });
7036
- function hasEarlierErrorGuard(statements, node, argText, sourceCode) {
7394
+ function hasEarlierErrorGuard(statements, node, argExpr, sourceCode) {
7037
7395
  for (const stmt of statements) {
7038
7396
  if (stmt.range[0] >= node.range[0]) {
7039
7397
  break;
7040
7398
  }
7041
7399
  if (stmt.type === "IfStatement" && stmt.alternate === null && branchTerminates(stmt.consequent)) {
7042
- const subject = positiveErrorSubject(stmt.test);
7043
- if (subject && sourceCode.getText(subject) === argText) {
7400
+ const subject = positiveErrorSubject(stmt.test, sourceCode);
7401
+ if (subject && sameSubjectBinding(subject, argExpr, sourceCode)) {
7044
7402
  return true;
7045
7403
  }
7046
7404
  }
@@ -8840,11 +9198,12 @@ var NO_OFFSET_PAGINATION_DOCUMENTATION = {
8840
9198
  limitations: ["A SELECT/FROM query shape or adjacent LIMIT/OFFSET fragment is required. Isolated OFFSET fragments and test files are excluded; this lexical context does not prove a database execution sink. Performance and concurrent-write behavior depend on indexes, ordering, isolation, and dialect; bounded pages and random page access can justify OFFSET."],
8841
9199
  examples: [
8842
9200
  { id: "keyset-pagination", title: "Page from a stable cursor", outcome: "no-match", files: [{ path: "src/runs.ts", source: "db.prepare(`SELECT id FROM runs WHERE id > ? ORDER BY id LIMIT ?`).all();" }], focusPath: "src/runs.ts", expectedCount: 0, public: true },
8843
- { id: "offset-pagination", title: "Do not page by offset", outcome: "match", files: [{ path: "src/runs.ts", source: "db.query(`SELECT id FROM runs ORDER BY id LIMIT ? OFFSET ?`);" }], focusPath: "src/runs.ts", expectedCount: 1, public: true }
9201
+ { id: "offset-pagination", title: "Do not page by offset", outcome: "match", files: [{ path: "src/runs.ts", source: "db.query(`SELECT id FROM runs ORDER BY id LIMIT ? OFFSET ?`);" }], focusPath: "src/runs.ts", expectedCount: 1, public: true },
9202
+ { id: "decoded-query", scenarioId: "native-decoded-sql", title: "Inspect native-decoded query text", outcome: "match", files: [{ path: "src/page.ts", source: 'db.query("SELECT id FROM runs ORDER BY id LIMIT 1 OFF\\u0053ET 1");' }], focusPath: "src/page.ts", expectedCount: 1, public: true },
9203
+ { id: "decoded-comment-data", scenarioId: "native-decoded-sql", title: "Ignore native-decoded comment data", outcome: "no-match", files: [{ path: "src/page.ts", source: 'db.query("SELECT id FROM runs /* OFF\\x53ET 1 */");' }], focusPath: "src/page.ts", expectedCount: 0, public: true }
8844
9204
  ]
8845
9205
  };
8846
9206
  var OFFSET_PAGINATION = /\bOFFSET\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)/i;
8847
- var OFFSET_GATE = /offset/i;
8848
9207
  var PAGINATION_CONTEXT = /\bSELECT\b[\s\S]*\bFROM\b[\s\S]*\bOFFSET\b|\bLIMIT\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)\s+OFFSET\b/i;
8849
9208
  var no_offset_pagination_default = createRule({
8850
9209
  name: "no-offset-pagination",
@@ -8861,7 +9220,7 @@ var no_offset_pagination_default = createRule({
8861
9220
  },
8862
9221
  defaultOptions: [],
8863
9222
  create(context) {
8864
- if (isTestFile(context.filename) || !OFFSET_GATE.test(context.sourceCode.text)) {
9223
+ if (isTestFile(context.filename)) {
8865
9224
  return {};
8866
9225
  }
8867
9226
  return createSqlListener((sql, node) => {
@@ -10978,7 +11337,9 @@ var NO_SELECT_STAR_DOCUMENTATION = {
10978
11337
  limitations: ["Only statically visible embedded SQL is checked; quoted strings (including PostgreSQL dollar strings), comments, function arguments such as COUNT(*), and stars inside EXISTS are excluded. This is a bounded lexical scan, not a complete SQL parser."],
10979
11338
  examples: [
10980
11339
  { id: "explicit-projection", title: "Select the required columns", outcome: "no-match", files: [{ path: "src/runs.ts", source: "db.prepare(`SELECT id, status FROM runs`).all();" }], focusPath: "src/runs.ts", expectedCount: 0, public: true },
10981
- { id: "wildcard-projection", title: "Do not select every column", outcome: "match", files: [{ path: "src/runs.ts", source: "db.prepare(`SELECT * FROM runs`).all();" }], focusPath: "src/runs.ts", expectedCount: 1, public: true }
11340
+ { id: "wildcard-projection", title: "Do not select every column", outcome: "match", files: [{ path: "src/runs.ts", source: "db.prepare(`SELECT * FROM runs`).all();" }], focusPath: "src/runs.ts", expectedCount: 1, public: true },
11341
+ { id: "decoded-query", scenarioId: "native-decoded-sql", title: "Inspect native-decoded query text", outcome: "match", files: [{ path: "src/page.ts", source: 'db.query("SEL\\u0045CT * FROM runs");' }], focusPath: "src/page.ts", expectedCount: 1, public: true },
11342
+ { id: "decoded-comment-data", scenarioId: "native-decoded-sql", title: "Ignore native-decoded comment data", outcome: "no-match", files: [{ path: "src/page.ts", source: 'db.query("SEL\\x45CT id FROM runs /* SELECT * FROM runs */");' }], focusPath: "src/page.ts", expectedCount: 0, public: true }
10982
11343
  ]
10983
11344
  };
10984
11345
  var QUERY_SHAPE = /\bSELECT\b[\s\S]*?\bFROM\b/i;
@@ -10986,7 +11347,6 @@ var SELECT_KEYWORD = /\bSELECT\b/gi;
10986
11347
  var FROM_KEYWORD = /^FROM\b/i;
10987
11348
  var EXISTS_BEFORE = /\bEXISTS\s*\(\s*$/i;
10988
11349
  var QUALIFIED_PREFIX = /\w\.$/;
10989
- var SELECT_GATE = /select/i;
10990
11350
  function hasRealSelectStar(sql) {
10991
11351
  const selects = [...sql.matchAll(SELECT_KEYWORD)].map((m) => m.index);
10992
11352
  for (let pos = 0; pos < sql.length; pos++) {
@@ -11035,7 +11395,7 @@ var no_select_star_default = createRule({
11035
11395
  },
11036
11396
  defaultOptions: [],
11037
11397
  create(context) {
11038
- if (isTestFile(context.filename) || !SELECT_GATE.test(context.sourceCode.text)) {
11398
+ if (isTestFile(context.filename)) {
11039
11399
  return {};
11040
11400
  }
11041
11401
  return createSqlListener((sql, node) => {
@@ -11492,10 +11852,12 @@ var NO_SILENT_PROMISE_CATCH_DOCUMENTATION = {
11492
11852
  rationale: "A swallowed rejection hides failures and gives callers an indistinguishable fallback value.",
11493
11853
  remediation: "Log, rethrow, or explicitly recover from the rejection; explain intentional teardown suppression.",
11494
11854
  category: "correctness",
11495
- limitations: ["Test files, teardown calls, explanatory comments, non-function handlers, and handlers that consume or report the error are excluded.", "Recognized imported Zod construction chains and their stable local aliases are excluded. Other untyped catch-like APIs are not proven to be Promises."],
11855
+ limitations: ["Test files, teardown calls, explanatory comments, non-function handlers, and handlers that consume or report the error are excluded.", "Recognized imported Zod construction chains and their stable local aliases are excluded. Other untyped catch-like APIs are not proven to be Promises.", "Computed methods resolve literal syntax and stable local string aliases in lexical scope. Function-derived, object-derived and mutated computed keys remain unproven."],
11496
11856
  examples: [
11497
11857
  { id: "reported-rejection", title: "Report the rejection", outcome: "no-match", files: [{ path: "src/load.ts", source: "load().catch((error) => logger.error({ error }, 'load failed'));" }], focusPath: "src/load.ts", expectedCount: 0, public: true },
11498
- { id: "silent-rejection", title: "Do not swallow the rejection", outcome: "match", files: [{ path: "src/load.ts", source: "load().catch(() => null);" }], focusPath: "src/load.ts", expectedCount: 1, public: true }
11858
+ { id: "silent-rejection", title: "Do not swallow the rejection", outcome: "match", files: [{ path: "src/load.ts", source: "load().catch(() => null);" }], focusPath: "src/load.ts", expectedCount: 1, public: true },
11859
+ { id: "stable-computed-catch", scenarioId: "computed-method-scope", title: "Report a rejection through a stable computed method", outcome: "match", files: [{ path: "src/load.ts", source: "const method = 'catch'; Promise.reject(new Error('lost'))[method](() => null);" }], focusPath: "src/load.ts", expectedCount: 1, public: true },
11860
+ { id: "stable-computed-body-parser", scenarioId: "computed-method-scope", title: "Preserve a recognized optional body parse fallback", outcome: "no-match", files: [{ path: "src/load.ts", source: "const method = 'json'; new Response('')[method]().catch(() => null);" }], focusPath: "src/load.ts", expectedCount: 0, public: true }
11499
11861
  ]
11500
11862
  };
11501
11863
  var BODY_PARSE_METHODS = /* @__PURE__ */ new Set([
@@ -11543,9 +11905,11 @@ var ZOD_CHAIN_METHODS = /* @__PURE__ */ new Set([
11543
11905
  "superRefine",
11544
11906
  "transform"
11545
11907
  ]);
11546
- function isBodyParseCall(node) {
11908
+ function isBodyParseCall(node, sourceCode) {
11547
11909
  const unwrappedNodeCallee = node.type === "CallExpression" || node.type === "NewExpression" ? unwrapExpression(node.callee) : null;
11548
- return node.type === import_utils68.AST_NODE_TYPES.CallExpression && node.arguments.length === 0 && unwrappedNodeCallee?.type === import_utils68.AST_NODE_TYPES.MemberExpression && import_utils68.ASTUtils.getPropertyName(unwrappedNodeCallee) !== null && BODY_PARSE_METHODS.has(import_utils68.ASTUtils.getPropertyName(unwrappedNodeCallee) ?? "");
11910
+ if (node.type !== import_utils68.AST_NODE_TYPES.CallExpression || node.arguments.length !== 0 || unwrappedNodeCallee?.type !== import_utils68.AST_NODE_TYPES.MemberExpression) return false;
11911
+ const method = staticPropertyName(unwrappedNodeCallee, sourceCode);
11912
+ return method !== null && BODY_PARSE_METHODS.has(method);
11549
11913
  }
11550
11914
  var TEARDOWN_METHODS = /* @__PURE__ */ new Set([
11551
11915
  "cancel",
@@ -11559,13 +11923,15 @@ var TEARDOWN_METHODS = /* @__PURE__ */ new Set([
11559
11923
  ]);
11560
11924
  var DIRECTIVE_COMMENT_RE = /^\s*(eslint-|@ts-|prettier-ignore|biome-ignore|c8 |v8 |istanbul )/;
11561
11925
  var isExplanatory = (comment) => !DIRECTIVE_COMMENT_RE.test(comment.value);
11562
- function isTeardownCall(node) {
11926
+ function isTeardownCall(node, sourceCode) {
11563
11927
  const unwrappedNodeCallee = node.type === "CallExpression" || node.type === "NewExpression" ? unwrapExpression(node.callee) : null;
11564
- return node.type === import_utils68.AST_NODE_TYPES.CallExpression && unwrappedNodeCallee?.type === import_utils68.AST_NODE_TYPES.MemberExpression && import_utils68.ASTUtils.getPropertyName(unwrappedNodeCallee) !== null && TEARDOWN_METHODS.has(import_utils68.ASTUtils.getPropertyName(unwrappedNodeCallee) ?? "");
11928
+ if (node.type !== import_utils68.AST_NODE_TYPES.CallExpression || unwrappedNodeCallee?.type !== import_utils68.AST_NODE_TYPES.MemberExpression) return false;
11929
+ const method = staticPropertyName(unwrappedNodeCallee, sourceCode);
11930
+ return method !== null && TEARDOWN_METHODS.has(method);
11565
11931
  }
11566
- function isCancelledWebShare(node) {
11932
+ function isCancelledWebShare(node, sourceCode) {
11567
11933
  const unwrappedNodeCallee = node.type === "CallExpression" || node.type === "NewExpression" ? unwrapExpression(node.callee) : null;
11568
- return node.type === import_utils68.AST_NODE_TYPES.CallExpression && unwrappedNodeCallee?.type === import_utils68.AST_NODE_TYPES.MemberExpression && unwrappedNodeCallee.object.type === import_utils68.AST_NODE_TYPES.Identifier && unwrappedNodeCallee.object.name === "navigator" && import_utils68.ASTUtils.getPropertyName(unwrappedNodeCallee) === "share";
11934
+ return node.type === import_utils68.AST_NODE_TYPES.CallExpression && unwrappedNodeCallee?.type === import_utils68.AST_NODE_TYPES.MemberExpression && unwrappedNodeCallee.object.type === import_utils68.AST_NODE_TYPES.Identifier && unwrappedNodeCallee.object.name === "navigator" && staticPropertyName(unwrappedNodeCallee, sourceCode) === "share";
11569
11935
  }
11570
11936
  function isSilentHandler(handler) {
11571
11937
  const body2 = handler.body;
@@ -11629,9 +11995,9 @@ var no_silent_promise_catch_default = createRule({
11629
11995
  const [definition] = binding.defs;
11630
11996
  return binding.defs.length === 1 && definition?.node.type === import_utils68.AST_NODE_TYPES.VariableDeclarator && definition.node.init !== null && isZodSchema(definition.node.init, seen);
11631
11997
  }
11632
- if (node.type !== import_utils68.AST_NODE_TYPES.CallExpression || unwrappedNodeCallee?.type !== import_utils68.AST_NODE_TYPES.MemberExpression || import_utils68.ASTUtils.getPropertyName(unwrappedNodeCallee) === null) return false;
11998
+ if (node.type !== import_utils68.AST_NODE_TYPES.CallExpression || unwrappedNodeCallee?.type !== import_utils68.AST_NODE_TYPES.MemberExpression) return false;
11633
11999
  const { object } = unwrappedNodeCallee;
11634
- const method = import_utils68.ASTUtils.getPropertyName(unwrappedNodeCallee);
12000
+ const method = staticPropertyName(unwrappedNodeCallee, context.sourceCode);
11635
12001
  if (object.type === import_utils68.AST_NODE_TYPES.Identifier && method !== null && ZOD_CONSTRUCTORS.has(method)) {
11636
12002
  const binding = import_utils68.ASTUtils.findVariable(context.sourceCode.getScope(object), object.name);
11637
12003
  if (binding?.defs.some((definition) => {
@@ -11660,23 +12026,23 @@ var no_silent_promise_catch_default = createRule({
11660
12026
  return {
11661
12027
  CallExpression(node) {
11662
12028
  const unwrappedNodeCallee = unwrapExpression(node.callee);
11663
- if (unwrappedNodeCallee.type !== import_utils68.AST_NODE_TYPES.MemberExpression || import_utils68.ASTUtils.getPropertyName(unwrappedNodeCallee) === null) {
12029
+ if (unwrappedNodeCallee.type !== import_utils68.AST_NODE_TYPES.MemberExpression) {
11664
12030
  return;
11665
12031
  }
11666
- const method = import_utils68.ASTUtils.getPropertyName(unwrappedNodeCallee) ?? "";
12032
+ const method = staticPropertyName(unwrappedNodeCallee, context.sourceCode);
11667
12033
  const handlerIndex = method === "catch" ? 0 : method === "then" ? 1 : null;
11668
12034
  if (handlerIndex === null) return;
11669
12035
  if (method === "catch" && isZodSchema(unwrappedNodeCallee.object)) return;
11670
- if (isBodyParseCall(unwrappedNodeCallee.object)) {
12036
+ if (isBodyParseCall(unwrappedNodeCallee.object, context.sourceCode)) {
11671
12037
  return;
11672
12038
  }
11673
- if (isTeardownCall(unwrappedNodeCallee.object)) {
12039
+ if (isTeardownCall(unwrappedNodeCallee.object, context.sourceCode)) {
11674
12040
  return;
11675
12041
  }
11676
- if (isCancelledWebShare(unwrappedNodeCallee.object)) {
12042
+ if (isCancelledWebShare(unwrappedNodeCallee.object, context.sourceCode)) {
11677
12043
  return;
11678
12044
  }
11679
- if (node.parent.type === import_utils68.AST_NODE_TYPES.MemberExpression && node.parent.object === node && import_utils68.ASTUtils.getPropertyName(node.parent) === "then") {
12045
+ if (node.parent.type === import_utils68.AST_NODE_TYPES.MemberExpression && node.parent.object === node && staticPropertyName(node.parent, context.sourceCode) === "then") {
11680
12046
  return;
11681
12047
  }
11682
12048
  const expectedArguments = method === "catch" ? 1 : 2;
@@ -18959,16 +19325,20 @@ var PREFER_SWITCH_FOR_REPEATED_EQUALITY_DOCUMENTATION = {
18959
19325
  ],
18960
19326
  examples: [
18961
19327
  { id: "switch-dispatch", title: "Make finite dispatch explicit", outcome: "no-match", files: [{ path: "src/render.ts", source: "switch (kind) { case 'a': return a(); case 'b': return b(); case 'c': return c(); default: return fallback(); }" }], focusPath: "src/render.ts", expectedCount: 0, public: true },
18962
- { id: "repeated-equality", title: "Avoid repeating the discriminant", outcome: "match", files: [{ path: "src/render.ts", source: "if (kind === 'a') return a(); else if (kind === 'b') return b(); else if (kind === 'c') return c();" }], focusPath: "src/render.ts", expectedCount: 1, public: true }
19328
+ { id: "repeated-equality", title: "Avoid repeating the discriminant", outcome: "match", files: [{ path: "src/render.ts", source: "if (kind === 'a') return a(); else if (kind === 'b') return b(); else if (kind === 'c') return c();" }], focusPath: "src/render.ts", expectedCount: 1, public: true },
19329
+ { id: "escaped-middle", scenarioId: "native-discriminant", title: "Use native identifier identity", outcome: "match", files: [{ path: "src/dispatch.ts", source: `function probe(kind){if(kind==='a')return 1;else if(\\u006bind==='b')return 2;else if(kind==='c')return 3;return 0;}
19330
+ console.log(JSON.stringify(["a","b","c","d"].map(probe)));` }], focusPath: "src/dispatch.ts", expectedCount: 1, public: true },
19331
+ { id: "different-discriminants", scenarioId: "native-discriminant", title: "Use native identifier identity", outcome: "no-match", files: [{ path: "src/dispatch.ts", source: `function probe(kind){const other="other";if(kind==='a')return 1;else if(other==='b')return 2;else if(kind==='c')return 3;return 0;}
19332
+ console.log(JSON.stringify(["a","b","c","d"].map(probe)));` }], focusPath: "src/dispatch.ts", expectedCount: 0, public: true }
18963
19333
  ]
18964
19334
  };
18965
- function discriminantText(sourceCode, test) {
19335
+ function discriminantName(test) {
18966
19336
  if (test.type !== import_utils110.AST_NODE_TYPES.BinaryExpression || test.operator !== "===") return null;
18967
19337
  const leftIsCase = isCaseValue(test.left);
18968
19338
  const rightIsCase = isCaseValue(test.right);
18969
19339
  if (leftIsCase === rightIsCase) return null;
18970
19340
  const discriminant = leftIsCase ? test.right : test.left;
18971
- return discriminant.type === import_utils110.AST_NODE_TYPES.Identifier ? sourceCode.getText(discriminant) : null;
19341
+ return discriminant.type === import_utils110.AST_NODE_TYPES.Identifier ? discriminant.name : null;
18972
19342
  }
18973
19343
  function isCaseValue(node) {
18974
19344
  if (node.type === import_utils110.AST_NODE_TYPES.Literal) return true;
@@ -18992,12 +19362,12 @@ var prefer_switch_for_repeated_equality_default = createRule({
18992
19362
  return {
18993
19363
  IfStatement(node) {
18994
19364
  if (node.parent.type === import_utils110.AST_NODE_TYPES.IfStatement && node.parent.alternate === node) return;
18995
- const first = discriminantText(context.sourceCode, node.test);
19365
+ const first = discriminantName(node.test);
18996
19366
  if (first === null) return;
18997
19367
  let count = 1;
18998
19368
  let current = node.alternate;
18999
19369
  while (current?.type === import_utils110.AST_NODE_TYPES.IfStatement) {
19000
- if (discriminantText(context.sourceCode, current.test) !== first) return;
19370
+ if (discriminantName(current.test) !== first) return;
19001
19371
  count += 1;
19002
19372
  current = current.alternate;
19003
19373
  }
@@ -22190,7 +22560,7 @@ var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
22190
22560
  }
22191
22561
  ]
22192
22562
  };
22193
- function staticPropertyName(property) {
22563
+ function staticPropertyName2(property) {
22194
22564
  if (property.computed) return null;
22195
22565
  if (property.key.type === import_utils122.AST_NODE_TYPES.Identifier) return property.key.name;
22196
22566
  if (property.key.type === import_utils122.AST_NODE_TYPES.Literal && typeof property.key.value === "string") return property.key.value;
@@ -22239,7 +22609,7 @@ var require_use_form_default_values_default = createRule({
22239
22609
  for (const property of node.properties) {
22240
22610
  if (property.type === import_utils122.AST_NODE_TYPES.SpreadElement || property.computed) return "unknown";
22241
22611
  if (property.type !== import_utils122.AST_NODE_TYPES.Property) continue;
22242
- const name = staticPropertyName(property);
22612
+ const name = staticPropertyName2(property);
22243
22613
  if (name === "defaultValues" || name === "values") initialization.set(name, !isDefinitelyUndefined(property.value));
22244
22614
  }
22245
22615
  return [...initialization.values()].some(Boolean) ? "initialized" : "uninitialized";
@@ -22265,7 +22635,7 @@ var require_use_form_default_values_default = createRule({
22265
22635
  for (const property of node.properties) {
22266
22636
  if (property.type === import_utils122.AST_NODE_TYPES.SpreadElement || property.computed) return false;
22267
22637
  if (property.type !== import_utils122.AST_NODE_TYPES.Property) continue;
22268
- const name = staticPropertyName(property);
22638
+ const name = staticPropertyName2(property);
22269
22639
  if (name === "control") control = property.value;
22270
22640
  if (name === "defaultValue") hasDefault = !isDefinitelyUndefined(property.value);
22271
22641
  }
@@ -22281,7 +22651,7 @@ var require_use_form_default_values_default = createRule({
22281
22651
  };
22282
22652
  const trackDestructuredControl = (pattern) => {
22283
22653
  for (const property of pattern.properties) {
22284
- if (property.type !== import_utils122.AST_NODE_TYPES.Property || staticPropertyName(property) !== "control" || property.value.type !== import_utils122.AST_NODE_TYPES.Identifier) continue;
22654
+ if (property.type !== import_utils122.AST_NODE_TYPES.Property || staticPropertyName2(property) !== "control" || property.value.type !== import_utils122.AST_NODE_TYPES.Identifier) continue;
22285
22655
  const variable = bindingOf(property.value);
22286
22656
  if (variable !== null) uninitializedControls.add(variable);
22287
22657
  }
@@ -22725,7 +23095,9 @@ var STORE_INSERT_REQUIRES_ON_CONFLICT_DOCUMENTATION = {
22725
23095
  limitations: ["Only the nearest statically named callable is considered; top-level inserts and anonymous callbacks are excluded. Recognized conflict syntax does not prove idempotence or concurrency safety: WHERE NOT EXISTS can race, and INSERT OR REPLACE can delete an existing row. Review unique constraints and dialect semantics manually."],
22726
23096
  examples: [
22727
23097
  { id: "conflict-safe-insert", title: "Review the conflict policy for a replayed insert", outcome: "no-match", files: [{ path: "src/store.ts", source: "function seed() { db.prepare(`INSERT INTO runs (id) VALUES (?) ON CONFLICT(id) DO NOTHING`).run(); }" }], focusPath: "src/store.ts", expectedCount: 0, public: true },
22728
- { id: "bare-insert", title: "Review a bare insert in a replay-named callable", outcome: "match", files: [{ path: "src/store.ts", source: "function seed() { db.prepare(`INSERT INTO runs (id) VALUES (?)`).run(); }" }], focusPath: "src/store.ts", expectedCount: 1, public: true }
23098
+ { id: "bare-insert", title: "Review a bare insert in a replay-named callable", outcome: "match", files: [{ path: "src/store.ts", source: "function seed() { db.prepare(`INSERT INTO runs (id) VALUES (?)`).run(); }" }], focusPath: "src/store.ts", expectedCount: 1, public: true },
23099
+ { id: "decoded-query", scenarioId: "native-decoded-sql", title: "Inspect native-decoded query text", outcome: "match", files: [{ path: "src/page.ts", source: `function seed(){db.query("INS\\u0045RT INTO runs(id,note) VALUES(4,'new')");}` }], focusPath: "src/page.ts", expectedCount: 1, public: true },
23100
+ { id: "decoded-comment-data", scenarioId: "native-decoded-sql", title: "Ignore native-decoded comment data", outcome: "no-match", files: [{ path: "src/page.ts", source: `function seed(){db.query("SELECT id FROM runs /* INS\\x45RT INTO runs(id,note) VALUES(4,'new') */");}` }], focusPath: "src/page.ts", expectedCount: 0, public: true }
22729
23101
  ]
22730
23102
  };
22731
23103
  var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
@@ -22745,7 +23117,6 @@ function owningCallableName(node) {
22745
23117
  }
22746
23118
  return null;
22747
23119
  }
22748
- var INSERT_GATE = /insert/i;
22749
23120
  var store_insert_requires_on_conflict_default = createRule({
22750
23121
  name: "store-insert-requires-on-conflict",
22751
23122
  documentation: STORE_INSERT_REQUIRES_ON_CONFLICT_DOCUMENTATION,
@@ -22761,7 +23132,7 @@ var store_insert_requires_on_conflict_default = createRule({
22761
23132
  },
22762
23133
  defaultOptions: [],
22763
23134
  create(context) {
22764
- if (isTestFile(context.filename) || !INSERT_GATE.test(context.sourceCode.text)) {
23135
+ if (isTestFile(context.filename)) {
22765
23136
  return {};
22766
23137
  }
22767
23138
  return createSqlListener((sql, node) => {
@@ -24314,7 +24685,7 @@ var RULES = {
24314
24685
  };
24315
24686
  var meta = {
24316
24687
  name: "@sarj/eslint-plugin",
24317
- version: "16.4.6"
24688
+ version: package_default.version
24318
24689
  };
24319
24690
  var APPLICATION_ONLY_RULES = [];
24320
24691
  var LIBRARY_IMPORT_POLICY = ["error", {