@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 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 words = body2.match(/[A-Za-z][\w$]*/g) ?? [];
1014
- const opener = words[0];
1015
- if (opener === void 0 || words.length > NARRATION_MAX_WORDS) return false;
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 = words.slice(1).map(normalizeToken).filter((word) => !NARRATION_STOPWORDS.has(word));
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 words = t.split(/\s+/);
1286
- if (words.length > 1 && words.length <= 4 && DUMMY_TRANSLATION_RE.test(t) && !/[():=]/.test(t)) {
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 words = contentTokens(normalized);
1369
- const described = words.slice(1);
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 interpolation or concatenation in SQL passed to statement-execution methods.",
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 recognizes SQL by syntax and configured method names; static fragments and parameterizing tagged templates are exempt."
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) => operand.type !== import_utils9.AST_NODE_TYPES.Literal && !isStaticFragment(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: "Disallow runtime interpolation or concatenation in SQL passed to statement-execution methods."
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 built into a SQL statement passed to `{{method}}()`. Use a `?` placeholder and pass the value through `.bind(...)` so the driver parameterises it."
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 BORDER_WIDTH = /^border(?:-[0-9]+)?$/u;
2939
- var CONTRASTING_EDGE = /^border-[trbl]-(?!0$|[0-9]+$).+/u;
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((token) => BORDER_WIDTH.test(token)) && classes.some((token) => CONTRASTING_EDGE.test(token))) {
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 STRONG_SECURITY_PATTERN = /token|secret|csrf|password|passwd|apikey|api[-_]?key|nonce|salt|uuid|authid/i;
3005
- 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;
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 isPartOfToString36Chain(node) {
3019
- let current = node;
3020
- let parent = current.parent;
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
- return parent.id.name;
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
- return key.name;
3122
+ names.push(key.name);
3059
3123
  }
3060
3124
  if (key.type === "Literal" && typeof key.value === "string") {
3061
- return key.value;
3125
+ names.push(key.value);
3062
3126
  }
3063
- return void 0;
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
- return key.name;
3132
+ names.push(key.name);
3069
3133
  }
3070
3134
  if (key.type === "Literal" && typeof key.value === "string") {
3071
- return key.value;
3135
+ names.push(key.value);
3072
3136
  }
3073
- return void 0;
3137
+ directBinding = false;
3074
3138
  }
3075
- if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression" || parent.type === "BlockStatement" || parent.type === "ReturnStatement" || parent.type === "ExpressionStatement") {
3076
- return void 0;
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 void 0;
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 name = findEnclosingName(node);
3168
- if (name !== void 0 && STRONG_SECURITY_PATTERN.test(name)) {
3244
+ const names = findEnclosingNames(node);
3245
+ if (names.some(isStrongSecurityName)) {
3169
3246
  context.report({ node, messageId: "insecureRandomId" });
3170
3247
  return;
3171
3248
  }
3172
- if (name !== void 0 && NON_SECURITY_ID_PATTERN.test(name)) {
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.startsWith("/api/");
5091
+ return typeof resolved.value === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(resolved.value);
5017
5092
  }
5018
5093
  if (resolved?.type === import_utils25.AST_NODE_TYPES.TemplateLiteral) {
5019
- return resolved.quasis[0]?.value.cooked?.startsWith("/api/") === true;
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.parent.type === import_utils33.AST_NODE_TYPES.MemberExpression && node.parent.object === 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 and recognizes only configured storage method names."],
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
- const compiled = [];
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
- return isStringLiteralInit(declarator.init);
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, loop) {
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 = loop.body;
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: ["Parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
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 words = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
7326
- if (words.length === 0) return false;
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 (!words.some((word) => UNIT_WORDS.has(word))) return false;
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 words.every(
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({ node: comment, messageId: "narratesValue" });
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 zodImportedNames = /* @__PURE__ */ new Map();
8427
- const zodNamespaces = /* @__PURE__ */ new Set();
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 && zodNamespaces.has(callee.object.name) && callee.property.type === import_utils45.AST_NODE_TYPES.Identifier) {
8431
- return callee.property.name === api;
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
- return zodImportedNames.get(callee.name) === api;
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
- zodNamespaces.add(spec.local.name);
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
- zodImportedNames.set(spec.local.name, spec.imported.name);
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 === 0) return false;
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 isZodParseCall = (node) => {
13071
- if (node.type !== import_utils66.AST_NODE_TYPES.CallExpression) return false;
13072
- const callee = node.callee;
13073
- if (callee.type !== import_utils66.AST_NODE_TYPES.MemberExpression) return false;
13074
- if (callee.computed) return false;
13075
- if (callee.property.type !== import_utils66.AST_NODE_TYPES.Identifier) return false;
13076
- const method = callee.property.name;
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.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
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 false;
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 hasZodParseAncestor = (node) => {
13452
+ const zodParseAncestor = (node) => {
13152
13453
  let parent = node.parent;
13153
13454
  while (parent !== null && parent !== void 0) {
13154
- if (isZodParseCall(parent)) return true;
13455
+ if (isZodParseCall(parent)) return parent;
13155
13456
  parent = parent.parent;
13156
13457
  }
13157
- return false;
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
- return variable.references.some(
13179
- (ref) => hasZodParseAncestor(ref.identifier) || isInstanceofNarrowing(ref.identifier)
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 an embedded SQL INSERT to carry ON CONFLICT; store writes replay under cron re-runs and queue redelivery and must be idempotent upserts.",
13201
- rationale: "A replayed bare insert can duplicate data or fail on a uniqueness constraint.",
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 an embedded SQL INSERT to carry ON CONFLICT; store writes replay under cron re-runs and queue redelivery and must be idempotent upserts."
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.0.0"
14341
+ version: "15.2.0"
13921
14342
  };
13922
14343
  var applicationOnlyRules = [
13923
14344
  "no-restricted-library-load",