@sarj/eslint-plugin 15.0.0 → 15.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +226 -74
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +226 -74
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2062,12 +2062,13 @@ function createSqlListener(handler) {
|
|
|
2062
2062
|
|
|
2063
2063
|
// src/rules/no-dynamic-sql.ts
|
|
2064
2064
|
var noDynamicSqlDocumentation = {
|
|
2065
|
-
summary: "Disallow runtime
|
|
2065
|
+
summary: "Disallow runtime values embedded inside quoted SQL values passed to statement-execution methods.",
|
|
2066
2066
|
rationale: "Embedding runtime values in SQL bypasses driver parameterization and can introduce injection defects or unstable query plans.",
|
|
2067
2067
|
remediation: "Use SQL placeholders and pass runtime values through the driver's binding API.",
|
|
2068
2068
|
category: "security",
|
|
2069
2069
|
limitations: [
|
|
2070
|
-
"The rule
|
|
2070
|
+
"The rule reports only visibly quoted runtime values; dynamic identifiers and unquoted fragments require provenance that syntax-only linting cannot prove.",
|
|
2071
|
+
"Static fragments and parameterizing tagged templates are exempt."
|
|
2071
2072
|
],
|
|
2072
2073
|
examples: [
|
|
2073
2074
|
{
|
|
@@ -2102,27 +2103,48 @@ function isStaticFragment(expression) {
|
|
|
2102
2103
|
if (expression.type === import_utils9.AST_NODE_TYPES.Literal) {
|
|
2103
2104
|
return typeof expression.value === "string";
|
|
2104
2105
|
}
|
|
2106
|
+
if (expression.type === import_utils9.AST_NODE_TYPES.TemplateLiteral) {
|
|
2107
|
+
return expression.expressions.length === 0;
|
|
2108
|
+
}
|
|
2105
2109
|
return false;
|
|
2106
2110
|
}
|
|
2107
2111
|
function runtimeInterpolations(template) {
|
|
2108
2112
|
return template.expressions.filter(
|
|
2109
|
-
(expression) => !isStaticFragment(expression)
|
|
2113
|
+
(expression, index) => !isStaticFragment(expression) && endsWithSqlQuote(template.quasis[index]?.value.raw ?? "") && startsWithSqlQuote(template.quasis[index + 1]?.value.raw ?? "")
|
|
2110
2114
|
);
|
|
2111
2115
|
}
|
|
2116
|
+
function endsWithSqlQuote(text) {
|
|
2117
|
+
return /['"]\s*$/u.test(text);
|
|
2118
|
+
}
|
|
2119
|
+
function startsWithSqlQuote(text) {
|
|
2120
|
+
return /^\s*['"]/u.test(text);
|
|
2121
|
+
}
|
|
2122
|
+
function staticLiteralText(node) {
|
|
2123
|
+
if (node.type === import_utils9.AST_NODE_TYPES.Literal && typeof node.value === "string") {
|
|
2124
|
+
return node.value;
|
|
2125
|
+
}
|
|
2126
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0) {
|
|
2127
|
+
return node.quasis[0]?.value.raw;
|
|
2128
|
+
}
|
|
2129
|
+
return void 0;
|
|
2130
|
+
}
|
|
2112
2131
|
function runtimeConcatOperands(node) {
|
|
2113
2132
|
if (node.type !== import_utils9.AST_NODE_TYPES.BinaryExpression || node.operator !== "+") {
|
|
2114
2133
|
return [];
|
|
2115
2134
|
}
|
|
2116
2135
|
const operands = concatOperands(node);
|
|
2117
2136
|
const hasStringLiteral = operands.some(
|
|
2118
|
-
(operand) => operand.type === import_utils9.AST_NODE_TYPES.Literal && typeof operand.value === "string"
|
|
2137
|
+
(operand) => operand.type === import_utils9.AST_NODE_TYPES.Literal && typeof operand.value === "string" || operand.type === import_utils9.AST_NODE_TYPES.TemplateLiteral && operand.expressions.length === 0
|
|
2119
2138
|
);
|
|
2120
2139
|
if (!hasStringLiteral) {
|
|
2121
2140
|
return [];
|
|
2122
2141
|
}
|
|
2123
|
-
return operands.filter(
|
|
2124
|
-
(operand)
|
|
2125
|
-
|
|
2142
|
+
return operands.filter((operand, index) => {
|
|
2143
|
+
if (isStaticFragment(operand)) return false;
|
|
2144
|
+
const before = operands[index - 1];
|
|
2145
|
+
const after = operands[index + 1];
|
|
2146
|
+
return before !== void 0 && after !== void 0 && endsWithSqlQuote(staticLiteralText(before) ?? "") && startsWithSqlQuote(staticLiteralText(after) ?? "");
|
|
2147
|
+
});
|
|
2126
2148
|
}
|
|
2127
2149
|
function concatOperands(node) {
|
|
2128
2150
|
if (node.type === import_utils9.AST_NODE_TYPES.BinaryExpression && node.operator === "+") {
|
|
@@ -2161,7 +2183,7 @@ var no_dynamic_sql_default = createRule({
|
|
|
2161
2183
|
meta: {
|
|
2162
2184
|
type: "problem",
|
|
2163
2185
|
docs: {
|
|
2164
|
-
description:
|
|
2186
|
+
description: noDynamicSqlDocumentation.summary
|
|
2165
2187
|
},
|
|
2166
2188
|
schema: [
|
|
2167
2189
|
{
|
|
@@ -2177,7 +2199,7 @@ var no_dynamic_sql_default = createRule({
|
|
|
2177
2199
|
}
|
|
2178
2200
|
],
|
|
2179
2201
|
messages: {
|
|
2180
|
-
dynamicSql: "Runtime value
|
|
2202
|
+
dynamicSql: "Runtime value embedded inside a quoted SQL value passed to `{{method}}()`. Replace the quoted interpolation with a placeholder and bind the value separately."
|
|
2181
2203
|
}
|
|
2182
2204
|
},
|
|
2183
2205
|
defaultOptions: [{}],
|
|
@@ -3001,8 +3023,59 @@ var noInsecureRandomIdDocumentation = {
|
|
|
3001
3023
|
{ id: "predictable-token", title: "Do not derive a token from Math.random", outcome: "match", files: [{ path: "src/session.ts", source: "const sessionToken = Math.random();" }], focusPath: "src/session.ts", expectedCount: 1, public: true }
|
|
3002
3024
|
]
|
|
3003
3025
|
};
|
|
3004
|
-
var
|
|
3005
|
-
|
|
3026
|
+
var STRONG_SECURITY_WORDS = /* @__PURE__ */ new Set([
|
|
3027
|
+
"apikey",
|
|
3028
|
+
"csrf",
|
|
3029
|
+
"nonce",
|
|
3030
|
+
"otp",
|
|
3031
|
+
"password",
|
|
3032
|
+
"passwd",
|
|
3033
|
+
"pin",
|
|
3034
|
+
"salt",
|
|
3035
|
+
"secret",
|
|
3036
|
+
"token",
|
|
3037
|
+
"uuid",
|
|
3038
|
+
"verificationcode"
|
|
3039
|
+
]);
|
|
3040
|
+
var NON_SECURITY_ID_WORDS = /* @__PURE__ */ new Set([
|
|
3041
|
+
"aria",
|
|
3042
|
+
"cache",
|
|
3043
|
+
"component",
|
|
3044
|
+
"correlation",
|
|
3045
|
+
"dev",
|
|
3046
|
+
"dialog",
|
|
3047
|
+
"dom",
|
|
3048
|
+
"element",
|
|
3049
|
+
"execution",
|
|
3050
|
+
"field",
|
|
3051
|
+
"form",
|
|
3052
|
+
"hmr",
|
|
3053
|
+
"input",
|
|
3054
|
+
"marker",
|
|
3055
|
+
"menu",
|
|
3056
|
+
"mock",
|
|
3057
|
+
"perf",
|
|
3058
|
+
"req",
|
|
3059
|
+
"request",
|
|
3060
|
+
"select",
|
|
3061
|
+
"tab",
|
|
3062
|
+
"temp",
|
|
3063
|
+
"test",
|
|
3064
|
+
"tmp",
|
|
3065
|
+
"trace"
|
|
3066
|
+
]);
|
|
3067
|
+
function nameWords(name) {
|
|
3068
|
+
return name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").split(/[^A-Za-z0-9]+/u).filter(Boolean).map((word) => word.toLowerCase());
|
|
3069
|
+
}
|
|
3070
|
+
function isStrongSecurityName(name) {
|
|
3071
|
+
const words = nameWords(name);
|
|
3072
|
+
return words.some((word) => STRONG_SECURITY_WORDS.has(word)) || words.some(
|
|
3073
|
+
(word, index) => word === "api" && words[index + 1] === "key" || word === "auth" && words[index + 1] === "id" || word === "verification" && words[index + 1] === "code"
|
|
3074
|
+
);
|
|
3075
|
+
}
|
|
3076
|
+
function isNonSecurityName(name) {
|
|
3077
|
+
return nameWords(name).some((word) => NON_SECURITY_ID_WORDS.has(word));
|
|
3078
|
+
}
|
|
3006
3079
|
var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
|
|
3007
3080
|
function isMathRandomCall(node) {
|
|
3008
3081
|
if (node.type !== "CallExpression") {
|
|
@@ -3015,70 +3088,57 @@ function isMathRandomCall(node) {
|
|
|
3015
3088
|
const { object, property } = callee;
|
|
3016
3089
|
return object.type === "Identifier" && object.name === "Math" && property.type === "Identifier" && property.name === "random";
|
|
3017
3090
|
}
|
|
3018
|
-
function
|
|
3019
|
-
|
|
3020
|
-
let
|
|
3021
|
-
while (parent) {
|
|
3022
|
-
if (parent.type === "MemberExpression" && parent.object === current && !parent.computed && parent.property.type === "Identifier" && parent.property.name === "toString") {
|
|
3023
|
-
const grandparent = parent.parent;
|
|
3024
|
-
if (grandparent && grandparent.type === "CallExpression" && grandparent.callee === parent) {
|
|
3025
|
-
const firstArg = grandparent.arguments[0];
|
|
3026
|
-
if (firstArg && firstArg.type === "Literal" && firstArg.value === 36) {
|
|
3027
|
-
return true;
|
|
3028
|
-
}
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
if (parent.type === "MemberExpression" && parent.object === current) {
|
|
3032
|
-
current = parent;
|
|
3033
|
-
parent = current.parent;
|
|
3034
|
-
continue;
|
|
3035
|
-
}
|
|
3036
|
-
if (parent.type === "CallExpression" && parent.callee === current) {
|
|
3037
|
-
current = parent;
|
|
3038
|
-
parent = current.parent;
|
|
3039
|
-
continue;
|
|
3040
|
-
}
|
|
3041
|
-
break;
|
|
3042
|
-
}
|
|
3043
|
-
return false;
|
|
3044
|
-
}
|
|
3045
|
-
function findEnclosingName(node) {
|
|
3091
|
+
function findEnclosingNames(node) {
|
|
3092
|
+
const names = [];
|
|
3093
|
+
let directBinding = true;
|
|
3046
3094
|
let current = node;
|
|
3047
3095
|
let parent = current.parent;
|
|
3048
3096
|
while (parent) {
|
|
3049
3097
|
if (parent.type === "VariableDeclarator" && parent.init === current) {
|
|
3050
|
-
if (parent.id.type === "Identifier") {
|
|
3051
|
-
|
|
3098
|
+
if (directBinding && parent.id.type === "Identifier") {
|
|
3099
|
+
names.push(parent.id.name);
|
|
3052
3100
|
}
|
|
3053
|
-
return void 0;
|
|
3054
3101
|
}
|
|
3055
3102
|
if (parent.type === "Property" && parent.value === current) {
|
|
3056
3103
|
const key = parent.key;
|
|
3057
3104
|
if (!parent.computed && key.type === "Identifier") {
|
|
3058
|
-
|
|
3105
|
+
names.push(key.name);
|
|
3059
3106
|
}
|
|
3060
3107
|
if (key.type === "Literal" && typeof key.value === "string") {
|
|
3061
|
-
|
|
3108
|
+
names.push(key.value);
|
|
3062
3109
|
}
|
|
3063
|
-
|
|
3110
|
+
directBinding = false;
|
|
3064
3111
|
}
|
|
3065
3112
|
if (parent.type === "PropertyDefinition" && parent.value === current) {
|
|
3066
3113
|
const key = parent.key;
|
|
3067
3114
|
if (!parent.computed && key.type === "Identifier") {
|
|
3068
|
-
|
|
3115
|
+
names.push(key.name);
|
|
3069
3116
|
}
|
|
3070
3117
|
if (key.type === "Literal" && typeof key.value === "string") {
|
|
3071
|
-
|
|
3118
|
+
names.push(key.value);
|
|
3072
3119
|
}
|
|
3073
|
-
|
|
3120
|
+
directBinding = false;
|
|
3074
3121
|
}
|
|
3075
|
-
if (parent.type === "
|
|
3076
|
-
|
|
3122
|
+
if (parent.type === "AssignmentExpression" && parent.right === current) {
|
|
3123
|
+
if (directBinding && parent.left.type === "Identifier") names.push(parent.left.name);
|
|
3124
|
+
if (directBinding && parent.left.type === "MemberExpression" && !parent.left.computed && parent.left.property.type === "Identifier") {
|
|
3125
|
+
names.push(parent.left.property.name);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
|
|
3129
|
+
directBinding = false;
|
|
3130
|
+
}
|
|
3131
|
+
if (parent.type === "FunctionDeclaration") {
|
|
3132
|
+
if (directBinding && parent.id !== null) names.push(parent.id.name);
|
|
3133
|
+
return names;
|
|
3134
|
+
}
|
|
3135
|
+
if (parent.type === "ExpressionStatement") {
|
|
3136
|
+
return names;
|
|
3077
3137
|
}
|
|
3078
3138
|
current = parent;
|
|
3079
3139
|
parent = current.parent;
|
|
3080
3140
|
}
|
|
3081
|
-
return
|
|
3141
|
+
return names;
|
|
3082
3142
|
}
|
|
3083
3143
|
function isConcatenatedIntoPathOrDomId(node) {
|
|
3084
3144
|
const valueNode = climbValueChain(node);
|
|
@@ -3164,20 +3224,17 @@ var no_insecure_random_id_default = createRule({
|
|
|
3164
3224
|
if (!isMathRandomCall(node)) {
|
|
3165
3225
|
return;
|
|
3166
3226
|
}
|
|
3167
|
-
const
|
|
3168
|
-
if (
|
|
3227
|
+
const names = findEnclosingNames(node);
|
|
3228
|
+
if (names.some(isStrongSecurityName)) {
|
|
3169
3229
|
context.report({ node, messageId: "insecureRandomId" });
|
|
3170
3230
|
return;
|
|
3171
3231
|
}
|
|
3172
|
-
if (
|
|
3232
|
+
if (names.some(isNonSecurityName)) {
|
|
3173
3233
|
return;
|
|
3174
3234
|
}
|
|
3175
3235
|
if (isConcatenatedIntoPathOrDomId(node)) {
|
|
3176
3236
|
return;
|
|
3177
3237
|
}
|
|
3178
|
-
if (isPartOfToString36Chain(node)) {
|
|
3179
|
-
context.report({ node, messageId: "insecureRandomId" });
|
|
3180
|
-
}
|
|
3181
3238
|
}
|
|
3182
3239
|
};
|
|
3183
3240
|
}
|
|
@@ -4839,7 +4896,8 @@ var DEFAULT_ALLOW = [
|
|
|
4839
4896
|
"[\\\\/](connectors|providers|integrations|adapters|fetchers)[\\\\/]",
|
|
4840
4897
|
"[\\\\/]notifications[\\\\/]",
|
|
4841
4898
|
"[Ss]ervice\\.[cm]?[jt]sx?$",
|
|
4842
|
-
"-(service|connector|adapter|sdk|fetcher)\\.[cm]?[jt]sx?$"
|
|
4899
|
+
"-(service|connector|adapter|sdk|fetcher)\\.[cm]?[jt]sx?$",
|
|
4900
|
+
"[\\\\/][^\\\\/]*(?:Client|client)\\.[cm]?[jt]sx?$"
|
|
4843
4901
|
];
|
|
4844
4902
|
var NON_PRODUCTION_TREE_RE = /[\\/](playwright|cypress|__testfixtures__)[\\/]/;
|
|
4845
4903
|
var GLOBAL_RECEIVERS = /* @__PURE__ */ new Set([
|
|
@@ -4973,7 +5031,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
4973
5031
|
defaultOptions: [{}],
|
|
4974
5032
|
create(context, [options]) {
|
|
4975
5033
|
const filename = context.filename;
|
|
4976
|
-
if (isTestFile(filename) || NON_PRODUCTION_TREE_RE.test(filename)) {
|
|
5034
|
+
if (isTestFile(filename) || isScriptFile(filename) || NON_PRODUCTION_TREE_RE.test(filename)) {
|
|
4977
5035
|
return {};
|
|
4978
5036
|
}
|
|
4979
5037
|
const patterns = options?.allow ?? DEFAULT_ALLOW;
|
|
@@ -5013,10 +5071,15 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
5013
5071
|
function isInternalApiUrl(node) {
|
|
5014
5072
|
const resolved = resolveNode2(node ?? void 0);
|
|
5015
5073
|
if (resolved?.type === import_utils25.AST_NODE_TYPES.Literal) {
|
|
5016
|
-
return typeof resolved.value === "string" && resolved.value
|
|
5074
|
+
return typeof resolved.value === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(resolved.value);
|
|
5017
5075
|
}
|
|
5018
5076
|
if (resolved?.type === import_utils25.AST_NODE_TYPES.TemplateLiteral) {
|
|
5019
|
-
|
|
5077
|
+
const prefix = resolved.quasis[0]?.value.cooked;
|
|
5078
|
+
return typeof prefix === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(prefix);
|
|
5079
|
+
}
|
|
5080
|
+
if (resolved?.type === import_utils25.AST_NODE_TYPES.CallExpression && resolved.callee.type === import_utils25.AST_NODE_TYPES.Identifier && resolved.callee.name === "withBase") {
|
|
5081
|
+
const first = resolved.arguments[0];
|
|
5082
|
+
return first !== void 0 && first.type !== import_utils25.AST_NODE_TYPES.SpreadElement ? isInternalApiUrl(first) : false;
|
|
5020
5083
|
}
|
|
5021
5084
|
return resolved?.type === import_utils25.AST_NODE_TYPES.BinaryExpression && resolved.operator === "+" && isInternalApiUrl(resolved.left);
|
|
5022
5085
|
}
|
|
@@ -5860,12 +5923,17 @@ var BLOB_REDACTION_TOKENS = /* @__PURE__ */ new Set([
|
|
|
5860
5923
|
"public"
|
|
5861
5924
|
]);
|
|
5862
5925
|
function rawBlobValueName(value) {
|
|
5926
|
+
if (value.type === "AwaitExpression") return rawBlobValueName(value.argument);
|
|
5927
|
+
if (value.type === "ChainExpression") return rawBlobValueName(value.expression);
|
|
5863
5928
|
if (value.type === "Identifier") {
|
|
5864
5929
|
return isRawBlobName(value.name) ? value.name : null;
|
|
5865
5930
|
}
|
|
5866
5931
|
if (value.type === "MemberExpression" && !value.computed && value.property.type === "Identifier") {
|
|
5867
5932
|
return isRawBlobName(value.property.name) ? value.property.name : null;
|
|
5868
5933
|
}
|
|
5934
|
+
if (value.type === "CallExpression" && value.arguments.length === 0 && value.callee.type === "MemberExpression" && !value.callee.computed && value.callee.object.type === "Identifier" && /^(?:res|response|\w+Response)$/.test(value.callee.object.name) && value.callee.property.type === "Identifier" && (value.callee.property.name === "json" || value.callee.property.name === "text")) {
|
|
5935
|
+
return `${value.callee.object.name}.${value.callee.property.name}()`;
|
|
5936
|
+
}
|
|
5869
5937
|
return null;
|
|
5870
5938
|
}
|
|
5871
5939
|
function isRawBlobName(name) {
|
|
@@ -6530,6 +6598,9 @@ var isExplanatory = (comment) => !DIRECTIVE_COMMENT_RE.test(comment.value);
|
|
|
6530
6598
|
function isTeardownCall(node) {
|
|
6531
6599
|
return node.type === import_utils33.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils33.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils33.AST_NODE_TYPES.Identifier && TEARDOWN_METHODS.has(node.callee.property.name);
|
|
6532
6600
|
}
|
|
6601
|
+
function isCancelledWebShare(node) {
|
|
6602
|
+
return node.type === import_utils33.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils33.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.object.type === import_utils33.AST_NODE_TYPES.Identifier && node.callee.object.name === "navigator" && node.callee.property.type === import_utils33.AST_NODE_TYPES.Identifier && node.callee.property.name === "share";
|
|
6603
|
+
}
|
|
6533
6604
|
function isSilentHandler(handler) {
|
|
6534
6605
|
const body2 = handler.body;
|
|
6535
6606
|
if (body2.type !== import_utils33.AST_NODE_TYPES.BlockStatement) {
|
|
@@ -6579,7 +6650,7 @@ var no_silent_promise_catch_default = createRule({
|
|
|
6579
6650
|
},
|
|
6580
6651
|
defaultOptions: [],
|
|
6581
6652
|
create(context) {
|
|
6582
|
-
if (isTestFile(context.filename)) {
|
|
6653
|
+
if (isTestFile(context.filename) || isScriptFile(context.filename)) {
|
|
6583
6654
|
return {};
|
|
6584
6655
|
}
|
|
6585
6656
|
const hasExplanatoryComment = (call, handler) => {
|
|
@@ -6612,7 +6683,10 @@ var no_silent_promise_catch_default = createRule({
|
|
|
6612
6683
|
if (isTeardownCall(node.callee.object)) {
|
|
6613
6684
|
return;
|
|
6614
6685
|
}
|
|
6615
|
-
if (node.
|
|
6686
|
+
if (isCancelledWebShare(node.callee.object)) {
|
|
6687
|
+
return;
|
|
6688
|
+
}
|
|
6689
|
+
if (node.parent.type === import_utils33.AST_NODE_TYPES.MemberExpression && node.parent.object === node && !node.parent.computed && node.parent.property.type === import_utils33.AST_NODE_TYPES.Identifier && node.parent.property.name === "then") {
|
|
6616
6690
|
return;
|
|
6617
6691
|
}
|
|
6618
6692
|
const expectedArguments = method === "catch" ? 1 : 2;
|
|
@@ -6924,7 +6998,20 @@ function isStringInitializedVariable(variable) {
|
|
|
6924
6998
|
if (declarator.type !== "VariableDeclarator") {
|
|
6925
6999
|
return false;
|
|
6926
7000
|
}
|
|
6927
|
-
|
|
7001
|
+
if (isStringLiteralInit(declarator.init)) return true;
|
|
7002
|
+
if (declarator.id.type === "Identifier" && declarator.id.typeAnnotation?.typeAnnotation.type === "TSStringKeyword") {
|
|
7003
|
+
return true;
|
|
7004
|
+
}
|
|
7005
|
+
return isTemplateStringsArrayElement(declarator.init, variable.scope);
|
|
7006
|
+
}
|
|
7007
|
+
function isTemplateStringsArrayElement(node, scope) {
|
|
7008
|
+
if (node?.type !== "MemberExpression" || !node.computed || node.object.type !== "Identifier") {
|
|
7009
|
+
return false;
|
|
7010
|
+
}
|
|
7011
|
+
const source = findVariable(scope, node.object.name);
|
|
7012
|
+
if (source?.defs.length !== 1) return false;
|
|
7013
|
+
const name = source.defs[0]?.name;
|
|
7014
|
+
return name?.type === "Identifier" && name.typeAnnotation?.typeAnnotation.type === "TSTypeReference" && name.typeAnnotation.typeAnnotation.typeName.type === "Identifier" && name.typeAnnotation.typeAnnotation.typeName.name === "TemplateStringsArray";
|
|
6928
7015
|
}
|
|
6929
7016
|
function isStringLiteralInit(node) {
|
|
6930
7017
|
if (node === null) {
|
|
@@ -6958,12 +7045,13 @@ function isConcatOperand(node, target) {
|
|
|
6958
7045
|
}
|
|
6959
7046
|
return false;
|
|
6960
7047
|
}
|
|
6961
|
-
function isDeclaredInsideLoop(variable,
|
|
7048
|
+
function isDeclaredInsideLoop(variable, repetition) {
|
|
6962
7049
|
const def = variable.defs[0];
|
|
6963
7050
|
if (def === void 0) {
|
|
6964
7051
|
return false;
|
|
6965
7052
|
}
|
|
6966
|
-
const body2 =
|
|
7053
|
+
const body2 = repetition.type === "CallExpression" ? repetition.arguments[0] : repetition.body;
|
|
7054
|
+
if (body2 === void 0 || body2.type === "SpreadElement") return false;
|
|
6967
7055
|
const [declStart, declEnd] = def.node.range;
|
|
6968
7056
|
const [bodyStart, bodyEnd] = body2.range;
|
|
6969
7057
|
return declStart >= bodyStart && declEnd <= bodyEnd;
|
|
@@ -6972,6 +7060,9 @@ function enclosingLoop(node) {
|
|
|
6972
7060
|
let child = node;
|
|
6973
7061
|
let parent = node.parent;
|
|
6974
7062
|
while (parent !== void 0 && parent !== null) {
|
|
7063
|
+
if ((parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression") && parent.parent.type === "CallExpression" && parent.parent.arguments[0] === parent && parent.parent.callee.type === "MemberExpression" && !parent.parent.callee.computed && parent.parent.callee.property.type === "Identifier" && parent.parent.callee.property.name === "forEach") {
|
|
7064
|
+
return parent.parent;
|
|
7065
|
+
}
|
|
6975
7066
|
if (LOOP_NODE_TYPES.has(parent.type)) {
|
|
6976
7067
|
const loop = parent;
|
|
6977
7068
|
if (loop.body === child) {
|
|
@@ -6983,6 +7074,17 @@ function enclosingLoop(node) {
|
|
|
6983
7074
|
}
|
|
6984
7075
|
return null;
|
|
6985
7076
|
}
|
|
7077
|
+
function isSmallStaticForLoop(node) {
|
|
7078
|
+
if (node.type !== "ForStatement" || node.init?.type !== "VariableDeclaration" || node.init.declarations.length !== 1 || node.test?.type !== "BinaryExpression" || node.test.operator !== "<" && node.test.operator !== "<=" || node.update?.type !== "UpdateExpression" || node.update.operator !== "++") {
|
|
7079
|
+
return false;
|
|
7080
|
+
}
|
|
7081
|
+
const declaration = node.init.declarations[0];
|
|
7082
|
+
if (declaration?.id.type !== "Identifier" || declaration.init?.type !== "Literal" || typeof declaration.init.value !== "number" || !Number.isInteger(declaration.init.value) || node.test.left.type !== "Identifier" || node.test.left.name !== declaration.id.name || node.test.right.type !== "Literal" || typeof node.test.right.value !== "number" || !Number.isInteger(node.test.right.value) || node.update.argument.type !== "Identifier" || node.update.argument.name !== declaration.id.name) {
|
|
7083
|
+
return false;
|
|
7084
|
+
}
|
|
7085
|
+
const iterations = node.test.right.value - declaration.init.value + (node.test.operator === "<=" ? 1 : 0);
|
|
7086
|
+
return iterations >= 0 && iterations <= 8;
|
|
7087
|
+
}
|
|
6986
7088
|
var no_string_concat_in_loop_default = createRule({
|
|
6987
7089
|
name: "no-string-concat-in-loop",
|
|
6988
7090
|
documentation: noStringConcatInLoopDocumentation,
|
|
@@ -7015,6 +7117,9 @@ var no_string_concat_in_loop_default = createRule({
|
|
|
7015
7117
|
if (loop === null) {
|
|
7016
7118
|
return;
|
|
7017
7119
|
}
|
|
7120
|
+
if (isSmallStaticForLoop(loop)) {
|
|
7121
|
+
return;
|
|
7122
|
+
}
|
|
7018
7123
|
const scope = context.sourceCode.getScope(node);
|
|
7019
7124
|
const variable = findVariable(scope, node.left.name);
|
|
7020
7125
|
if (variable === void 0) {
|
|
@@ -8862,8 +8967,10 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
|
|
|
8862
8967
|
let statusMemberCount = 0;
|
|
8863
8968
|
let hasFailurePayload = false;
|
|
8864
8969
|
let hasSuccessPayload = false;
|
|
8970
|
+
let hasUnrecognizedMember = false;
|
|
8865
8971
|
for (const member of typeLiteral.members) {
|
|
8866
8972
|
if (member.type !== import_utils49.AST_NODE_TYPES.TSPropertySignature) {
|
|
8973
|
+
hasUnrecognizedMember = true;
|
|
8867
8974
|
continue;
|
|
8868
8975
|
}
|
|
8869
8976
|
const name = getMemberName(member);
|
|
@@ -8872,15 +8979,18 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
|
|
|
8872
8979
|
continue;
|
|
8873
8980
|
}
|
|
8874
8981
|
if (!member.optional || isBooleanTyped(member) || name === null) {
|
|
8982
|
+
hasUnrecognizedMember = true;
|
|
8875
8983
|
continue;
|
|
8876
8984
|
}
|
|
8877
8985
|
if (FAILURE_MEMBER_NAMES.has(name)) {
|
|
8878
8986
|
hasFailurePayload = true;
|
|
8879
8987
|
} else if (SUCCESS_PAYLOAD_MEMBER_NAMES.has(name)) {
|
|
8880
8988
|
hasSuccessPayload = true;
|
|
8989
|
+
} else {
|
|
8990
|
+
hasUnrecognizedMember = true;
|
|
8881
8991
|
}
|
|
8882
8992
|
}
|
|
8883
|
-
return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && hasSuccessPayload;
|
|
8993
|
+
return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && (hasSuccessPayload || !hasUnrecognizedMember);
|
|
8884
8994
|
}
|
|
8885
8995
|
function getMemberName(member) {
|
|
8886
8996
|
if (member.type !== import_utils49.AST_NODE_TYPES.TSPropertySignature) {
|
|
@@ -10781,7 +10891,7 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
10781
10891
|
},
|
|
10782
10892
|
defaultOptions: [],
|
|
10783
10893
|
create(context) {
|
|
10784
|
-
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
10894
|
+
if (isTestFile(context.filename) || isScriptFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
10785
10895
|
return {};
|
|
10786
10896
|
}
|
|
10787
10897
|
const unvalidatedVariables = /* @__PURE__ */ new Set();
|
|
@@ -12433,6 +12543,26 @@ var require_fetch_timeout_default = createRule({
|
|
|
12433
12543
|
}
|
|
12434
12544
|
return callee.type === import_utils63.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils63.AST_NODE_TYPES.Identifier && callee.property.name === "fetch" && callee.object.type === import_utils63.AST_NODE_TYPES.Identifier && GLOBAL_OBJECTS2.has(callee.object.name) && resolvesToGlobal(callee.object);
|
|
12435
12545
|
}
|
|
12546
|
+
function localConstInitProvablyLacksSignal(identifier) {
|
|
12547
|
+
const variable = import_utils63.ASTUtils.findVariable(
|
|
12548
|
+
context.sourceCode.getScope(identifier),
|
|
12549
|
+
identifier.name
|
|
12550
|
+
);
|
|
12551
|
+
if (variable?.defs.length !== 1) return false;
|
|
12552
|
+
const definition = variable.defs[0];
|
|
12553
|
+
if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init?.type !== import_utils63.AST_NODE_TYPES.ObjectExpression || !initProvablyLacksSignal(definition.node.init)) {
|
|
12554
|
+
return false;
|
|
12555
|
+
}
|
|
12556
|
+
for (const reference of variable.references) {
|
|
12557
|
+
const ref = reference.identifier;
|
|
12558
|
+
if (ref === identifier || ref === definition.name) continue;
|
|
12559
|
+
const member = ref.parent;
|
|
12560
|
+
if (member.type !== import_utils63.AST_NODE_TYPES.MemberExpression || member.object !== ref || member.computed || member.property.type !== import_utils63.AST_NODE_TYPES.Identifier || member.property.name === "signal" || member.parent.type !== import_utils63.AST_NODE_TYPES.AssignmentExpression || member.parent.left !== member) {
|
|
12561
|
+
return false;
|
|
12562
|
+
}
|
|
12563
|
+
}
|
|
12564
|
+
return true;
|
|
12565
|
+
}
|
|
12436
12566
|
return {
|
|
12437
12567
|
CallExpression(node) {
|
|
12438
12568
|
if (!isGlobalFetchCall2(node.callee)) {
|
|
@@ -12442,7 +12572,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
12442
12572
|
if (node.arguments.length === 1 && first !== void 0 && !isInlineUrl(first, resolvesToGlobal)) {
|
|
12443
12573
|
return;
|
|
12444
12574
|
}
|
|
12445
|
-
if (init === void 0 || initProvablyLacksSignal(init)) {
|
|
12575
|
+
if (init === void 0 || initProvablyLacksSignal(init) || init.type === import_utils63.AST_NODE_TYPES.Identifier && localConstInitProvablyLacksSignal(init)) {
|
|
12446
12576
|
context.report({ node, messageId: "missingSignal" });
|
|
12447
12577
|
}
|
|
12448
12578
|
}
|
|
@@ -13197,8 +13327,8 @@ var require_zod_form_validation_default = createRule({
|
|
|
13197
13327
|
// src/rules/store-insert-requires-on-conflict.ts
|
|
13198
13328
|
var import_utils67 = require("@typescript-eslint/utils");
|
|
13199
13329
|
var storeInsertRequiresOnConflictDocumentation = {
|
|
13200
|
-
summary: "Require
|
|
13201
|
-
rationale: "A
|
|
13330
|
+
summary: "Require embedded inserts in explicitly replayable callables to carry conflict handling.",
|
|
13331
|
+
rationale: "A callable named as an enqueue, seed, migration, schedule, ensure, or upsert promises replay safety.",
|
|
13202
13332
|
remediation: "Add an appropriate `ON CONFLICT` action or supported replay-safe insert form.",
|
|
13203
13333
|
category: "correctness",
|
|
13204
13334
|
examples: [
|
|
@@ -13207,7 +13337,25 @@ var storeInsertRequiresOnConflictDocumentation = {
|
|
|
13207
13337
|
]
|
|
13208
13338
|
};
|
|
13209
13339
|
var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
|
|
13210
|
-
var CONFLICT_HANDLED = /\bON\s+CONFLICT\b|\bON\s+DUPLICATE\s+KEY\b|\bINSERT\s+OR\s+(?:IGNORE|REPLACE)\b/i;
|
|
13340
|
+
var CONFLICT_HANDLED = /\bON\s+CONFLICT\b|\bON\s+DUPLICATE\s+KEY\b|\bINSERT\s+OR\s+(?:IGNORE|REPLACE)\b|\bINSERT\b[\s\S]*?\bSELECT\b[\s\S]*?\bWHERE\s+NOT\s+EXISTS\b/i;
|
|
13341
|
+
var REPLAY_CONTRACT_NAME = /(?:enqueue|ensure|migrate|recordOnce|schedule|seed|upsert|getOrCreate|createIfAbsent|insertIfAbsent)/i;
|
|
13342
|
+
function owningCallableName(node) {
|
|
13343
|
+
for (let current = node.parent; current !== null && current !== void 0; current = current.parent) {
|
|
13344
|
+
if (current.type === "FunctionDeclaration") {
|
|
13345
|
+
return current.id?.name ?? null;
|
|
13346
|
+
}
|
|
13347
|
+
if (current.type === "MethodDefinition") {
|
|
13348
|
+
return current.key.type === "Identifier" ? current.key.name : null;
|
|
13349
|
+
}
|
|
13350
|
+
if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "VariableDeclarator" && current.parent.id.type === "Identifier") {
|
|
13351
|
+
return current.parent.id.name;
|
|
13352
|
+
}
|
|
13353
|
+
if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "Property" && current.parent.key.type === "Identifier") {
|
|
13354
|
+
return current.parent.key.name;
|
|
13355
|
+
}
|
|
13356
|
+
}
|
|
13357
|
+
return null;
|
|
13358
|
+
}
|
|
13211
13359
|
var INSERT_GATE = /insert/i;
|
|
13212
13360
|
var store_insert_requires_on_conflict_default = createRule({
|
|
13213
13361
|
name: "store-insert-requires-on-conflict",
|
|
@@ -13215,7 +13363,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
13215
13363
|
meta: {
|
|
13216
13364
|
type: "problem",
|
|
13217
13365
|
docs: {
|
|
13218
|
-
description: "Require
|
|
13366
|
+
description: "Require embedded inserts in explicitly replayable callables to carry conflict handling."
|
|
13219
13367
|
},
|
|
13220
13368
|
schema: [],
|
|
13221
13369
|
messages: {
|
|
@@ -13231,6 +13379,10 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
13231
13379
|
if (!INSERT_WRITE.test(sql) || CONFLICT_HANDLED.test(sql)) {
|
|
13232
13380
|
return;
|
|
13233
13381
|
}
|
|
13382
|
+
const owner = owningCallableName(node);
|
|
13383
|
+
if (owner !== null && !REPLAY_CONTRACT_NAME.test(owner)) {
|
|
13384
|
+
return;
|
|
13385
|
+
}
|
|
13234
13386
|
context.report({ node, messageId: "storeInsertRequiresOnConflict" });
|
|
13235
13387
|
});
|
|
13236
13388
|
}
|
|
@@ -13917,7 +14069,7 @@ var rules = {
|
|
|
13917
14069
|
};
|
|
13918
14070
|
var meta = {
|
|
13919
14071
|
name: "@sarj/eslint-plugin",
|
|
13920
|
-
version: "15.
|
|
14072
|
+
version: "15.1.0"
|
|
13921
14073
|
};
|
|
13922
14074
|
var applicationOnlyRules = [
|
|
13923
14075
|
"no-restricted-library-load",
|
package/dist/index.d.cts
CHANGED
|
@@ -414,7 +414,7 @@ type FlatPreset = {
|
|
|
414
414
|
declare const plugin: {
|
|
415
415
|
readonly meta: {
|
|
416
416
|
readonly name: "@sarj/eslint-plugin";
|
|
417
|
-
readonly version: "15.
|
|
417
|
+
readonly version: "15.1.0";
|
|
418
418
|
};
|
|
419
419
|
readonly rules: {
|
|
420
420
|
readonly "duplicate-test-body": DocumentedRule<readonly [], "duplicateTestBody">;
|
package/dist/index.d.ts
CHANGED
|
@@ -414,7 +414,7 @@ type FlatPreset = {
|
|
|
414
414
|
declare const plugin: {
|
|
415
415
|
readonly meta: {
|
|
416
416
|
readonly name: "@sarj/eslint-plugin";
|
|
417
|
-
readonly version: "15.
|
|
417
|
+
readonly version: "15.1.0";
|
|
418
418
|
};
|
|
419
419
|
readonly rules: {
|
|
420
420
|
readonly "duplicate-test-body": DocumentedRule<readonly [], "duplicateTestBody">;
|
package/dist/index.js
CHANGED
|
@@ -2022,12 +2022,13 @@ function createSqlListener(handler) {
|
|
|
2022
2022
|
|
|
2023
2023
|
// src/rules/no-dynamic-sql.ts
|
|
2024
2024
|
var noDynamicSqlDocumentation = {
|
|
2025
|
-
summary: "Disallow runtime
|
|
2025
|
+
summary: "Disallow runtime values embedded inside quoted SQL values passed to statement-execution methods.",
|
|
2026
2026
|
rationale: "Embedding runtime values in SQL bypasses driver parameterization and can introduce injection defects or unstable query plans.",
|
|
2027
2027
|
remediation: "Use SQL placeholders and pass runtime values through the driver's binding API.",
|
|
2028
2028
|
category: "security",
|
|
2029
2029
|
limitations: [
|
|
2030
|
-
"The rule
|
|
2030
|
+
"The rule reports only visibly quoted runtime values; dynamic identifiers and unquoted fragments require provenance that syntax-only linting cannot prove.",
|
|
2031
|
+
"Static fragments and parameterizing tagged templates are exempt."
|
|
2031
2032
|
],
|
|
2032
2033
|
examples: [
|
|
2033
2034
|
{
|
|
@@ -2062,27 +2063,48 @@ function isStaticFragment(expression) {
|
|
|
2062
2063
|
if (expression.type === AST_NODE_TYPES8.Literal) {
|
|
2063
2064
|
return typeof expression.value === "string";
|
|
2064
2065
|
}
|
|
2066
|
+
if (expression.type === AST_NODE_TYPES8.TemplateLiteral) {
|
|
2067
|
+
return expression.expressions.length === 0;
|
|
2068
|
+
}
|
|
2065
2069
|
return false;
|
|
2066
2070
|
}
|
|
2067
2071
|
function runtimeInterpolations(template) {
|
|
2068
2072
|
return template.expressions.filter(
|
|
2069
|
-
(expression) => !isStaticFragment(expression)
|
|
2073
|
+
(expression, index) => !isStaticFragment(expression) && endsWithSqlQuote(template.quasis[index]?.value.raw ?? "") && startsWithSqlQuote(template.quasis[index + 1]?.value.raw ?? "")
|
|
2070
2074
|
);
|
|
2071
2075
|
}
|
|
2076
|
+
function endsWithSqlQuote(text) {
|
|
2077
|
+
return /['"]\s*$/u.test(text);
|
|
2078
|
+
}
|
|
2079
|
+
function startsWithSqlQuote(text) {
|
|
2080
|
+
return /^\s*['"]/u.test(text);
|
|
2081
|
+
}
|
|
2082
|
+
function staticLiteralText(node) {
|
|
2083
|
+
if (node.type === AST_NODE_TYPES8.Literal && typeof node.value === "string") {
|
|
2084
|
+
return node.value;
|
|
2085
|
+
}
|
|
2086
|
+
if (node.type === AST_NODE_TYPES8.TemplateLiteral && node.expressions.length === 0) {
|
|
2087
|
+
return node.quasis[0]?.value.raw;
|
|
2088
|
+
}
|
|
2089
|
+
return void 0;
|
|
2090
|
+
}
|
|
2072
2091
|
function runtimeConcatOperands(node) {
|
|
2073
2092
|
if (node.type !== AST_NODE_TYPES8.BinaryExpression || node.operator !== "+") {
|
|
2074
2093
|
return [];
|
|
2075
2094
|
}
|
|
2076
2095
|
const operands = concatOperands(node);
|
|
2077
2096
|
const hasStringLiteral = operands.some(
|
|
2078
|
-
(operand) => operand.type === AST_NODE_TYPES8.Literal && typeof operand.value === "string"
|
|
2097
|
+
(operand) => operand.type === AST_NODE_TYPES8.Literal && typeof operand.value === "string" || operand.type === AST_NODE_TYPES8.TemplateLiteral && operand.expressions.length === 0
|
|
2079
2098
|
);
|
|
2080
2099
|
if (!hasStringLiteral) {
|
|
2081
2100
|
return [];
|
|
2082
2101
|
}
|
|
2083
|
-
return operands.filter(
|
|
2084
|
-
(operand)
|
|
2085
|
-
|
|
2102
|
+
return operands.filter((operand, index) => {
|
|
2103
|
+
if (isStaticFragment(operand)) return false;
|
|
2104
|
+
const before = operands[index - 1];
|
|
2105
|
+
const after = operands[index + 1];
|
|
2106
|
+
return before !== void 0 && after !== void 0 && endsWithSqlQuote(staticLiteralText(before) ?? "") && startsWithSqlQuote(staticLiteralText(after) ?? "");
|
|
2107
|
+
});
|
|
2086
2108
|
}
|
|
2087
2109
|
function concatOperands(node) {
|
|
2088
2110
|
if (node.type === AST_NODE_TYPES8.BinaryExpression && node.operator === "+") {
|
|
@@ -2121,7 +2143,7 @@ var no_dynamic_sql_default = createRule({
|
|
|
2121
2143
|
meta: {
|
|
2122
2144
|
type: "problem",
|
|
2123
2145
|
docs: {
|
|
2124
|
-
description:
|
|
2146
|
+
description: noDynamicSqlDocumentation.summary
|
|
2125
2147
|
},
|
|
2126
2148
|
schema: [
|
|
2127
2149
|
{
|
|
@@ -2137,7 +2159,7 @@ var no_dynamic_sql_default = createRule({
|
|
|
2137
2159
|
}
|
|
2138
2160
|
],
|
|
2139
2161
|
messages: {
|
|
2140
|
-
dynamicSql: "Runtime value
|
|
2162
|
+
dynamicSql: "Runtime value embedded inside a quoted SQL value passed to `{{method}}()`. Replace the quoted interpolation with a placeholder and bind the value separately."
|
|
2141
2163
|
}
|
|
2142
2164
|
},
|
|
2143
2165
|
defaultOptions: [{}],
|
|
@@ -2961,8 +2983,59 @@ var noInsecureRandomIdDocumentation = {
|
|
|
2961
2983
|
{ id: "predictable-token", title: "Do not derive a token from Math.random", outcome: "match", files: [{ path: "src/session.ts", source: "const sessionToken = Math.random();" }], focusPath: "src/session.ts", expectedCount: 1, public: true }
|
|
2962
2984
|
]
|
|
2963
2985
|
};
|
|
2964
|
-
var
|
|
2965
|
-
|
|
2986
|
+
var STRONG_SECURITY_WORDS = /* @__PURE__ */ new Set([
|
|
2987
|
+
"apikey",
|
|
2988
|
+
"csrf",
|
|
2989
|
+
"nonce",
|
|
2990
|
+
"otp",
|
|
2991
|
+
"password",
|
|
2992
|
+
"passwd",
|
|
2993
|
+
"pin",
|
|
2994
|
+
"salt",
|
|
2995
|
+
"secret",
|
|
2996
|
+
"token",
|
|
2997
|
+
"uuid",
|
|
2998
|
+
"verificationcode"
|
|
2999
|
+
]);
|
|
3000
|
+
var NON_SECURITY_ID_WORDS = /* @__PURE__ */ new Set([
|
|
3001
|
+
"aria",
|
|
3002
|
+
"cache",
|
|
3003
|
+
"component",
|
|
3004
|
+
"correlation",
|
|
3005
|
+
"dev",
|
|
3006
|
+
"dialog",
|
|
3007
|
+
"dom",
|
|
3008
|
+
"element",
|
|
3009
|
+
"execution",
|
|
3010
|
+
"field",
|
|
3011
|
+
"form",
|
|
3012
|
+
"hmr",
|
|
3013
|
+
"input",
|
|
3014
|
+
"marker",
|
|
3015
|
+
"menu",
|
|
3016
|
+
"mock",
|
|
3017
|
+
"perf",
|
|
3018
|
+
"req",
|
|
3019
|
+
"request",
|
|
3020
|
+
"select",
|
|
3021
|
+
"tab",
|
|
3022
|
+
"temp",
|
|
3023
|
+
"test",
|
|
3024
|
+
"tmp",
|
|
3025
|
+
"trace"
|
|
3026
|
+
]);
|
|
3027
|
+
function nameWords(name) {
|
|
3028
|
+
return name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").split(/[^A-Za-z0-9]+/u).filter(Boolean).map((word) => word.toLowerCase());
|
|
3029
|
+
}
|
|
3030
|
+
function isStrongSecurityName(name) {
|
|
3031
|
+
const words = nameWords(name);
|
|
3032
|
+
return words.some((word) => STRONG_SECURITY_WORDS.has(word)) || words.some(
|
|
3033
|
+
(word, index) => word === "api" && words[index + 1] === "key" || word === "auth" && words[index + 1] === "id" || word === "verification" && words[index + 1] === "code"
|
|
3034
|
+
);
|
|
3035
|
+
}
|
|
3036
|
+
function isNonSecurityName(name) {
|
|
3037
|
+
return nameWords(name).some((word) => NON_SECURITY_ID_WORDS.has(word));
|
|
3038
|
+
}
|
|
2966
3039
|
var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
|
|
2967
3040
|
function isMathRandomCall(node) {
|
|
2968
3041
|
if (node.type !== "CallExpression") {
|
|
@@ -2975,70 +3048,57 @@ function isMathRandomCall(node) {
|
|
|
2975
3048
|
const { object, property } = callee;
|
|
2976
3049
|
return object.type === "Identifier" && object.name === "Math" && property.type === "Identifier" && property.name === "random";
|
|
2977
3050
|
}
|
|
2978
|
-
function
|
|
2979
|
-
|
|
2980
|
-
let
|
|
2981
|
-
while (parent) {
|
|
2982
|
-
if (parent.type === "MemberExpression" && parent.object === current && !parent.computed && parent.property.type === "Identifier" && parent.property.name === "toString") {
|
|
2983
|
-
const grandparent = parent.parent;
|
|
2984
|
-
if (grandparent && grandparent.type === "CallExpression" && grandparent.callee === parent) {
|
|
2985
|
-
const firstArg = grandparent.arguments[0];
|
|
2986
|
-
if (firstArg && firstArg.type === "Literal" && firstArg.value === 36) {
|
|
2987
|
-
return true;
|
|
2988
|
-
}
|
|
2989
|
-
}
|
|
2990
|
-
}
|
|
2991
|
-
if (parent.type === "MemberExpression" && parent.object === current) {
|
|
2992
|
-
current = parent;
|
|
2993
|
-
parent = current.parent;
|
|
2994
|
-
continue;
|
|
2995
|
-
}
|
|
2996
|
-
if (parent.type === "CallExpression" && parent.callee === current) {
|
|
2997
|
-
current = parent;
|
|
2998
|
-
parent = current.parent;
|
|
2999
|
-
continue;
|
|
3000
|
-
}
|
|
3001
|
-
break;
|
|
3002
|
-
}
|
|
3003
|
-
return false;
|
|
3004
|
-
}
|
|
3005
|
-
function findEnclosingName(node) {
|
|
3051
|
+
function findEnclosingNames(node) {
|
|
3052
|
+
const names = [];
|
|
3053
|
+
let directBinding = true;
|
|
3006
3054
|
let current = node;
|
|
3007
3055
|
let parent = current.parent;
|
|
3008
3056
|
while (parent) {
|
|
3009
3057
|
if (parent.type === "VariableDeclarator" && parent.init === current) {
|
|
3010
|
-
if (parent.id.type === "Identifier") {
|
|
3011
|
-
|
|
3058
|
+
if (directBinding && parent.id.type === "Identifier") {
|
|
3059
|
+
names.push(parent.id.name);
|
|
3012
3060
|
}
|
|
3013
|
-
return void 0;
|
|
3014
3061
|
}
|
|
3015
3062
|
if (parent.type === "Property" && parent.value === current) {
|
|
3016
3063
|
const key = parent.key;
|
|
3017
3064
|
if (!parent.computed && key.type === "Identifier") {
|
|
3018
|
-
|
|
3065
|
+
names.push(key.name);
|
|
3019
3066
|
}
|
|
3020
3067
|
if (key.type === "Literal" && typeof key.value === "string") {
|
|
3021
|
-
|
|
3068
|
+
names.push(key.value);
|
|
3022
3069
|
}
|
|
3023
|
-
|
|
3070
|
+
directBinding = false;
|
|
3024
3071
|
}
|
|
3025
3072
|
if (parent.type === "PropertyDefinition" && parent.value === current) {
|
|
3026
3073
|
const key = parent.key;
|
|
3027
3074
|
if (!parent.computed && key.type === "Identifier") {
|
|
3028
|
-
|
|
3075
|
+
names.push(key.name);
|
|
3029
3076
|
}
|
|
3030
3077
|
if (key.type === "Literal" && typeof key.value === "string") {
|
|
3031
|
-
|
|
3078
|
+
names.push(key.value);
|
|
3032
3079
|
}
|
|
3033
|
-
|
|
3080
|
+
directBinding = false;
|
|
3034
3081
|
}
|
|
3035
|
-
if (parent.type === "
|
|
3036
|
-
|
|
3082
|
+
if (parent.type === "AssignmentExpression" && parent.right === current) {
|
|
3083
|
+
if (directBinding && parent.left.type === "Identifier") names.push(parent.left.name);
|
|
3084
|
+
if (directBinding && parent.left.type === "MemberExpression" && !parent.left.computed && parent.left.property.type === "Identifier") {
|
|
3085
|
+
names.push(parent.left.property.name);
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
|
|
3089
|
+
directBinding = false;
|
|
3090
|
+
}
|
|
3091
|
+
if (parent.type === "FunctionDeclaration") {
|
|
3092
|
+
if (directBinding && parent.id !== null) names.push(parent.id.name);
|
|
3093
|
+
return names;
|
|
3094
|
+
}
|
|
3095
|
+
if (parent.type === "ExpressionStatement") {
|
|
3096
|
+
return names;
|
|
3037
3097
|
}
|
|
3038
3098
|
current = parent;
|
|
3039
3099
|
parent = current.parent;
|
|
3040
3100
|
}
|
|
3041
|
-
return
|
|
3101
|
+
return names;
|
|
3042
3102
|
}
|
|
3043
3103
|
function isConcatenatedIntoPathOrDomId(node) {
|
|
3044
3104
|
const valueNode = climbValueChain(node);
|
|
@@ -3124,20 +3184,17 @@ var no_insecure_random_id_default = createRule({
|
|
|
3124
3184
|
if (!isMathRandomCall(node)) {
|
|
3125
3185
|
return;
|
|
3126
3186
|
}
|
|
3127
|
-
const
|
|
3128
|
-
if (
|
|
3187
|
+
const names = findEnclosingNames(node);
|
|
3188
|
+
if (names.some(isStrongSecurityName)) {
|
|
3129
3189
|
context.report({ node, messageId: "insecureRandomId" });
|
|
3130
3190
|
return;
|
|
3131
3191
|
}
|
|
3132
|
-
if (
|
|
3192
|
+
if (names.some(isNonSecurityName)) {
|
|
3133
3193
|
return;
|
|
3134
3194
|
}
|
|
3135
3195
|
if (isConcatenatedIntoPathOrDomId(node)) {
|
|
3136
3196
|
return;
|
|
3137
3197
|
}
|
|
3138
|
-
if (isPartOfToString36Chain(node)) {
|
|
3139
|
-
context.report({ node, messageId: "insecureRandomId" });
|
|
3140
|
-
}
|
|
3141
3198
|
}
|
|
3142
3199
|
};
|
|
3143
3200
|
}
|
|
@@ -4801,7 +4858,8 @@ var DEFAULT_ALLOW = [
|
|
|
4801
4858
|
"[\\\\/](connectors|providers|integrations|adapters|fetchers)[\\\\/]",
|
|
4802
4859
|
"[\\\\/]notifications[\\\\/]",
|
|
4803
4860
|
"[Ss]ervice\\.[cm]?[jt]sx?$",
|
|
4804
|
-
"-(service|connector|adapter|sdk|fetcher)\\.[cm]?[jt]sx?$"
|
|
4861
|
+
"-(service|connector|adapter|sdk|fetcher)\\.[cm]?[jt]sx?$",
|
|
4862
|
+
"[\\\\/][^\\\\/]*(?:Client|client)\\.[cm]?[jt]sx?$"
|
|
4805
4863
|
];
|
|
4806
4864
|
var NON_PRODUCTION_TREE_RE = /[\\/](playwright|cypress|__testfixtures__)[\\/]/;
|
|
4807
4865
|
var GLOBAL_RECEIVERS = /* @__PURE__ */ new Set([
|
|
@@ -4935,7 +4993,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
4935
4993
|
defaultOptions: [{}],
|
|
4936
4994
|
create(context, [options]) {
|
|
4937
4995
|
const filename = context.filename;
|
|
4938
|
-
if (isTestFile(filename) || NON_PRODUCTION_TREE_RE.test(filename)) {
|
|
4996
|
+
if (isTestFile(filename) || isScriptFile(filename) || NON_PRODUCTION_TREE_RE.test(filename)) {
|
|
4939
4997
|
return {};
|
|
4940
4998
|
}
|
|
4941
4999
|
const patterns = options?.allow ?? DEFAULT_ALLOW;
|
|
@@ -4975,10 +5033,15 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
4975
5033
|
function isInternalApiUrl(node) {
|
|
4976
5034
|
const resolved = resolveNode2(node ?? void 0);
|
|
4977
5035
|
if (resolved?.type === AST_NODE_TYPES18.Literal) {
|
|
4978
|
-
return typeof resolved.value === "string" && resolved.value
|
|
5036
|
+
return typeof resolved.value === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(resolved.value);
|
|
4979
5037
|
}
|
|
4980
5038
|
if (resolved?.type === AST_NODE_TYPES18.TemplateLiteral) {
|
|
4981
|
-
|
|
5039
|
+
const prefix = resolved.quasis[0]?.value.cooked;
|
|
5040
|
+
return typeof prefix === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(prefix);
|
|
5041
|
+
}
|
|
5042
|
+
if (resolved?.type === AST_NODE_TYPES18.CallExpression && resolved.callee.type === AST_NODE_TYPES18.Identifier && resolved.callee.name === "withBase") {
|
|
5043
|
+
const first = resolved.arguments[0];
|
|
5044
|
+
return first !== void 0 && first.type !== AST_NODE_TYPES18.SpreadElement ? isInternalApiUrl(first) : false;
|
|
4982
5045
|
}
|
|
4983
5046
|
return resolved?.type === AST_NODE_TYPES18.BinaryExpression && resolved.operator === "+" && isInternalApiUrl(resolved.left);
|
|
4984
5047
|
}
|
|
@@ -5822,12 +5885,17 @@ var BLOB_REDACTION_TOKENS = /* @__PURE__ */ new Set([
|
|
|
5822
5885
|
"public"
|
|
5823
5886
|
]);
|
|
5824
5887
|
function rawBlobValueName(value) {
|
|
5888
|
+
if (value.type === "AwaitExpression") return rawBlobValueName(value.argument);
|
|
5889
|
+
if (value.type === "ChainExpression") return rawBlobValueName(value.expression);
|
|
5825
5890
|
if (value.type === "Identifier") {
|
|
5826
5891
|
return isRawBlobName(value.name) ? value.name : null;
|
|
5827
5892
|
}
|
|
5828
5893
|
if (value.type === "MemberExpression" && !value.computed && value.property.type === "Identifier") {
|
|
5829
5894
|
return isRawBlobName(value.property.name) ? value.property.name : null;
|
|
5830
5895
|
}
|
|
5896
|
+
if (value.type === "CallExpression" && value.arguments.length === 0 && value.callee.type === "MemberExpression" && !value.callee.computed && value.callee.object.type === "Identifier" && /^(?:res|response|\w+Response)$/.test(value.callee.object.name) && value.callee.property.type === "Identifier" && (value.callee.property.name === "json" || value.callee.property.name === "text")) {
|
|
5897
|
+
return `${value.callee.object.name}.${value.callee.property.name}()`;
|
|
5898
|
+
}
|
|
5831
5899
|
return null;
|
|
5832
5900
|
}
|
|
5833
5901
|
function isRawBlobName(name) {
|
|
@@ -6492,6 +6560,9 @@ var isExplanatory = (comment) => !DIRECTIVE_COMMENT_RE.test(comment.value);
|
|
|
6492
6560
|
function isTeardownCall(node) {
|
|
6493
6561
|
return node.type === AST_NODE_TYPES24.CallExpression && node.callee.type === AST_NODE_TYPES24.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES24.Identifier && TEARDOWN_METHODS.has(node.callee.property.name);
|
|
6494
6562
|
}
|
|
6563
|
+
function isCancelledWebShare(node) {
|
|
6564
|
+
return node.type === AST_NODE_TYPES24.CallExpression && node.callee.type === AST_NODE_TYPES24.MemberExpression && !node.callee.computed && node.callee.object.type === AST_NODE_TYPES24.Identifier && node.callee.object.name === "navigator" && node.callee.property.type === AST_NODE_TYPES24.Identifier && node.callee.property.name === "share";
|
|
6565
|
+
}
|
|
6495
6566
|
function isSilentHandler(handler) {
|
|
6496
6567
|
const body2 = handler.body;
|
|
6497
6568
|
if (body2.type !== AST_NODE_TYPES24.BlockStatement) {
|
|
@@ -6541,7 +6612,7 @@ var no_silent_promise_catch_default = createRule({
|
|
|
6541
6612
|
},
|
|
6542
6613
|
defaultOptions: [],
|
|
6543
6614
|
create(context) {
|
|
6544
|
-
if (isTestFile(context.filename)) {
|
|
6615
|
+
if (isTestFile(context.filename) || isScriptFile(context.filename)) {
|
|
6545
6616
|
return {};
|
|
6546
6617
|
}
|
|
6547
6618
|
const hasExplanatoryComment = (call, handler) => {
|
|
@@ -6574,7 +6645,10 @@ var no_silent_promise_catch_default = createRule({
|
|
|
6574
6645
|
if (isTeardownCall(node.callee.object)) {
|
|
6575
6646
|
return;
|
|
6576
6647
|
}
|
|
6577
|
-
if (node.
|
|
6648
|
+
if (isCancelledWebShare(node.callee.object)) {
|
|
6649
|
+
return;
|
|
6650
|
+
}
|
|
6651
|
+
if (node.parent.type === AST_NODE_TYPES24.MemberExpression && node.parent.object === node && !node.parent.computed && node.parent.property.type === AST_NODE_TYPES24.Identifier && node.parent.property.name === "then") {
|
|
6578
6652
|
return;
|
|
6579
6653
|
}
|
|
6580
6654
|
const expectedArguments = method === "catch" ? 1 : 2;
|
|
@@ -6886,7 +6960,20 @@ function isStringInitializedVariable(variable) {
|
|
|
6886
6960
|
if (declarator.type !== "VariableDeclarator") {
|
|
6887
6961
|
return false;
|
|
6888
6962
|
}
|
|
6889
|
-
|
|
6963
|
+
if (isStringLiteralInit(declarator.init)) return true;
|
|
6964
|
+
if (declarator.id.type === "Identifier" && declarator.id.typeAnnotation?.typeAnnotation.type === "TSStringKeyword") {
|
|
6965
|
+
return true;
|
|
6966
|
+
}
|
|
6967
|
+
return isTemplateStringsArrayElement(declarator.init, variable.scope);
|
|
6968
|
+
}
|
|
6969
|
+
function isTemplateStringsArrayElement(node, scope) {
|
|
6970
|
+
if (node?.type !== "MemberExpression" || !node.computed || node.object.type !== "Identifier") {
|
|
6971
|
+
return false;
|
|
6972
|
+
}
|
|
6973
|
+
const source = findVariable(scope, node.object.name);
|
|
6974
|
+
if (source?.defs.length !== 1) return false;
|
|
6975
|
+
const name = source.defs[0]?.name;
|
|
6976
|
+
return name?.type === "Identifier" && name.typeAnnotation?.typeAnnotation.type === "TSTypeReference" && name.typeAnnotation.typeAnnotation.typeName.type === "Identifier" && name.typeAnnotation.typeAnnotation.typeName.name === "TemplateStringsArray";
|
|
6890
6977
|
}
|
|
6891
6978
|
function isStringLiteralInit(node) {
|
|
6892
6979
|
if (node === null) {
|
|
@@ -6920,12 +7007,13 @@ function isConcatOperand(node, target) {
|
|
|
6920
7007
|
}
|
|
6921
7008
|
return false;
|
|
6922
7009
|
}
|
|
6923
|
-
function isDeclaredInsideLoop(variable,
|
|
7010
|
+
function isDeclaredInsideLoop(variable, repetition) {
|
|
6924
7011
|
const def = variable.defs[0];
|
|
6925
7012
|
if (def === void 0) {
|
|
6926
7013
|
return false;
|
|
6927
7014
|
}
|
|
6928
|
-
const body2 =
|
|
7015
|
+
const body2 = repetition.type === "CallExpression" ? repetition.arguments[0] : repetition.body;
|
|
7016
|
+
if (body2 === void 0 || body2.type === "SpreadElement") return false;
|
|
6929
7017
|
const [declStart, declEnd] = def.node.range;
|
|
6930
7018
|
const [bodyStart, bodyEnd] = body2.range;
|
|
6931
7019
|
return declStart >= bodyStart && declEnd <= bodyEnd;
|
|
@@ -6934,6 +7022,9 @@ function enclosingLoop(node) {
|
|
|
6934
7022
|
let child = node;
|
|
6935
7023
|
let parent = node.parent;
|
|
6936
7024
|
while (parent !== void 0 && parent !== null) {
|
|
7025
|
+
if ((parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression") && parent.parent.type === "CallExpression" && parent.parent.arguments[0] === parent && parent.parent.callee.type === "MemberExpression" && !parent.parent.callee.computed && parent.parent.callee.property.type === "Identifier" && parent.parent.callee.property.name === "forEach") {
|
|
7026
|
+
return parent.parent;
|
|
7027
|
+
}
|
|
6937
7028
|
if (LOOP_NODE_TYPES.has(parent.type)) {
|
|
6938
7029
|
const loop = parent;
|
|
6939
7030
|
if (loop.body === child) {
|
|
@@ -6945,6 +7036,17 @@ function enclosingLoop(node) {
|
|
|
6945
7036
|
}
|
|
6946
7037
|
return null;
|
|
6947
7038
|
}
|
|
7039
|
+
function isSmallStaticForLoop(node) {
|
|
7040
|
+
if (node.type !== "ForStatement" || node.init?.type !== "VariableDeclaration" || node.init.declarations.length !== 1 || node.test?.type !== "BinaryExpression" || node.test.operator !== "<" && node.test.operator !== "<=" || node.update?.type !== "UpdateExpression" || node.update.operator !== "++") {
|
|
7041
|
+
return false;
|
|
7042
|
+
}
|
|
7043
|
+
const declaration = node.init.declarations[0];
|
|
7044
|
+
if (declaration?.id.type !== "Identifier" || declaration.init?.type !== "Literal" || typeof declaration.init.value !== "number" || !Number.isInteger(declaration.init.value) || node.test.left.type !== "Identifier" || node.test.left.name !== declaration.id.name || node.test.right.type !== "Literal" || typeof node.test.right.value !== "number" || !Number.isInteger(node.test.right.value) || node.update.argument.type !== "Identifier" || node.update.argument.name !== declaration.id.name) {
|
|
7045
|
+
return false;
|
|
7046
|
+
}
|
|
7047
|
+
const iterations = node.test.right.value - declaration.init.value + (node.test.operator === "<=" ? 1 : 0);
|
|
7048
|
+
return iterations >= 0 && iterations <= 8;
|
|
7049
|
+
}
|
|
6948
7050
|
var no_string_concat_in_loop_default = createRule({
|
|
6949
7051
|
name: "no-string-concat-in-loop",
|
|
6950
7052
|
documentation: noStringConcatInLoopDocumentation,
|
|
@@ -6977,6 +7079,9 @@ var no_string_concat_in_loop_default = createRule({
|
|
|
6977
7079
|
if (loop === null) {
|
|
6978
7080
|
return;
|
|
6979
7081
|
}
|
|
7082
|
+
if (isSmallStaticForLoop(loop)) {
|
|
7083
|
+
return;
|
|
7084
|
+
}
|
|
6980
7085
|
const scope = context.sourceCode.getScope(node);
|
|
6981
7086
|
const variable = findVariable(scope, node.left.name);
|
|
6982
7087
|
if (variable === void 0) {
|
|
@@ -8830,8 +8935,10 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
|
|
|
8830
8935
|
let statusMemberCount = 0;
|
|
8831
8936
|
let hasFailurePayload = false;
|
|
8832
8937
|
let hasSuccessPayload = false;
|
|
8938
|
+
let hasUnrecognizedMember = false;
|
|
8833
8939
|
for (const member of typeLiteral.members) {
|
|
8834
8940
|
if (member.type !== AST_NODE_TYPES37.TSPropertySignature) {
|
|
8941
|
+
hasUnrecognizedMember = true;
|
|
8835
8942
|
continue;
|
|
8836
8943
|
}
|
|
8837
8944
|
const name = getMemberName(member);
|
|
@@ -8840,15 +8947,18 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
|
|
|
8840
8947
|
continue;
|
|
8841
8948
|
}
|
|
8842
8949
|
if (!member.optional || isBooleanTyped(member) || name === null) {
|
|
8950
|
+
hasUnrecognizedMember = true;
|
|
8843
8951
|
continue;
|
|
8844
8952
|
}
|
|
8845
8953
|
if (FAILURE_MEMBER_NAMES.has(name)) {
|
|
8846
8954
|
hasFailurePayload = true;
|
|
8847
8955
|
} else if (SUCCESS_PAYLOAD_MEMBER_NAMES.has(name)) {
|
|
8848
8956
|
hasSuccessPayload = true;
|
|
8957
|
+
} else {
|
|
8958
|
+
hasUnrecognizedMember = true;
|
|
8849
8959
|
}
|
|
8850
8960
|
}
|
|
8851
|
-
return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && hasSuccessPayload;
|
|
8961
|
+
return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && (hasSuccessPayload || !hasUnrecognizedMember);
|
|
8852
8962
|
}
|
|
8853
8963
|
function getMemberName(member) {
|
|
8854
8964
|
if (member.type !== AST_NODE_TYPES37.TSPropertySignature) {
|
|
@@ -10749,7 +10859,7 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
10749
10859
|
},
|
|
10750
10860
|
defaultOptions: [],
|
|
10751
10861
|
create(context) {
|
|
10752
|
-
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
10862
|
+
if (isTestFile(context.filename) || isScriptFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
10753
10863
|
return {};
|
|
10754
10864
|
}
|
|
10755
10865
|
const unvalidatedVariables = /* @__PURE__ */ new Set();
|
|
@@ -12404,6 +12514,26 @@ var require_fetch_timeout_default = createRule({
|
|
|
12404
12514
|
}
|
|
12405
12515
|
return callee.type === AST_NODE_TYPES50.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES50.Identifier && callee.property.name === "fetch" && callee.object.type === AST_NODE_TYPES50.Identifier && GLOBAL_OBJECTS2.has(callee.object.name) && resolvesToGlobal(callee.object);
|
|
12406
12516
|
}
|
|
12517
|
+
function localConstInitProvablyLacksSignal(identifier) {
|
|
12518
|
+
const variable = ASTUtils12.findVariable(
|
|
12519
|
+
context.sourceCode.getScope(identifier),
|
|
12520
|
+
identifier.name
|
|
12521
|
+
);
|
|
12522
|
+
if (variable?.defs.length !== 1) return false;
|
|
12523
|
+
const definition = variable.defs[0];
|
|
12524
|
+
if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init?.type !== AST_NODE_TYPES50.ObjectExpression || !initProvablyLacksSignal(definition.node.init)) {
|
|
12525
|
+
return false;
|
|
12526
|
+
}
|
|
12527
|
+
for (const reference of variable.references) {
|
|
12528
|
+
const ref = reference.identifier;
|
|
12529
|
+
if (ref === identifier || ref === definition.name) continue;
|
|
12530
|
+
const member = ref.parent;
|
|
12531
|
+
if (member.type !== AST_NODE_TYPES50.MemberExpression || member.object !== ref || member.computed || member.property.type !== AST_NODE_TYPES50.Identifier || member.property.name === "signal" || member.parent.type !== AST_NODE_TYPES50.AssignmentExpression || member.parent.left !== member) {
|
|
12532
|
+
return false;
|
|
12533
|
+
}
|
|
12534
|
+
}
|
|
12535
|
+
return true;
|
|
12536
|
+
}
|
|
12407
12537
|
return {
|
|
12408
12538
|
CallExpression(node) {
|
|
12409
12539
|
if (!isGlobalFetchCall2(node.callee)) {
|
|
@@ -12413,7 +12543,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
12413
12543
|
if (node.arguments.length === 1 && first !== void 0 && !isInlineUrl(first, resolvesToGlobal)) {
|
|
12414
12544
|
return;
|
|
12415
12545
|
}
|
|
12416
|
-
if (init === void 0 || initProvablyLacksSignal(init)) {
|
|
12546
|
+
if (init === void 0 || initProvablyLacksSignal(init) || init.type === AST_NODE_TYPES50.Identifier && localConstInitProvablyLacksSignal(init)) {
|
|
12417
12547
|
context.report({ node, messageId: "missingSignal" });
|
|
12418
12548
|
}
|
|
12419
12549
|
}
|
|
@@ -13168,8 +13298,8 @@ var require_zod_form_validation_default = createRule({
|
|
|
13168
13298
|
// src/rules/store-insert-requires-on-conflict.ts
|
|
13169
13299
|
import "@typescript-eslint/utils";
|
|
13170
13300
|
var storeInsertRequiresOnConflictDocumentation = {
|
|
13171
|
-
summary: "Require
|
|
13172
|
-
rationale: "A
|
|
13301
|
+
summary: "Require embedded inserts in explicitly replayable callables to carry conflict handling.",
|
|
13302
|
+
rationale: "A callable named as an enqueue, seed, migration, schedule, ensure, or upsert promises replay safety.",
|
|
13173
13303
|
remediation: "Add an appropriate `ON CONFLICT` action or supported replay-safe insert form.",
|
|
13174
13304
|
category: "correctness",
|
|
13175
13305
|
examples: [
|
|
@@ -13178,7 +13308,25 @@ var storeInsertRequiresOnConflictDocumentation = {
|
|
|
13178
13308
|
]
|
|
13179
13309
|
};
|
|
13180
13310
|
var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
|
|
13181
|
-
var CONFLICT_HANDLED = /\bON\s+CONFLICT\b|\bON\s+DUPLICATE\s+KEY\b|\bINSERT\s+OR\s+(?:IGNORE|REPLACE)\b/i;
|
|
13311
|
+
var CONFLICT_HANDLED = /\bON\s+CONFLICT\b|\bON\s+DUPLICATE\s+KEY\b|\bINSERT\s+OR\s+(?:IGNORE|REPLACE)\b|\bINSERT\b[\s\S]*?\bSELECT\b[\s\S]*?\bWHERE\s+NOT\s+EXISTS\b/i;
|
|
13312
|
+
var REPLAY_CONTRACT_NAME = /(?:enqueue|ensure|migrate|recordOnce|schedule|seed|upsert|getOrCreate|createIfAbsent|insertIfAbsent)/i;
|
|
13313
|
+
function owningCallableName(node) {
|
|
13314
|
+
for (let current = node.parent; current !== null && current !== void 0; current = current.parent) {
|
|
13315
|
+
if (current.type === "FunctionDeclaration") {
|
|
13316
|
+
return current.id?.name ?? null;
|
|
13317
|
+
}
|
|
13318
|
+
if (current.type === "MethodDefinition") {
|
|
13319
|
+
return current.key.type === "Identifier" ? current.key.name : null;
|
|
13320
|
+
}
|
|
13321
|
+
if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "VariableDeclarator" && current.parent.id.type === "Identifier") {
|
|
13322
|
+
return current.parent.id.name;
|
|
13323
|
+
}
|
|
13324
|
+
if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "Property" && current.parent.key.type === "Identifier") {
|
|
13325
|
+
return current.parent.key.name;
|
|
13326
|
+
}
|
|
13327
|
+
}
|
|
13328
|
+
return null;
|
|
13329
|
+
}
|
|
13182
13330
|
var INSERT_GATE = /insert/i;
|
|
13183
13331
|
var store_insert_requires_on_conflict_default = createRule({
|
|
13184
13332
|
name: "store-insert-requires-on-conflict",
|
|
@@ -13186,7 +13334,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
13186
13334
|
meta: {
|
|
13187
13335
|
type: "problem",
|
|
13188
13336
|
docs: {
|
|
13189
|
-
description: "Require
|
|
13337
|
+
description: "Require embedded inserts in explicitly replayable callables to carry conflict handling."
|
|
13190
13338
|
},
|
|
13191
13339
|
schema: [],
|
|
13192
13340
|
messages: {
|
|
@@ -13202,6 +13350,10 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
13202
13350
|
if (!INSERT_WRITE.test(sql) || CONFLICT_HANDLED.test(sql)) {
|
|
13203
13351
|
return;
|
|
13204
13352
|
}
|
|
13353
|
+
const owner = owningCallableName(node);
|
|
13354
|
+
if (owner !== null && !REPLAY_CONTRACT_NAME.test(owner)) {
|
|
13355
|
+
return;
|
|
13356
|
+
}
|
|
13205
13357
|
context.report({ node, messageId: "storeInsertRequiresOnConflict" });
|
|
13206
13358
|
});
|
|
13207
13359
|
}
|
|
@@ -13891,7 +14043,7 @@ var rules = {
|
|
|
13891
14043
|
};
|
|
13892
14044
|
var meta = {
|
|
13893
14045
|
name: "@sarj/eslint-plugin",
|
|
13894
|
-
version: "15.
|
|
14046
|
+
version: "15.1.0"
|
|
13895
14047
|
};
|
|
13896
14048
|
var applicationOnlyRules = [
|
|
13897
14049
|
"no-restricted-library-load",
|