@sarj/eslint-plugin 16.4.6 → 16.4.7
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 +68 -49
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +68 -49
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -262,6 +262,7 @@ function deepFreeze(value) {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
// src/rules/_paths.ts
|
|
265
|
+
var LINE_TERMINATOR_RE = /[\n\r\u2028\u2029]/u;
|
|
265
266
|
var SCRIPT_FILE_RE = /(?:^|[\\/])scripts[\\/]|\.mjs$/;
|
|
266
267
|
var STORY_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
|
|
267
268
|
var STORY_TREE_RE = /(^|\/)stories(?:[_-][^/]*)?\//i;
|
|
@@ -311,8 +312,8 @@ function leadingCommentBodies(sourceText) {
|
|
|
311
312
|
const bodies = [];
|
|
312
313
|
let index = 0;
|
|
313
314
|
if (sourceText.startsWith("#!")) {
|
|
314
|
-
const shebangEnd = sourceText
|
|
315
|
-
index = shebangEnd ===
|
|
315
|
+
const shebangEnd = commentLineEnd(sourceText, 2);
|
|
316
|
+
index = shebangEnd === sourceText.length ? sourceText.length : shebangEnd + 1;
|
|
316
317
|
}
|
|
317
318
|
while (index < sourceText.length) {
|
|
318
319
|
while (/\s/u.test(sourceText[index] ?? "")) index++;
|
|
@@ -326,7 +327,7 @@ function leadingCommentBodies(sourceText) {
|
|
|
326
327
|
const end = sourceText.indexOf("*/", index + 2);
|
|
327
328
|
if (end === -1) break;
|
|
328
329
|
bodies.push(
|
|
329
|
-
...sourceText.slice(index + 2, end).split(
|
|
330
|
+
...sourceText.slice(index + 2, end).split(LINE_TERMINATOR_RE).map((line) => line.replace(/^\s*\*?\s?/u, "").trim()).filter((line) => line.length > 0)
|
|
330
331
|
);
|
|
331
332
|
index = end + 2;
|
|
332
333
|
continue;
|
|
@@ -345,8 +346,8 @@ function isScriptFile(filename) {
|
|
|
345
346
|
return SCRIPT_FILE_RE.test(filename);
|
|
346
347
|
}
|
|
347
348
|
function commentLineEnd(sourceText, start) {
|
|
348
|
-
const end = sourceText.
|
|
349
|
-
return end === -1 ? sourceText.length : end;
|
|
349
|
+
const end = sourceText.slice(start).search(LINE_TERMINATOR_RE);
|
|
350
|
+
return end === -1 ? sourceText.length : start + end;
|
|
350
351
|
}
|
|
351
352
|
|
|
352
353
|
// src/rules/require-explicit-contract-implementation.ts
|
|
@@ -5473,7 +5474,9 @@ var NO_ENUM_DOCUMENTATION = {
|
|
|
5473
5474
|
focusPath: "src/status.ts",
|
|
5474
5475
|
expectedCount: 1,
|
|
5475
5476
|
public: true
|
|
5476
|
-
}
|
|
5477
|
+
},
|
|
5478
|
+
{ 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 },
|
|
5479
|
+
{ 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
5480
|
]
|
|
5478
5481
|
};
|
|
5479
5482
|
function matchesAnyPattern(filename, patterns) {
|
|
@@ -6833,7 +6836,9 @@ var NO_JSON_STRINGIFY_ERROR_DOCUMENTATION = {
|
|
|
6833
6836
|
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."],
|
|
6834
6837
|
examples: [
|
|
6835
6838
|
{ 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 }
|
|
6839
|
+
{ 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 },
|
|
6840
|
+
{ 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 },
|
|
6841
|
+
{ 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 }
|
|
6837
6842
|
]
|
|
6838
6843
|
};
|
|
6839
6844
|
var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
|
|
@@ -6863,25 +6868,25 @@ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
|
6863
6868
|
"TypeError",
|
|
6864
6869
|
"URIError"
|
|
6865
6870
|
]);
|
|
6866
|
-
function identifierIsProvenError(identifier,
|
|
6867
|
-
const variable = import_utils41.ASTUtils.findVariable(
|
|
6871
|
+
function identifierIsProvenError(identifier, sourceCode) {
|
|
6872
|
+
const variable = import_utils41.ASTUtils.findVariable(sourceCode.getScope(identifier), identifier.name);
|
|
6868
6873
|
if (variable === null || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
|
|
6869
6874
|
const definition = variable.defs[0];
|
|
6870
6875
|
if (definition?.type === "CatchClause") return true;
|
|
6871
6876
|
if (definition?.type !== "Variable") return false;
|
|
6872
6877
|
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,
|
|
6878
|
+
return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && isGlobalIdentifier(initializer.callee.name, sourceCode.getScope(initializer.callee));
|
|
6874
6879
|
}
|
|
6875
6880
|
function isGlobalIdentifier(name, scope) {
|
|
6876
6881
|
const binding = import_utils41.ASTUtils.findVariable(scope, name);
|
|
6877
6882
|
return binding === null || binding.defs.length === 0;
|
|
6878
6883
|
}
|
|
6879
|
-
function positiveErrorSubject(test) {
|
|
6880
|
-
return instanceofErrorSubject(test) ?? typeGuardSubject(test);
|
|
6884
|
+
function positiveErrorSubject(test, sourceCode) {
|
|
6885
|
+
return instanceofErrorSubject(test, sourceCode) ?? typeGuardSubject(test);
|
|
6881
6886
|
}
|
|
6882
6887
|
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") {
|
|
6888
|
+
function instanceofErrorSubject(test, sourceCode) {
|
|
6889
|
+
if (test.type === "BinaryExpression" && test.operator === "instanceof" && test.right.type === "Identifier" && test.right.name === "Error" && isGlobalIdentifier("Error", sourceCode.getScope(test.right))) {
|
|
6885
6890
|
return test.left;
|
|
6886
6891
|
}
|
|
6887
6892
|
return null;
|
|
@@ -6895,36 +6900,34 @@ function typeGuardSubject(test) {
|
|
|
6895
6900
|
return null;
|
|
6896
6901
|
}
|
|
6897
6902
|
function isNarrowedByEarlyReturn(node, argExpr, sourceCode) {
|
|
6898
|
-
const argText = sourceCode.getText(argExpr);
|
|
6899
6903
|
let current = node.parent;
|
|
6900
6904
|
while (current) {
|
|
6901
6905
|
if (current.type === "BlockStatement" || current.type === "Program") {
|
|
6902
|
-
if (hasEarlierErrorGuard(current.body, node,
|
|
6906
|
+
if (hasEarlierErrorGuard(current.body, node, argExpr, sourceCode)) return true;
|
|
6903
6907
|
}
|
|
6904
6908
|
current = current.parent;
|
|
6905
6909
|
}
|
|
6906
6910
|
return false;
|
|
6907
6911
|
}
|
|
6908
6912
|
function isGuardedByInstanceofError(node, argExpr, sourceCode) {
|
|
6909
|
-
const
|
|
6910
|
-
const sameSubject = (subject) => sourceCode.getText(subject) === argText;
|
|
6913
|
+
const sameSubject = (subject) => sameSubjectBinding(subject, argExpr, sourceCode);
|
|
6911
6914
|
let current = node.parent;
|
|
6912
6915
|
while (current) {
|
|
6913
6916
|
if (current.type === "ConditionalExpression") {
|
|
6914
|
-
const subject = positiveErrorSubject(current.test);
|
|
6917
|
+
const subject = positiveErrorSubject(current.test, sourceCode);
|
|
6915
6918
|
if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
|
|
6916
6919
|
return true;
|
|
6917
6920
|
}
|
|
6918
|
-
const negated = negatedInstanceofErrorSubject(current.test);
|
|
6921
|
+
const negated = negatedInstanceofErrorSubject(current.test, sourceCode);
|
|
6919
6922
|
if (negated && sameSubject(negated) && nodeWithin(node, current.consequent)) {
|
|
6920
6923
|
return true;
|
|
6921
6924
|
}
|
|
6922
6925
|
} else if (current.type === "IfStatement") {
|
|
6923
|
-
const subject = positiveErrorSubject(current.test);
|
|
6926
|
+
const subject = positiveErrorSubject(current.test, sourceCode);
|
|
6924
6927
|
if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
|
|
6925
6928
|
return true;
|
|
6926
6929
|
}
|
|
6927
|
-
const negated = negatedInstanceofErrorSubject(current.test);
|
|
6930
|
+
const negated = negatedInstanceofErrorSubject(current.test, sourceCode);
|
|
6928
6931
|
if (negated && sameSubject(negated) && nodeWithin(node, current.consequent)) {
|
|
6929
6932
|
return true;
|
|
6930
6933
|
}
|
|
@@ -6933,12 +6936,21 @@ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
|
|
|
6933
6936
|
}
|
|
6934
6937
|
return false;
|
|
6935
6938
|
}
|
|
6936
|
-
function negatedInstanceofErrorSubject(test) {
|
|
6939
|
+
function negatedInstanceofErrorSubject(test, sourceCode) {
|
|
6937
6940
|
if (test.type === "UnaryExpression" && test.operator === "!") {
|
|
6938
|
-
return positiveErrorSubject(test.argument);
|
|
6941
|
+
return positiveErrorSubject(test.argument, sourceCode);
|
|
6939
6942
|
}
|
|
6940
6943
|
return null;
|
|
6941
6944
|
}
|
|
6945
|
+
function sameSubjectBinding(subject, value, sourceCode) {
|
|
6946
|
+
const left = unwrapExpression(subject);
|
|
6947
|
+
const right = unwrapExpression(value);
|
|
6948
|
+
if (left.type === "Identifier" && right.type === "Identifier") {
|
|
6949
|
+
const binding = import_utils41.ASTUtils.findVariable(sourceCode.getScope(left), left.name);
|
|
6950
|
+
return binding !== null && binding === import_utils41.ASTUtils.findVariable(sourceCode.getScope(right), right.name);
|
|
6951
|
+
}
|
|
6952
|
+
return sourceCode.getText(subject) === sourceCode.getText(value);
|
|
6953
|
+
}
|
|
6942
6954
|
function nodeWithin(node, container) {
|
|
6943
6955
|
return container !== null && node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
|
|
6944
6956
|
}
|
|
@@ -6966,21 +6978,21 @@ function directLiteralValues(argument) {
|
|
|
6966
6978
|
}
|
|
6967
6979
|
return argument.type === "SpreadElement" ? [] : [argument];
|
|
6968
6980
|
}
|
|
6969
|
-
function expressionSuggestsError(expression,
|
|
6981
|
+
function expressionSuggestsError(expression, sourceCode) {
|
|
6970
6982
|
expression = unwrapExpression(expression);
|
|
6971
6983
|
const unwrappedExpressionCallee = expression.type === "CallExpression" || expression.type === "NewExpression" ? unwrapExpression(expression.callee) : null;
|
|
6972
6984
|
if (expression.type === "Identifier") {
|
|
6973
|
-
return identifierIsProvenError(expression,
|
|
6985
|
+
return identifierIsProvenError(expression, sourceCode);
|
|
6974
6986
|
}
|
|
6975
6987
|
if (expression.type === "NewExpression" && unwrappedExpressionCallee?.type === "Identifier") {
|
|
6976
|
-
return BUILTIN_ERROR_CONSTRUCTORS.has(unwrappedExpressionCallee.name) && isGlobalIdentifier(unwrappedExpressionCallee.name,
|
|
6988
|
+
return BUILTIN_ERROR_CONSTRUCTORS.has(unwrappedExpressionCallee.name) && isGlobalIdentifier(unwrappedExpressionCallee.name, sourceCode.getScope(unwrappedExpressionCallee));
|
|
6977
6989
|
}
|
|
6978
|
-
return expression.type === "MemberExpression" && memberSuggestsError(expression,
|
|
6990
|
+
return expression.type === "MemberExpression" && memberSuggestsError(expression, sourceCode);
|
|
6979
6991
|
}
|
|
6980
|
-
function memberSuggestsError(member,
|
|
6992
|
+
function memberSuggestsError(member, sourceCode) {
|
|
6981
6993
|
const propName2 = import_utils41.ASTUtils.getPropertyName(member);
|
|
6982
6994
|
const base = member.object;
|
|
6983
|
-
const baseSuggestsError = base.type === "Identifier" && identifierIsProvenError(base,
|
|
6995
|
+
const baseSuggestsError = base.type === "Identifier" && identifierIsProvenError(base, sourceCode);
|
|
6984
6996
|
if (baseSuggestsError) {
|
|
6985
6997
|
if (propName2 === null) {
|
|
6986
6998
|
return true;
|
|
@@ -7020,7 +7032,7 @@ var no_json_stringify_error_default = createRule({
|
|
|
7020
7032
|
const replacer = node.arguments[1] === void 0 ? void 0 : unwrapExpression(node.arguments[1]);
|
|
7021
7033
|
if (replacer !== void 0 && !(replacer.type === "Literal" && replacer.value === null) && !(replacer.type === "Identifier" && replacer.name === "undefined" && isGlobalIdentifier("undefined", scope))) return;
|
|
7022
7034
|
const unsafeValue = directLiteralValues(firstArg).find(
|
|
7023
|
-
(value) => expressionSuggestsError(value,
|
|
7035
|
+
(value) => expressionSuggestsError(value, context.sourceCode) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
|
|
7024
7036
|
);
|
|
7025
7037
|
if (unsafeValue === void 0) {
|
|
7026
7038
|
return;
|
|
@@ -7033,14 +7045,14 @@ var no_json_stringify_error_default = createRule({
|
|
|
7033
7045
|
};
|
|
7034
7046
|
}
|
|
7035
7047
|
});
|
|
7036
|
-
function hasEarlierErrorGuard(statements, node,
|
|
7048
|
+
function hasEarlierErrorGuard(statements, node, argExpr, sourceCode) {
|
|
7037
7049
|
for (const stmt of statements) {
|
|
7038
7050
|
if (stmt.range[0] >= node.range[0]) {
|
|
7039
7051
|
break;
|
|
7040
7052
|
}
|
|
7041
7053
|
if (stmt.type === "IfStatement" && stmt.alternate === null && branchTerminates(stmt.consequent)) {
|
|
7042
|
-
const subject = positiveErrorSubject(stmt.test);
|
|
7043
|
-
if (subject &&
|
|
7054
|
+
const subject = positiveErrorSubject(stmt.test, sourceCode);
|
|
7055
|
+
if (subject && sameSubjectBinding(subject, argExpr, sourceCode)) {
|
|
7044
7056
|
return true;
|
|
7045
7057
|
}
|
|
7046
7058
|
}
|
|
@@ -8840,11 +8852,12 @@ var NO_OFFSET_PAGINATION_DOCUMENTATION = {
|
|
|
8840
8852
|
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
8853
|
examples: [
|
|
8842
8854
|
{ 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 }
|
|
8855
|
+
{ 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 },
|
|
8856
|
+
{ 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 },
|
|
8857
|
+
{ 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
8858
|
]
|
|
8845
8859
|
};
|
|
8846
8860
|
var OFFSET_PAGINATION = /\bOFFSET\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)/i;
|
|
8847
|
-
var OFFSET_GATE = /offset/i;
|
|
8848
8861
|
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
8862
|
var no_offset_pagination_default = createRule({
|
|
8850
8863
|
name: "no-offset-pagination",
|
|
@@ -8861,7 +8874,7 @@ var no_offset_pagination_default = createRule({
|
|
|
8861
8874
|
},
|
|
8862
8875
|
defaultOptions: [],
|
|
8863
8876
|
create(context) {
|
|
8864
|
-
if (isTestFile(context.filename)
|
|
8877
|
+
if (isTestFile(context.filename)) {
|
|
8865
8878
|
return {};
|
|
8866
8879
|
}
|
|
8867
8880
|
return createSqlListener((sql, node) => {
|
|
@@ -10978,7 +10991,9 @@ var NO_SELECT_STAR_DOCUMENTATION = {
|
|
|
10978
10991
|
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
10992
|
examples: [
|
|
10980
10993
|
{ 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 }
|
|
10994
|
+
{ 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 },
|
|
10995
|
+
{ 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 },
|
|
10996
|
+
{ 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
10997
|
]
|
|
10983
10998
|
};
|
|
10984
10999
|
var QUERY_SHAPE = /\bSELECT\b[\s\S]*?\bFROM\b/i;
|
|
@@ -10986,7 +11001,6 @@ var SELECT_KEYWORD = /\bSELECT\b/gi;
|
|
|
10986
11001
|
var FROM_KEYWORD = /^FROM\b/i;
|
|
10987
11002
|
var EXISTS_BEFORE = /\bEXISTS\s*\(\s*$/i;
|
|
10988
11003
|
var QUALIFIED_PREFIX = /\w\.$/;
|
|
10989
|
-
var SELECT_GATE = /select/i;
|
|
10990
11004
|
function hasRealSelectStar(sql) {
|
|
10991
11005
|
const selects = [...sql.matchAll(SELECT_KEYWORD)].map((m) => m.index);
|
|
10992
11006
|
for (let pos = 0; pos < sql.length; pos++) {
|
|
@@ -11035,7 +11049,7 @@ var no_select_star_default = createRule({
|
|
|
11035
11049
|
},
|
|
11036
11050
|
defaultOptions: [],
|
|
11037
11051
|
create(context) {
|
|
11038
|
-
if (isTestFile(context.filename)
|
|
11052
|
+
if (isTestFile(context.filename)) {
|
|
11039
11053
|
return {};
|
|
11040
11054
|
}
|
|
11041
11055
|
return createSqlListener((sql, node) => {
|
|
@@ -18959,16 +18973,20 @@ var PREFER_SWITCH_FOR_REPEATED_EQUALITY_DOCUMENTATION = {
|
|
|
18959
18973
|
],
|
|
18960
18974
|
examples: [
|
|
18961
18975
|
{ 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 }
|
|
18976
|
+
{ 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 },
|
|
18977
|
+
{ 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;}
|
|
18978
|
+
console.log(JSON.stringify(["a","b","c","d"].map(probe)));` }], focusPath: "src/dispatch.ts", expectedCount: 1, public: true },
|
|
18979
|
+
{ 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;}
|
|
18980
|
+
console.log(JSON.stringify(["a","b","c","d"].map(probe)));` }], focusPath: "src/dispatch.ts", expectedCount: 0, public: true }
|
|
18963
18981
|
]
|
|
18964
18982
|
};
|
|
18965
|
-
function
|
|
18983
|
+
function discriminantName(test) {
|
|
18966
18984
|
if (test.type !== import_utils110.AST_NODE_TYPES.BinaryExpression || test.operator !== "===") return null;
|
|
18967
18985
|
const leftIsCase = isCaseValue(test.left);
|
|
18968
18986
|
const rightIsCase = isCaseValue(test.right);
|
|
18969
18987
|
if (leftIsCase === rightIsCase) return null;
|
|
18970
18988
|
const discriminant = leftIsCase ? test.right : test.left;
|
|
18971
|
-
return discriminant.type === import_utils110.AST_NODE_TYPES.Identifier ?
|
|
18989
|
+
return discriminant.type === import_utils110.AST_NODE_TYPES.Identifier ? discriminant.name : null;
|
|
18972
18990
|
}
|
|
18973
18991
|
function isCaseValue(node) {
|
|
18974
18992
|
if (node.type === import_utils110.AST_NODE_TYPES.Literal) return true;
|
|
@@ -18992,12 +19010,12 @@ var prefer_switch_for_repeated_equality_default = createRule({
|
|
|
18992
19010
|
return {
|
|
18993
19011
|
IfStatement(node) {
|
|
18994
19012
|
if (node.parent.type === import_utils110.AST_NODE_TYPES.IfStatement && node.parent.alternate === node) return;
|
|
18995
|
-
const first =
|
|
19013
|
+
const first = discriminantName(node.test);
|
|
18996
19014
|
if (first === null) return;
|
|
18997
19015
|
let count = 1;
|
|
18998
19016
|
let current = node.alternate;
|
|
18999
19017
|
while (current?.type === import_utils110.AST_NODE_TYPES.IfStatement) {
|
|
19000
|
-
if (
|
|
19018
|
+
if (discriminantName(current.test) !== first) return;
|
|
19001
19019
|
count += 1;
|
|
19002
19020
|
current = current.alternate;
|
|
19003
19021
|
}
|
|
@@ -22725,7 +22743,9 @@ var STORE_INSERT_REQUIRES_ON_CONFLICT_DOCUMENTATION = {
|
|
|
22725
22743
|
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
22744
|
examples: [
|
|
22727
22745
|
{ 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 }
|
|
22746
|
+
{ 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 },
|
|
22747
|
+
{ 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 },
|
|
22748
|
+
{ 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
22749
|
]
|
|
22730
22750
|
};
|
|
22731
22751
|
var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
|
|
@@ -22745,7 +22765,6 @@ function owningCallableName(node) {
|
|
|
22745
22765
|
}
|
|
22746
22766
|
return null;
|
|
22747
22767
|
}
|
|
22748
|
-
var INSERT_GATE = /insert/i;
|
|
22749
22768
|
var store_insert_requires_on_conflict_default = createRule({
|
|
22750
22769
|
name: "store-insert-requires-on-conflict",
|
|
22751
22770
|
documentation: STORE_INSERT_REQUIRES_ON_CONFLICT_DOCUMENTATION,
|
|
@@ -22761,7 +22780,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
22761
22780
|
},
|
|
22762
22781
|
defaultOptions: [],
|
|
22763
22782
|
create(context) {
|
|
22764
|
-
if (isTestFile(context.filename)
|
|
22783
|
+
if (isTestFile(context.filename)) {
|
|
22765
22784
|
return {};
|
|
22766
22785
|
}
|
|
22767
22786
|
return createSqlListener((sql, node) => {
|
|
@@ -24314,7 +24333,7 @@ var RULES = {
|
|
|
24314
24333
|
};
|
|
24315
24334
|
var meta = {
|
|
24316
24335
|
name: "@sarj/eslint-plugin",
|
|
24317
|
-
version: "16.4.
|
|
24336
|
+
version: "16.4.7"
|
|
24318
24337
|
};
|
|
24319
24338
|
var APPLICATION_ONLY_RULES = [];
|
|
24320
24339
|
var LIBRARY_IMPORT_POLICY = ["error", {
|
package/dist/index.d.cts
CHANGED
|
@@ -927,7 +927,7 @@ type FlatPreset = {
|
|
|
927
927
|
declare const PLUGIN: {
|
|
928
928
|
readonly meta: {
|
|
929
929
|
readonly name: "@sarj/eslint-plugin";
|
|
930
|
-
readonly version: "16.4.
|
|
930
|
+
readonly version: "16.4.7";
|
|
931
931
|
};
|
|
932
932
|
readonly rules: {
|
|
933
933
|
readonly "require-explicit-contract-implementation": DocumentedRule<readonly [], "declareActualContract">;
|
package/dist/index.d.ts
CHANGED
|
@@ -927,7 +927,7 @@ type FlatPreset = {
|
|
|
927
927
|
declare const PLUGIN: {
|
|
928
928
|
readonly meta: {
|
|
929
929
|
readonly name: "@sarj/eslint-plugin";
|
|
930
|
-
readonly version: "16.4.
|
|
930
|
+
readonly version: "16.4.7";
|
|
931
931
|
};
|
|
932
932
|
readonly rules: {
|
|
933
933
|
readonly "require-explicit-contract-implementation": DocumentedRule<readonly [], "declareActualContract">;
|
package/dist/index.js
CHANGED
|
@@ -211,6 +211,7 @@ function deepFreeze(value) {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
// src/rules/_paths.ts
|
|
214
|
+
var LINE_TERMINATOR_RE = /[\n\r\u2028\u2029]/u;
|
|
214
215
|
var SCRIPT_FILE_RE = /(?:^|[\\/])scripts[\\/]|\.mjs$/;
|
|
215
216
|
var STORY_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
|
|
216
217
|
var STORY_TREE_RE = /(^|\/)stories(?:[_-][^/]*)?\//i;
|
|
@@ -260,8 +261,8 @@ function leadingCommentBodies(sourceText) {
|
|
|
260
261
|
const bodies = [];
|
|
261
262
|
let index = 0;
|
|
262
263
|
if (sourceText.startsWith("#!")) {
|
|
263
|
-
const shebangEnd = sourceText
|
|
264
|
-
index = shebangEnd ===
|
|
264
|
+
const shebangEnd = commentLineEnd(sourceText, 2);
|
|
265
|
+
index = shebangEnd === sourceText.length ? sourceText.length : shebangEnd + 1;
|
|
265
266
|
}
|
|
266
267
|
while (index < sourceText.length) {
|
|
267
268
|
while (/\s/u.test(sourceText[index] ?? "")) index++;
|
|
@@ -275,7 +276,7 @@ function leadingCommentBodies(sourceText) {
|
|
|
275
276
|
const end = sourceText.indexOf("*/", index + 2);
|
|
276
277
|
if (end === -1) break;
|
|
277
278
|
bodies.push(
|
|
278
|
-
...sourceText.slice(index + 2, end).split(
|
|
279
|
+
...sourceText.slice(index + 2, end).split(LINE_TERMINATOR_RE).map((line) => line.replace(/^\s*\*?\s?/u, "").trim()).filter((line) => line.length > 0)
|
|
279
280
|
);
|
|
280
281
|
index = end + 2;
|
|
281
282
|
continue;
|
|
@@ -294,8 +295,8 @@ function isScriptFile(filename) {
|
|
|
294
295
|
return SCRIPT_FILE_RE.test(filename);
|
|
295
296
|
}
|
|
296
297
|
function commentLineEnd(sourceText, start) {
|
|
297
|
-
const end = sourceText.
|
|
298
|
-
return end === -1 ? sourceText.length : end;
|
|
298
|
+
const end = sourceText.slice(start).search(LINE_TERMINATOR_RE);
|
|
299
|
+
return end === -1 ? sourceText.length : start + end;
|
|
299
300
|
}
|
|
300
301
|
|
|
301
302
|
// src/rules/require-explicit-contract-implementation.ts
|
|
@@ -5437,7 +5438,9 @@ var NO_ENUM_DOCUMENTATION = {
|
|
|
5437
5438
|
focusPath: "src/status.ts",
|
|
5438
5439
|
expectedCount: 1,
|
|
5439
5440
|
public: true
|
|
5440
|
-
}
|
|
5441
|
+
},
|
|
5442
|
+
{ 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 },
|
|
5443
|
+
{ 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 }
|
|
5441
5444
|
]
|
|
5442
5445
|
};
|
|
5443
5446
|
function matchesAnyPattern(filename, patterns) {
|
|
@@ -6800,7 +6803,9 @@ var NO_JSON_STRINGIFY_ERROR_DOCUMENTATION = {
|
|
|
6800
6803
|
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."],
|
|
6801
6804
|
examples: [
|
|
6802
6805
|
{ 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 },
|
|
6803
|
-
{ 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 }
|
|
6806
|
+
{ 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 },
|
|
6807
|
+
{ 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 },
|
|
6808
|
+
{ 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 }
|
|
6804
6809
|
]
|
|
6805
6810
|
};
|
|
6806
6811
|
var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
|
|
@@ -6830,25 +6835,25 @@ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
|
6830
6835
|
"TypeError",
|
|
6831
6836
|
"URIError"
|
|
6832
6837
|
]);
|
|
6833
|
-
function identifierIsProvenError(identifier,
|
|
6834
|
-
const variable = ASTUtils14.findVariable(
|
|
6838
|
+
function identifierIsProvenError(identifier, sourceCode) {
|
|
6839
|
+
const variable = ASTUtils14.findVariable(sourceCode.getScope(identifier), identifier.name);
|
|
6835
6840
|
if (variable === null || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
|
|
6836
6841
|
const definition = variable.defs[0];
|
|
6837
6842
|
if (definition?.type === "CatchClause") return true;
|
|
6838
6843
|
if (definition?.type !== "Variable") return false;
|
|
6839
6844
|
const initializer = definition.node.init;
|
|
6840
|
-
return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && isGlobalIdentifier(initializer.callee.name,
|
|
6845
|
+
return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && isGlobalIdentifier(initializer.callee.name, sourceCode.getScope(initializer.callee));
|
|
6841
6846
|
}
|
|
6842
6847
|
function isGlobalIdentifier(name, scope) {
|
|
6843
6848
|
const binding = ASTUtils14.findVariable(scope, name);
|
|
6844
6849
|
return binding === null || binding.defs.length === 0;
|
|
6845
6850
|
}
|
|
6846
|
-
function positiveErrorSubject(test) {
|
|
6847
|
-
return instanceofErrorSubject(test) ?? typeGuardSubject(test);
|
|
6851
|
+
function positiveErrorSubject(test, sourceCode) {
|
|
6852
|
+
return instanceofErrorSubject(test, sourceCode) ?? typeGuardSubject(test);
|
|
6848
6853
|
}
|
|
6849
6854
|
var TYPE_GUARD_PATTERN = /^(is|has)[A-Z]/;
|
|
6850
|
-
function instanceofErrorSubject(test) {
|
|
6851
|
-
if (test.type === "BinaryExpression" && test.operator === "instanceof" && test.right.type === "Identifier" && test.right.name === "Error") {
|
|
6855
|
+
function instanceofErrorSubject(test, sourceCode) {
|
|
6856
|
+
if (test.type === "BinaryExpression" && test.operator === "instanceof" && test.right.type === "Identifier" && test.right.name === "Error" && isGlobalIdentifier("Error", sourceCode.getScope(test.right))) {
|
|
6852
6857
|
return test.left;
|
|
6853
6858
|
}
|
|
6854
6859
|
return null;
|
|
@@ -6862,36 +6867,34 @@ function typeGuardSubject(test) {
|
|
|
6862
6867
|
return null;
|
|
6863
6868
|
}
|
|
6864
6869
|
function isNarrowedByEarlyReturn(node, argExpr, sourceCode) {
|
|
6865
|
-
const argText = sourceCode.getText(argExpr);
|
|
6866
6870
|
let current = node.parent;
|
|
6867
6871
|
while (current) {
|
|
6868
6872
|
if (current.type === "BlockStatement" || current.type === "Program") {
|
|
6869
|
-
if (hasEarlierErrorGuard(current.body, node,
|
|
6873
|
+
if (hasEarlierErrorGuard(current.body, node, argExpr, sourceCode)) return true;
|
|
6870
6874
|
}
|
|
6871
6875
|
current = current.parent;
|
|
6872
6876
|
}
|
|
6873
6877
|
return false;
|
|
6874
6878
|
}
|
|
6875
6879
|
function isGuardedByInstanceofError(node, argExpr, sourceCode) {
|
|
6876
|
-
const
|
|
6877
|
-
const sameSubject = (subject) => sourceCode.getText(subject) === argText;
|
|
6880
|
+
const sameSubject = (subject) => sameSubjectBinding(subject, argExpr, sourceCode);
|
|
6878
6881
|
let current = node.parent;
|
|
6879
6882
|
while (current) {
|
|
6880
6883
|
if (current.type === "ConditionalExpression") {
|
|
6881
|
-
const subject = positiveErrorSubject(current.test);
|
|
6884
|
+
const subject = positiveErrorSubject(current.test, sourceCode);
|
|
6882
6885
|
if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
|
|
6883
6886
|
return true;
|
|
6884
6887
|
}
|
|
6885
|
-
const negated = negatedInstanceofErrorSubject(current.test);
|
|
6888
|
+
const negated = negatedInstanceofErrorSubject(current.test, sourceCode);
|
|
6886
6889
|
if (negated && sameSubject(negated) && nodeWithin(node, current.consequent)) {
|
|
6887
6890
|
return true;
|
|
6888
6891
|
}
|
|
6889
6892
|
} else if (current.type === "IfStatement") {
|
|
6890
|
-
const subject = positiveErrorSubject(current.test);
|
|
6893
|
+
const subject = positiveErrorSubject(current.test, sourceCode);
|
|
6891
6894
|
if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
|
|
6892
6895
|
return true;
|
|
6893
6896
|
}
|
|
6894
|
-
const negated = negatedInstanceofErrorSubject(current.test);
|
|
6897
|
+
const negated = negatedInstanceofErrorSubject(current.test, sourceCode);
|
|
6895
6898
|
if (negated && sameSubject(negated) && nodeWithin(node, current.consequent)) {
|
|
6896
6899
|
return true;
|
|
6897
6900
|
}
|
|
@@ -6900,12 +6903,21 @@ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
|
|
|
6900
6903
|
}
|
|
6901
6904
|
return false;
|
|
6902
6905
|
}
|
|
6903
|
-
function negatedInstanceofErrorSubject(test) {
|
|
6906
|
+
function negatedInstanceofErrorSubject(test, sourceCode) {
|
|
6904
6907
|
if (test.type === "UnaryExpression" && test.operator === "!") {
|
|
6905
|
-
return positiveErrorSubject(test.argument);
|
|
6908
|
+
return positiveErrorSubject(test.argument, sourceCode);
|
|
6906
6909
|
}
|
|
6907
6910
|
return null;
|
|
6908
6911
|
}
|
|
6912
|
+
function sameSubjectBinding(subject, value, sourceCode) {
|
|
6913
|
+
const left = unwrapExpression(subject);
|
|
6914
|
+
const right = unwrapExpression(value);
|
|
6915
|
+
if (left.type === "Identifier" && right.type === "Identifier") {
|
|
6916
|
+
const binding = ASTUtils14.findVariable(sourceCode.getScope(left), left.name);
|
|
6917
|
+
return binding !== null && binding === ASTUtils14.findVariable(sourceCode.getScope(right), right.name);
|
|
6918
|
+
}
|
|
6919
|
+
return sourceCode.getText(subject) === sourceCode.getText(value);
|
|
6920
|
+
}
|
|
6909
6921
|
function nodeWithin(node, container) {
|
|
6910
6922
|
return container !== null && node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
|
|
6911
6923
|
}
|
|
@@ -6933,21 +6945,21 @@ function directLiteralValues(argument) {
|
|
|
6933
6945
|
}
|
|
6934
6946
|
return argument.type === "SpreadElement" ? [] : [argument];
|
|
6935
6947
|
}
|
|
6936
|
-
function expressionSuggestsError(expression,
|
|
6948
|
+
function expressionSuggestsError(expression, sourceCode) {
|
|
6937
6949
|
expression = unwrapExpression(expression);
|
|
6938
6950
|
const unwrappedExpressionCallee = expression.type === "CallExpression" || expression.type === "NewExpression" ? unwrapExpression(expression.callee) : null;
|
|
6939
6951
|
if (expression.type === "Identifier") {
|
|
6940
|
-
return identifierIsProvenError(expression,
|
|
6952
|
+
return identifierIsProvenError(expression, sourceCode);
|
|
6941
6953
|
}
|
|
6942
6954
|
if (expression.type === "NewExpression" && unwrappedExpressionCallee?.type === "Identifier") {
|
|
6943
|
-
return BUILTIN_ERROR_CONSTRUCTORS.has(unwrappedExpressionCallee.name) && isGlobalIdentifier(unwrappedExpressionCallee.name,
|
|
6955
|
+
return BUILTIN_ERROR_CONSTRUCTORS.has(unwrappedExpressionCallee.name) && isGlobalIdentifier(unwrappedExpressionCallee.name, sourceCode.getScope(unwrappedExpressionCallee));
|
|
6944
6956
|
}
|
|
6945
|
-
return expression.type === "MemberExpression" && memberSuggestsError(expression,
|
|
6957
|
+
return expression.type === "MemberExpression" && memberSuggestsError(expression, sourceCode);
|
|
6946
6958
|
}
|
|
6947
|
-
function memberSuggestsError(member,
|
|
6959
|
+
function memberSuggestsError(member, sourceCode) {
|
|
6948
6960
|
const propName2 = ASTUtils14.getPropertyName(member);
|
|
6949
6961
|
const base = member.object;
|
|
6950
|
-
const baseSuggestsError = base.type === "Identifier" && identifierIsProvenError(base,
|
|
6962
|
+
const baseSuggestsError = base.type === "Identifier" && identifierIsProvenError(base, sourceCode);
|
|
6951
6963
|
if (baseSuggestsError) {
|
|
6952
6964
|
if (propName2 === null) {
|
|
6953
6965
|
return true;
|
|
@@ -6987,7 +6999,7 @@ var no_json_stringify_error_default = createRule({
|
|
|
6987
6999
|
const replacer = node.arguments[1] === void 0 ? void 0 : unwrapExpression(node.arguments[1]);
|
|
6988
7000
|
if (replacer !== void 0 && !(replacer.type === "Literal" && replacer.value === null) && !(replacer.type === "Identifier" && replacer.name === "undefined" && isGlobalIdentifier("undefined", scope))) return;
|
|
6989
7001
|
const unsafeValue = directLiteralValues(firstArg).find(
|
|
6990
|
-
(value) => expressionSuggestsError(value,
|
|
7002
|
+
(value) => expressionSuggestsError(value, context.sourceCode) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
|
|
6991
7003
|
);
|
|
6992
7004
|
if (unsafeValue === void 0) {
|
|
6993
7005
|
return;
|
|
@@ -7000,14 +7012,14 @@ var no_json_stringify_error_default = createRule({
|
|
|
7000
7012
|
};
|
|
7001
7013
|
}
|
|
7002
7014
|
});
|
|
7003
|
-
function hasEarlierErrorGuard(statements, node,
|
|
7015
|
+
function hasEarlierErrorGuard(statements, node, argExpr, sourceCode) {
|
|
7004
7016
|
for (const stmt of statements) {
|
|
7005
7017
|
if (stmt.range[0] >= node.range[0]) {
|
|
7006
7018
|
break;
|
|
7007
7019
|
}
|
|
7008
7020
|
if (stmt.type === "IfStatement" && stmt.alternate === null && branchTerminates(stmt.consequent)) {
|
|
7009
|
-
const subject = positiveErrorSubject(stmt.test);
|
|
7010
|
-
if (subject &&
|
|
7021
|
+
const subject = positiveErrorSubject(stmt.test, sourceCode);
|
|
7022
|
+
if (subject && sameSubjectBinding(subject, argExpr, sourceCode)) {
|
|
7011
7023
|
return true;
|
|
7012
7024
|
}
|
|
7013
7025
|
}
|
|
@@ -8824,11 +8836,12 @@ var NO_OFFSET_PAGINATION_DOCUMENTATION = {
|
|
|
8824
8836
|
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."],
|
|
8825
8837
|
examples: [
|
|
8826
8838
|
{ 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 },
|
|
8827
|
-
{ 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 }
|
|
8839
|
+
{ 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 },
|
|
8840
|
+
{ 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 },
|
|
8841
|
+
{ 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 }
|
|
8828
8842
|
]
|
|
8829
8843
|
};
|
|
8830
8844
|
var OFFSET_PAGINATION = /\bOFFSET\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)/i;
|
|
8831
|
-
var OFFSET_GATE = /offset/i;
|
|
8832
8845
|
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;
|
|
8833
8846
|
var no_offset_pagination_default = createRule({
|
|
8834
8847
|
name: "no-offset-pagination",
|
|
@@ -8845,7 +8858,7 @@ var no_offset_pagination_default = createRule({
|
|
|
8845
8858
|
},
|
|
8846
8859
|
defaultOptions: [],
|
|
8847
8860
|
create(context) {
|
|
8848
|
-
if (isTestFile(context.filename)
|
|
8861
|
+
if (isTestFile(context.filename)) {
|
|
8849
8862
|
return {};
|
|
8850
8863
|
}
|
|
8851
8864
|
return createSqlListener((sql, node) => {
|
|
@@ -10962,7 +10975,9 @@ var NO_SELECT_STAR_DOCUMENTATION = {
|
|
|
10962
10975
|
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."],
|
|
10963
10976
|
examples: [
|
|
10964
10977
|
{ 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 },
|
|
10965
|
-
{ 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 }
|
|
10978
|
+
{ 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 },
|
|
10979
|
+
{ 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 },
|
|
10980
|
+
{ 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 }
|
|
10966
10981
|
]
|
|
10967
10982
|
};
|
|
10968
10983
|
var QUERY_SHAPE = /\bSELECT\b[\s\S]*?\bFROM\b/i;
|
|
@@ -10970,7 +10985,6 @@ var SELECT_KEYWORD = /\bSELECT\b/gi;
|
|
|
10970
10985
|
var FROM_KEYWORD = /^FROM\b/i;
|
|
10971
10986
|
var EXISTS_BEFORE = /\bEXISTS\s*\(\s*$/i;
|
|
10972
10987
|
var QUALIFIED_PREFIX = /\w\.$/;
|
|
10973
|
-
var SELECT_GATE = /select/i;
|
|
10974
10988
|
function hasRealSelectStar(sql) {
|
|
10975
10989
|
const selects = [...sql.matchAll(SELECT_KEYWORD)].map((m) => m.index);
|
|
10976
10990
|
for (let pos = 0; pos < sql.length; pos++) {
|
|
@@ -11019,7 +11033,7 @@ var no_select_star_default = createRule({
|
|
|
11019
11033
|
},
|
|
11020
11034
|
defaultOptions: [],
|
|
11021
11035
|
create(context) {
|
|
11022
|
-
if (isTestFile(context.filename)
|
|
11036
|
+
if (isTestFile(context.filename)) {
|
|
11023
11037
|
return {};
|
|
11024
11038
|
}
|
|
11025
11039
|
return createSqlListener((sql, node) => {
|
|
@@ -18970,16 +18984,20 @@ var PREFER_SWITCH_FOR_REPEATED_EQUALITY_DOCUMENTATION = {
|
|
|
18970
18984
|
],
|
|
18971
18985
|
examples: [
|
|
18972
18986
|
{ 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 },
|
|
18973
|
-
{ 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 }
|
|
18987
|
+
{ 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 },
|
|
18988
|
+
{ 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;}
|
|
18989
|
+
console.log(JSON.stringify(["a","b","c","d"].map(probe)));` }], focusPath: "src/dispatch.ts", expectedCount: 1, public: true },
|
|
18990
|
+
{ 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;}
|
|
18991
|
+
console.log(JSON.stringify(["a","b","c","d"].map(probe)));` }], focusPath: "src/dispatch.ts", expectedCount: 0, public: true }
|
|
18974
18992
|
]
|
|
18975
18993
|
};
|
|
18976
|
-
function
|
|
18994
|
+
function discriminantName(test) {
|
|
18977
18995
|
if (test.type !== AST_NODE_TYPES95.BinaryExpression || test.operator !== "===") return null;
|
|
18978
18996
|
const leftIsCase = isCaseValue(test.left);
|
|
18979
18997
|
const rightIsCase = isCaseValue(test.right);
|
|
18980
18998
|
if (leftIsCase === rightIsCase) return null;
|
|
18981
18999
|
const discriminant = leftIsCase ? test.right : test.left;
|
|
18982
|
-
return discriminant.type === AST_NODE_TYPES95.Identifier ?
|
|
19000
|
+
return discriminant.type === AST_NODE_TYPES95.Identifier ? discriminant.name : null;
|
|
18983
19001
|
}
|
|
18984
19002
|
function isCaseValue(node) {
|
|
18985
19003
|
if (node.type === AST_NODE_TYPES95.Literal) return true;
|
|
@@ -19003,12 +19021,12 @@ var prefer_switch_for_repeated_equality_default = createRule({
|
|
|
19003
19021
|
return {
|
|
19004
19022
|
IfStatement(node) {
|
|
19005
19023
|
if (node.parent.type === AST_NODE_TYPES95.IfStatement && node.parent.alternate === node) return;
|
|
19006
|
-
const first =
|
|
19024
|
+
const first = discriminantName(node.test);
|
|
19007
19025
|
if (first === null) return;
|
|
19008
19026
|
let count = 1;
|
|
19009
19027
|
let current = node.alternate;
|
|
19010
19028
|
while (current?.type === AST_NODE_TYPES95.IfStatement) {
|
|
19011
|
-
if (
|
|
19029
|
+
if (discriminantName(current.test) !== first) return;
|
|
19012
19030
|
count += 1;
|
|
19013
19031
|
current = current.alternate;
|
|
19014
19032
|
}
|
|
@@ -22746,7 +22764,9 @@ var STORE_INSERT_REQUIRES_ON_CONFLICT_DOCUMENTATION = {
|
|
|
22746
22764
|
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."],
|
|
22747
22765
|
examples: [
|
|
22748
22766
|
{ 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 },
|
|
22749
|
-
{ 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 }
|
|
22767
|
+
{ 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 },
|
|
22768
|
+
{ 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 },
|
|
22769
|
+
{ 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 }
|
|
22750
22770
|
]
|
|
22751
22771
|
};
|
|
22752
22772
|
var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
|
|
@@ -22766,7 +22786,6 @@ function owningCallableName(node) {
|
|
|
22766
22786
|
}
|
|
22767
22787
|
return null;
|
|
22768
22788
|
}
|
|
22769
|
-
var INSERT_GATE = /insert/i;
|
|
22770
22789
|
var store_insert_requires_on_conflict_default = createRule({
|
|
22771
22790
|
name: "store-insert-requires-on-conflict",
|
|
22772
22791
|
documentation: STORE_INSERT_REQUIRES_ON_CONFLICT_DOCUMENTATION,
|
|
@@ -22782,7 +22801,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
22782
22801
|
},
|
|
22783
22802
|
defaultOptions: [],
|
|
22784
22803
|
create(context) {
|
|
22785
|
-
if (isTestFile(context.filename)
|
|
22804
|
+
if (isTestFile(context.filename)) {
|
|
22786
22805
|
return {};
|
|
22787
22806
|
}
|
|
22788
22807
|
return createSqlListener((sql, node) => {
|
|
@@ -24338,7 +24357,7 @@ var RULES = {
|
|
|
24338
24357
|
};
|
|
24339
24358
|
var meta = {
|
|
24340
24359
|
name: "@sarj/eslint-plugin",
|
|
24341
|
-
version: "16.4.
|
|
24360
|
+
version: "16.4.7"
|
|
24342
24361
|
};
|
|
24343
24362
|
var APPLICATION_ONLY_RULES = [];
|
|
24344
24363
|
var LIBRARY_IMPORT_POLICY = ["error", {
|