@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.js CHANGED
@@ -967,11 +967,11 @@ function isTrivialInitializer(node) {
967
967
  }
968
968
  function restatesStatementHead(body2, statement) {
969
969
  if (statement === null) return false;
970
- const words = body2.match(/[A-Za-z][\w$]*/g) ?? [];
971
- const opener = words[0];
972
- if (opener === void 0 || words.length > NARRATION_MAX_WORDS) return false;
970
+ const words2 = body2.match(/[A-Za-z][\w$]*/g) ?? [];
971
+ const opener = words2[0];
972
+ if (opener === void 0 || words2.length > NARRATION_MAX_WORDS) return false;
973
973
  if (!NARRATION_VERB_RE.test(opener)) return false;
974
- const content = words.slice(1).map(normalizeToken).filter((word) => !NARRATION_STOPWORDS.has(word));
974
+ const content = words2.slice(1).map(normalizeToken).filter((word) => !NARRATION_STOPWORDS.has(word));
975
975
  if (content.length < NARRATION_MIN_CONTENT) return false;
976
976
  const head = statement.split("(")[0] ?? statement;
977
977
  const code = headTokens(head);
@@ -1239,8 +1239,8 @@ function isRedundantNarration(body2, statementBelow, standalone, isolatedEnumera
1239
1239
  if (META_COMMENTARY_RE.test(t) && !justified) return true;
1240
1240
  if (isBareDeferral(t) && !justified) return true;
1241
1241
  if (HELPER_OPENER_RE.test(t) || LETS_RE.test(t)) return true;
1242
- const words = t.split(/\s+/);
1243
- if (words.length > 1 && words.length <= 4 && DUMMY_TRANSLATION_RE.test(t) && !/[():=]/.test(t)) {
1242
+ const words2 = t.split(/\s+/);
1243
+ if (words2.length > 1 && words2.length <= 4 && DUMMY_TRANSLATION_RE.test(t) && !/[():=]/.test(t)) {
1244
1244
  const lowerT = t.toLowerCase();
1245
1245
  if (!RATIONALE_WORDS.some((word) => lowerT.includes(word)) && restatesWholeStatement(t, statementBelow)) {
1246
1246
  return true;
@@ -1322,8 +1322,8 @@ function isWeakWalkthroughComment(body2, statement) {
1322
1322
  if (normalized.length === 0 || normalized.endsWith("?") || normalized.split(/\s+/).length > WALL_MAX_WORDS || isDirective(normalized) || isProtected(normalized) || !WALL_NARRATION_RE.test(normalized)) {
1323
1323
  return false;
1324
1324
  }
1325
- const words = contentTokens(normalized);
1326
- const described = words.slice(1);
1325
+ const words2 = contentTokens(normalized);
1326
+ const described = words2.slice(1);
1327
1327
  if (described.length === 0) return false;
1328
1328
  const code = codeTokens(statement);
1329
1329
  const matched = described.filter((word) => restates([word], code)).length;
@@ -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 interpolation or concatenation in SQL passed to statement-execution methods.",
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 recognizes SQL by syntax and configured method names; static fragments and parameterizing tagged templates are exempt."
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) => operand.type !== AST_NODE_TYPES8.Literal && !isStaticFragment(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: "Disallow runtime interpolation or concatenation in SQL passed to statement-execution methods."
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 built into a SQL statement passed to `{{method}}()`. Use a `?` placeholder and pass the value through `.bind(...)` so the driver parameterises it."
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: [{}],
@@ -2648,7 +2670,7 @@ var no_fat_try_blocks_default = createRule({
2648
2670
  const sourceCode = context.sourceCode;
2649
2671
  return {
2650
2672
  TryStatement(node) {
2651
- if (node.finalizer !== null) {
2673
+ if (node.finalizer !== null && node.handler === null) {
2652
2674
  return;
2653
2675
  }
2654
2676
  if (handlerRethrows(node.handler)) {
@@ -2888,15 +2910,32 @@ var noHandRolledSpinnerDocumentation = {
2888
2910
  rationale: "One-off loading indicators duplicate a shared primitive and let accessibility and styling diverge.",
2889
2911
  remediation: "Render the design-system Spinner component instead.",
2890
2912
  category: "maintainability",
2891
- limitations: ["Only static className values on div and span elements are inspected."],
2913
+ limitations: ["Only static className values on div and span elements are inspected; tests, stories, generated files, and the design-system implementation are excluded."],
2892
2914
  examples: [
2893
2915
  { 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 },
2894
2916
  { 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 }
2895
2917
  ]
2896
2918
  };
2897
2919
  var DESIGN_SYSTEM_PATH = /(?:^|[/\\])components[/\\]ui[/\\]/u;
2898
- var BORDER_WIDTH = /^border(?:-[0-9]+)?$/u;
2899
- var CONTRASTING_EDGE = /^border-[trbl]-(?!0$|[0-9]+$).+/u;
2920
+ var DIRECTIONAL_BORDER = /^border-([trblsexy])-(.+)$/u;
2921
+ 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;
2922
+ var ARBITRARY_LENGTH_FUNCTION = /^(?:calc|clamp|max|min)\(.+\)$/u;
2923
+ function isBorderWidthValue(value) {
2924
+ if (/^\d+$/u.test(value)) return true;
2925
+ if (value.startsWith("[") && value.endsWith("]")) {
2926
+ const arbitrary = value.slice(1, -1);
2927
+ const length = arbitrary.startsWith("length:") ? arbitrary.slice("length:".length) : arbitrary;
2928
+ return CSS_LENGTH.test(length) || ARBITRARY_LENGTH_FUNCTION.test(length) || arbitrary.startsWith("length:") && /^var\(.+\)$/u.test(length);
2929
+ }
2930
+ return value.startsWith("(length:") && value.endsWith(")") && value.length > "(length:)".length;
2931
+ }
2932
+ function isBorderWidth(token) {
2933
+ return token === "border" || token.startsWith("border-") && isBorderWidthValue(token.slice("border-".length));
2934
+ }
2935
+ function isContrastingEdge(token) {
2936
+ const match = DIRECTIONAL_BORDER.exec(token);
2937
+ return match?.[2] !== void 0 && !isBorderWidthValue(match[2]);
2938
+ }
2900
2939
  function staticClassName(attribute) {
2901
2940
  const value = attribute.value;
2902
2941
  if (value?.type === AST_NODE_TYPES11.Literal && typeof value.value === "string") {
@@ -2925,7 +2964,7 @@ var no_hand_rolled_spinner_default = createRule({
2925
2964
  },
2926
2965
  defaultOptions: [],
2927
2966
  create(context) {
2928
- if (DESIGN_SYSTEM_PATH.test(context.filename)) {
2967
+ if (DESIGN_SYSTEM_PATH.test(context.filename) || isTestFile(context.filename) || isStoryFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
2929
2968
  return {};
2930
2969
  }
2931
2970
  return {
@@ -2940,7 +2979,7 @@ var no_hand_rolled_spinner_default = createRule({
2940
2979
  const className = staticClassName(classNameAttribute);
2941
2980
  if (className === null) return;
2942
2981
  const classes = className.split(/\s+/u);
2943
- if (classes.includes("animate-spin") && classes.includes("rounded-full") && classes.some((token) => BORDER_WIDTH.test(token)) && classes.some((token) => CONTRASTING_EDGE.test(token))) {
2982
+ if (classes.includes("animate-spin") && classes.includes("rounded-full") && classes.some(isBorderWidth) && classes.some(isContrastingEdge)) {
2944
2983
  context.report({ node, messageId: "handRolledSpinner" });
2945
2984
  }
2946
2985
  }
@@ -2961,8 +3000,59 @@ var noInsecureRandomIdDocumentation = {
2961
3000
  { 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
3001
  ]
2963
3002
  };
2964
- var STRONG_SECURITY_PATTERN = /token|secret|csrf|password|passwd|apikey|api[-_]?key|nonce|salt|uuid|authid/i;
2965
- var NON_SECURITY_ID_PATTERN = /temp|tmp|cache|correlation|request|req|trace|execution|dev|hmr|mock|test|perf|marker|dialog|select|menu|tab|field|input|form|element|dom|aria|component/i;
3003
+ var STRONG_SECURITY_WORDS = /* @__PURE__ */ new Set([
3004
+ "apikey",
3005
+ "csrf",
3006
+ "nonce",
3007
+ "otp",
3008
+ "password",
3009
+ "passwd",
3010
+ "pin",
3011
+ "salt",
3012
+ "secret",
3013
+ "token",
3014
+ "uuid",
3015
+ "verificationcode"
3016
+ ]);
3017
+ var NON_SECURITY_ID_WORDS = /* @__PURE__ */ new Set([
3018
+ "aria",
3019
+ "cache",
3020
+ "component",
3021
+ "correlation",
3022
+ "dev",
3023
+ "dialog",
3024
+ "dom",
3025
+ "element",
3026
+ "execution",
3027
+ "field",
3028
+ "form",
3029
+ "hmr",
3030
+ "input",
3031
+ "marker",
3032
+ "menu",
3033
+ "mock",
3034
+ "perf",
3035
+ "req",
3036
+ "request",
3037
+ "select",
3038
+ "tab",
3039
+ "temp",
3040
+ "test",
3041
+ "tmp",
3042
+ "trace"
3043
+ ]);
3044
+ function nameWords(name) {
3045
+ return name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").split(/[^A-Za-z0-9]+/u).filter(Boolean).map((word) => word.toLowerCase());
3046
+ }
3047
+ function isStrongSecurityName(name) {
3048
+ const words2 = nameWords(name);
3049
+ return words2.some((word) => STRONG_SECURITY_WORDS.has(word)) || words2.some(
3050
+ (word, index) => word === "api" && words2[index + 1] === "key" || word === "auth" && words2[index + 1] === "id" || word === "verification" && words2[index + 1] === "code"
3051
+ );
3052
+ }
3053
+ function isNonSecurityName(name) {
3054
+ return nameWords(name).some((word) => NON_SECURITY_ID_WORDS.has(word));
3055
+ }
2966
3056
  var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
2967
3057
  function isMathRandomCall(node) {
2968
3058
  if (node.type !== "CallExpression") {
@@ -2975,70 +3065,57 @@ function isMathRandomCall(node) {
2975
3065
  const { object, property } = callee;
2976
3066
  return object.type === "Identifier" && object.name === "Math" && property.type === "Identifier" && property.name === "random";
2977
3067
  }
2978
- function isPartOfToString36Chain(node) {
2979
- let current = node;
2980
- let parent = current.parent;
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) {
3068
+ function findEnclosingNames(node) {
3069
+ const names = [];
3070
+ let directBinding = true;
3006
3071
  let current = node;
3007
3072
  let parent = current.parent;
3008
3073
  while (parent) {
3009
3074
  if (parent.type === "VariableDeclarator" && parent.init === current) {
3010
- if (parent.id.type === "Identifier") {
3011
- return parent.id.name;
3075
+ if (directBinding && parent.id.type === "Identifier") {
3076
+ names.push(parent.id.name);
3012
3077
  }
3013
- return void 0;
3014
3078
  }
3015
3079
  if (parent.type === "Property" && parent.value === current) {
3016
3080
  const key = parent.key;
3017
3081
  if (!parent.computed && key.type === "Identifier") {
3018
- return key.name;
3082
+ names.push(key.name);
3019
3083
  }
3020
3084
  if (key.type === "Literal" && typeof key.value === "string") {
3021
- return key.value;
3085
+ names.push(key.value);
3022
3086
  }
3023
- return void 0;
3087
+ directBinding = false;
3024
3088
  }
3025
3089
  if (parent.type === "PropertyDefinition" && parent.value === current) {
3026
3090
  const key = parent.key;
3027
3091
  if (!parent.computed && key.type === "Identifier") {
3028
- return key.name;
3092
+ names.push(key.name);
3029
3093
  }
3030
3094
  if (key.type === "Literal" && typeof key.value === "string") {
3031
- return key.value;
3095
+ names.push(key.value);
3032
3096
  }
3033
- return void 0;
3097
+ directBinding = false;
3034
3098
  }
3035
- if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression" || parent.type === "BlockStatement" || parent.type === "ReturnStatement" || parent.type === "ExpressionStatement") {
3036
- return void 0;
3099
+ if (parent.type === "AssignmentExpression" && parent.right === current) {
3100
+ if (directBinding && parent.left.type === "Identifier") names.push(parent.left.name);
3101
+ if (directBinding && parent.left.type === "MemberExpression" && !parent.left.computed && parent.left.property.type === "Identifier") {
3102
+ names.push(parent.left.property.name);
3103
+ }
3104
+ }
3105
+ if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
3106
+ directBinding = false;
3107
+ }
3108
+ if (parent.type === "FunctionDeclaration") {
3109
+ if (directBinding && parent.id !== null) names.push(parent.id.name);
3110
+ return names;
3111
+ }
3112
+ if (parent.type === "ExpressionStatement") {
3113
+ return names;
3037
3114
  }
3038
3115
  current = parent;
3039
3116
  parent = current.parent;
3040
3117
  }
3041
- return void 0;
3118
+ return names;
3042
3119
  }
3043
3120
  function isConcatenatedIntoPathOrDomId(node) {
3044
3121
  const valueNode = climbValueChain(node);
@@ -3124,20 +3201,17 @@ var no_insecure_random_id_default = createRule({
3124
3201
  if (!isMathRandomCall(node)) {
3125
3202
  return;
3126
3203
  }
3127
- const name = findEnclosingName(node);
3128
- if (name !== void 0 && STRONG_SECURITY_PATTERN.test(name)) {
3204
+ const names = findEnclosingNames(node);
3205
+ if (names.some(isStrongSecurityName)) {
3129
3206
  context.report({ node, messageId: "insecureRandomId" });
3130
3207
  return;
3131
3208
  }
3132
- if (name !== void 0 && NON_SECURITY_ID_PATTERN.test(name)) {
3209
+ if (names.some(isNonSecurityName)) {
3133
3210
  return;
3134
3211
  }
3135
3212
  if (isConcatenatedIntoPathOrDomId(node)) {
3136
3213
  return;
3137
3214
  }
3138
- if (isPartOfToString36Chain(node)) {
3139
- context.report({ node, messageId: "insecureRandomId" });
3140
- }
3141
3215
  }
3142
3216
  };
3143
3217
  }
@@ -3937,7 +4011,7 @@ var VALUE_TAG_RE = /@(example|deprecated|see|remarks|throws|internal|public|alph
3937
4011
  var BOUNDARY_RE = /(?<=[.!?])["'`)\]]*\s+(?=[A-Z0-9`])/;
3938
4012
  var BULLET_RE = /^\s*(?:[-*+] |\d+[.)] )/;
3939
4013
  var HEADING_RE = /^[A-Za-z][A-Za-z ]+:$/;
3940
- 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;
4014
+ 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;
3941
4015
  function body(comment) {
3942
4016
  return comment.value.replace(/^\*/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, "")).join("\n").trim();
3943
4017
  }
@@ -4801,7 +4875,8 @@ var DEFAULT_ALLOW = [
4801
4875
  "[\\\\/](connectors|providers|integrations|adapters|fetchers)[\\\\/]",
4802
4876
  "[\\\\/]notifications[\\\\/]",
4803
4877
  "[Ss]ervice\\.[cm]?[jt]sx?$",
4804
- "-(service|connector|adapter|sdk|fetcher)\\.[cm]?[jt]sx?$"
4878
+ "-(service|connector|adapter|sdk|fetcher)\\.[cm]?[jt]sx?$",
4879
+ "[\\\\/][^\\\\/]*(?:Client|client)\\.[cm]?[jt]sx?$"
4805
4880
  ];
4806
4881
  var NON_PRODUCTION_TREE_RE = /[\\/](playwright|cypress|__testfixtures__)[\\/]/;
4807
4882
  var GLOBAL_RECEIVERS = /* @__PURE__ */ new Set([
@@ -4935,7 +5010,7 @@ var no_raw_fetch_outside_clients_default = createRule({
4935
5010
  defaultOptions: [{}],
4936
5011
  create(context, [options]) {
4937
5012
  const filename = context.filename;
4938
- if (isTestFile(filename) || NON_PRODUCTION_TREE_RE.test(filename)) {
5013
+ if (isTestFile(filename) || isScriptFile(filename) || NON_PRODUCTION_TREE_RE.test(filename)) {
4939
5014
  return {};
4940
5015
  }
4941
5016
  const patterns = options?.allow ?? DEFAULT_ALLOW;
@@ -4975,10 +5050,15 @@ var no_raw_fetch_outside_clients_default = createRule({
4975
5050
  function isInternalApiUrl(node) {
4976
5051
  const resolved = resolveNode2(node ?? void 0);
4977
5052
  if (resolved?.type === AST_NODE_TYPES18.Literal) {
4978
- return typeof resolved.value === "string" && resolved.value.startsWith("/api/");
5053
+ return typeof resolved.value === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(resolved.value);
4979
5054
  }
4980
5055
  if (resolved?.type === AST_NODE_TYPES18.TemplateLiteral) {
4981
- return resolved.quasis[0]?.value.cooked?.startsWith("/api/") === true;
5056
+ const prefix = resolved.quasis[0]?.value.cooked;
5057
+ return typeof prefix === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(prefix);
5058
+ }
5059
+ if (resolved?.type === AST_NODE_TYPES18.CallExpression && resolved.callee.type === AST_NODE_TYPES18.Identifier && resolved.callee.name === "withBase") {
5060
+ const first = resolved.arguments[0];
5061
+ return first !== void 0 && first.type !== AST_NODE_TYPES18.SpreadElement ? isInternalApiUrl(first) : false;
4982
5062
  }
4983
5063
  return resolved?.type === AST_NODE_TYPES18.BinaryExpression && resolved.operator === "+" && isInternalApiUrl(resolved.left);
4984
5064
  }
@@ -5822,12 +5902,17 @@ var BLOB_REDACTION_TOKENS = /* @__PURE__ */ new Set([
5822
5902
  "public"
5823
5903
  ]);
5824
5904
  function rawBlobValueName(value) {
5905
+ if (value.type === "AwaitExpression") return rawBlobValueName(value.argument);
5906
+ if (value.type === "ChainExpression") return rawBlobValueName(value.expression);
5825
5907
  if (value.type === "Identifier") {
5826
5908
  return isRawBlobName(value.name) ? value.name : null;
5827
5909
  }
5828
5910
  if (value.type === "MemberExpression" && !value.computed && value.property.type === "Identifier") {
5829
5911
  return isRawBlobName(value.property.name) ? value.property.name : null;
5830
5912
  }
5913
+ 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")) {
5914
+ return `${value.callee.object.name}.${value.callee.property.name}()`;
5915
+ }
5831
5916
  return null;
5832
5917
  }
5833
5918
  function isRawBlobName(name) {
@@ -6492,6 +6577,9 @@ var isExplanatory = (comment) => !DIRECTIVE_COMMENT_RE.test(comment.value);
6492
6577
  function isTeardownCall(node) {
6493
6578
  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
6579
  }
6580
+ function isCancelledWebShare(node) {
6581
+ 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";
6582
+ }
6495
6583
  function isSilentHandler(handler) {
6496
6584
  const body2 = handler.body;
6497
6585
  if (body2.type !== AST_NODE_TYPES24.BlockStatement) {
@@ -6541,7 +6629,7 @@ var no_silent_promise_catch_default = createRule({
6541
6629
  },
6542
6630
  defaultOptions: [],
6543
6631
  create(context) {
6544
- if (isTestFile(context.filename)) {
6632
+ if (isTestFile(context.filename) || isScriptFile(context.filename)) {
6545
6633
  return {};
6546
6634
  }
6547
6635
  const hasExplanatoryComment = (call, handler) => {
@@ -6574,7 +6662,10 @@ var no_silent_promise_catch_default = createRule({
6574
6662
  if (isTeardownCall(node.callee.object)) {
6575
6663
  return;
6576
6664
  }
6577
- if (node.parent.type === AST_NODE_TYPES24.MemberExpression && node.parent.object === node) {
6665
+ if (isCancelledWebShare(node.callee.object)) {
6666
+ return;
6667
+ }
6668
+ 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
6669
  return;
6579
6670
  }
6580
6671
  const expectedArguments = method === "catch" ? 1 : 2;
@@ -6736,22 +6827,25 @@ var noStorageInStatelessModulesDocumentation = {
6736
6827
  rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
6737
6828
  remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
6738
6829
  category: "architecture",
6739
- limitations: ["The rule is disabled until module path patterns are configured and recognizes only configured storage method names."],
6830
+ 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."],
6740
6831
  examples: [
6741
6832
  { 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 },
6742
6833
  { 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 }
6743
6834
  ]
6744
6835
  };
6745
6836
  function compile2(patterns) {
6746
- const compiled = [];
6747
- for (const pattern of patterns) {
6748
- try {
6749
- compiled.push(new RegExp(pattern));
6750
- } catch {
6751
- }
6752
- }
6753
- return compiled;
6837
+ return patterns.map((pattern) => new RegExp(pattern));
6754
6838
  }
6839
+ var STORAGE_RECEIVER_WORDS = /* @__PURE__ */ new Set([
6840
+ "bucket",
6841
+ "cache",
6842
+ "kv",
6843
+ "namespace",
6844
+ "r2",
6845
+ "redis",
6846
+ "storage",
6847
+ "store"
6848
+ ]);
6755
6849
  function storageMethodName(node, methods) {
6756
6850
  const callee = node.callee;
6757
6851
  if (callee.type !== AST_NODE_TYPES26.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES26.Identifier) {
@@ -6764,8 +6858,31 @@ function storageMethodName(node, methods) {
6764
6858
  if (node.arguments.length < (MIN_ARGUMENTS.get(name) ?? 1)) {
6765
6859
  return null;
6766
6860
  }
6861
+ if (name === "put" && !isStorageLikeReceiver(callee.object)) {
6862
+ return null;
6863
+ }
6767
6864
  return name;
6768
6865
  }
6866
+ function isStorageLikeReceiver(node) {
6867
+ if (node.type === AST_NODE_TYPES26.Identifier) {
6868
+ return isStorageIdentifier(node.name);
6869
+ }
6870
+ if (node.type !== AST_NODE_TYPES26.MemberExpression) {
6871
+ return false;
6872
+ }
6873
+ if (!node.computed && node.property.type === AST_NODE_TYPES26.Identifier && isStorageIdentifier(node.property.name)) {
6874
+ return true;
6875
+ }
6876
+ return isStorageLikeReceiver(node.object);
6877
+ }
6878
+ function isStorageIdentifier(name) {
6879
+ return identifierWords(name).some(
6880
+ (word) => STORAGE_RECEIVER_WORDS.has(word.toLowerCase())
6881
+ );
6882
+ }
6883
+ function identifierWords(name) {
6884
+ return name.match(/[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)|\d+/gu) ?? [name];
6885
+ }
6769
6886
  var no_storage_in_stateless_modules_default = createRule({
6770
6887
  name: "no-storage-in-stateless-modules",
6771
6888
  documentation: noStorageInStatelessModulesDocumentation,
@@ -6886,7 +7003,20 @@ function isStringInitializedVariable(variable) {
6886
7003
  if (declarator.type !== "VariableDeclarator") {
6887
7004
  return false;
6888
7005
  }
6889
- return isStringLiteralInit(declarator.init);
7006
+ if (isStringLiteralInit(declarator.init)) return true;
7007
+ if (declarator.id.type === "Identifier" && declarator.id.typeAnnotation?.typeAnnotation.type === "TSStringKeyword") {
7008
+ return true;
7009
+ }
7010
+ return isTemplateStringsArrayElement(declarator.init, variable.scope);
7011
+ }
7012
+ function isTemplateStringsArrayElement(node, scope) {
7013
+ if (node?.type !== "MemberExpression" || !node.computed || node.object.type !== "Identifier") {
7014
+ return false;
7015
+ }
7016
+ const source = findVariable(scope, node.object.name);
7017
+ if (source?.defs.length !== 1) return false;
7018
+ const name = source.defs[0]?.name;
7019
+ return name?.type === "Identifier" && name.typeAnnotation?.typeAnnotation.type === "TSTypeReference" && name.typeAnnotation.typeAnnotation.typeName.type === "Identifier" && name.typeAnnotation.typeAnnotation.typeName.name === "TemplateStringsArray";
6890
7020
  }
6891
7021
  function isStringLiteralInit(node) {
6892
7022
  if (node === null) {
@@ -6920,12 +7050,13 @@ function isConcatOperand(node, target) {
6920
7050
  }
6921
7051
  return false;
6922
7052
  }
6923
- function isDeclaredInsideLoop(variable, loop) {
7053
+ function isDeclaredInsideLoop(variable, repetition) {
6924
7054
  const def = variable.defs[0];
6925
7055
  if (def === void 0) {
6926
7056
  return false;
6927
7057
  }
6928
- const body2 = loop.body;
7058
+ const body2 = repetition.type === "CallExpression" ? repetition.arguments[0] : repetition.body;
7059
+ if (body2 === void 0 || body2.type === "SpreadElement") return false;
6929
7060
  const [declStart, declEnd] = def.node.range;
6930
7061
  const [bodyStart, bodyEnd] = body2.range;
6931
7062
  return declStart >= bodyStart && declEnd <= bodyEnd;
@@ -6934,6 +7065,9 @@ function enclosingLoop(node) {
6934
7065
  let child = node;
6935
7066
  let parent = node.parent;
6936
7067
  while (parent !== void 0 && parent !== null) {
7068
+ 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") {
7069
+ return parent.parent;
7070
+ }
6937
7071
  if (LOOP_NODE_TYPES.has(parent.type)) {
6938
7072
  const loop = parent;
6939
7073
  if (loop.body === child) {
@@ -6945,6 +7079,17 @@ function enclosingLoop(node) {
6945
7079
  }
6946
7080
  return null;
6947
7081
  }
7082
+ function isSmallStaticForLoop(node) {
7083
+ 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 !== "++") {
7084
+ return false;
7085
+ }
7086
+ const declaration = node.init.declarations[0];
7087
+ 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) {
7088
+ return false;
7089
+ }
7090
+ const iterations = node.test.right.value - declaration.init.value + (node.test.operator === "<=" ? 1 : 0);
7091
+ return iterations >= 0 && iterations <= 8;
7092
+ }
6948
7093
  var no_string_concat_in_loop_default = createRule({
6949
7094
  name: "no-string-concat-in-loop",
6950
7095
  documentation: noStringConcatInLoopDocumentation,
@@ -6977,6 +7122,9 @@ var no_string_concat_in_loop_default = createRule({
6977
7122
  if (loop === null) {
6978
7123
  return;
6979
7124
  }
7125
+ if (isSmallStaticForLoop(loop)) {
7126
+ return;
7127
+ }
6980
7128
  const scope = context.sourceCode.getScope(node);
6981
7129
  const variable = findVariable(scope, node.left.name);
6982
7130
  if (variable === void 0) {
@@ -7136,12 +7284,86 @@ var no_tautological_expect_default = createRule({
7136
7284
  });
7137
7285
 
7138
7286
  // src/rules/no-typed-doc-sections.ts
7287
+ var TYPED_TAG_RE2 = /^\s*@(arg|argument|param|return|returns|yield|yields)\b(.*)$/iu;
7288
+ var PARAM_TAGS2 = /* @__PURE__ */ new Set(["arg", "argument", "param"]);
7289
+ var PARAMETER_FILLER = /* @__PURE__ */ new Set([
7290
+ "a",
7291
+ "an",
7292
+ "argument",
7293
+ "given",
7294
+ "input",
7295
+ "parameter",
7296
+ "passed",
7297
+ "provided",
7298
+ "the",
7299
+ "value"
7300
+ ]);
7301
+ var RESULT_FILLER = /* @__PURE__ */ new Set([
7302
+ "a",
7303
+ "an",
7304
+ "array",
7305
+ "boolean",
7306
+ "generator",
7307
+ "number",
7308
+ "object",
7309
+ "output",
7310
+ "promise",
7311
+ "result",
7312
+ "return",
7313
+ "returned",
7314
+ "returns",
7315
+ "string",
7316
+ "the",
7317
+ "value"
7318
+ ]);
7319
+ function hasVacuousTypedTag(text) {
7320
+ const tags = typedTags(text);
7321
+ return tags.length > 0 && tags.some(isVacuousTag);
7322
+ }
7323
+ function typedTags(text) {
7324
+ const tags = [];
7325
+ for (const raw of text.split("\n")) {
7326
+ const match = TYPED_TAG_RE2.exec(raw);
7327
+ if (match !== null) {
7328
+ tags.push({ kind: (match[1] ?? "").toLowerCase(), payload: (match[2] ?? "").trim() });
7329
+ } else if (tags.length > 0 && raw.trim().length > 0 && !raw.trim().startsWith("@")) {
7330
+ const last = tags.at(-1);
7331
+ last.payload = `${last.payload} ${raw.trim()}`.trim();
7332
+ }
7333
+ }
7334
+ return tags.map(({ kind, payload }) => {
7335
+ let rest = payload.replace(/^\{[^}\n]+\}\s*/u, "").trim();
7336
+ if (!PARAM_TAGS2.has(kind)) {
7337
+ return { kind, name: null, description: rest.replace(/^-\s*/u, "").trim() };
7338
+ }
7339
+ const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s*|\s+)?(.*)$/u.exec(rest);
7340
+ if (match === null) return { kind, name: null, description: "" };
7341
+ const rawName = (match[1] ?? "").replace(/^\[/u, "").replace(/\]$/u, "").split("=")[0] ?? "";
7342
+ rest = (match[2] ?? "").trim();
7343
+ return { kind, name: rawName, description: rest };
7344
+ });
7345
+ }
7346
+ function isVacuousTag(tag) {
7347
+ const description = words(tag.description).map(canonicalWord);
7348
+ if (description.length === 0) return true;
7349
+ if (tag.name === null) return description.every((word) => RESULT_FILLER.has(word));
7350
+ const nameWords2 = new Set(words(tag.name).map(canonicalWord));
7351
+ return description.every((word) => PARAMETER_FILLER.has(word) || nameWords2.has(word));
7352
+ }
7353
+ function words(text) {
7354
+ return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[a-z][a-z0-9]*/gu) ?? [];
7355
+ }
7356
+ function canonicalWord(word) {
7357
+ if (["identifier", "identifiers", "ids"].includes(word)) return "id";
7358
+ if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
7359
+ return word;
7360
+ }
7139
7361
  var noTypedDocSectionsDocumentation = {
7140
7362
  summary: "Reject typed-signature repetition while preserving behavior that types cannot express.",
7141
7363
  rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
7142
7364
  remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
7143
7365
  category: "maintainability",
7144
- limitations: ["Parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
7366
+ limitations: ["Description-free or name-restating parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
7145
7367
  examples: [
7146
7368
  {
7147
7369
  id: "behavioral-documentation",
@@ -7179,7 +7401,7 @@ var no_typed_doc_sections_default = createRule({
7179
7401
  return {
7180
7402
  Program() {
7181
7403
  for (const group of proseGroups(context.filename, context.sourceCode, true)) {
7182
- if (group.hasTypedTags && documentsTypedFunction(context.sourceCode, group.comment)) {
7404
+ if (group.hasTypedTags && hasVacuousTypedTag(group.text) && documentsTypedFunction(context.sourceCode, group.comment)) {
7183
7405
  context.report({ node: group.comment, messageId: "typedSection" });
7184
7406
  }
7185
7407
  }
@@ -7280,28 +7502,32 @@ var STOPWORDS3 = /* @__PURE__ */ new Set([
7280
7502
  "with"
7281
7503
  ]);
7282
7504
  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;
7505
+ 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;
7283
7506
  function narratesValue(body2, code) {
7284
7507
  if (body2.length === 0 || DIRECTIVE_RE5.test(body2) || hasExternalReference(body2)) return false;
7285
7508
  const codeNumbers = numbersIn(code);
7286
7509
  if (codeNumbers.size === 0) return false;
7287
- const words = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
7288
- if (words.length === 0) return false;
7510
+ const words2 = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
7511
+ if (words2.length === 0) return false;
7289
7512
  const commentNumbers = numbersIn(body2);
7290
7513
  if (commentNumbers.size === 0) return false;
7291
7514
  for (const number of commentNumbers) {
7292
7515
  if (!codeNumbers.has(number)) return false;
7293
7516
  }
7294
- if (!words.some((word) => UNIT_WORDS.has(word))) return false;
7517
+ if (!words2.some((word) => UNIT_WORDS.has(word))) return false;
7295
7518
  const identifiers = codeTokens(code);
7296
7519
  const stems = /* @__PURE__ */ new Set();
7297
7520
  for (const token of identifiers) stems.add(stem(token));
7298
- return words.every(
7521
+ return words2.every(
7299
7522
  (word) => STOPWORDS3.has(word) || UNIT_WORDS.has(word) || commentNumbers.has(word) || identifiers.has(word) || stems.has(stem(word))
7300
7523
  );
7301
7524
  }
7302
7525
  function numbersIn(text) {
7303
7526
  return new Set(text.match(NUMBER_RE) ?? []);
7304
7527
  }
7528
+ function nameAlreadyCarriesUnit(code) {
7529
+ return (code.match(/[A-Za-z_$][\w$]*/gu) ?? []).some((identifier) => UNIT_NAME_SUFFIX_RE.test(identifier));
7530
+ }
7305
7531
  var no_trailing_value_narration_default = createRule({
7306
7532
  name: "no-trailing-value-narration",
7307
7533
  documentation: noTrailingValueNarrationDocumentation,
@@ -7312,6 +7538,7 @@ var no_trailing_value_narration_default = createRule({
7312
7538
  },
7313
7539
  schema: [],
7314
7540
  messages: {
7541
+ deleteNarration: "Trailing comment restates the literal and the identifier already names its unit \u2014 delete the comment so it cannot drift.",
7315
7542
  narratesValue: "Trailing comment restates the literal on this line \u2014 put the unit in the name (STALE_TIME_MS) so it cannot drift."
7316
7543
  }
7317
7544
  },
@@ -7344,7 +7571,10 @@ var no_trailing_value_narration_default = createRule({
7344
7571
  const code = line.slice(0, comment.loc.start.column);
7345
7572
  const body2 = comment.value.replace(/^\*+/, "").replace(/\*+$/, "").trim();
7346
7573
  if (narratesValue(body2, code)) {
7347
- context.report({ node: comment, messageId: "narratesValue" });
7574
+ context.report({
7575
+ node: comment,
7576
+ messageId: nameAlreadyCarriesUnit(code) ? "deleteNarration" : "narratesValue"
7577
+ });
7348
7578
  }
7349
7579
  }
7350
7580
  }
@@ -8262,7 +8492,8 @@ var no_unsafe_mock_casting_default = createRule({
8262
8492
  // src/rules/no-zod-native-enum.ts
8263
8493
  import {
8264
8494
  ESLintUtils as ESLintUtils2,
8265
- AST_NODE_TYPES as AST_NODE_TYPES34
8495
+ AST_NODE_TYPES as AST_NODE_TYPES34,
8496
+ ASTUtils as ASTUtils8
8266
8497
  } from "@typescript-eslint/utils";
8267
8498
  import * as ts from "typescript";
8268
8499
  var noZodNativeEnumDocumentation = {
@@ -8391,15 +8622,23 @@ var no_zod_native_enum_default = createRule({
8391
8622
  } catch {
8392
8623
  services = null;
8393
8624
  }
8394
- const zodImportedNames = /* @__PURE__ */ new Map();
8395
- const zodNamespaces = /* @__PURE__ */ new Set();
8625
+ const zodImportedBindings = /* @__PURE__ */ new Map();
8626
+ const zodNamespaceBindings = /* @__PURE__ */ new Set();
8627
+ function resolvedBinding(identifier) {
8628
+ return ASTUtils8.findVariable(
8629
+ sourceCode.getScope(identifier),
8630
+ identifier.name
8631
+ );
8632
+ }
8396
8633
  function isZodMemberCall(node, api) {
8397
8634
  const callee = node.callee;
8398
- if (callee.type === AST_NODE_TYPES34.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES34.Identifier && zodNamespaces.has(callee.object.name) && callee.property.type === AST_NODE_TYPES34.Identifier) {
8399
- return callee.property.name === api;
8635
+ if (callee.type === AST_NODE_TYPES34.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES34.Identifier && callee.property.type === AST_NODE_TYPES34.Identifier) {
8636
+ const binding = resolvedBinding(callee.object);
8637
+ return binding !== null && zodNamespaceBindings.has(binding) && callee.property.name === api;
8400
8638
  }
8401
8639
  if (callee.type === AST_NODE_TYPES34.Identifier) {
8402
- return zodImportedNames.get(callee.name) === api;
8640
+ const binding = resolvedBinding(callee);
8641
+ return binding !== null && zodImportedBindings.get(binding) === api;
8403
8642
  }
8404
8643
  return false;
8405
8644
  }
@@ -8434,10 +8673,14 @@ var no_zod_native_enum_default = createRule({
8434
8673
  }
8435
8674
  for (const spec of node.specifiers) {
8436
8675
  if (spec.type === AST_NODE_TYPES34.ImportNamespaceSpecifier || spec.type === AST_NODE_TYPES34.ImportDefaultSpecifier || spec.type === AST_NODE_TYPES34.ImportSpecifier && (spec.imported.type === AST_NODE_TYPES34.Identifier ? spec.imported.name === "z" : spec.imported.value === "z")) {
8437
- zodNamespaces.add(spec.local.name);
8676
+ const binding = resolvedBinding(spec.local);
8677
+ if (binding !== null) zodNamespaceBindings.add(binding);
8438
8678
  }
8439
8679
  if (spec.type === AST_NODE_TYPES34.ImportSpecifier && spec.imported.type === AST_NODE_TYPES34.Identifier) {
8440
- zodImportedNames.set(spec.local.name, spec.imported.name);
8680
+ const binding = resolvedBinding(spec.local);
8681
+ if (binding !== null) {
8682
+ zodImportedBindings.set(binding, spec.imported.name);
8683
+ }
8441
8684
  }
8442
8685
  }
8443
8686
  },
@@ -8474,7 +8717,7 @@ var no_zod_native_enum_default = createRule({
8474
8717
  });
8475
8718
 
8476
8719
  // src/rules/test-loops-over-literal-cases.ts
8477
- import { AST_NODE_TYPES as AST_NODE_TYPES35, ASTUtils as ASTUtils8 } from "@typescript-eslint/utils";
8720
+ import { AST_NODE_TYPES as AST_NODE_TYPES35, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
8478
8721
  var testLoopsOverLiteralCasesDocumentation = {
8479
8722
  summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
8480
8723
  rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
@@ -8637,7 +8880,7 @@ var test_loops_over_literal_cases_default = createRule({
8637
8880
  return {};
8638
8881
  }
8639
8882
  const isFrameworkIdentifier = (identifier, modules) => {
8640
- const variable = ASTUtils8.findVariable(context.sourceCode.getScope(identifier), identifier.name);
8883
+ const variable = ASTUtils9.findVariable(context.sourceCode.getScope(identifier), identifier.name);
8641
8884
  if (variable === null || variable.defs.length === 0) return true;
8642
8885
  return variable.defs.some((definition) => {
8643
8886
  let current = definition.node;
@@ -8830,8 +9073,10 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
8830
9073
  let statusMemberCount = 0;
8831
9074
  let hasFailurePayload = false;
8832
9075
  let hasSuccessPayload = false;
9076
+ let hasUnrecognizedMember = false;
8833
9077
  for (const member of typeLiteral.members) {
8834
9078
  if (member.type !== AST_NODE_TYPES37.TSPropertySignature) {
9079
+ hasUnrecognizedMember = true;
8835
9080
  continue;
8836
9081
  }
8837
9082
  const name = getMemberName(member);
@@ -8840,15 +9085,18 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
8840
9085
  continue;
8841
9086
  }
8842
9087
  if (!member.optional || isBooleanTyped(member) || name === null) {
9088
+ hasUnrecognizedMember = true;
8843
9089
  continue;
8844
9090
  }
8845
9091
  if (FAILURE_MEMBER_NAMES.has(name)) {
8846
9092
  hasFailurePayload = true;
8847
9093
  } else if (SUCCESS_PAYLOAD_MEMBER_NAMES.has(name)) {
8848
9094
  hasSuccessPayload = true;
9095
+ } else {
9096
+ hasUnrecognizedMember = true;
8849
9097
  }
8850
9098
  }
8851
- return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && hasSuccessPayload;
9099
+ return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && (hasSuccessPayload || !hasUnrecognizedMember);
8852
9100
  }
8853
9101
  function getMemberName(member) {
8854
9102
  if (member.type !== AST_NODE_TYPES37.TSPropertySignature) {
@@ -9069,7 +9317,7 @@ var prefer_input_group_search_default = createRule({
9069
9317
  });
9070
9318
 
9071
9319
  // src/rules/prefer-immutable-module-constant.ts
9072
- import { AST_NODE_TYPES as AST_NODE_TYPES39, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
9320
+ import { AST_NODE_TYPES as AST_NODE_TYPES39, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
9073
9321
  var preferImmutableModuleConstantDocumentation = {
9074
9322
  summary: "Require module-level constant collections to expose readonly state.",
9075
9323
  rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
@@ -9222,7 +9470,7 @@ var prefer_immutable_module_constant_default = createRule({
9222
9470
  create(context) {
9223
9471
  const sourceCode = context.sourceCode;
9224
9472
  const isUnshadowedGlobal = (identifier) => {
9225
- const variable = ASTUtils9.findVariable(sourceCode.getScope(identifier), identifier.name);
9473
+ const variable = ASTUtils10.findVariable(sourceCode.getScope(identifier), identifier.name);
9226
9474
  return variable === null || variable.defs.length === 0;
9227
9475
  };
9228
9476
  if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
@@ -10139,7 +10387,7 @@ var prefer_module_level_schema_default = createRule({
10139
10387
  });
10140
10388
 
10141
10389
  // src/rules/prefer-native-random-uuid.ts
10142
- import { AST_NODE_TYPES as AST_NODE_TYPES43, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
10390
+ import { AST_NODE_TYPES as AST_NODE_TYPES43, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
10143
10391
  var preferNativeRandomUuidDocumentation = {
10144
10392
  summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
10145
10393
  rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
@@ -10175,7 +10423,7 @@ var prefer_native_random_uuid_default = createRule({
10175
10423
  const directBindings = /* @__PURE__ */ new Set();
10176
10424
  const namespaceBindings = /* @__PURE__ */ new Set();
10177
10425
  function resolve(identifier) {
10178
- return ASTUtils10.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10426
+ return ASTUtils11.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10179
10427
  }
10180
10428
  function record(identifier, destination) {
10181
10429
  const variable = resolve(identifier);
@@ -10238,7 +10486,7 @@ var prefer_native_random_uuid_default = createRule({
10238
10486
  });
10239
10487
 
10240
10488
  // src/rules/prefer-non-nullable-collection.ts
10241
- import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
10489
+ import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
10242
10490
  var preferNonNullableCollectionDocumentation = {
10243
10491
  summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
10244
10492
  rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
@@ -10368,14 +10616,14 @@ function directlyCoalesced(node) {
10368
10616
  return parent?.type === AST_NODE_TYPES44.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
10369
10617
  }
10370
10618
  function identifierIsOnlyCoalesced(context, binding, fn) {
10371
- const variable = ASTUtils11.findVariable(context.sourceCode.getScope(binding), binding.name);
10619
+ const variable = ASTUtils12.findVariable(context.sourceCode.getScope(binding), binding.name);
10372
10620
  if (variable === null || variable.references.length === 0) return false;
10373
10621
  return variable.references.every(
10374
10622
  (reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
10375
10623
  );
10376
10624
  }
10377
10625
  function memberIsOnlyCoalesced(context, object, property, fn) {
10378
- const variable = ASTUtils11.findVariable(context.sourceCode.getScope(object), object.name);
10626
+ const variable = ASTUtils12.findVariable(context.sourceCode.getScope(object), object.name);
10379
10627
  if (variable === null) return false;
10380
10628
  const accesses = variable.references.flatMap((reference) => {
10381
10629
  if (!belongsToFunction(reference.identifier, fn)) return [null];
@@ -10749,7 +10997,7 @@ var prefer_schema_for_api_payload_default = createRule({
10749
10997
  },
10750
10998
  defaultOptions: [],
10751
10999
  create(context) {
10752
- if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
11000
+ if (isTestFile(context.filename) || isScriptFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
10753
11001
  return {};
10754
11002
  }
10755
11003
  const unvalidatedVariables = /* @__PURE__ */ new Set();
@@ -12217,6 +12465,9 @@ var requireAssertNeverDocumentation = {
12217
12465
  };
12218
12466
  var isRuntimeHandlingStatement = (statement) => {
12219
12467
  if (statement.type === AST_NODE_TYPES49.EmptyStatement) return false;
12468
+ if (statement.type === AST_NODE_TYPES49.BreakStatement) {
12469
+ return statement.label !== null;
12470
+ }
12220
12471
  if (statement.type === AST_NODE_TYPES49.TSTypeAliasDeclaration || statement.type === AST_NODE_TYPES49.TSInterfaceDeclaration) {
12221
12472
  return false;
12222
12473
  }
@@ -12246,7 +12497,12 @@ function isExhaustiveFiniteSwitch(node, services) {
12246
12497
  const discriminant = services.esTreeNodeToTSNodeMap.get(node.discriminant);
12247
12498
  const discriminantType = checker.getTypeAtLocation(discriminant);
12248
12499
  const constituents = discriminantType.isUnion() ? discriminantType.types : [discriminantType];
12249
- if (constituents.length === 0) return false;
12500
+ if (!discriminantType.isUnion() || constituents.length < 2) return false;
12501
+ if (constituents.every(
12502
+ (constituent) => (constituent.flags & ts2.TypeFlags.BooleanLiteral) !== 0
12503
+ )) {
12504
+ return false;
12505
+ }
12250
12506
  const expected = /* @__PURE__ */ new Set();
12251
12507
  for (const constituent of constituents) {
12252
12508
  const key = finiteTypeKey(constituent, checker);
@@ -12314,7 +12570,7 @@ var require_assert_never_default = createRule({
12314
12570
  });
12315
12571
 
12316
12572
  // src/rules/require-fetch-timeout.ts
12317
- import { AST_NODE_TYPES as AST_NODE_TYPES50, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
12573
+ import { AST_NODE_TYPES as AST_NODE_TYPES50, ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
12318
12574
  var requireFetchTimeoutDocumentation = {
12319
12575
  summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
12320
12576
  rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
@@ -12395,7 +12651,7 @@ var require_fetch_timeout_default = createRule({
12395
12651
  }
12396
12652
  function resolvesToGlobal(identifier) {
12397
12653
  const scope = context.sourceCode.getScope(identifier);
12398
- const variable = ASTUtils12.findVariable(scope, identifier.name);
12654
+ const variable = ASTUtils13.findVariable(scope, identifier.name);
12399
12655
  return variable === null || variable.defs.length === 0;
12400
12656
  }
12401
12657
  function isGlobalFetchCall2(callee) {
@@ -12404,6 +12660,26 @@ var require_fetch_timeout_default = createRule({
12404
12660
  }
12405
12661
  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
12662
  }
12663
+ function localConstInitProvablyLacksSignal(identifier) {
12664
+ const variable = ASTUtils13.findVariable(
12665
+ context.sourceCode.getScope(identifier),
12666
+ identifier.name
12667
+ );
12668
+ if (variable?.defs.length !== 1) return false;
12669
+ const definition = variable.defs[0];
12670
+ if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init?.type !== AST_NODE_TYPES50.ObjectExpression || !initProvablyLacksSignal(definition.node.init)) {
12671
+ return false;
12672
+ }
12673
+ for (const reference of variable.references) {
12674
+ const ref = reference.identifier;
12675
+ if (ref === identifier || ref === definition.name) continue;
12676
+ const member = ref.parent;
12677
+ 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) {
12678
+ return false;
12679
+ }
12680
+ }
12681
+ return true;
12682
+ }
12407
12683
  return {
12408
12684
  CallExpression(node) {
12409
12685
  if (!isGlobalFetchCall2(node.callee)) {
@@ -12413,7 +12689,7 @@ var require_fetch_timeout_default = createRule({
12413
12689
  if (node.arguments.length === 1 && first !== void 0 && !isInlineUrl(first, resolvesToGlobal)) {
12414
12690
  return;
12415
12691
  }
12416
- if (init === void 0 || initProvablyLacksSignal(init)) {
12692
+ if (init === void 0 || initProvablyLacksSignal(init) || init.type === AST_NODE_TYPES50.Identifier && localConstInitProvablyLacksSignal(init)) {
12417
12693
  context.report({ node, messageId: "missingSignal" });
12418
12694
  }
12419
12695
  }
@@ -13027,34 +13303,35 @@ var require_static_next_matcher_default = createRule({
13027
13303
  });
13028
13304
 
13029
13305
  // src/rules/require-zod-form-validation.ts
13030
- import { AST_NODE_TYPES as AST_NODE_TYPES53 } from "@typescript-eslint/utils";
13306
+ import {
13307
+ AST_NODE_TYPES as AST_NODE_TYPES53,
13308
+ ASTUtils as ASTUtils14
13309
+ } from "@typescript-eslint/utils";
13031
13310
  var requireZodFormValidationDocumentation = {
13032
13311
  summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
13033
13312
  rationale: "FormData values are untrusted strings or files and need runtime validation before use.",
13034
13313
  remediation: "Read the value inside a Zod schema's `parse` or `safeParse` input.",
13035
13314
  category: "security",
13315
+ limitations: [
13316
+ "Tests are excluded; imported schema-shaped names are trusted when their implementation is outside the linted file.",
13317
+ "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."
13318
+ ],
13036
13319
  examples: [
13037
13320
  { 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 },
13038
13321
  { 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 }
13039
13322
  ]
13040
13323
  };
13041
- var isZodParseCall = (node) => {
13042
- if (node.type !== AST_NODE_TYPES53.CallExpression) return false;
13043
- const callee = node.callee;
13044
- if (callee.type !== AST_NODE_TYPES53.MemberExpression) return false;
13045
- if (callee.computed) return false;
13046
- if (callee.property.type !== AST_NODE_TYPES53.Identifier) return false;
13047
- const method = callee.property.name;
13048
- if (method !== "parse" && method !== "safeParse" && method !== "parseAsync" && method !== "safeParseAsync") {
13049
- return false;
13050
- }
13051
- return looksLikeZodSchema(callee.object);
13052
- };
13053
- var looksLikeZodSchema = (node) => {
13324
+ var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
13325
+ "parse",
13326
+ "safeParse",
13327
+ "parseAsync",
13328
+ "safeParseAsync"
13329
+ ]);
13330
+ var zodReceiverRoot = (node) => {
13054
13331
  let current = node;
13055
13332
  while (true) {
13056
13333
  if (current.type === AST_NODE_TYPES53.Identifier) {
13057
- return current.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
13334
+ return current;
13058
13335
  }
13059
13336
  if (current.type === AST_NODE_TYPES53.CallExpression) {
13060
13337
  current = current.callee;
@@ -13064,7 +13341,7 @@ var looksLikeZodSchema = (node) => {
13064
13341
  current = current.object;
13065
13342
  continue;
13066
13343
  }
13067
- return false;
13344
+ return null;
13068
13345
  }
13069
13346
  };
13070
13347
  var isFormDataMethodCall = (node) => {
@@ -13094,6 +13371,34 @@ var require_zod_form_validation_default = createRule({
13094
13371
  if (isTestFile(context.filename)) {
13095
13372
  return {};
13096
13373
  }
13374
+ const zodBindings = /* @__PURE__ */ new Set();
13375
+ const resolvedBinding = (identifier) => ASTUtils14.findVariable(
13376
+ context.sourceCode.getScope(identifier),
13377
+ identifier.name
13378
+ );
13379
+ const isProvablyNonZodLocal = (identifier) => {
13380
+ const binding = resolvedBinding(identifier);
13381
+ if (binding === null || zodBindings.has(binding) || binding.defs.length !== 1) {
13382
+ return false;
13383
+ }
13384
+ const definition = binding.defs[0];
13385
+ if (definition?.type !== "Variable" || definition.node.type !== AST_NODE_TYPES53.VariableDeclarator) {
13386
+ return false;
13387
+ }
13388
+ const init = definition.node.init;
13389
+ return init?.type === AST_NODE_TYPES53.ObjectExpression || init?.type === AST_NODE_TYPES53.ArrayExpression || init?.type === AST_NODE_TYPES53.Literal || init?.type === AST_NODE_TYPES53.ArrowFunctionExpression || init?.type === AST_NODE_TYPES53.FunctionExpression;
13390
+ };
13391
+ const isZodParseCall = (node) => {
13392
+ if (node.type !== AST_NODE_TYPES53.CallExpression) return false;
13393
+ const callee = node.callee;
13394
+ if (callee.type !== AST_NODE_TYPES53.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES53.Identifier || !ZOD_PARSE_METHODS.has(callee.property.name)) {
13395
+ return false;
13396
+ }
13397
+ const root = zodReceiverRoot(callee.object);
13398
+ if (root === null) return false;
13399
+ const binding = resolvedBinding(root);
13400
+ return binding !== null && zodBindings.has(binding) || (root.name === "z" || ZOD_SCHEMA_NAME_RE.test(root.name)) && !isProvablyNonZodLocal(root);
13401
+ };
13097
13402
  const isFormSourceIdentifier = (node) => {
13098
13403
  if (node.type !== AST_NODE_TYPES53.Identifier) return false;
13099
13404
  if (/formdata/i.test(node.name)) return true;
@@ -13119,14 +13424,15 @@ var require_zod_form_validation_default = createRule({
13119
13424
  }
13120
13425
  return isFormSourceIdentifier(callee.object);
13121
13426
  };
13122
- const hasZodParseAncestor = (node) => {
13427
+ const zodParseAncestor = (node) => {
13123
13428
  let parent = node.parent;
13124
13429
  while (parent !== null && parent !== void 0) {
13125
- if (isZodParseCall(parent)) return true;
13430
+ if (isZodParseCall(parent)) return parent;
13126
13431
  parent = parent.parent;
13127
13432
  }
13128
- return false;
13433
+ return null;
13129
13434
  };
13435
+ const hasZodParseAncestor = (node) => zodParseAncestor(node) !== null;
13130
13436
  const isInstanceofNarrowing = (node) => {
13131
13437
  const parent = node.parent;
13132
13438
  return parent !== null && parent !== void 0 && parent.type === AST_NODE_TYPES53.BinaryExpression && parent.operator === "instanceof" && parent.left === node && parent.right.type === AST_NODE_TYPES53.Identifier && (parent.right.name === "File" || parent.right.name === "Blob");
@@ -13143,14 +13449,111 @@ var require_zod_form_validation_default = createRule({
13143
13449
  }
13144
13450
  return null;
13145
13451
  };
13452
+ const containingStatement = (node) => {
13453
+ let current = node;
13454
+ while (current.parent !== void 0) {
13455
+ const parent = current.parent;
13456
+ if (parent.type === AST_NODE_TYPES53.BlockStatement || parent.type === AST_NODE_TYPES53.Program) {
13457
+ return current;
13458
+ }
13459
+ current = parent;
13460
+ }
13461
+ return null;
13462
+ };
13463
+ const zodParseMethod = (call) => {
13464
+ const callee = call.callee;
13465
+ return callee.type === AST_NODE_TYPES53.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES53.Identifier ? callee.property.name : null;
13466
+ };
13467
+ const hasConditionalAncestorBeforeStatement = (node, statement) => {
13468
+ let current = node.parent;
13469
+ while (current !== void 0 && current !== statement) {
13470
+ if (current.type === AST_NODE_TYPES53.LogicalExpression || current.type === AST_NODE_TYPES53.ConditionalExpression) {
13471
+ return true;
13472
+ }
13473
+ current = current.parent;
13474
+ }
13475
+ return false;
13476
+ };
13477
+ const isAwaitedBeforeStatement = (node, statement) => {
13478
+ let current = node.parent;
13479
+ while (current !== void 0 && current !== statement) {
13480
+ if (current.type === AST_NODE_TYPES53.AwaitExpression) return true;
13481
+ current = current.parent;
13482
+ }
13483
+ return false;
13484
+ };
13485
+ const guaranteedValidationStatement = (declarator, reference) => {
13486
+ const parse2 = zodParseAncestor(reference);
13487
+ if (parse2 === null) return null;
13488
+ const declarationStatement = containingStatement(declarator);
13489
+ const validationStatement = containingStatement(parse2);
13490
+ if (declarationStatement === null || validationStatement === null || declarationStatement.parent !== validationStatement.parent || validationStatement.range[0] <= declarationStatement.range[1] || hasConditionalAncestorBeforeStatement(parse2, validationStatement)) {
13491
+ return null;
13492
+ }
13493
+ if (validationStatement.type !== AST_NODE_TYPES53.VariableDeclaration && validationStatement.type !== AST_NODE_TYPES53.ExpressionStatement) {
13494
+ return null;
13495
+ }
13496
+ const method = zodParseMethod(parse2);
13497
+ if (method === "parse") return validationStatement;
13498
+ if (method === "parseAsync" && isAwaitedBeforeStatement(parse2, validationStatement)) {
13499
+ return validationStatement;
13500
+ }
13501
+ return null;
13502
+ };
13503
+ const isSafePrevalidationInspection = (identifier) => {
13504
+ const parent = identifier.parent;
13505
+ if (parent.type === AST_NODE_TYPES53.UnaryExpression && parent.operator === "typeof") {
13506
+ return true;
13507
+ }
13508
+ if (parent.type !== AST_NODE_TYPES53.BinaryExpression || parent.left !== identifier) {
13509
+ return false;
13510
+ }
13511
+ if (parent.operator === "instanceof") {
13512
+ return parent.right.type === AST_NODE_TYPES53.Identifier && (parent.right.name === "File" || parent.right.name === "Blob");
13513
+ }
13514
+ return ["===", "!==", "==", "!="].includes(parent.operator) && (parent.right.type === AST_NODE_TYPES53.Literal && parent.right.value === null || parent.right.type === AST_NODE_TYPES53.Identifier && parent.right.name === "undefined");
13515
+ };
13516
+ const statementWithinBlock = (node, block) => {
13517
+ let current = node;
13518
+ while (current.parent !== void 0 && current.parent !== block) {
13519
+ current = current.parent;
13520
+ }
13521
+ return current.parent === block ? current : null;
13522
+ };
13146
13523
  const bindingIsValidated = (declarator) => {
13147
13524
  const variable = context.sourceCode.getDeclaredVariables(declarator)[0];
13148
13525
  if (variable === void 0) return false;
13149
- return variable.references.some(
13150
- (ref) => hasZodParseAncestor(ref.identifier) || isInstanceofNarrowing(ref.identifier)
13526
+ const references = variable.references.filter((reference) => !reference.isWriteOnly()).map((reference) => reference.identifier).filter(
13527
+ (identifier) => identifier.type === AST_NODE_TYPES53.Identifier
13528
+ );
13529
+ if (references.length === 0) return false;
13530
+ if (references.some(isInstanceofNarrowing)) return true;
13531
+ const validationStatements = references.map((reference) => guaranteedValidationStatement(declarator, reference)).filter(
13532
+ (statement) => statement !== null
13151
13533
  );
13534
+ const declarationStatement = containingStatement(declarator);
13535
+ const declarationBlock = declarationStatement?.parent;
13536
+ return references.every((reference) => {
13537
+ if (zodParseAncestor(reference) !== null || isSafePrevalidationInspection(reference)) {
13538
+ return true;
13539
+ }
13540
+ if (declarationBlock === void 0) return false;
13541
+ const useStatement = statementWithinBlock(reference, declarationBlock);
13542
+ return useStatement !== null && validationStatements.some(
13543
+ (statement) => statement.range[1] < useStatement.range[0]
13544
+ );
13545
+ });
13152
13546
  };
13153
13547
  return {
13548
+ ImportDeclaration(node) {
13549
+ if (!isZodModule(node.source.value)) return;
13550
+ for (const specifier of node.specifiers) {
13551
+ if (specifier.type === AST_NODE_TYPES53.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES53.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES53.ImportSpecifier && (specifier.imported.type === AST_NODE_TYPES53.Identifier ? specifier.imported.name === "z" : specifier.imported.value === "z")) {
13552
+ const binding = resolvedBinding(specifier.local);
13553
+ if (binding !== null) zodBindings.add(binding);
13554
+ }
13555
+ }
13556
+ },
13154
13557
  CallExpression(node) {
13155
13558
  if (!isFormDataGetCall(node)) return;
13156
13559
  if (hasZodParseAncestor(node) || isInstanceofNarrowing(node)) return;
@@ -13168,8 +13571,8 @@ var require_zod_form_validation_default = createRule({
13168
13571
  // src/rules/store-insert-requires-on-conflict.ts
13169
13572
  import "@typescript-eslint/utils";
13170
13573
  var storeInsertRequiresOnConflictDocumentation = {
13171
- summary: "Require an embedded SQL INSERT to carry ON CONFLICT; store writes replay under cron re-runs and queue redelivery and must be idempotent upserts.",
13172
- rationale: "A replayed bare insert can duplicate data or fail on a uniqueness constraint.",
13574
+ summary: "Require embedded inserts in explicitly replayable callables to carry conflict handling.",
13575
+ rationale: "A callable named as an enqueue, seed, migration, schedule, ensure, or upsert promises replay safety.",
13173
13576
  remediation: "Add an appropriate `ON CONFLICT` action or supported replay-safe insert form.",
13174
13577
  category: "correctness",
13175
13578
  examples: [
@@ -13178,7 +13581,25 @@ var storeInsertRequiresOnConflictDocumentation = {
13178
13581
  ]
13179
13582
  };
13180
13583
  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;
13584
+ 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;
13585
+ var REPLAY_CONTRACT_NAME = /(?:enqueue|ensure|migrate|recordOnce|schedule|seed|upsert|getOrCreate|createIfAbsent|insertIfAbsent)/i;
13586
+ function owningCallableName(node) {
13587
+ for (let current = node.parent; current !== null && current !== void 0; current = current.parent) {
13588
+ if (current.type === "FunctionDeclaration") {
13589
+ return current.id?.name ?? null;
13590
+ }
13591
+ if (current.type === "MethodDefinition") {
13592
+ return current.key.type === "Identifier" ? current.key.name : null;
13593
+ }
13594
+ if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "VariableDeclarator" && current.parent.id.type === "Identifier") {
13595
+ return current.parent.id.name;
13596
+ }
13597
+ if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "Property" && current.parent.key.type === "Identifier") {
13598
+ return current.parent.key.name;
13599
+ }
13600
+ }
13601
+ return null;
13602
+ }
13182
13603
  var INSERT_GATE = /insert/i;
13183
13604
  var store_insert_requires_on_conflict_default = createRule({
13184
13605
  name: "store-insert-requires-on-conflict",
@@ -13186,7 +13607,7 @@ var store_insert_requires_on_conflict_default = createRule({
13186
13607
  meta: {
13187
13608
  type: "problem",
13188
13609
  docs: {
13189
- description: "Require an embedded SQL INSERT to carry ON CONFLICT; store writes replay under cron re-runs and queue redelivery and must be idempotent upserts."
13610
+ description: "Require embedded inserts in explicitly replayable callables to carry conflict handling."
13190
13611
  },
13191
13612
  schema: [],
13192
13613
  messages: {
@@ -13202,13 +13623,17 @@ var store_insert_requires_on_conflict_default = createRule({
13202
13623
  if (!INSERT_WRITE.test(sql) || CONFLICT_HANDLED.test(sql)) {
13203
13624
  return;
13204
13625
  }
13626
+ const owner = owningCallableName(node);
13627
+ if (owner !== null && !REPLAY_CONTRACT_NAME.test(owner)) {
13628
+ return;
13629
+ }
13205
13630
  context.report({ node, messageId: "storeInsertRequiresOnConflict" });
13206
13631
  });
13207
13632
  }
13208
13633
  });
13209
13634
 
13210
13635
  // src/rules/stepdown.ts
13211
- import { AST_NODE_TYPES as AST_NODE_TYPES54, ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
13636
+ import { AST_NODE_TYPES as AST_NODE_TYPES54, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
13212
13637
  var stepdownDocumentation = {
13213
13638
  summary: "Place a private helper below its sole direct same-scope caller.",
13214
13639
  rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
@@ -13401,7 +13826,7 @@ function methodName(node) {
13401
13826
  return !node.computed && node.key.type === AST_NODE_TYPES54.Identifier ? node.key.name : null;
13402
13827
  }
13403
13828
  function referencedMethod(context, node, classVariables) {
13404
- const objectVariable = node.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils13.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
13829
+ const objectVariable = node.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils15.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
13405
13830
  const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
13406
13831
  if (node.object.type !== AST_NODE_TYPES54.ThisExpression && !isClassReference) return null;
13407
13832
  if (node.property.type === AST_NODE_TYPES54.PrivateIdentifier) return `#${node.property.name}`;
@@ -13454,11 +13879,11 @@ function classScope(context, node, computedReferenceNames) {
13454
13879
  const pinned = /* @__PURE__ */ new Set();
13455
13880
  const classVariables = /* @__PURE__ */ new Set();
13456
13881
  if (node.id !== null) {
13457
- const internal = ASTUtils13.findVariable(context.sourceCode.getScope(node), node.id.name);
13882
+ const internal = ASTUtils15.findVariable(context.sourceCode.getScope(node), node.id.name);
13458
13883
  if (internal !== null) classVariables.add(internal);
13459
13884
  }
13460
13885
  if (node.type === AST_NODE_TYPES54.ClassExpression && node.parent.type === AST_NODE_TYPES54.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES54.Identifier) {
13461
- const outer = ASTUtils13.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
13886
+ const outer = ASTUtils15.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
13462
13887
  if (outer !== null) classVariables.add(outer);
13463
13888
  }
13464
13889
  for (const method of methods) {
@@ -13494,7 +13919,7 @@ function classScope(context, node, computedReferenceNames) {
13494
13919
  return;
13495
13920
  }
13496
13921
  if (binding.type !== AST_NODE_TYPES54.Identifier) return;
13497
- const variable = ASTUtils13.findVariable(context.sourceCode.getScope(binding), binding.name);
13922
+ const variable = ASTUtils15.findVariable(context.sourceCode.getScope(binding), binding.name);
13498
13923
  if (variable !== null) {
13499
13924
  methodClassVariables.add(variable);
13500
13925
  methodAliases.add(variable);
@@ -13524,7 +13949,7 @@ function classScope(context, node, computedReferenceNames) {
13524
13949
  return;
13525
13950
  }
13526
13951
  if (!privateNames.has(target)) return;
13527
- const objectVariable = current.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils13.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
13952
+ const objectVariable = current.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils15.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
13528
13953
  if (objectVariable !== null && methodAliases.has(objectVariable)) {
13529
13954
  pinned.add(target);
13530
13955
  return;
@@ -13608,7 +14033,7 @@ var stepdown_default = createRule({
13608
14033
  // src/rules/zod-naming-convention.ts
13609
14034
  import {
13610
14035
  AST_NODE_TYPES as AST_NODE_TYPES55,
13611
- ASTUtils as ASTUtils14
14036
+ ASTUtils as ASTUtils16
13612
14037
  } from "@typescript-eslint/utils";
13613
14038
  var zodNamingConventionDocumentation = {
13614
14039
  summary: "Enforce a consistent Zod schema naming convention \u2014 a `Z` prefix (`ZUser`) or a `Schema` suffix (`userSchema`); both are accepted by default.",
@@ -13695,7 +14120,7 @@ var zod_naming_convention_default = createRule({
13695
14120
  const acceptsSchemaWord = convention !== "prefix";
13696
14121
  const zodBindings = /* @__PURE__ */ new Set();
13697
14122
  function resolvedBinding(identifier) {
13698
- return ASTUtils14.findVariable(
14123
+ return ASTUtils16.findVariable(
13699
14124
  context.sourceCode.getScope(identifier),
13700
14125
  identifier.name
13701
14126
  );
@@ -13891,7 +14316,7 @@ var rules = {
13891
14316
  };
13892
14317
  var meta = {
13893
14318
  name: "@sarj/eslint-plugin",
13894
- version: "15.0.0"
14319
+ version: "15.2.0"
13895
14320
  };
13896
14321
  var applicationOnlyRules = [
13897
14322
  "no-restricted-library-load",