@sarj/eslint-plugin 15.0.0 → 15.2.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 +554 -133
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +579 -154
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1010,11 +1010,11 @@ function isTrivialInitializer(node) {
|
|
|
1010
1010
|
}
|
|
1011
1011
|
function restatesStatementHead(body2, statement) {
|
|
1012
1012
|
if (statement === null) return false;
|
|
1013
|
-
const
|
|
1014
|
-
const opener =
|
|
1015
|
-
if (opener === void 0 ||
|
|
1013
|
+
const words2 = body2.match(/[A-Za-z][\w$]*/g) ?? [];
|
|
1014
|
+
const opener = words2[0];
|
|
1015
|
+
if (opener === void 0 || words2.length > NARRATION_MAX_WORDS) return false;
|
|
1016
1016
|
if (!NARRATION_VERB_RE.test(opener)) return false;
|
|
1017
|
-
const content =
|
|
1017
|
+
const content = words2.slice(1).map(normalizeToken).filter((word) => !NARRATION_STOPWORDS.has(word));
|
|
1018
1018
|
if (content.length < NARRATION_MIN_CONTENT) return false;
|
|
1019
1019
|
const head = statement.split("(")[0] ?? statement;
|
|
1020
1020
|
const code = headTokens(head);
|
|
@@ -1282,8 +1282,8 @@ function isRedundantNarration(body2, statementBelow, standalone, isolatedEnumera
|
|
|
1282
1282
|
if (META_COMMENTARY_RE.test(t) && !justified) return true;
|
|
1283
1283
|
if (isBareDeferral(t) && !justified) return true;
|
|
1284
1284
|
if (HELPER_OPENER_RE.test(t) || LETS_RE.test(t)) return true;
|
|
1285
|
-
const
|
|
1286
|
-
if (
|
|
1285
|
+
const words2 = t.split(/\s+/);
|
|
1286
|
+
if (words2.length > 1 && words2.length <= 4 && DUMMY_TRANSLATION_RE.test(t) && !/[():=]/.test(t)) {
|
|
1287
1287
|
const lowerT = t.toLowerCase();
|
|
1288
1288
|
if (!RATIONALE_WORDS.some((word) => lowerT.includes(word)) && restatesWholeStatement(t, statementBelow)) {
|
|
1289
1289
|
return true;
|
|
@@ -1365,8 +1365,8 @@ function isWeakWalkthroughComment(body2, statement) {
|
|
|
1365
1365
|
if (normalized.length === 0 || normalized.endsWith("?") || normalized.split(/\s+/).length > WALL_MAX_WORDS || isDirective(normalized) || isProtected(normalized) || !WALL_NARRATION_RE.test(normalized)) {
|
|
1366
1366
|
return false;
|
|
1367
1367
|
}
|
|
1368
|
-
const
|
|
1369
|
-
const described =
|
|
1368
|
+
const words2 = contentTokens(normalized);
|
|
1369
|
+
const described = words2.slice(1);
|
|
1370
1370
|
if (described.length === 0) return false;
|
|
1371
1371
|
const code = codeTokens(statement);
|
|
1372
1372
|
const matched = described.filter((word) => restates([word], code)).length;
|
|
@@ -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: [{}],
|
|
@@ -2688,7 +2710,7 @@ var no_fat_try_blocks_default = createRule({
|
|
|
2688
2710
|
const sourceCode = context.sourceCode;
|
|
2689
2711
|
return {
|
|
2690
2712
|
TryStatement(node) {
|
|
2691
|
-
if (node.finalizer !== null) {
|
|
2713
|
+
if (node.finalizer !== null && node.handler === null) {
|
|
2692
2714
|
return;
|
|
2693
2715
|
}
|
|
2694
2716
|
if (handlerRethrows(node.handler)) {
|
|
@@ -2928,15 +2950,32 @@ var noHandRolledSpinnerDocumentation = {
|
|
|
2928
2950
|
rationale: "One-off loading indicators duplicate a shared primitive and let accessibility and styling diverge.",
|
|
2929
2951
|
remediation: "Render the design-system Spinner component instead.",
|
|
2930
2952
|
category: "maintainability",
|
|
2931
|
-
limitations: ["Only static className values on div and span elements are inspected."],
|
|
2953
|
+
limitations: ["Only static className values on div and span elements are inspected; tests, stories, generated files, and the design-system implementation are excluded."],
|
|
2932
2954
|
examples: [
|
|
2933
2955
|
{ id: "design-system-spinner", title: "Use the shared spinner", outcome: "no-match", files: [{ path: "src/loading-state.tsx", source: '<Spinner className="size-4" />' }], focusPath: "src/loading-state.tsx", expectedCount: 0, public: true },
|
|
2934
2956
|
{ id: "border-ring-spinner", title: "Do not rebuild a spinner", outcome: "match", files: [{ path: "src/loading-state.tsx", source: '<div className="size-4 animate-spin rounded-full border-2 border-t-transparent" />' }], focusPath: "src/loading-state.tsx", expectedCount: 1, public: true }
|
|
2935
2957
|
]
|
|
2936
2958
|
};
|
|
2937
2959
|
var DESIGN_SYSTEM_PATH = /(?:^|[/\\])components[/\\]ui[/\\]/u;
|
|
2938
|
-
var
|
|
2939
|
-
var
|
|
2960
|
+
var DIRECTIONAL_BORDER = /^border-([trblsexy])-(.+)$/u;
|
|
2961
|
+
var CSS_LENGTH = /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:cap|ch|cm|dvh|dvw|em|ex|ic|in|lh|lvh|lvw|mm|pc|pt|px|q|rcap|rch|rem|rex|ric|rlh|svh|svw|vb|vh|vi|vmax|vmin|vw|%)$/u;
|
|
2962
|
+
var ARBITRARY_LENGTH_FUNCTION = /^(?:calc|clamp|max|min)\(.+\)$/u;
|
|
2963
|
+
function isBorderWidthValue(value) {
|
|
2964
|
+
if (/^\d+$/u.test(value)) return true;
|
|
2965
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
2966
|
+
const arbitrary = value.slice(1, -1);
|
|
2967
|
+
const length = arbitrary.startsWith("length:") ? arbitrary.slice("length:".length) : arbitrary;
|
|
2968
|
+
return CSS_LENGTH.test(length) || ARBITRARY_LENGTH_FUNCTION.test(length) || arbitrary.startsWith("length:") && /^var\(.+\)$/u.test(length);
|
|
2969
|
+
}
|
|
2970
|
+
return value.startsWith("(length:") && value.endsWith(")") && value.length > "(length:)".length;
|
|
2971
|
+
}
|
|
2972
|
+
function isBorderWidth(token) {
|
|
2973
|
+
return token === "border" || token.startsWith("border-") && isBorderWidthValue(token.slice("border-".length));
|
|
2974
|
+
}
|
|
2975
|
+
function isContrastingEdge(token) {
|
|
2976
|
+
const match = DIRECTIONAL_BORDER.exec(token);
|
|
2977
|
+
return match?.[2] !== void 0 && !isBorderWidthValue(match[2]);
|
|
2978
|
+
}
|
|
2940
2979
|
function staticClassName(attribute) {
|
|
2941
2980
|
const value = attribute.value;
|
|
2942
2981
|
if (value?.type === import_utils13.AST_NODE_TYPES.Literal && typeof value.value === "string") {
|
|
@@ -2965,7 +3004,7 @@ var no_hand_rolled_spinner_default = createRule({
|
|
|
2965
3004
|
},
|
|
2966
3005
|
defaultOptions: [],
|
|
2967
3006
|
create(context) {
|
|
2968
|
-
if (DESIGN_SYSTEM_PATH.test(context.filename)) {
|
|
3007
|
+
if (DESIGN_SYSTEM_PATH.test(context.filename) || isTestFile(context.filename) || isStoryFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
2969
3008
|
return {};
|
|
2970
3009
|
}
|
|
2971
3010
|
return {
|
|
@@ -2980,7 +3019,7 @@ var no_hand_rolled_spinner_default = createRule({
|
|
|
2980
3019
|
const className = staticClassName(classNameAttribute);
|
|
2981
3020
|
if (className === null) return;
|
|
2982
3021
|
const classes = className.split(/\s+/u);
|
|
2983
|
-
if (classes.includes("animate-spin") && classes.includes("rounded-full") && classes.some(
|
|
3022
|
+
if (classes.includes("animate-spin") && classes.includes("rounded-full") && classes.some(isBorderWidth) && classes.some(isContrastingEdge)) {
|
|
2984
3023
|
context.report({ node, messageId: "handRolledSpinner" });
|
|
2985
3024
|
}
|
|
2986
3025
|
}
|
|
@@ -3001,8 +3040,59 @@ var noInsecureRandomIdDocumentation = {
|
|
|
3001
3040
|
{ 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
3041
|
]
|
|
3003
3042
|
};
|
|
3004
|
-
var
|
|
3005
|
-
|
|
3043
|
+
var STRONG_SECURITY_WORDS = /* @__PURE__ */ new Set([
|
|
3044
|
+
"apikey",
|
|
3045
|
+
"csrf",
|
|
3046
|
+
"nonce",
|
|
3047
|
+
"otp",
|
|
3048
|
+
"password",
|
|
3049
|
+
"passwd",
|
|
3050
|
+
"pin",
|
|
3051
|
+
"salt",
|
|
3052
|
+
"secret",
|
|
3053
|
+
"token",
|
|
3054
|
+
"uuid",
|
|
3055
|
+
"verificationcode"
|
|
3056
|
+
]);
|
|
3057
|
+
var NON_SECURITY_ID_WORDS = /* @__PURE__ */ new Set([
|
|
3058
|
+
"aria",
|
|
3059
|
+
"cache",
|
|
3060
|
+
"component",
|
|
3061
|
+
"correlation",
|
|
3062
|
+
"dev",
|
|
3063
|
+
"dialog",
|
|
3064
|
+
"dom",
|
|
3065
|
+
"element",
|
|
3066
|
+
"execution",
|
|
3067
|
+
"field",
|
|
3068
|
+
"form",
|
|
3069
|
+
"hmr",
|
|
3070
|
+
"input",
|
|
3071
|
+
"marker",
|
|
3072
|
+
"menu",
|
|
3073
|
+
"mock",
|
|
3074
|
+
"perf",
|
|
3075
|
+
"req",
|
|
3076
|
+
"request",
|
|
3077
|
+
"select",
|
|
3078
|
+
"tab",
|
|
3079
|
+
"temp",
|
|
3080
|
+
"test",
|
|
3081
|
+
"tmp",
|
|
3082
|
+
"trace"
|
|
3083
|
+
]);
|
|
3084
|
+
function nameWords(name) {
|
|
3085
|
+
return name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").split(/[^A-Za-z0-9]+/u).filter(Boolean).map((word) => word.toLowerCase());
|
|
3086
|
+
}
|
|
3087
|
+
function isStrongSecurityName(name) {
|
|
3088
|
+
const words2 = nameWords(name);
|
|
3089
|
+
return words2.some((word) => STRONG_SECURITY_WORDS.has(word)) || words2.some(
|
|
3090
|
+
(word, index) => word === "api" && words2[index + 1] === "key" || word === "auth" && words2[index + 1] === "id" || word === "verification" && words2[index + 1] === "code"
|
|
3091
|
+
);
|
|
3092
|
+
}
|
|
3093
|
+
function isNonSecurityName(name) {
|
|
3094
|
+
return nameWords(name).some((word) => NON_SECURITY_ID_WORDS.has(word));
|
|
3095
|
+
}
|
|
3006
3096
|
var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
|
|
3007
3097
|
function isMathRandomCall(node) {
|
|
3008
3098
|
if (node.type !== "CallExpression") {
|
|
@@ -3015,70 +3105,57 @@ function isMathRandomCall(node) {
|
|
|
3015
3105
|
const { object, property } = callee;
|
|
3016
3106
|
return object.type === "Identifier" && object.name === "Math" && property.type === "Identifier" && property.name === "random";
|
|
3017
3107
|
}
|
|
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) {
|
|
3108
|
+
function findEnclosingNames(node) {
|
|
3109
|
+
const names = [];
|
|
3110
|
+
let directBinding = true;
|
|
3046
3111
|
let current = node;
|
|
3047
3112
|
let parent = current.parent;
|
|
3048
3113
|
while (parent) {
|
|
3049
3114
|
if (parent.type === "VariableDeclarator" && parent.init === current) {
|
|
3050
|
-
if (parent.id.type === "Identifier") {
|
|
3051
|
-
|
|
3115
|
+
if (directBinding && parent.id.type === "Identifier") {
|
|
3116
|
+
names.push(parent.id.name);
|
|
3052
3117
|
}
|
|
3053
|
-
return void 0;
|
|
3054
3118
|
}
|
|
3055
3119
|
if (parent.type === "Property" && parent.value === current) {
|
|
3056
3120
|
const key = parent.key;
|
|
3057
3121
|
if (!parent.computed && key.type === "Identifier") {
|
|
3058
|
-
|
|
3122
|
+
names.push(key.name);
|
|
3059
3123
|
}
|
|
3060
3124
|
if (key.type === "Literal" && typeof key.value === "string") {
|
|
3061
|
-
|
|
3125
|
+
names.push(key.value);
|
|
3062
3126
|
}
|
|
3063
|
-
|
|
3127
|
+
directBinding = false;
|
|
3064
3128
|
}
|
|
3065
3129
|
if (parent.type === "PropertyDefinition" && parent.value === current) {
|
|
3066
3130
|
const key = parent.key;
|
|
3067
3131
|
if (!parent.computed && key.type === "Identifier") {
|
|
3068
|
-
|
|
3132
|
+
names.push(key.name);
|
|
3069
3133
|
}
|
|
3070
3134
|
if (key.type === "Literal" && typeof key.value === "string") {
|
|
3071
|
-
|
|
3135
|
+
names.push(key.value);
|
|
3072
3136
|
}
|
|
3073
|
-
|
|
3137
|
+
directBinding = false;
|
|
3074
3138
|
}
|
|
3075
|
-
if (parent.type === "
|
|
3076
|
-
|
|
3139
|
+
if (parent.type === "AssignmentExpression" && parent.right === current) {
|
|
3140
|
+
if (directBinding && parent.left.type === "Identifier") names.push(parent.left.name);
|
|
3141
|
+
if (directBinding && parent.left.type === "MemberExpression" && !parent.left.computed && parent.left.property.type === "Identifier") {
|
|
3142
|
+
names.push(parent.left.property.name);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
|
|
3146
|
+
directBinding = false;
|
|
3147
|
+
}
|
|
3148
|
+
if (parent.type === "FunctionDeclaration") {
|
|
3149
|
+
if (directBinding && parent.id !== null) names.push(parent.id.name);
|
|
3150
|
+
return names;
|
|
3151
|
+
}
|
|
3152
|
+
if (parent.type === "ExpressionStatement") {
|
|
3153
|
+
return names;
|
|
3077
3154
|
}
|
|
3078
3155
|
current = parent;
|
|
3079
3156
|
parent = current.parent;
|
|
3080
3157
|
}
|
|
3081
|
-
return
|
|
3158
|
+
return names;
|
|
3082
3159
|
}
|
|
3083
3160
|
function isConcatenatedIntoPathOrDomId(node) {
|
|
3084
3161
|
const valueNode = climbValueChain(node);
|
|
@@ -3164,20 +3241,17 @@ var no_insecure_random_id_default = createRule({
|
|
|
3164
3241
|
if (!isMathRandomCall(node)) {
|
|
3165
3242
|
return;
|
|
3166
3243
|
}
|
|
3167
|
-
const
|
|
3168
|
-
if (
|
|
3244
|
+
const names = findEnclosingNames(node);
|
|
3245
|
+
if (names.some(isStrongSecurityName)) {
|
|
3169
3246
|
context.report({ node, messageId: "insecureRandomId" });
|
|
3170
3247
|
return;
|
|
3171
3248
|
}
|
|
3172
|
-
if (
|
|
3249
|
+
if (names.some(isNonSecurityName)) {
|
|
3173
3250
|
return;
|
|
3174
3251
|
}
|
|
3175
3252
|
if (isConcatenatedIntoPathOrDomId(node)) {
|
|
3176
3253
|
return;
|
|
3177
3254
|
}
|
|
3178
|
-
if (isPartOfToString36Chain(node)) {
|
|
3179
|
-
context.report({ node, messageId: "insecureRandomId" });
|
|
3180
|
-
}
|
|
3181
3255
|
}
|
|
3182
3256
|
};
|
|
3183
3257
|
}
|
|
@@ -3975,7 +4049,7 @@ var VALUE_TAG_RE = /@(example|deprecated|see|remarks|throws|internal|public|alph
|
|
|
3975
4049
|
var BOUNDARY_RE = /(?<=[.!?])["'`)\]]*\s+(?=[A-Z0-9`])/;
|
|
3976
4050
|
var BULLET_RE = /^\s*(?:[-*+] |\d+[.)] )/;
|
|
3977
4051
|
var HEADING_RE = /^[A-Za-z][A-Za-z ]+:$/;
|
|
3978
|
-
var TECHNICAL_ANCHOR_RE = /https?:\/\/|`[^`\n]+`|:[a-z][a-z0-9_-]*:|(["'])[^"'\n]+\1|\d|\b[a-z][a-z0-9]*[A-Z][A-Za-z0-9]*\b|\b[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9_]+\b|(?:^|\s)(?:[\w.-]+\/)+[\w.-]+|\b[\w.-]+\.(?:py|pyi|js|jsx|ts|tsx|json|ya?ml|toml|csv|parquet|md)\b|->|=>|==|!=|<=|>=|\|/mu;
|
|
4052
|
+
var TECHNICAL_ANCHOR_RE = /https?:\/\/|`[^`\n]+`|:[a-z][a-z0-9_-]*:|(["'])[^"'\n]+\1|\bv?\d+\.\d+(?:\.\d+)?\b|\b\d+(?:\.\d+)?\s?(?:ns|us|ms|s|sec|secs|seconds?|mins?|minutes?|hours?|days?|bytes?|kib|mib|gib|kb|mb|gb|hz|khz|mhz|px|%)\b|\b[a-z][a-z0-9]*[A-Z][A-Za-z0-9]*\b|\b[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9_]+\b|(?:^|\s)(?:[\w.-]+\/)+[\w.-]+|\b[\w.-]+\.(?:py|pyi|js|jsx|ts|tsx|json|ya?ml|toml|csv|parquet|md)\b|->|=>|==|!=|<=|>=|\|/mu;
|
|
3979
4053
|
function body(comment) {
|
|
3980
4054
|
return comment.value.replace(/^\*/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, "")).join("\n").trim();
|
|
3981
4055
|
}
|
|
@@ -4839,7 +4913,8 @@ var DEFAULT_ALLOW = [
|
|
|
4839
4913
|
"[\\\\/](connectors|providers|integrations|adapters|fetchers)[\\\\/]",
|
|
4840
4914
|
"[\\\\/]notifications[\\\\/]",
|
|
4841
4915
|
"[Ss]ervice\\.[cm]?[jt]sx?$",
|
|
4842
|
-
"-(service|connector|adapter|sdk|fetcher)\\.[cm]?[jt]sx?$"
|
|
4916
|
+
"-(service|connector|adapter|sdk|fetcher)\\.[cm]?[jt]sx?$",
|
|
4917
|
+
"[\\\\/][^\\\\/]*(?:Client|client)\\.[cm]?[jt]sx?$"
|
|
4843
4918
|
];
|
|
4844
4919
|
var NON_PRODUCTION_TREE_RE = /[\\/](playwright|cypress|__testfixtures__)[\\/]/;
|
|
4845
4920
|
var GLOBAL_RECEIVERS = /* @__PURE__ */ new Set([
|
|
@@ -4973,7 +5048,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
4973
5048
|
defaultOptions: [{}],
|
|
4974
5049
|
create(context, [options]) {
|
|
4975
5050
|
const filename = context.filename;
|
|
4976
|
-
if (isTestFile(filename) || NON_PRODUCTION_TREE_RE.test(filename)) {
|
|
5051
|
+
if (isTestFile(filename) || isScriptFile(filename) || NON_PRODUCTION_TREE_RE.test(filename)) {
|
|
4977
5052
|
return {};
|
|
4978
5053
|
}
|
|
4979
5054
|
const patterns = options?.allow ?? DEFAULT_ALLOW;
|
|
@@ -5013,10 +5088,15 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
5013
5088
|
function isInternalApiUrl(node) {
|
|
5014
5089
|
const resolved = resolveNode2(node ?? void 0);
|
|
5015
5090
|
if (resolved?.type === import_utils25.AST_NODE_TYPES.Literal) {
|
|
5016
|
-
return typeof resolved.value === "string" && resolved.value
|
|
5091
|
+
return typeof resolved.value === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(resolved.value);
|
|
5017
5092
|
}
|
|
5018
5093
|
if (resolved?.type === import_utils25.AST_NODE_TYPES.TemplateLiteral) {
|
|
5019
|
-
|
|
5094
|
+
const prefix = resolved.quasis[0]?.value.cooked;
|
|
5095
|
+
return typeof prefix === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(prefix);
|
|
5096
|
+
}
|
|
5097
|
+
if (resolved?.type === import_utils25.AST_NODE_TYPES.CallExpression && resolved.callee.type === import_utils25.AST_NODE_TYPES.Identifier && resolved.callee.name === "withBase") {
|
|
5098
|
+
const first = resolved.arguments[0];
|
|
5099
|
+
return first !== void 0 && first.type !== import_utils25.AST_NODE_TYPES.SpreadElement ? isInternalApiUrl(first) : false;
|
|
5020
5100
|
}
|
|
5021
5101
|
return resolved?.type === import_utils25.AST_NODE_TYPES.BinaryExpression && resolved.operator === "+" && isInternalApiUrl(resolved.left);
|
|
5022
5102
|
}
|
|
@@ -5860,12 +5940,17 @@ var BLOB_REDACTION_TOKENS = /* @__PURE__ */ new Set([
|
|
|
5860
5940
|
"public"
|
|
5861
5941
|
]);
|
|
5862
5942
|
function rawBlobValueName(value) {
|
|
5943
|
+
if (value.type === "AwaitExpression") return rawBlobValueName(value.argument);
|
|
5944
|
+
if (value.type === "ChainExpression") return rawBlobValueName(value.expression);
|
|
5863
5945
|
if (value.type === "Identifier") {
|
|
5864
5946
|
return isRawBlobName(value.name) ? value.name : null;
|
|
5865
5947
|
}
|
|
5866
5948
|
if (value.type === "MemberExpression" && !value.computed && value.property.type === "Identifier") {
|
|
5867
5949
|
return isRawBlobName(value.property.name) ? value.property.name : null;
|
|
5868
5950
|
}
|
|
5951
|
+
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")) {
|
|
5952
|
+
return `${value.callee.object.name}.${value.callee.property.name}()`;
|
|
5953
|
+
}
|
|
5869
5954
|
return null;
|
|
5870
5955
|
}
|
|
5871
5956
|
function isRawBlobName(name) {
|
|
@@ -6530,6 +6615,9 @@ var isExplanatory = (comment) => !DIRECTIVE_COMMENT_RE.test(comment.value);
|
|
|
6530
6615
|
function isTeardownCall(node) {
|
|
6531
6616
|
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
6617
|
}
|
|
6618
|
+
function isCancelledWebShare(node) {
|
|
6619
|
+
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";
|
|
6620
|
+
}
|
|
6533
6621
|
function isSilentHandler(handler) {
|
|
6534
6622
|
const body2 = handler.body;
|
|
6535
6623
|
if (body2.type !== import_utils33.AST_NODE_TYPES.BlockStatement) {
|
|
@@ -6579,7 +6667,7 @@ var no_silent_promise_catch_default = createRule({
|
|
|
6579
6667
|
},
|
|
6580
6668
|
defaultOptions: [],
|
|
6581
6669
|
create(context) {
|
|
6582
|
-
if (isTestFile(context.filename)) {
|
|
6670
|
+
if (isTestFile(context.filename) || isScriptFile(context.filename)) {
|
|
6583
6671
|
return {};
|
|
6584
6672
|
}
|
|
6585
6673
|
const hasExplanatoryComment = (call, handler) => {
|
|
@@ -6612,7 +6700,10 @@ var no_silent_promise_catch_default = createRule({
|
|
|
6612
6700
|
if (isTeardownCall(node.callee.object)) {
|
|
6613
6701
|
return;
|
|
6614
6702
|
}
|
|
6615
|
-
if (node.
|
|
6703
|
+
if (isCancelledWebShare(node.callee.object)) {
|
|
6704
|
+
return;
|
|
6705
|
+
}
|
|
6706
|
+
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
6707
|
return;
|
|
6617
6708
|
}
|
|
6618
6709
|
const expectedArguments = method === "catch" ? 1 : 2;
|
|
@@ -6774,22 +6865,25 @@ var noStorageInStatelessModulesDocumentation = {
|
|
|
6774
6865
|
rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
|
|
6775
6866
|
remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
|
|
6776
6867
|
category: "architecture",
|
|
6777
|
-
limitations: ["The rule is disabled until module path patterns are configured
|
|
6868
|
+
limitations: ["The rule is disabled until module path patterns are configured, recognizes only configured storage method names, and requires storage-like receiver evidence for the overloaded `put` method."],
|
|
6778
6869
|
examples: [
|
|
6779
6870
|
{ id: "system-of-record", title: "Read from the system of record", outcome: "no-match", files: [{ path: "src/engineer-digest/post.ts", source: "const issues = await linear.listIssues();" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 0, public: true },
|
|
6780
6871
|
{ id: "private-storage", title: "Do not write private state in a stateless module", outcome: "match", files: [{ path: "src/engineer-digest/post.ts", source: "await kv.put('digest:last', timestamp);" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 1, public: true }
|
|
6781
6872
|
]
|
|
6782
6873
|
};
|
|
6783
6874
|
function compile2(patterns) {
|
|
6784
|
-
|
|
6785
|
-
for (const pattern of patterns) {
|
|
6786
|
-
try {
|
|
6787
|
-
compiled.push(new RegExp(pattern));
|
|
6788
|
-
} catch {
|
|
6789
|
-
}
|
|
6790
|
-
}
|
|
6791
|
-
return compiled;
|
|
6875
|
+
return patterns.map((pattern) => new RegExp(pattern));
|
|
6792
6876
|
}
|
|
6877
|
+
var STORAGE_RECEIVER_WORDS = /* @__PURE__ */ new Set([
|
|
6878
|
+
"bucket",
|
|
6879
|
+
"cache",
|
|
6880
|
+
"kv",
|
|
6881
|
+
"namespace",
|
|
6882
|
+
"r2",
|
|
6883
|
+
"redis",
|
|
6884
|
+
"storage",
|
|
6885
|
+
"store"
|
|
6886
|
+
]);
|
|
6793
6887
|
function storageMethodName(node, methods) {
|
|
6794
6888
|
const callee = node.callee;
|
|
6795
6889
|
if (callee.type !== import_utils35.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== import_utils35.AST_NODE_TYPES.Identifier) {
|
|
@@ -6802,8 +6896,31 @@ function storageMethodName(node, methods) {
|
|
|
6802
6896
|
if (node.arguments.length < (MIN_ARGUMENTS.get(name) ?? 1)) {
|
|
6803
6897
|
return null;
|
|
6804
6898
|
}
|
|
6899
|
+
if (name === "put" && !isStorageLikeReceiver(callee.object)) {
|
|
6900
|
+
return null;
|
|
6901
|
+
}
|
|
6805
6902
|
return name;
|
|
6806
6903
|
}
|
|
6904
|
+
function isStorageLikeReceiver(node) {
|
|
6905
|
+
if (node.type === import_utils35.AST_NODE_TYPES.Identifier) {
|
|
6906
|
+
return isStorageIdentifier(node.name);
|
|
6907
|
+
}
|
|
6908
|
+
if (node.type !== import_utils35.AST_NODE_TYPES.MemberExpression) {
|
|
6909
|
+
return false;
|
|
6910
|
+
}
|
|
6911
|
+
if (!node.computed && node.property.type === import_utils35.AST_NODE_TYPES.Identifier && isStorageIdentifier(node.property.name)) {
|
|
6912
|
+
return true;
|
|
6913
|
+
}
|
|
6914
|
+
return isStorageLikeReceiver(node.object);
|
|
6915
|
+
}
|
|
6916
|
+
function isStorageIdentifier(name) {
|
|
6917
|
+
return identifierWords(name).some(
|
|
6918
|
+
(word) => STORAGE_RECEIVER_WORDS.has(word.toLowerCase())
|
|
6919
|
+
);
|
|
6920
|
+
}
|
|
6921
|
+
function identifierWords(name) {
|
|
6922
|
+
return name.match(/[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)|\d+/gu) ?? [name];
|
|
6923
|
+
}
|
|
6807
6924
|
var no_storage_in_stateless_modules_default = createRule({
|
|
6808
6925
|
name: "no-storage-in-stateless-modules",
|
|
6809
6926
|
documentation: noStorageInStatelessModulesDocumentation,
|
|
@@ -6924,7 +7041,20 @@ function isStringInitializedVariable(variable) {
|
|
|
6924
7041
|
if (declarator.type !== "VariableDeclarator") {
|
|
6925
7042
|
return false;
|
|
6926
7043
|
}
|
|
6927
|
-
|
|
7044
|
+
if (isStringLiteralInit(declarator.init)) return true;
|
|
7045
|
+
if (declarator.id.type === "Identifier" && declarator.id.typeAnnotation?.typeAnnotation.type === "TSStringKeyword") {
|
|
7046
|
+
return true;
|
|
7047
|
+
}
|
|
7048
|
+
return isTemplateStringsArrayElement(declarator.init, variable.scope);
|
|
7049
|
+
}
|
|
7050
|
+
function isTemplateStringsArrayElement(node, scope) {
|
|
7051
|
+
if (node?.type !== "MemberExpression" || !node.computed || node.object.type !== "Identifier") {
|
|
7052
|
+
return false;
|
|
7053
|
+
}
|
|
7054
|
+
const source = findVariable(scope, node.object.name);
|
|
7055
|
+
if (source?.defs.length !== 1) return false;
|
|
7056
|
+
const name = source.defs[0]?.name;
|
|
7057
|
+
return name?.type === "Identifier" && name.typeAnnotation?.typeAnnotation.type === "TSTypeReference" && name.typeAnnotation.typeAnnotation.typeName.type === "Identifier" && name.typeAnnotation.typeAnnotation.typeName.name === "TemplateStringsArray";
|
|
6928
7058
|
}
|
|
6929
7059
|
function isStringLiteralInit(node) {
|
|
6930
7060
|
if (node === null) {
|
|
@@ -6958,12 +7088,13 @@ function isConcatOperand(node, target) {
|
|
|
6958
7088
|
}
|
|
6959
7089
|
return false;
|
|
6960
7090
|
}
|
|
6961
|
-
function isDeclaredInsideLoop(variable,
|
|
7091
|
+
function isDeclaredInsideLoop(variable, repetition) {
|
|
6962
7092
|
const def = variable.defs[0];
|
|
6963
7093
|
if (def === void 0) {
|
|
6964
7094
|
return false;
|
|
6965
7095
|
}
|
|
6966
|
-
const body2 =
|
|
7096
|
+
const body2 = repetition.type === "CallExpression" ? repetition.arguments[0] : repetition.body;
|
|
7097
|
+
if (body2 === void 0 || body2.type === "SpreadElement") return false;
|
|
6967
7098
|
const [declStart, declEnd] = def.node.range;
|
|
6968
7099
|
const [bodyStart, bodyEnd] = body2.range;
|
|
6969
7100
|
return declStart >= bodyStart && declEnd <= bodyEnd;
|
|
@@ -6972,6 +7103,9 @@ function enclosingLoop(node) {
|
|
|
6972
7103
|
let child = node;
|
|
6973
7104
|
let parent = node.parent;
|
|
6974
7105
|
while (parent !== void 0 && parent !== null) {
|
|
7106
|
+
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") {
|
|
7107
|
+
return parent.parent;
|
|
7108
|
+
}
|
|
6975
7109
|
if (LOOP_NODE_TYPES.has(parent.type)) {
|
|
6976
7110
|
const loop = parent;
|
|
6977
7111
|
if (loop.body === child) {
|
|
@@ -6983,6 +7117,17 @@ function enclosingLoop(node) {
|
|
|
6983
7117
|
}
|
|
6984
7118
|
return null;
|
|
6985
7119
|
}
|
|
7120
|
+
function isSmallStaticForLoop(node) {
|
|
7121
|
+
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 !== "++") {
|
|
7122
|
+
return false;
|
|
7123
|
+
}
|
|
7124
|
+
const declaration = node.init.declarations[0];
|
|
7125
|
+
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) {
|
|
7126
|
+
return false;
|
|
7127
|
+
}
|
|
7128
|
+
const iterations = node.test.right.value - declaration.init.value + (node.test.operator === "<=" ? 1 : 0);
|
|
7129
|
+
return iterations >= 0 && iterations <= 8;
|
|
7130
|
+
}
|
|
6986
7131
|
var no_string_concat_in_loop_default = createRule({
|
|
6987
7132
|
name: "no-string-concat-in-loop",
|
|
6988
7133
|
documentation: noStringConcatInLoopDocumentation,
|
|
@@ -7015,6 +7160,9 @@ var no_string_concat_in_loop_default = createRule({
|
|
|
7015
7160
|
if (loop === null) {
|
|
7016
7161
|
return;
|
|
7017
7162
|
}
|
|
7163
|
+
if (isSmallStaticForLoop(loop)) {
|
|
7164
|
+
return;
|
|
7165
|
+
}
|
|
7018
7166
|
const scope = context.sourceCode.getScope(node);
|
|
7019
7167
|
const variable = findVariable(scope, node.left.name);
|
|
7020
7168
|
if (variable === void 0) {
|
|
@@ -7174,12 +7322,86 @@ var no_tautological_expect_default = createRule({
|
|
|
7174
7322
|
});
|
|
7175
7323
|
|
|
7176
7324
|
// src/rules/no-typed-doc-sections.ts
|
|
7325
|
+
var TYPED_TAG_RE2 = /^\s*@(arg|argument|param|return|returns|yield|yields)\b(.*)$/iu;
|
|
7326
|
+
var PARAM_TAGS2 = /* @__PURE__ */ new Set(["arg", "argument", "param"]);
|
|
7327
|
+
var PARAMETER_FILLER = /* @__PURE__ */ new Set([
|
|
7328
|
+
"a",
|
|
7329
|
+
"an",
|
|
7330
|
+
"argument",
|
|
7331
|
+
"given",
|
|
7332
|
+
"input",
|
|
7333
|
+
"parameter",
|
|
7334
|
+
"passed",
|
|
7335
|
+
"provided",
|
|
7336
|
+
"the",
|
|
7337
|
+
"value"
|
|
7338
|
+
]);
|
|
7339
|
+
var RESULT_FILLER = /* @__PURE__ */ new Set([
|
|
7340
|
+
"a",
|
|
7341
|
+
"an",
|
|
7342
|
+
"array",
|
|
7343
|
+
"boolean",
|
|
7344
|
+
"generator",
|
|
7345
|
+
"number",
|
|
7346
|
+
"object",
|
|
7347
|
+
"output",
|
|
7348
|
+
"promise",
|
|
7349
|
+
"result",
|
|
7350
|
+
"return",
|
|
7351
|
+
"returned",
|
|
7352
|
+
"returns",
|
|
7353
|
+
"string",
|
|
7354
|
+
"the",
|
|
7355
|
+
"value"
|
|
7356
|
+
]);
|
|
7357
|
+
function hasVacuousTypedTag(text) {
|
|
7358
|
+
const tags = typedTags(text);
|
|
7359
|
+
return tags.length > 0 && tags.some(isVacuousTag);
|
|
7360
|
+
}
|
|
7361
|
+
function typedTags(text) {
|
|
7362
|
+
const tags = [];
|
|
7363
|
+
for (const raw of text.split("\n")) {
|
|
7364
|
+
const match = TYPED_TAG_RE2.exec(raw);
|
|
7365
|
+
if (match !== null) {
|
|
7366
|
+
tags.push({ kind: (match[1] ?? "").toLowerCase(), payload: (match[2] ?? "").trim() });
|
|
7367
|
+
} else if (tags.length > 0 && raw.trim().length > 0 && !raw.trim().startsWith("@")) {
|
|
7368
|
+
const last = tags.at(-1);
|
|
7369
|
+
last.payload = `${last.payload} ${raw.trim()}`.trim();
|
|
7370
|
+
}
|
|
7371
|
+
}
|
|
7372
|
+
return tags.map(({ kind, payload }) => {
|
|
7373
|
+
let rest = payload.replace(/^\{[^}\n]+\}\s*/u, "").trim();
|
|
7374
|
+
if (!PARAM_TAGS2.has(kind)) {
|
|
7375
|
+
return { kind, name: null, description: rest.replace(/^-\s*/u, "").trim() };
|
|
7376
|
+
}
|
|
7377
|
+
const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s*|\s+)?(.*)$/u.exec(rest);
|
|
7378
|
+
if (match === null) return { kind, name: null, description: "" };
|
|
7379
|
+
const rawName = (match[1] ?? "").replace(/^\[/u, "").replace(/\]$/u, "").split("=")[0] ?? "";
|
|
7380
|
+
rest = (match[2] ?? "").trim();
|
|
7381
|
+
return { kind, name: rawName, description: rest };
|
|
7382
|
+
});
|
|
7383
|
+
}
|
|
7384
|
+
function isVacuousTag(tag) {
|
|
7385
|
+
const description = words(tag.description).map(canonicalWord);
|
|
7386
|
+
if (description.length === 0) return true;
|
|
7387
|
+
if (tag.name === null) return description.every((word) => RESULT_FILLER.has(word));
|
|
7388
|
+
const nameWords2 = new Set(words(tag.name).map(canonicalWord));
|
|
7389
|
+
return description.every((word) => PARAMETER_FILLER.has(word) || nameWords2.has(word));
|
|
7390
|
+
}
|
|
7391
|
+
function words(text) {
|
|
7392
|
+
return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[a-z][a-z0-9]*/gu) ?? [];
|
|
7393
|
+
}
|
|
7394
|
+
function canonicalWord(word) {
|
|
7395
|
+
if (["identifier", "identifiers", "ids"].includes(word)) return "id";
|
|
7396
|
+
if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
|
|
7397
|
+
return word;
|
|
7398
|
+
}
|
|
7177
7399
|
var noTypedDocSectionsDocumentation = {
|
|
7178
7400
|
summary: "Reject typed-signature repetition while preserving behavior that types cannot express.",
|
|
7179
7401
|
rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
|
|
7180
7402
|
remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
|
|
7181
7403
|
category: "maintainability",
|
|
7182
|
-
limitations: ["
|
|
7404
|
+
limitations: ["Description-free or name-restating parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
|
|
7183
7405
|
examples: [
|
|
7184
7406
|
{
|
|
7185
7407
|
id: "behavioral-documentation",
|
|
@@ -7217,7 +7439,7 @@ var no_typed_doc_sections_default = createRule({
|
|
|
7217
7439
|
return {
|
|
7218
7440
|
Program() {
|
|
7219
7441
|
for (const group of proseGroups(context.filename, context.sourceCode, true)) {
|
|
7220
|
-
if (group.hasTypedTags && documentsTypedFunction(context.sourceCode, group.comment)) {
|
|
7442
|
+
if (group.hasTypedTags && hasVacuousTypedTag(group.text) && documentsTypedFunction(context.sourceCode, group.comment)) {
|
|
7221
7443
|
context.report({ node: group.comment, messageId: "typedSection" });
|
|
7222
7444
|
}
|
|
7223
7445
|
}
|
|
@@ -7318,28 +7540,32 @@ var STOPWORDS3 = /* @__PURE__ */ new Set([
|
|
|
7318
7540
|
"with"
|
|
7319
7541
|
]);
|
|
7320
7542
|
var DIRECTIVE_RE5 = /^\s*(?:eslint\b|eslint-|sarj-noqa\b|@ts-|prettier|biome-|c8\b|v8\b|istanbul\b|todo\b|fixme\b|hack\b|xxx\b)/i;
|
|
7543
|
+
var UNIT_NAME_SUFFIX_RE = /(?:_(?:NS|US|MS|S|SEC|SECS|SECOND|SECONDS|MIN|MINS|MINUTE|MINUTES|HOUR|HOURS|DAY|DAYS|BYTE|BYTES|KB|MB|GB|HZ|KHZ|MHZ|PX)|(?:Ns|Us|Ms|Sec|Secs|Second|Seconds|Min|Mins|Minute|Minutes|Hour|Hours|Day|Days|Byte|Bytes|Kb|Mb|Gb|Hz|Khz|Mhz|Px))$/u;
|
|
7321
7544
|
function narratesValue(body2, code) {
|
|
7322
7545
|
if (body2.length === 0 || DIRECTIVE_RE5.test(body2) || hasExternalReference(body2)) return false;
|
|
7323
7546
|
const codeNumbers = numbersIn(code);
|
|
7324
7547
|
if (codeNumbers.size === 0) return false;
|
|
7325
|
-
const
|
|
7326
|
-
if (
|
|
7548
|
+
const words2 = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
|
|
7549
|
+
if (words2.length === 0) return false;
|
|
7327
7550
|
const commentNumbers = numbersIn(body2);
|
|
7328
7551
|
if (commentNumbers.size === 0) return false;
|
|
7329
7552
|
for (const number of commentNumbers) {
|
|
7330
7553
|
if (!codeNumbers.has(number)) return false;
|
|
7331
7554
|
}
|
|
7332
|
-
if (!
|
|
7555
|
+
if (!words2.some((word) => UNIT_WORDS.has(word))) return false;
|
|
7333
7556
|
const identifiers = codeTokens(code);
|
|
7334
7557
|
const stems = /* @__PURE__ */ new Set();
|
|
7335
7558
|
for (const token of identifiers) stems.add(stem(token));
|
|
7336
|
-
return
|
|
7559
|
+
return words2.every(
|
|
7337
7560
|
(word) => STOPWORDS3.has(word) || UNIT_WORDS.has(word) || commentNumbers.has(word) || identifiers.has(word) || stems.has(stem(word))
|
|
7338
7561
|
);
|
|
7339
7562
|
}
|
|
7340
7563
|
function numbersIn(text) {
|
|
7341
7564
|
return new Set(text.match(NUMBER_RE) ?? []);
|
|
7342
7565
|
}
|
|
7566
|
+
function nameAlreadyCarriesUnit(code) {
|
|
7567
|
+
return (code.match(/[A-Za-z_$][\w$]*/gu) ?? []).some((identifier) => UNIT_NAME_SUFFIX_RE.test(identifier));
|
|
7568
|
+
}
|
|
7343
7569
|
var no_trailing_value_narration_default = createRule({
|
|
7344
7570
|
name: "no-trailing-value-narration",
|
|
7345
7571
|
documentation: noTrailingValueNarrationDocumentation,
|
|
@@ -7350,6 +7576,7 @@ var no_trailing_value_narration_default = createRule({
|
|
|
7350
7576
|
},
|
|
7351
7577
|
schema: [],
|
|
7352
7578
|
messages: {
|
|
7579
|
+
deleteNarration: "Trailing comment restates the literal and the identifier already names its unit \u2014 delete the comment so it cannot drift.",
|
|
7353
7580
|
narratesValue: "Trailing comment restates the literal on this line \u2014 put the unit in the name (STALE_TIME_MS) so it cannot drift."
|
|
7354
7581
|
}
|
|
7355
7582
|
},
|
|
@@ -7382,7 +7609,10 @@ var no_trailing_value_narration_default = createRule({
|
|
|
7382
7609
|
const code = line.slice(0, comment.loc.start.column);
|
|
7383
7610
|
const body2 = comment.value.replace(/^\*+/, "").replace(/\*+$/, "").trim();
|
|
7384
7611
|
if (narratesValue(body2, code)) {
|
|
7385
|
-
context.report({
|
|
7612
|
+
context.report({
|
|
7613
|
+
node: comment,
|
|
7614
|
+
messageId: nameAlreadyCarriesUnit(code) ? "deleteNarration" : "narratesValue"
|
|
7615
|
+
});
|
|
7386
7616
|
}
|
|
7387
7617
|
}
|
|
7388
7618
|
}
|
|
@@ -8423,15 +8653,23 @@ var no_zod_native_enum_default = createRule({
|
|
|
8423
8653
|
} catch {
|
|
8424
8654
|
services = null;
|
|
8425
8655
|
}
|
|
8426
|
-
const
|
|
8427
|
-
const
|
|
8656
|
+
const zodImportedBindings = /* @__PURE__ */ new Map();
|
|
8657
|
+
const zodNamespaceBindings = /* @__PURE__ */ new Set();
|
|
8658
|
+
function resolvedBinding(identifier) {
|
|
8659
|
+
return import_utils45.ASTUtils.findVariable(
|
|
8660
|
+
sourceCode.getScope(identifier),
|
|
8661
|
+
identifier.name
|
|
8662
|
+
);
|
|
8663
|
+
}
|
|
8428
8664
|
function isZodMemberCall(node, api) {
|
|
8429
8665
|
const callee = node.callee;
|
|
8430
|
-
if (callee.type === import_utils45.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.object.type === import_utils45.AST_NODE_TYPES.Identifier &&
|
|
8431
|
-
|
|
8666
|
+
if (callee.type === import_utils45.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.object.type === import_utils45.AST_NODE_TYPES.Identifier && callee.property.type === import_utils45.AST_NODE_TYPES.Identifier) {
|
|
8667
|
+
const binding = resolvedBinding(callee.object);
|
|
8668
|
+
return binding !== null && zodNamespaceBindings.has(binding) && callee.property.name === api;
|
|
8432
8669
|
}
|
|
8433
8670
|
if (callee.type === import_utils45.AST_NODE_TYPES.Identifier) {
|
|
8434
|
-
|
|
8671
|
+
const binding = resolvedBinding(callee);
|
|
8672
|
+
return binding !== null && zodImportedBindings.get(binding) === api;
|
|
8435
8673
|
}
|
|
8436
8674
|
return false;
|
|
8437
8675
|
}
|
|
@@ -8466,10 +8704,14 @@ var no_zod_native_enum_default = createRule({
|
|
|
8466
8704
|
}
|
|
8467
8705
|
for (const spec of node.specifiers) {
|
|
8468
8706
|
if (spec.type === import_utils45.AST_NODE_TYPES.ImportNamespaceSpecifier || spec.type === import_utils45.AST_NODE_TYPES.ImportDefaultSpecifier || spec.type === import_utils45.AST_NODE_TYPES.ImportSpecifier && (spec.imported.type === import_utils45.AST_NODE_TYPES.Identifier ? spec.imported.name === "z" : spec.imported.value === "z")) {
|
|
8469
|
-
|
|
8707
|
+
const binding = resolvedBinding(spec.local);
|
|
8708
|
+
if (binding !== null) zodNamespaceBindings.add(binding);
|
|
8470
8709
|
}
|
|
8471
8710
|
if (spec.type === import_utils45.AST_NODE_TYPES.ImportSpecifier && spec.imported.type === import_utils45.AST_NODE_TYPES.Identifier) {
|
|
8472
|
-
|
|
8711
|
+
const binding = resolvedBinding(spec.local);
|
|
8712
|
+
if (binding !== null) {
|
|
8713
|
+
zodImportedBindings.set(binding, spec.imported.name);
|
|
8714
|
+
}
|
|
8473
8715
|
}
|
|
8474
8716
|
}
|
|
8475
8717
|
},
|
|
@@ -8862,8 +9104,10 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
|
|
|
8862
9104
|
let statusMemberCount = 0;
|
|
8863
9105
|
let hasFailurePayload = false;
|
|
8864
9106
|
let hasSuccessPayload = false;
|
|
9107
|
+
let hasUnrecognizedMember = false;
|
|
8865
9108
|
for (const member of typeLiteral.members) {
|
|
8866
9109
|
if (member.type !== import_utils49.AST_NODE_TYPES.TSPropertySignature) {
|
|
9110
|
+
hasUnrecognizedMember = true;
|
|
8867
9111
|
continue;
|
|
8868
9112
|
}
|
|
8869
9113
|
const name = getMemberName(member);
|
|
@@ -8872,15 +9116,18 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
|
|
|
8872
9116
|
continue;
|
|
8873
9117
|
}
|
|
8874
9118
|
if (!member.optional || isBooleanTyped(member) || name === null) {
|
|
9119
|
+
hasUnrecognizedMember = true;
|
|
8875
9120
|
continue;
|
|
8876
9121
|
}
|
|
8877
9122
|
if (FAILURE_MEMBER_NAMES.has(name)) {
|
|
8878
9123
|
hasFailurePayload = true;
|
|
8879
9124
|
} else if (SUCCESS_PAYLOAD_MEMBER_NAMES.has(name)) {
|
|
8880
9125
|
hasSuccessPayload = true;
|
|
9126
|
+
} else {
|
|
9127
|
+
hasUnrecognizedMember = true;
|
|
8881
9128
|
}
|
|
8882
9129
|
}
|
|
8883
|
-
return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && hasSuccessPayload;
|
|
9130
|
+
return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && (hasSuccessPayload || !hasUnrecognizedMember);
|
|
8884
9131
|
}
|
|
8885
9132
|
function getMemberName(member) {
|
|
8886
9133
|
if (member.type !== import_utils49.AST_NODE_TYPES.TSPropertySignature) {
|
|
@@ -10781,7 +11028,7 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
10781
11028
|
},
|
|
10782
11029
|
defaultOptions: [],
|
|
10783
11030
|
create(context) {
|
|
10784
|
-
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
11031
|
+
if (isTestFile(context.filename) || isScriptFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
10785
11032
|
return {};
|
|
10786
11033
|
}
|
|
10787
11034
|
const unvalidatedVariables = /* @__PURE__ */ new Set();
|
|
@@ -12246,6 +12493,9 @@ var requireAssertNeverDocumentation = {
|
|
|
12246
12493
|
};
|
|
12247
12494
|
var isRuntimeHandlingStatement = (statement) => {
|
|
12248
12495
|
if (statement.type === import_utils62.AST_NODE_TYPES.EmptyStatement) return false;
|
|
12496
|
+
if (statement.type === import_utils62.AST_NODE_TYPES.BreakStatement) {
|
|
12497
|
+
return statement.label !== null;
|
|
12498
|
+
}
|
|
12249
12499
|
if (statement.type === import_utils62.AST_NODE_TYPES.TSTypeAliasDeclaration || statement.type === import_utils62.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
12250
12500
|
return false;
|
|
12251
12501
|
}
|
|
@@ -12275,7 +12525,12 @@ function isExhaustiveFiniteSwitch(node, services) {
|
|
|
12275
12525
|
const discriminant = services.esTreeNodeToTSNodeMap.get(node.discriminant);
|
|
12276
12526
|
const discriminantType = checker.getTypeAtLocation(discriminant);
|
|
12277
12527
|
const constituents = discriminantType.isUnion() ? discriminantType.types : [discriminantType];
|
|
12278
|
-
if (constituents.length
|
|
12528
|
+
if (!discriminantType.isUnion() || constituents.length < 2) return false;
|
|
12529
|
+
if (constituents.every(
|
|
12530
|
+
(constituent) => (constituent.flags & import_typescript.default.TypeFlags.BooleanLiteral) !== 0
|
|
12531
|
+
)) {
|
|
12532
|
+
return false;
|
|
12533
|
+
}
|
|
12279
12534
|
const expected = /* @__PURE__ */ new Set();
|
|
12280
12535
|
for (const constituent of constituents) {
|
|
12281
12536
|
const key = finiteTypeKey(constituent, checker);
|
|
@@ -12433,6 +12688,26 @@ var require_fetch_timeout_default = createRule({
|
|
|
12433
12688
|
}
|
|
12434
12689
|
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
12690
|
}
|
|
12691
|
+
function localConstInitProvablyLacksSignal(identifier) {
|
|
12692
|
+
const variable = import_utils63.ASTUtils.findVariable(
|
|
12693
|
+
context.sourceCode.getScope(identifier),
|
|
12694
|
+
identifier.name
|
|
12695
|
+
);
|
|
12696
|
+
if (variable?.defs.length !== 1) return false;
|
|
12697
|
+
const definition = variable.defs[0];
|
|
12698
|
+
if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init?.type !== import_utils63.AST_NODE_TYPES.ObjectExpression || !initProvablyLacksSignal(definition.node.init)) {
|
|
12699
|
+
return false;
|
|
12700
|
+
}
|
|
12701
|
+
for (const reference of variable.references) {
|
|
12702
|
+
const ref = reference.identifier;
|
|
12703
|
+
if (ref === identifier || ref === definition.name) continue;
|
|
12704
|
+
const member = ref.parent;
|
|
12705
|
+
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) {
|
|
12706
|
+
return false;
|
|
12707
|
+
}
|
|
12708
|
+
}
|
|
12709
|
+
return true;
|
|
12710
|
+
}
|
|
12436
12711
|
return {
|
|
12437
12712
|
CallExpression(node) {
|
|
12438
12713
|
if (!isGlobalFetchCall2(node.callee)) {
|
|
@@ -12442,7 +12717,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
12442
12717
|
if (node.arguments.length === 1 && first !== void 0 && !isInlineUrl(first, resolvesToGlobal)) {
|
|
12443
12718
|
return;
|
|
12444
12719
|
}
|
|
12445
|
-
if (init === void 0 || initProvablyLacksSignal(init)) {
|
|
12720
|
+
if (init === void 0 || initProvablyLacksSignal(init) || init.type === import_utils63.AST_NODE_TYPES.Identifier && localConstInitProvablyLacksSignal(init)) {
|
|
12446
12721
|
context.report({ node, messageId: "missingSignal" });
|
|
12447
12722
|
}
|
|
12448
12723
|
}
|
|
@@ -13062,28 +13337,26 @@ var requireZodFormValidationDocumentation = {
|
|
|
13062
13337
|
rationale: "FormData values are untrusted strings or files and need runtime validation before use.",
|
|
13063
13338
|
remediation: "Read the value inside a Zod schema's `parse` or `safeParse` input.",
|
|
13064
13339
|
category: "security",
|
|
13340
|
+
limitations: [
|
|
13341
|
+
"Tests are excluded; imported schema-shaped names are trusted when their implementation is outside the linted file.",
|
|
13342
|
+
"Delayed raw-value use is accepted only after an unconditional successful parse in the same block; safeParse remains valid when the raw binding has no unvalidated consumer."
|
|
13343
|
+
],
|
|
13065
13344
|
examples: [
|
|
13066
13345
|
{ id: "validated-form-value", title: "Validate the form value", outcome: "no-match", files: [{ path: "src/action.ts", source: "const input = UserSchema.parse({ name: formData.get('name') });" }], focusPath: "src/action.ts", expectedCount: 0, public: true },
|
|
13067
13346
|
{ id: "raw-form-value", title: "Do not use a raw form value", outcome: "match", files: [{ path: "src/action.ts", source: "const name = formData.get('name');" }], focusPath: "src/action.ts", expectedCount: 1, public: true }
|
|
13068
13347
|
]
|
|
13069
13348
|
};
|
|
13070
|
-
var
|
|
13071
|
-
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
|
|
13075
|
-
|
|
13076
|
-
|
|
13077
|
-
if (method !== "parse" && method !== "safeParse" && method !== "parseAsync" && method !== "safeParseAsync") {
|
|
13078
|
-
return false;
|
|
13079
|
-
}
|
|
13080
|
-
return looksLikeZodSchema(callee.object);
|
|
13081
|
-
};
|
|
13082
|
-
var looksLikeZodSchema = (node) => {
|
|
13349
|
+
var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
|
|
13350
|
+
"parse",
|
|
13351
|
+
"safeParse",
|
|
13352
|
+
"parseAsync",
|
|
13353
|
+
"safeParseAsync"
|
|
13354
|
+
]);
|
|
13355
|
+
var zodReceiverRoot = (node) => {
|
|
13083
13356
|
let current = node;
|
|
13084
13357
|
while (true) {
|
|
13085
13358
|
if (current.type === import_utils66.AST_NODE_TYPES.Identifier) {
|
|
13086
|
-
return current
|
|
13359
|
+
return current;
|
|
13087
13360
|
}
|
|
13088
13361
|
if (current.type === import_utils66.AST_NODE_TYPES.CallExpression) {
|
|
13089
13362
|
current = current.callee;
|
|
@@ -13093,7 +13366,7 @@ var looksLikeZodSchema = (node) => {
|
|
|
13093
13366
|
current = current.object;
|
|
13094
13367
|
continue;
|
|
13095
13368
|
}
|
|
13096
|
-
return
|
|
13369
|
+
return null;
|
|
13097
13370
|
}
|
|
13098
13371
|
};
|
|
13099
13372
|
var isFormDataMethodCall = (node) => {
|
|
@@ -13123,6 +13396,34 @@ var require_zod_form_validation_default = createRule({
|
|
|
13123
13396
|
if (isTestFile(context.filename)) {
|
|
13124
13397
|
return {};
|
|
13125
13398
|
}
|
|
13399
|
+
const zodBindings = /* @__PURE__ */ new Set();
|
|
13400
|
+
const resolvedBinding = (identifier) => import_utils66.ASTUtils.findVariable(
|
|
13401
|
+
context.sourceCode.getScope(identifier),
|
|
13402
|
+
identifier.name
|
|
13403
|
+
);
|
|
13404
|
+
const isProvablyNonZodLocal = (identifier) => {
|
|
13405
|
+
const binding = resolvedBinding(identifier);
|
|
13406
|
+
if (binding === null || zodBindings.has(binding) || binding.defs.length !== 1) {
|
|
13407
|
+
return false;
|
|
13408
|
+
}
|
|
13409
|
+
const definition = binding.defs[0];
|
|
13410
|
+
if (definition?.type !== "Variable" || definition.node.type !== import_utils66.AST_NODE_TYPES.VariableDeclarator) {
|
|
13411
|
+
return false;
|
|
13412
|
+
}
|
|
13413
|
+
const init = definition.node.init;
|
|
13414
|
+
return init?.type === import_utils66.AST_NODE_TYPES.ObjectExpression || init?.type === import_utils66.AST_NODE_TYPES.ArrayExpression || init?.type === import_utils66.AST_NODE_TYPES.Literal || init?.type === import_utils66.AST_NODE_TYPES.ArrowFunctionExpression || init?.type === import_utils66.AST_NODE_TYPES.FunctionExpression;
|
|
13415
|
+
};
|
|
13416
|
+
const isZodParseCall = (node) => {
|
|
13417
|
+
if (node.type !== import_utils66.AST_NODE_TYPES.CallExpression) return false;
|
|
13418
|
+
const callee = node.callee;
|
|
13419
|
+
if (callee.type !== import_utils66.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== import_utils66.AST_NODE_TYPES.Identifier || !ZOD_PARSE_METHODS.has(callee.property.name)) {
|
|
13420
|
+
return false;
|
|
13421
|
+
}
|
|
13422
|
+
const root = zodReceiverRoot(callee.object);
|
|
13423
|
+
if (root === null) return false;
|
|
13424
|
+
const binding = resolvedBinding(root);
|
|
13425
|
+
return binding !== null && zodBindings.has(binding) || (root.name === "z" || ZOD_SCHEMA_NAME_RE.test(root.name)) && !isProvablyNonZodLocal(root);
|
|
13426
|
+
};
|
|
13126
13427
|
const isFormSourceIdentifier = (node) => {
|
|
13127
13428
|
if (node.type !== import_utils66.AST_NODE_TYPES.Identifier) return false;
|
|
13128
13429
|
if (/formdata/i.test(node.name)) return true;
|
|
@@ -13148,14 +13449,15 @@ var require_zod_form_validation_default = createRule({
|
|
|
13148
13449
|
}
|
|
13149
13450
|
return isFormSourceIdentifier(callee.object);
|
|
13150
13451
|
};
|
|
13151
|
-
const
|
|
13452
|
+
const zodParseAncestor = (node) => {
|
|
13152
13453
|
let parent = node.parent;
|
|
13153
13454
|
while (parent !== null && parent !== void 0) {
|
|
13154
|
-
if (isZodParseCall(parent)) return
|
|
13455
|
+
if (isZodParseCall(parent)) return parent;
|
|
13155
13456
|
parent = parent.parent;
|
|
13156
13457
|
}
|
|
13157
|
-
return
|
|
13458
|
+
return null;
|
|
13158
13459
|
};
|
|
13460
|
+
const hasZodParseAncestor = (node) => zodParseAncestor(node) !== null;
|
|
13159
13461
|
const isInstanceofNarrowing = (node) => {
|
|
13160
13462
|
const parent = node.parent;
|
|
13161
13463
|
return parent !== null && parent !== void 0 && parent.type === import_utils66.AST_NODE_TYPES.BinaryExpression && parent.operator === "instanceof" && parent.left === node && parent.right.type === import_utils66.AST_NODE_TYPES.Identifier && (parent.right.name === "File" || parent.right.name === "Blob");
|
|
@@ -13172,14 +13474,111 @@ var require_zod_form_validation_default = createRule({
|
|
|
13172
13474
|
}
|
|
13173
13475
|
return null;
|
|
13174
13476
|
};
|
|
13477
|
+
const containingStatement = (node) => {
|
|
13478
|
+
let current = node;
|
|
13479
|
+
while (current.parent !== void 0) {
|
|
13480
|
+
const parent = current.parent;
|
|
13481
|
+
if (parent.type === import_utils66.AST_NODE_TYPES.BlockStatement || parent.type === import_utils66.AST_NODE_TYPES.Program) {
|
|
13482
|
+
return current;
|
|
13483
|
+
}
|
|
13484
|
+
current = parent;
|
|
13485
|
+
}
|
|
13486
|
+
return null;
|
|
13487
|
+
};
|
|
13488
|
+
const zodParseMethod = (call) => {
|
|
13489
|
+
const callee = call.callee;
|
|
13490
|
+
return callee.type === import_utils66.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils66.AST_NODE_TYPES.Identifier ? callee.property.name : null;
|
|
13491
|
+
};
|
|
13492
|
+
const hasConditionalAncestorBeforeStatement = (node, statement) => {
|
|
13493
|
+
let current = node.parent;
|
|
13494
|
+
while (current !== void 0 && current !== statement) {
|
|
13495
|
+
if (current.type === import_utils66.AST_NODE_TYPES.LogicalExpression || current.type === import_utils66.AST_NODE_TYPES.ConditionalExpression) {
|
|
13496
|
+
return true;
|
|
13497
|
+
}
|
|
13498
|
+
current = current.parent;
|
|
13499
|
+
}
|
|
13500
|
+
return false;
|
|
13501
|
+
};
|
|
13502
|
+
const isAwaitedBeforeStatement = (node, statement) => {
|
|
13503
|
+
let current = node.parent;
|
|
13504
|
+
while (current !== void 0 && current !== statement) {
|
|
13505
|
+
if (current.type === import_utils66.AST_NODE_TYPES.AwaitExpression) return true;
|
|
13506
|
+
current = current.parent;
|
|
13507
|
+
}
|
|
13508
|
+
return false;
|
|
13509
|
+
};
|
|
13510
|
+
const guaranteedValidationStatement = (declarator, reference) => {
|
|
13511
|
+
const parse2 = zodParseAncestor(reference);
|
|
13512
|
+
if (parse2 === null) return null;
|
|
13513
|
+
const declarationStatement = containingStatement(declarator);
|
|
13514
|
+
const validationStatement = containingStatement(parse2);
|
|
13515
|
+
if (declarationStatement === null || validationStatement === null || declarationStatement.parent !== validationStatement.parent || validationStatement.range[0] <= declarationStatement.range[1] || hasConditionalAncestorBeforeStatement(parse2, validationStatement)) {
|
|
13516
|
+
return null;
|
|
13517
|
+
}
|
|
13518
|
+
if (validationStatement.type !== import_utils66.AST_NODE_TYPES.VariableDeclaration && validationStatement.type !== import_utils66.AST_NODE_TYPES.ExpressionStatement) {
|
|
13519
|
+
return null;
|
|
13520
|
+
}
|
|
13521
|
+
const method = zodParseMethod(parse2);
|
|
13522
|
+
if (method === "parse") return validationStatement;
|
|
13523
|
+
if (method === "parseAsync" && isAwaitedBeforeStatement(parse2, validationStatement)) {
|
|
13524
|
+
return validationStatement;
|
|
13525
|
+
}
|
|
13526
|
+
return null;
|
|
13527
|
+
};
|
|
13528
|
+
const isSafePrevalidationInspection = (identifier) => {
|
|
13529
|
+
const parent = identifier.parent;
|
|
13530
|
+
if (parent.type === import_utils66.AST_NODE_TYPES.UnaryExpression && parent.operator === "typeof") {
|
|
13531
|
+
return true;
|
|
13532
|
+
}
|
|
13533
|
+
if (parent.type !== import_utils66.AST_NODE_TYPES.BinaryExpression || parent.left !== identifier) {
|
|
13534
|
+
return false;
|
|
13535
|
+
}
|
|
13536
|
+
if (parent.operator === "instanceof") {
|
|
13537
|
+
return parent.right.type === import_utils66.AST_NODE_TYPES.Identifier && (parent.right.name === "File" || parent.right.name === "Blob");
|
|
13538
|
+
}
|
|
13539
|
+
return ["===", "!==", "==", "!="].includes(parent.operator) && (parent.right.type === import_utils66.AST_NODE_TYPES.Literal && parent.right.value === null || parent.right.type === import_utils66.AST_NODE_TYPES.Identifier && parent.right.name === "undefined");
|
|
13540
|
+
};
|
|
13541
|
+
const statementWithinBlock = (node, block) => {
|
|
13542
|
+
let current = node;
|
|
13543
|
+
while (current.parent !== void 0 && current.parent !== block) {
|
|
13544
|
+
current = current.parent;
|
|
13545
|
+
}
|
|
13546
|
+
return current.parent === block ? current : null;
|
|
13547
|
+
};
|
|
13175
13548
|
const bindingIsValidated = (declarator) => {
|
|
13176
13549
|
const variable = context.sourceCode.getDeclaredVariables(declarator)[0];
|
|
13177
13550
|
if (variable === void 0) return false;
|
|
13178
|
-
|
|
13179
|
-
(
|
|
13551
|
+
const references = variable.references.filter((reference) => !reference.isWriteOnly()).map((reference) => reference.identifier).filter(
|
|
13552
|
+
(identifier) => identifier.type === import_utils66.AST_NODE_TYPES.Identifier
|
|
13553
|
+
);
|
|
13554
|
+
if (references.length === 0) return false;
|
|
13555
|
+
if (references.some(isInstanceofNarrowing)) return true;
|
|
13556
|
+
const validationStatements = references.map((reference) => guaranteedValidationStatement(declarator, reference)).filter(
|
|
13557
|
+
(statement) => statement !== null
|
|
13180
13558
|
);
|
|
13559
|
+
const declarationStatement = containingStatement(declarator);
|
|
13560
|
+
const declarationBlock = declarationStatement?.parent;
|
|
13561
|
+
return references.every((reference) => {
|
|
13562
|
+
if (zodParseAncestor(reference) !== null || isSafePrevalidationInspection(reference)) {
|
|
13563
|
+
return true;
|
|
13564
|
+
}
|
|
13565
|
+
if (declarationBlock === void 0) return false;
|
|
13566
|
+
const useStatement = statementWithinBlock(reference, declarationBlock);
|
|
13567
|
+
return useStatement !== null && validationStatements.some(
|
|
13568
|
+
(statement) => statement.range[1] < useStatement.range[0]
|
|
13569
|
+
);
|
|
13570
|
+
});
|
|
13181
13571
|
};
|
|
13182
13572
|
return {
|
|
13573
|
+
ImportDeclaration(node) {
|
|
13574
|
+
if (!isZodModule(node.source.value)) return;
|
|
13575
|
+
for (const specifier of node.specifiers) {
|
|
13576
|
+
if (specifier.type === import_utils66.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils66.AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === import_utils66.AST_NODE_TYPES.ImportSpecifier && (specifier.imported.type === import_utils66.AST_NODE_TYPES.Identifier ? specifier.imported.name === "z" : specifier.imported.value === "z")) {
|
|
13577
|
+
const binding = resolvedBinding(specifier.local);
|
|
13578
|
+
if (binding !== null) zodBindings.add(binding);
|
|
13579
|
+
}
|
|
13580
|
+
}
|
|
13581
|
+
},
|
|
13183
13582
|
CallExpression(node) {
|
|
13184
13583
|
if (!isFormDataGetCall(node)) return;
|
|
13185
13584
|
if (hasZodParseAncestor(node) || isInstanceofNarrowing(node)) return;
|
|
@@ -13197,8 +13596,8 @@ var require_zod_form_validation_default = createRule({
|
|
|
13197
13596
|
// src/rules/store-insert-requires-on-conflict.ts
|
|
13198
13597
|
var import_utils67 = require("@typescript-eslint/utils");
|
|
13199
13598
|
var storeInsertRequiresOnConflictDocumentation = {
|
|
13200
|
-
summary: "Require
|
|
13201
|
-
rationale: "A
|
|
13599
|
+
summary: "Require embedded inserts in explicitly replayable callables to carry conflict handling.",
|
|
13600
|
+
rationale: "A callable named as an enqueue, seed, migration, schedule, ensure, or upsert promises replay safety.",
|
|
13202
13601
|
remediation: "Add an appropriate `ON CONFLICT` action or supported replay-safe insert form.",
|
|
13203
13602
|
category: "correctness",
|
|
13204
13603
|
examples: [
|
|
@@ -13207,7 +13606,25 @@ var storeInsertRequiresOnConflictDocumentation = {
|
|
|
13207
13606
|
]
|
|
13208
13607
|
};
|
|
13209
13608
|
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;
|
|
13609
|
+
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;
|
|
13610
|
+
var REPLAY_CONTRACT_NAME = /(?:enqueue|ensure|migrate|recordOnce|schedule|seed|upsert|getOrCreate|createIfAbsent|insertIfAbsent)/i;
|
|
13611
|
+
function owningCallableName(node) {
|
|
13612
|
+
for (let current = node.parent; current !== null && current !== void 0; current = current.parent) {
|
|
13613
|
+
if (current.type === "FunctionDeclaration") {
|
|
13614
|
+
return current.id?.name ?? null;
|
|
13615
|
+
}
|
|
13616
|
+
if (current.type === "MethodDefinition") {
|
|
13617
|
+
return current.key.type === "Identifier" ? current.key.name : null;
|
|
13618
|
+
}
|
|
13619
|
+
if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "VariableDeclarator" && current.parent.id.type === "Identifier") {
|
|
13620
|
+
return current.parent.id.name;
|
|
13621
|
+
}
|
|
13622
|
+
if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "Property" && current.parent.key.type === "Identifier") {
|
|
13623
|
+
return current.parent.key.name;
|
|
13624
|
+
}
|
|
13625
|
+
}
|
|
13626
|
+
return null;
|
|
13627
|
+
}
|
|
13211
13628
|
var INSERT_GATE = /insert/i;
|
|
13212
13629
|
var store_insert_requires_on_conflict_default = createRule({
|
|
13213
13630
|
name: "store-insert-requires-on-conflict",
|
|
@@ -13215,7 +13632,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
13215
13632
|
meta: {
|
|
13216
13633
|
type: "problem",
|
|
13217
13634
|
docs: {
|
|
13218
|
-
description: "Require
|
|
13635
|
+
description: "Require embedded inserts in explicitly replayable callables to carry conflict handling."
|
|
13219
13636
|
},
|
|
13220
13637
|
schema: [],
|
|
13221
13638
|
messages: {
|
|
@@ -13231,6 +13648,10 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
13231
13648
|
if (!INSERT_WRITE.test(sql) || CONFLICT_HANDLED.test(sql)) {
|
|
13232
13649
|
return;
|
|
13233
13650
|
}
|
|
13651
|
+
const owner = owningCallableName(node);
|
|
13652
|
+
if (owner !== null && !REPLAY_CONTRACT_NAME.test(owner)) {
|
|
13653
|
+
return;
|
|
13654
|
+
}
|
|
13234
13655
|
context.report({ node, messageId: "storeInsertRequiresOnConflict" });
|
|
13235
13656
|
});
|
|
13236
13657
|
}
|
|
@@ -13917,7 +14338,7 @@ var rules = {
|
|
|
13917
14338
|
};
|
|
13918
14339
|
var meta = {
|
|
13919
14340
|
name: "@sarj/eslint-plugin",
|
|
13920
|
-
version: "15.
|
|
14341
|
+
version: "15.2.0"
|
|
13921
14342
|
};
|
|
13922
14343
|
var applicationOnlyRules = [
|
|
13923
14344
|
"no-restricted-library-load",
|