@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 +480 -109
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +757 -386
- package/package.json +1 -1
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
|
|
315
|
-
index = shebangEnd ===
|
|
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(
|
|
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.
|
|
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
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
const
|
|
6870
|
-
if (
|
|
6871
|
-
if (
|
|
6872
|
-
|
|
6873
|
-
|
|
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,
|
|
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
|
|
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" &&
|
|
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" ||
|
|
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
|
|
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,
|
|
7335
|
+
return identifierIsProvenError(expression, sourceCode);
|
|
6974
7336
|
}
|
|
6975
7337
|
if (expression.type === "NewExpression" && unwrappedExpressionCallee?.type === "Identifier") {
|
|
6976
|
-
return
|
|
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,
|
|
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,
|
|
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 &&
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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" &&
|
|
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
|
|
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 =
|
|
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
|
|
12029
|
+
if (unwrappedNodeCallee.type !== import_utils68.AST_NODE_TYPES.MemberExpression) {
|
|
11664
12030
|
return;
|
|
11665
12031
|
}
|
|
11666
|
-
const method =
|
|
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 &&
|
|
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
|
|
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 ?
|
|
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 =
|
|
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 (
|
|
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
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
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)
|
|
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:
|
|
24688
|
+
version: package_default.version
|
|
24318
24689
|
};
|
|
24319
24690
|
var APPLICATION_ONLY_RULES = [];
|
|
24320
24691
|
var LIBRARY_IMPORT_POLICY = ["error", {
|