@sarj/eslint-plugin 15.1.0 → 15.3.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;
@@ -2670,7 +2670,7 @@ var no_fat_try_blocks_default = createRule({
2670
2670
  const sourceCode = context.sourceCode;
2671
2671
  return {
2672
2672
  TryStatement(node) {
2673
- if (node.finalizer !== null) {
2673
+ if (node.finalizer !== null && node.handler === null) {
2674
2674
  return;
2675
2675
  }
2676
2676
  if (handlerRethrows(node.handler)) {
@@ -2910,15 +2910,32 @@ var noHandRolledSpinnerDocumentation = {
2910
2910
  rationale: "One-off loading indicators duplicate a shared primitive and let accessibility and styling diverge.",
2911
2911
  remediation: "Render the design-system Spinner component instead.",
2912
2912
  category: "maintainability",
2913
- 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."],
2914
2914
  examples: [
2915
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 },
2916
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 }
2917
2917
  ]
2918
2918
  };
2919
2919
  var DESIGN_SYSTEM_PATH = /(?:^|[/\\])components[/\\]ui[/\\]/u;
2920
- var BORDER_WIDTH = /^border(?:-[0-9]+)?$/u;
2921
- 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
+ }
2922
2939
  function staticClassName(attribute) {
2923
2940
  const value = attribute.value;
2924
2941
  if (value?.type === AST_NODE_TYPES11.Literal && typeof value.value === "string") {
@@ -2947,7 +2964,7 @@ var no_hand_rolled_spinner_default = createRule({
2947
2964
  },
2948
2965
  defaultOptions: [],
2949
2966
  create(context) {
2950
- 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)) {
2951
2968
  return {};
2952
2969
  }
2953
2970
  return {
@@ -2962,7 +2979,7 @@ var no_hand_rolled_spinner_default = createRule({
2962
2979
  const className = staticClassName(classNameAttribute);
2963
2980
  if (className === null) return;
2964
2981
  const classes = className.split(/\s+/u);
2965
- 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)) {
2966
2983
  context.report({ node, messageId: "handRolledSpinner" });
2967
2984
  }
2968
2985
  }
@@ -3028,9 +3045,9 @@ function nameWords(name) {
3028
3045
  return name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").split(/[^A-Za-z0-9]+/u).filter(Boolean).map((word) => word.toLowerCase());
3029
3046
  }
3030
3047
  function isStrongSecurityName(name) {
3031
- const words = nameWords(name);
3032
- return words.some((word) => STRONG_SECURITY_WORDS.has(word)) || words.some(
3033
- (word, index) => word === "api" && words[index + 1] === "key" || word === "auth" && words[index + 1] === "id" || word === "verification" && words[index + 1] === "code"
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"
3034
3051
  );
3035
3052
  }
3036
3053
  function isNonSecurityName(name) {
@@ -3207,14 +3224,12 @@ var noJsonStringifyErrorDocumentation = {
3207
3224
  rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
3208
3225
  remediation: "Serialize explicit error fields or use an error-aware serializer.",
3209
3226
  category: "correctness",
3210
- limitations: ["The rule uses local syntax and naming evidence rather than type information."],
3227
+ limitations: ["The rule uses local catch-binding and constructor provenance rather than type information."],
3211
3228
  examples: [
3212
3229
  { id: "explicit-error-message", title: "Serialize an enumerable error field", outcome: "no-match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err.message }); }" }], focusPath: "src/report.ts", expectedCount: 0, public: true },
3213
3230
  { id: "stringified-error", title: "Do not stringify an Error object", outcome: "match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err }); }" }], focusPath: "src/report.ts", expectedCount: 1, public: true }
3214
3231
  ]
3215
3232
  };
3216
- var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
3217
- var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
3218
3233
  var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
3219
3234
  var PAYLOAD_PROPS = /* @__PURE__ */ new Set([
3220
3235
  "data",
@@ -3242,6 +3257,21 @@ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
3242
3257
  "TypeError",
3243
3258
  "URIError"
3244
3259
  ]);
3260
+ function identifierIsProvenError(identifier, scope) {
3261
+ if (isCatchBinding(scope, identifier.name)) return true;
3262
+ let current = scope;
3263
+ while (current !== null && !current.set.has(identifier.name)) {
3264
+ current = current.upper;
3265
+ }
3266
+ const variable = current?.set.get(identifier.name);
3267
+ if (variable === void 0 || variable.defs.length !== 1) return false;
3268
+ const definition = variable.defs[0];
3269
+ if (definition?.type !== "Variable") return false;
3270
+ const initializer = definition.node.init;
3271
+ return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && variable.references.every(
3272
+ (reference) => !reference.isWrite() || reference.init === true
3273
+ );
3274
+ }
3245
3275
  function isCatchBinding(scope, name) {
3246
3276
  let current = scope;
3247
3277
  while (current) {
@@ -3257,22 +3287,6 @@ function isCatchBinding(scope, name) {
3257
3287
  }
3258
3288
  return false;
3259
3289
  }
3260
- function memberSuggestsError(member, scope) {
3261
- const propName2 = !member.computed && member.property.type === "Identifier" ? member.property.name : null;
3262
- if (propName2 !== null && ERROR_PROP_PATTERN.test(propName2)) {
3263
- return true;
3264
- }
3265
- const base = member.object;
3266
- const baseSuggestsError = base.type === "Identifier" && (ERROR_NAME_PATTERN.test(base.name) || isCatchBinding(scope, base.name));
3267
- if (baseSuggestsError) {
3268
- if (propName2 === null) {
3269
- return true;
3270
- }
3271
- const lowered = propName2.toLowerCase();
3272
- return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
3273
- }
3274
- return false;
3275
- }
3276
3290
  function positiveErrorSubject(test) {
3277
3291
  return instanceofErrorSubject(test) ?? typeGuardSubject(test);
3278
3292
  }
@@ -3376,27 +3390,25 @@ function directLiteralValues(argument) {
3376
3390
  }
3377
3391
  function expressionSuggestsError(expression, scope) {
3378
3392
  if (expression.type === "Identifier") {
3379
- return ERROR_NAME_PATTERN.test(expression.name) || isCatchBinding(scope, expression.name);
3393
+ return identifierIsProvenError(expression, scope);
3394
+ }
3395
+ if (expression.type === "NewExpression" && expression.callee.type === "Identifier") {
3396
+ return BUILTIN_ERROR_CONSTRUCTORS.has(expression.callee.name);
3380
3397
  }
3381
3398
  return expression.type === "MemberExpression" && memberSuggestsError(expression, scope);
3382
3399
  }
3383
- function nestedExpressionSuggestsError(expression, scope) {
3384
- if (expression.type === "Identifier") {
3385
- if (isCatchBinding(scope, expression.name)) return true;
3386
- let current = scope;
3387
- while (current !== null && !current.set.has(expression.name)) {
3388
- current = current.upper;
3389
- }
3390
- const variable = current?.set.get(expression.name);
3391
- if (variable === void 0 || variable.defs.length !== 1) return false;
3392
- const definition = variable.defs[0];
3393
- if (definition?.type !== "Variable") return false;
3394
- const initializer = definition.node.init;
3395
- return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && variable.references.every(
3396
- (reference) => !reference.isWrite() || reference.init === true
3397
- );
3400
+ function memberSuggestsError(member, scope) {
3401
+ const propName2 = !member.computed && member.property.type === "Identifier" ? member.property.name : null;
3402
+ const base = member.object;
3403
+ const baseSuggestsError = base.type === "Identifier" && identifierIsProvenError(base, scope);
3404
+ if (baseSuggestsError) {
3405
+ if (propName2 === null) {
3406
+ return true;
3407
+ }
3408
+ const lowered = propName2.toLowerCase();
3409
+ return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
3398
3410
  }
3399
- return expression.type === "MemberExpression" && memberSuggestsError(expression, scope);
3411
+ return false;
3400
3412
  }
3401
3413
  var no_json_stringify_error_default = createRule({
3402
3414
  name: "no-json-stringify-error",
@@ -3423,9 +3435,8 @@ var no_json_stringify_error_default = createRule({
3423
3435
  return;
3424
3436
  }
3425
3437
  const scope = context.sourceCode.getScope(firstArg);
3426
- const isNestedLiteral = firstArg.type === "ObjectExpression" || firstArg.type === "ArrayExpression";
3427
3438
  const unsafeValue = directLiteralValues(firstArg).find(
3428
- (value) => (isNestedLiteral ? nestedExpressionSuggestsError(value, scope) : expressionSuggestsError(value, scope)) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
3439
+ (value) => expressionSuggestsError(value, scope) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
3429
3440
  );
3430
3441
  if (unsafeValue === void 0) {
3431
3442
  return;
@@ -3994,7 +4005,7 @@ var VALUE_TAG_RE = /@(example|deprecated|see|remarks|throws|internal|public|alph
3994
4005
  var BOUNDARY_RE = /(?<=[.!?])["'`)\]]*\s+(?=[A-Z0-9`])/;
3995
4006
  var BULLET_RE = /^\s*(?:[-*+] |\d+[.)] )/;
3996
4007
  var HEADING_RE = /^[A-Za-z][A-Za-z ]+:$/;
3997
- 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;
4008
+ 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;
3998
4009
  function body(comment) {
3999
4010
  return comment.value.replace(/^\*/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, "")).join("\n").trim();
4000
4011
  }
@@ -4886,6 +4897,7 @@ var ANALYTICS_SEGMENTS2 = /* @__PURE__ */ new Set([
4886
4897
  ]);
4887
4898
  var SERVER_ACTION_SKIP_FILE_RE = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
4888
4899
  var NON_REACT_FRAMEWORK_RE = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
4900
+ var NEXT_MODULE_PATH_RE = /(?:^|[/\\])(?:app|pages)[/\\]/u;
4889
4901
  function isGlobalFetchCall(node, resolvesToGlobal) {
4890
4902
  const callee = node.callee;
4891
4903
  if (callee.type === "Identifier") {
@@ -5001,6 +5013,13 @@ var no_raw_fetch_outside_clients_default = createRule({
5001
5013
  const nonReactFramework = context.sourceCode.ast.body.some(
5002
5014
  (statement) => statement.type === AST_NODE_TYPES18.ImportDeclaration && typeof statement.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(statement.source.value)
5003
5015
  );
5016
+ const hasUseClientDirective = context.sourceCode.ast.body.some(
5017
+ (statement) => statement.type === AST_NODE_TYPES18.ExpressionStatement && statement.expression.type === AST_NODE_TYPES18.Literal && statement.expression.value === "use client"
5018
+ );
5019
+ const hasNextImport = context.sourceCode.ast.body.some(
5020
+ (statement) => statement.type === AST_NODE_TYPES18.ImportDeclaration && typeof statement.source.value === "string" && (statement.source.value === "next" || statement.source.value.startsWith("next/"))
5021
+ );
5022
+ const hasNextEvidence = hasNextImport || hasUseClientDirective && NEXT_MODULE_PATH_RE.test(filename);
5004
5023
  function resolvesToGlobal(identifier) {
5005
5024
  const variable = ASTUtils5.findVariable(
5006
5025
  context.sourceCode.getScope(identifier),
@@ -5061,7 +5080,7 @@ var no_raw_fetch_outside_clients_default = createRule({
5061
5080
  return resolved?.type === AST_NODE_TYPES18.LogicalExpression && resolved.operator === "||" && (isMutationMethod2(resolved.left) || isMutationMethod2(resolved.right));
5062
5081
  }
5063
5082
  function serverActionOwns(node) {
5064
- if (node.callee.type !== AST_NODE_TYPES18.Identifier || SERVER_ACTION_SKIP_FILE_RE.test(filename) || nonReactFramework) {
5083
+ if (node.callee.type !== AST_NODE_TYPES18.Identifier || !hasNextEvidence || SERVER_ACTION_SKIP_FILE_RE.test(filename) || nonReactFramework) {
5065
5084
  return false;
5066
5085
  }
5067
5086
  const url = node.arguments[0];
@@ -6280,6 +6299,20 @@ var SAFE_PARSE_CONSTRUCTORS = /* @__PURE__ */ new Set([
6280
6299
  function isBodyDecodeNode(node) {
6281
6300
  return node.type === AST_NODE_TYPES23.CallExpression && node.callee.type === AST_NODE_TYPES23.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES23.Identifier && BODY_DECODE_METHODS.has(node.callee.property.name);
6282
6301
  }
6302
+ function isSafeParseSupportCall(node) {
6303
+ const callee = node.callee;
6304
+ if (callee.type !== AST_NODE_TYPES23.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES23.Identifier) {
6305
+ return false;
6306
+ }
6307
+ if (callee.property.name === "isArray" && callee.object.type === AST_NODE_TYPES23.Identifier && callee.object.name === "Array") {
6308
+ return true;
6309
+ }
6310
+ if (callee.property.name !== "getItem") return false;
6311
+ if (callee.object.type === AST_NODE_TYPES23.Identifier && (callee.object.name === "localStorage" || callee.object.name === "sessionStorage")) {
6312
+ return true;
6313
+ }
6314
+ return callee.object.type === AST_NODE_TYPES23.MemberExpression && !callee.object.computed && callee.object.object.type === AST_NODE_TYPES23.Identifier && (callee.object.object.name === "window" || callee.object.object.name === "globalThis") && callee.object.property.type === AST_NODE_TYPES23.Identifier && (callee.object.property.name === "localStorage" || callee.object.property.name === "sessionStorage");
6315
+ }
6283
6316
  var BODY_DECODE_METHODS = /* @__PURE__ */ new Set([
6284
6317
  "json",
6285
6318
  "text",
@@ -6348,6 +6381,7 @@ function tryReturnsSafeParse(catchNode) {
6348
6381
  if (current.type === AST_NODE_TYPES23.CallExpression || current.type === AST_NODE_TYPES23.NewExpression) {
6349
6382
  if (isParseShapedNode(current) || isBodyDecodeNode(current)) {
6350
6383
  sawSafeParse = true;
6384
+ } else if (current.type === AST_NODE_TYPES23.CallExpression && isSafeParseSupportCall(current)) {
6351
6385
  } else {
6352
6386
  sawUnsafeOperation = true;
6353
6387
  return;
@@ -6810,22 +6844,25 @@ var noStorageInStatelessModulesDocumentation = {
6810
6844
  rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
6811
6845
  remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
6812
6846
  category: "architecture",
6813
- limitations: ["The rule is disabled until module path patterns are configured and recognizes only configured storage method names."],
6847
+ 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."],
6814
6848
  examples: [
6815
6849
  { 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 },
6816
6850
  { 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 }
6817
6851
  ]
6818
6852
  };
6819
6853
  function compile2(patterns) {
6820
- const compiled = [];
6821
- for (const pattern of patterns) {
6822
- try {
6823
- compiled.push(new RegExp(pattern));
6824
- } catch {
6825
- }
6826
- }
6827
- return compiled;
6854
+ return patterns.map((pattern) => new RegExp(pattern));
6828
6855
  }
6856
+ var STORAGE_RECEIVER_WORDS = /* @__PURE__ */ new Set([
6857
+ "bucket",
6858
+ "cache",
6859
+ "kv",
6860
+ "namespace",
6861
+ "r2",
6862
+ "redis",
6863
+ "storage",
6864
+ "store"
6865
+ ]);
6829
6866
  function storageMethodName(node, methods) {
6830
6867
  const callee = node.callee;
6831
6868
  if (callee.type !== AST_NODE_TYPES26.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES26.Identifier) {
@@ -6838,8 +6875,31 @@ function storageMethodName(node, methods) {
6838
6875
  if (node.arguments.length < (MIN_ARGUMENTS.get(name) ?? 1)) {
6839
6876
  return null;
6840
6877
  }
6878
+ if (name === "put" && !isStorageLikeReceiver(callee.object)) {
6879
+ return null;
6880
+ }
6841
6881
  return name;
6842
6882
  }
6883
+ function isStorageLikeReceiver(node) {
6884
+ if (node.type === AST_NODE_TYPES26.Identifier) {
6885
+ return isStorageIdentifier(node.name);
6886
+ }
6887
+ if (node.type !== AST_NODE_TYPES26.MemberExpression) {
6888
+ return false;
6889
+ }
6890
+ if (!node.computed && node.property.type === AST_NODE_TYPES26.Identifier && isStorageIdentifier(node.property.name)) {
6891
+ return true;
6892
+ }
6893
+ return isStorageLikeReceiver(node.object);
6894
+ }
6895
+ function isStorageIdentifier(name) {
6896
+ return identifierWords(name).some(
6897
+ (word) => STORAGE_RECEIVER_WORDS.has(word.toLowerCase())
6898
+ );
6899
+ }
6900
+ function identifierWords(name) {
6901
+ return name.match(/[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)|\d+/gu) ?? [name];
6902
+ }
6843
6903
  var no_storage_in_stateless_modules_default = createRule({
6844
6904
  name: "no-storage-in-stateless-modules",
6845
6905
  documentation: noStorageInStatelessModulesDocumentation,
@@ -7241,12 +7301,86 @@ var no_tautological_expect_default = createRule({
7241
7301
  });
7242
7302
 
7243
7303
  // src/rules/no-typed-doc-sections.ts
7304
+ var TYPED_TAG_RE2 = /^\s*@(arg|argument|param|return|returns|yield|yields)\b(.*)$/iu;
7305
+ var PARAM_TAGS2 = /* @__PURE__ */ new Set(["arg", "argument", "param"]);
7306
+ var PARAMETER_FILLER = /* @__PURE__ */ new Set([
7307
+ "a",
7308
+ "an",
7309
+ "argument",
7310
+ "given",
7311
+ "input",
7312
+ "parameter",
7313
+ "passed",
7314
+ "provided",
7315
+ "the",
7316
+ "value"
7317
+ ]);
7318
+ var RESULT_FILLER = /* @__PURE__ */ new Set([
7319
+ "a",
7320
+ "an",
7321
+ "array",
7322
+ "boolean",
7323
+ "generator",
7324
+ "number",
7325
+ "object",
7326
+ "output",
7327
+ "promise",
7328
+ "result",
7329
+ "return",
7330
+ "returned",
7331
+ "returns",
7332
+ "string",
7333
+ "the",
7334
+ "value"
7335
+ ]);
7336
+ function hasVacuousTypedTag(text) {
7337
+ const tags = typedTags(text);
7338
+ return tags.length > 0 && tags.some(isVacuousTag);
7339
+ }
7340
+ function typedTags(text) {
7341
+ const tags = [];
7342
+ for (const raw of text.split("\n")) {
7343
+ const match = TYPED_TAG_RE2.exec(raw);
7344
+ if (match !== null) {
7345
+ tags.push({ kind: (match[1] ?? "").toLowerCase(), payload: (match[2] ?? "").trim() });
7346
+ } else if (tags.length > 0 && raw.trim().length > 0 && !raw.trim().startsWith("@")) {
7347
+ const last = tags.at(-1);
7348
+ last.payload = `${last.payload} ${raw.trim()}`.trim();
7349
+ }
7350
+ }
7351
+ return tags.map(({ kind, payload }) => {
7352
+ let rest = payload.replace(/^\{[^}\n]+\}\s*/u, "").trim();
7353
+ if (!PARAM_TAGS2.has(kind)) {
7354
+ return { kind, name: null, description: rest.replace(/^-\s*/u, "").trim() };
7355
+ }
7356
+ const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s*|\s+)?(.*)$/u.exec(rest);
7357
+ if (match === null) return { kind, name: null, description: "" };
7358
+ const rawName = (match[1] ?? "").replace(/^\[/u, "").replace(/\]$/u, "").split("=")[0] ?? "";
7359
+ rest = (match[2] ?? "").trim();
7360
+ return { kind, name: rawName, description: rest };
7361
+ });
7362
+ }
7363
+ function isVacuousTag(tag) {
7364
+ const description = words(tag.description).map(canonicalWord);
7365
+ if (description.length === 0) return true;
7366
+ if (tag.name === null) return description.every((word) => RESULT_FILLER.has(word));
7367
+ const nameWords2 = new Set(words(tag.name).map(canonicalWord));
7368
+ return description.every((word) => PARAMETER_FILLER.has(word) || nameWords2.has(word));
7369
+ }
7370
+ function words(text) {
7371
+ return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[a-z][a-z0-9]*/gu) ?? [];
7372
+ }
7373
+ function canonicalWord(word) {
7374
+ if (["identifier", "identifiers", "ids"].includes(word)) return "id";
7375
+ if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
7376
+ return word;
7377
+ }
7244
7378
  var noTypedDocSectionsDocumentation = {
7245
7379
  summary: "Reject typed-signature repetition while preserving behavior that types cannot express.",
7246
7380
  rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
7247
7381
  remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
7248
7382
  category: "maintainability",
7249
- limitations: ["Parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
7383
+ limitations: ["Description-free or name-restating parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
7250
7384
  examples: [
7251
7385
  {
7252
7386
  id: "behavioral-documentation",
@@ -7284,7 +7418,7 @@ var no_typed_doc_sections_default = createRule({
7284
7418
  return {
7285
7419
  Program() {
7286
7420
  for (const group of proseGroups(context.filename, context.sourceCode, true)) {
7287
- if (group.hasTypedTags && documentsTypedFunction(context.sourceCode, group.comment)) {
7421
+ if (group.hasTypedTags && hasVacuousTypedTag(group.text) && documentsTypedFunction(context.sourceCode, group.comment)) {
7288
7422
  context.report({ node: group.comment, messageId: "typedSection" });
7289
7423
  }
7290
7424
  }
@@ -7385,28 +7519,32 @@ var STOPWORDS3 = /* @__PURE__ */ new Set([
7385
7519
  "with"
7386
7520
  ]);
7387
7521
  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;
7522
+ 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;
7388
7523
  function narratesValue(body2, code) {
7389
7524
  if (body2.length === 0 || DIRECTIVE_RE5.test(body2) || hasExternalReference(body2)) return false;
7390
7525
  const codeNumbers = numbersIn(code);
7391
7526
  if (codeNumbers.size === 0) return false;
7392
- const words = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
7393
- if (words.length === 0) return false;
7527
+ const words2 = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
7528
+ if (words2.length === 0) return false;
7394
7529
  const commentNumbers = numbersIn(body2);
7395
7530
  if (commentNumbers.size === 0) return false;
7396
7531
  for (const number of commentNumbers) {
7397
7532
  if (!codeNumbers.has(number)) return false;
7398
7533
  }
7399
- if (!words.some((word) => UNIT_WORDS.has(word))) return false;
7534
+ if (!words2.some((word) => UNIT_WORDS.has(word))) return false;
7400
7535
  const identifiers = codeTokens(code);
7401
7536
  const stems = /* @__PURE__ */ new Set();
7402
7537
  for (const token of identifiers) stems.add(stem(token));
7403
- return words.every(
7538
+ return words2.every(
7404
7539
  (word) => STOPWORDS3.has(word) || UNIT_WORDS.has(word) || commentNumbers.has(word) || identifiers.has(word) || stems.has(stem(word))
7405
7540
  );
7406
7541
  }
7407
7542
  function numbersIn(text) {
7408
7543
  return new Set(text.match(NUMBER_RE) ?? []);
7409
7544
  }
7545
+ function nameAlreadyCarriesUnit(code) {
7546
+ return (code.match(/[A-Za-z_$][\w$]*/gu) ?? []).some((identifier) => UNIT_NAME_SUFFIX_RE.test(identifier));
7547
+ }
7410
7548
  var no_trailing_value_narration_default = createRule({
7411
7549
  name: "no-trailing-value-narration",
7412
7550
  documentation: noTrailingValueNarrationDocumentation,
@@ -7417,6 +7555,7 @@ var no_trailing_value_narration_default = createRule({
7417
7555
  },
7418
7556
  schema: [],
7419
7557
  messages: {
7558
+ deleteNarration: "Trailing comment restates the literal and the identifier already names its unit \u2014 delete the comment so it cannot drift.",
7420
7559
  narratesValue: "Trailing comment restates the literal on this line \u2014 put the unit in the name (STALE_TIME_MS) so it cannot drift."
7421
7560
  }
7422
7561
  },
@@ -7449,7 +7588,10 @@ var no_trailing_value_narration_default = createRule({
7449
7588
  const code = line.slice(0, comment.loc.start.column);
7450
7589
  const body2 = comment.value.replace(/^\*+/, "").replace(/\*+$/, "").trim();
7451
7590
  if (narratesValue(body2, code)) {
7452
- context.report({ node: comment, messageId: "narratesValue" });
7591
+ context.report({
7592
+ node: comment,
7593
+ messageId: nameAlreadyCarriesUnit(code) ? "deleteNarration" : "narratesValue"
7594
+ });
7453
7595
  }
7454
7596
  }
7455
7597
  }
@@ -8367,7 +8509,8 @@ var no_unsafe_mock_casting_default = createRule({
8367
8509
  // src/rules/no-zod-native-enum.ts
8368
8510
  import {
8369
8511
  ESLintUtils as ESLintUtils2,
8370
- AST_NODE_TYPES as AST_NODE_TYPES34
8512
+ AST_NODE_TYPES as AST_NODE_TYPES34,
8513
+ ASTUtils as ASTUtils8
8371
8514
  } from "@typescript-eslint/utils";
8372
8515
  import * as ts from "typescript";
8373
8516
  var noZodNativeEnumDocumentation = {
@@ -8496,15 +8639,23 @@ var no_zod_native_enum_default = createRule({
8496
8639
  } catch {
8497
8640
  services = null;
8498
8641
  }
8499
- const zodImportedNames = /* @__PURE__ */ new Map();
8500
- const zodNamespaces = /* @__PURE__ */ new Set();
8642
+ const zodImportedBindings = /* @__PURE__ */ new Map();
8643
+ const zodNamespaceBindings = /* @__PURE__ */ new Set();
8644
+ function resolvedBinding(identifier) {
8645
+ return ASTUtils8.findVariable(
8646
+ sourceCode.getScope(identifier),
8647
+ identifier.name
8648
+ );
8649
+ }
8501
8650
  function isZodMemberCall(node, api) {
8502
8651
  const callee = node.callee;
8503
- 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) {
8504
- return callee.property.name === api;
8652
+ if (callee.type === AST_NODE_TYPES34.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES34.Identifier && callee.property.type === AST_NODE_TYPES34.Identifier) {
8653
+ const binding = resolvedBinding(callee.object);
8654
+ return binding !== null && zodNamespaceBindings.has(binding) && callee.property.name === api;
8505
8655
  }
8506
8656
  if (callee.type === AST_NODE_TYPES34.Identifier) {
8507
- return zodImportedNames.get(callee.name) === api;
8657
+ const binding = resolvedBinding(callee);
8658
+ return binding !== null && zodImportedBindings.get(binding) === api;
8508
8659
  }
8509
8660
  return false;
8510
8661
  }
@@ -8539,10 +8690,14 @@ var no_zod_native_enum_default = createRule({
8539
8690
  }
8540
8691
  for (const spec of node.specifiers) {
8541
8692
  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")) {
8542
- zodNamespaces.add(spec.local.name);
8693
+ const binding = resolvedBinding(spec.local);
8694
+ if (binding !== null) zodNamespaceBindings.add(binding);
8543
8695
  }
8544
8696
  if (spec.type === AST_NODE_TYPES34.ImportSpecifier && spec.imported.type === AST_NODE_TYPES34.Identifier) {
8545
- zodImportedNames.set(spec.local.name, spec.imported.name);
8697
+ const binding = resolvedBinding(spec.local);
8698
+ if (binding !== null) {
8699
+ zodImportedBindings.set(binding, spec.imported.name);
8700
+ }
8546
8701
  }
8547
8702
  }
8548
8703
  },
@@ -8579,7 +8734,7 @@ var no_zod_native_enum_default = createRule({
8579
8734
  });
8580
8735
 
8581
8736
  // src/rules/test-loops-over-literal-cases.ts
8582
- import { AST_NODE_TYPES as AST_NODE_TYPES35, ASTUtils as ASTUtils8 } from "@typescript-eslint/utils";
8737
+ import { AST_NODE_TYPES as AST_NODE_TYPES35, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
8583
8738
  var testLoopsOverLiteralCasesDocumentation = {
8584
8739
  summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
8585
8740
  rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
@@ -8742,7 +8897,7 @@ var test_loops_over_literal_cases_default = createRule({
8742
8897
  return {};
8743
8898
  }
8744
8899
  const isFrameworkIdentifier = (identifier, modules) => {
8745
- const variable = ASTUtils8.findVariable(context.sourceCode.getScope(identifier), identifier.name);
8900
+ const variable = ASTUtils9.findVariable(context.sourceCode.getScope(identifier), identifier.name);
8746
8901
  if (variable === null || variable.defs.length === 0) return true;
8747
8902
  return variable.defs.some((definition) => {
8748
8903
  let current = definition.node;
@@ -9179,7 +9334,7 @@ var prefer_input_group_search_default = createRule({
9179
9334
  });
9180
9335
 
9181
9336
  // src/rules/prefer-immutable-module-constant.ts
9182
- import { AST_NODE_TYPES as AST_NODE_TYPES39, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
9337
+ import { AST_NODE_TYPES as AST_NODE_TYPES39, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
9183
9338
  var preferImmutableModuleConstantDocumentation = {
9184
9339
  summary: "Require module-level constant collections to expose readonly state.",
9185
9340
  rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
@@ -9332,7 +9487,7 @@ var prefer_immutable_module_constant_default = createRule({
9332
9487
  create(context) {
9333
9488
  const sourceCode = context.sourceCode;
9334
9489
  const isUnshadowedGlobal = (identifier) => {
9335
- const variable = ASTUtils9.findVariable(sourceCode.getScope(identifier), identifier.name);
9490
+ const variable = ASTUtils10.findVariable(sourceCode.getScope(identifier), identifier.name);
9336
9491
  return variable === null || variable.defs.length === 0;
9337
9492
  };
9338
9493
  if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
@@ -10249,7 +10404,7 @@ var prefer_module_level_schema_default = createRule({
10249
10404
  });
10250
10405
 
10251
10406
  // src/rules/prefer-native-random-uuid.ts
10252
- import { AST_NODE_TYPES as AST_NODE_TYPES43, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
10407
+ import { AST_NODE_TYPES as AST_NODE_TYPES43, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
10253
10408
  var preferNativeRandomUuidDocumentation = {
10254
10409
  summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
10255
10410
  rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
@@ -10285,7 +10440,7 @@ var prefer_native_random_uuid_default = createRule({
10285
10440
  const directBindings = /* @__PURE__ */ new Set();
10286
10441
  const namespaceBindings = /* @__PURE__ */ new Set();
10287
10442
  function resolve(identifier) {
10288
- return ASTUtils10.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10443
+ return ASTUtils11.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10289
10444
  }
10290
10445
  function record(identifier, destination) {
10291
10446
  const variable = resolve(identifier);
@@ -10348,7 +10503,7 @@ var prefer_native_random_uuid_default = createRule({
10348
10503
  });
10349
10504
 
10350
10505
  // src/rules/prefer-non-nullable-collection.ts
10351
- import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
10506
+ import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
10352
10507
  var preferNonNullableCollectionDocumentation = {
10353
10508
  summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
10354
10509
  rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
@@ -10478,14 +10633,14 @@ function directlyCoalesced(node) {
10478
10633
  return parent?.type === AST_NODE_TYPES44.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
10479
10634
  }
10480
10635
  function identifierIsOnlyCoalesced(context, binding, fn) {
10481
- const variable = ASTUtils11.findVariable(context.sourceCode.getScope(binding), binding.name);
10636
+ const variable = ASTUtils12.findVariable(context.sourceCode.getScope(binding), binding.name);
10482
10637
  if (variable === null || variable.references.length === 0) return false;
10483
10638
  return variable.references.every(
10484
10639
  (reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
10485
10640
  );
10486
10641
  }
10487
10642
  function memberIsOnlyCoalesced(context, object, property, fn) {
10488
- const variable = ASTUtils11.findVariable(context.sourceCode.getScope(object), object.name);
10643
+ const variable = ASTUtils12.findVariable(context.sourceCode.getScope(object), object.name);
10489
10644
  if (variable === null) return false;
10490
10645
  const accesses = variable.references.flatMap((reference) => {
10491
10646
  if (!belongsToFunction(reference.identifier, fn)) return [null];
@@ -10757,7 +10912,70 @@ var bindingValidationPolarity = (test, bindingName) => {
10757
10912
  }
10758
10913
  return test.type === AST_NODE_TYPES45.CallExpression && test.arguments.length === 1 && test.arguments[0]?.type === AST_NODE_TYPES45.Identifier && test.arguments[0].name === bindingName && test.callee.type === AST_NODE_TYPES45.MemberExpression && !test.callee.computed && test.callee.object.type === AST_NODE_TYPES45.Identifier && test.callee.object.name === "Array" && test.callee.property.type === AST_NODE_TYPES45.Identifier && test.callee.property.name === "isArray" ? "valid-when-true" : null;
10759
10914
  };
10915
+ var plainMemberAccess = (node) => node.type === AST_NODE_TYPES45.MemberExpression && !node.computed && node.object.type === AST_NODE_TYPES45.Identifier && node.property.type === AST_NODE_TYPES45.Identifier ? { object: node.object.name, property: node.property.name } : null;
10916
+ var isSamePlainMember = (node, access) => {
10917
+ const candidate = plainMemberAccess(node);
10918
+ return candidate !== null && candidate.object === access.object && candidate.property === access.property;
10919
+ };
10760
10920
  var nodeWithin2 = (node, container) => node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
10921
+ var isUseWithinValidatedBranch = (node, bindingName) => {
10922
+ for (let current = node.parent; current !== void 0 && current !== null; current = current.parent) {
10923
+ if (current.type === AST_NODE_TYPES45.ConditionalExpression) {
10924
+ const polarity = bindingValidationPolarity(current.test, bindingName);
10925
+ if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && nodeWithin2(node, current.alternate)) {
10926
+ return true;
10927
+ }
10928
+ }
10929
+ if (current.type === AST_NODE_TYPES45.IfStatement) {
10930
+ const polarity = bindingValidationPolarity(current.test, bindingName);
10931
+ if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && current.alternate !== null && nodeWithin2(node, current.alternate)) {
10932
+ return true;
10933
+ }
10934
+ }
10935
+ if (current.type === AST_NODE_TYPES45.FunctionDeclaration || current.type === AST_NODE_TYPES45.FunctionExpression || current.type === AST_NODE_TYPES45.ArrowFunctionExpression) {
10936
+ return false;
10937
+ }
10938
+ }
10939
+ return false;
10940
+ };
10941
+ var isMemberUseWithinValidatedBranch = (node, access) => {
10942
+ for (let current = node.parent; current !== void 0 && current !== null; current = current.parent) {
10943
+ if (current.type === AST_NODE_TYPES45.ConditionalExpression) {
10944
+ const polarity = memberValidationPolarity(current.test, access);
10945
+ if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && nodeWithin2(node, current.alternate)) {
10946
+ return true;
10947
+ }
10948
+ }
10949
+ if (current.type === AST_NODE_TYPES45.IfStatement) {
10950
+ const polarity = memberValidationPolarity(current.test, access);
10951
+ if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && current.alternate !== null && nodeWithin2(node, current.alternate)) {
10952
+ return true;
10953
+ }
10954
+ }
10955
+ if (current.type === AST_NODE_TYPES45.FunctionDeclaration || current.type === AST_NODE_TYPES45.FunctionExpression || current.type === AST_NODE_TYPES45.ArrowFunctionExpression) {
10956
+ return false;
10957
+ }
10958
+ }
10959
+ return false;
10960
+ };
10961
+ var memberValidationPolarity = (test, access) => {
10962
+ if (test.type === AST_NODE_TYPES45.UnaryExpression && test.operator === "!") {
10963
+ const inner = memberValidationPolarity(test.argument, access);
10964
+ return inner === "valid-when-true" ? "valid-when-false" : inner === "valid-when-false" ? "valid-when-true" : null;
10965
+ }
10966
+ if (test.type === AST_NODE_TYPES45.BinaryExpression) {
10967
+ const isMatchingTypeof = (node) => node.type === AST_NODE_TYPES45.UnaryExpression && node.operator === "typeof" && isSamePlainMember(node.argument, access);
10968
+ const isPrimitiveType = (node) => node.type === AST_NODE_TYPES45.Literal && typeof node.value === "string" && PRIMITIVE_TYPEOF_RESULTS.has(node.value);
10969
+ if (!(isMatchingTypeof(test.left) && isPrimitiveType(test.right) || isMatchingTypeof(test.right) && isPrimitiveType(test.left))) {
10970
+ return null;
10971
+ }
10972
+ if (test.operator === "===" || test.operator === "==") {
10973
+ return "valid-when-true";
10974
+ }
10975
+ return test.operator === "!==" || test.operator === "!=" ? "valid-when-false" : null;
10976
+ }
10977
+ return test.type === AST_NODE_TYPES45.CallExpression && test.arguments.length === 1 && test.arguments[0] !== void 0 && test.arguments[0].type !== AST_NODE_TYPES45.SpreadElement && isSamePlainMember(test.arguments[0], access) && test.callee.type === AST_NODE_TYPES45.MemberExpression && !test.callee.computed && test.callee.object.type === AST_NODE_TYPES45.Identifier && test.callee.object.name === "Array" && test.callee.property.type === AST_NODE_TYPES45.Identifier && test.callee.property.name === "isArray" ? "valid-when-true" : null;
10978
+ };
10761
10979
  var isFullyValidatedExtractedBinding = (member, source, context) => {
10762
10980
  const isValidationReference = (identifier) => {
10763
10981
  for (let current = identifier.parent; current !== void 0 && current !== null; current = current.parent) {
@@ -11027,7 +11245,14 @@ var prefer_schema_for_api_payload_default = createRule({
11027
11245
  return;
11028
11246
  }
11029
11247
  const variable = obj?.type === AST_NODE_TYPES45.Identifier ? unvalidatedVariableRef(obj, scope, unvalidatedVariables) : null;
11030
- if (variable !== null) {
11248
+ if (variable !== null && obj?.type === AST_NODE_TYPES45.Identifier) {
11249
+ if (isUseWithinValidatedBranch(node, obj.name)) {
11250
+ return;
11251
+ }
11252
+ const access = plainMemberAccess(node);
11253
+ if (access !== null && isMemberUseWithinValidatedBranch(node, access)) {
11254
+ return;
11255
+ }
11031
11256
  if (isFullyValidatedExtractedBinding(node, variable, context)) {
11032
11257
  return;
11033
11258
  }
@@ -11479,16 +11704,17 @@ var preferServerActionsDocumentation = {
11479
11704
  rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
11480
11705
  remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
11481
11706
  category: "architecture",
11482
- limitations: ["Only statically recognizable /api/ mutations in applicable React modules are reported."],
11707
+ limitations: ["Only statically recognizable /api/ mutations in modules with positive Next.js evidence are reported: an explicit next import, or an app/pages path with a top-level use-client directive."],
11483
11708
  examples: [
11484
11709
  { id: "server-action-call", title: "Call a Server Action", outcome: "no-match", files: [{ path: "app/tasks/page.tsx", source: "import { createTask } from './actions'; await createTask(input);" }], focusPath: "app/tasks/page.tsx", expectedCount: 0, public: true },
11485
- { id: "api-mutation", title: "Do not mutate through an API route", outcome: "match", files: [{ path: "app/tasks/page.tsx", source: "await fetch('/api/tasks', { method: 'POST', body });" }], focusPath: "app/tasks/page.tsx", expectedCount: 1, public: true }
11710
+ { id: "api-mutation", title: "Do not mutate through an API route", outcome: "match", files: [{ path: "app/tasks/page.tsx", source: "'use client'; await fetch('/api/tasks', { method: 'POST', body });" }], focusPath: "app/tasks/page.tsx", expectedCount: 1, public: true }
11486
11711
  ]
11487
11712
  };
11488
11713
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
11489
11714
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
11490
11715
  var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
11491
11716
  var NON_REACT_FRAMEWORK_RE2 = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
11717
+ var NEXT_MODULE_PATH_RE2 = /(?:^|[/\\])(?:app|pages)[/\\]/u;
11492
11718
  function getScope(context, node) {
11493
11719
  return context.sourceCode.getScope(node);
11494
11720
  }
@@ -11602,6 +11828,16 @@ var prefer_server_actions_default = createRule({
11602
11828
  const isNonReactFramework = context.sourceCode.ast.body.some(
11603
11829
  (node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE2.test(node.source.value)
11604
11830
  );
11831
+ const hasUseClientDirective = context.sourceCode.ast.body.some(
11832
+ (node) => node.type === "ExpressionStatement" && node.expression.type === "Literal" && node.expression.value === "use client"
11833
+ );
11834
+ const hasNextImport = context.sourceCode.ast.body.some(
11835
+ (node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && (node.source.value === "next" || node.source.value.startsWith("next/"))
11836
+ );
11837
+ const hasNextEvidence = hasNextImport || hasUseClientDirective && NEXT_MODULE_PATH_RE2.test(filename);
11838
+ if (!hasNextEvidence) {
11839
+ return {};
11840
+ }
11605
11841
  return {
11606
11842
  CallExpression(node) {
11607
11843
  if (isNonReactFramework) return;
@@ -12327,6 +12563,9 @@ var requireAssertNeverDocumentation = {
12327
12563
  };
12328
12564
  var isRuntimeHandlingStatement = (statement) => {
12329
12565
  if (statement.type === AST_NODE_TYPES49.EmptyStatement) return false;
12566
+ if (statement.type === AST_NODE_TYPES49.BreakStatement) {
12567
+ return statement.label !== null;
12568
+ }
12330
12569
  if (statement.type === AST_NODE_TYPES49.TSTypeAliasDeclaration || statement.type === AST_NODE_TYPES49.TSInterfaceDeclaration) {
12331
12570
  return false;
12332
12571
  }
@@ -12356,7 +12595,12 @@ function isExhaustiveFiniteSwitch(node, services) {
12356
12595
  const discriminant = services.esTreeNodeToTSNodeMap.get(node.discriminant);
12357
12596
  const discriminantType = checker.getTypeAtLocation(discriminant);
12358
12597
  const constituents = discriminantType.isUnion() ? discriminantType.types : [discriminantType];
12359
- if (constituents.length === 0) return false;
12598
+ if (!discriminantType.isUnion() || constituents.length < 2) return false;
12599
+ if (constituents.every(
12600
+ (constituent) => (constituent.flags & ts2.TypeFlags.BooleanLiteral) !== 0
12601
+ )) {
12602
+ return false;
12603
+ }
12360
12604
  const expected = /* @__PURE__ */ new Set();
12361
12605
  for (const constituent of constituents) {
12362
12606
  const key = finiteTypeKey(constituent, checker);
@@ -12424,7 +12668,7 @@ var require_assert_never_default = createRule({
12424
12668
  });
12425
12669
 
12426
12670
  // src/rules/require-fetch-timeout.ts
12427
- import { AST_NODE_TYPES as AST_NODE_TYPES50, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
12671
+ import { AST_NODE_TYPES as AST_NODE_TYPES50, ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
12428
12672
  var requireFetchTimeoutDocumentation = {
12429
12673
  summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
12430
12674
  rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
@@ -12505,7 +12749,7 @@ var require_fetch_timeout_default = createRule({
12505
12749
  }
12506
12750
  function resolvesToGlobal(identifier) {
12507
12751
  const scope = context.sourceCode.getScope(identifier);
12508
- const variable = ASTUtils12.findVariable(scope, identifier.name);
12752
+ const variable = ASTUtils13.findVariable(scope, identifier.name);
12509
12753
  return variable === null || variable.defs.length === 0;
12510
12754
  }
12511
12755
  function isGlobalFetchCall2(callee) {
@@ -12515,7 +12759,7 @@ var require_fetch_timeout_default = createRule({
12515
12759
  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);
12516
12760
  }
12517
12761
  function localConstInitProvablyLacksSignal(identifier) {
12518
- const variable = ASTUtils12.findVariable(
12762
+ const variable = ASTUtils13.findVariable(
12519
12763
  context.sourceCode.getScope(identifier),
12520
12764
  identifier.name
12521
12765
  );
@@ -13157,34 +13401,35 @@ var require_static_next_matcher_default = createRule({
13157
13401
  });
13158
13402
 
13159
13403
  // src/rules/require-zod-form-validation.ts
13160
- import { AST_NODE_TYPES as AST_NODE_TYPES53 } from "@typescript-eslint/utils";
13404
+ import {
13405
+ AST_NODE_TYPES as AST_NODE_TYPES53,
13406
+ ASTUtils as ASTUtils14
13407
+ } from "@typescript-eslint/utils";
13161
13408
  var requireZodFormValidationDocumentation = {
13162
13409
  summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
13163
13410
  rationale: "FormData values are untrusted strings or files and need runtime validation before use.",
13164
13411
  remediation: "Read the value inside a Zod schema's `parse` or `safeParse` input.",
13165
13412
  category: "security",
13413
+ limitations: [
13414
+ "Tests are excluded; imported schema-shaped names are trusted when their implementation is outside the linted file.",
13415
+ "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."
13416
+ ],
13166
13417
  examples: [
13167
13418
  { 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 },
13168
13419
  { 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 }
13169
13420
  ]
13170
13421
  };
13171
- var isZodParseCall = (node) => {
13172
- if (node.type !== AST_NODE_TYPES53.CallExpression) return false;
13173
- const callee = node.callee;
13174
- if (callee.type !== AST_NODE_TYPES53.MemberExpression) return false;
13175
- if (callee.computed) return false;
13176
- if (callee.property.type !== AST_NODE_TYPES53.Identifier) return false;
13177
- const method = callee.property.name;
13178
- if (method !== "parse" && method !== "safeParse" && method !== "parseAsync" && method !== "safeParseAsync") {
13179
- return false;
13180
- }
13181
- return looksLikeZodSchema(callee.object);
13182
- };
13183
- var looksLikeZodSchema = (node) => {
13422
+ var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
13423
+ "parse",
13424
+ "safeParse",
13425
+ "parseAsync",
13426
+ "safeParseAsync"
13427
+ ]);
13428
+ var zodReceiverRoot = (node) => {
13184
13429
  let current = node;
13185
13430
  while (true) {
13186
13431
  if (current.type === AST_NODE_TYPES53.Identifier) {
13187
- return current.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
13432
+ return current;
13188
13433
  }
13189
13434
  if (current.type === AST_NODE_TYPES53.CallExpression) {
13190
13435
  current = current.callee;
@@ -13194,7 +13439,7 @@ var looksLikeZodSchema = (node) => {
13194
13439
  current = current.object;
13195
13440
  continue;
13196
13441
  }
13197
- return false;
13442
+ return null;
13198
13443
  }
13199
13444
  };
13200
13445
  var isFormDataMethodCall = (node) => {
@@ -13224,6 +13469,34 @@ var require_zod_form_validation_default = createRule({
13224
13469
  if (isTestFile(context.filename)) {
13225
13470
  return {};
13226
13471
  }
13472
+ const zodBindings = /* @__PURE__ */ new Set();
13473
+ const resolvedBinding = (identifier) => ASTUtils14.findVariable(
13474
+ context.sourceCode.getScope(identifier),
13475
+ identifier.name
13476
+ );
13477
+ const isProvablyNonZodLocal = (identifier) => {
13478
+ const binding = resolvedBinding(identifier);
13479
+ if (binding === null || zodBindings.has(binding) || binding.defs.length !== 1) {
13480
+ return false;
13481
+ }
13482
+ const definition = binding.defs[0];
13483
+ if (definition?.type !== "Variable" || definition.node.type !== AST_NODE_TYPES53.VariableDeclarator) {
13484
+ return false;
13485
+ }
13486
+ const init = definition.node.init;
13487
+ 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;
13488
+ };
13489
+ const isZodParseCall = (node) => {
13490
+ if (node.type !== AST_NODE_TYPES53.CallExpression) return false;
13491
+ const callee = node.callee;
13492
+ if (callee.type !== AST_NODE_TYPES53.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES53.Identifier || !ZOD_PARSE_METHODS.has(callee.property.name)) {
13493
+ return false;
13494
+ }
13495
+ const root = zodReceiverRoot(callee.object);
13496
+ if (root === null) return false;
13497
+ const binding = resolvedBinding(root);
13498
+ return binding !== null && zodBindings.has(binding) || (root.name === "z" || ZOD_SCHEMA_NAME_RE.test(root.name)) && !isProvablyNonZodLocal(root);
13499
+ };
13227
13500
  const isFormSourceIdentifier = (node) => {
13228
13501
  if (node.type !== AST_NODE_TYPES53.Identifier) return false;
13229
13502
  if (/formdata/i.test(node.name)) return true;
@@ -13249,14 +13522,15 @@ var require_zod_form_validation_default = createRule({
13249
13522
  }
13250
13523
  return isFormSourceIdentifier(callee.object);
13251
13524
  };
13252
- const hasZodParseAncestor = (node) => {
13525
+ const zodParseAncestor = (node) => {
13253
13526
  let parent = node.parent;
13254
13527
  while (parent !== null && parent !== void 0) {
13255
- if (isZodParseCall(parent)) return true;
13528
+ if (isZodParseCall(parent)) return parent;
13256
13529
  parent = parent.parent;
13257
13530
  }
13258
- return false;
13531
+ return null;
13259
13532
  };
13533
+ const hasZodParseAncestor = (node) => zodParseAncestor(node) !== null;
13260
13534
  const isInstanceofNarrowing = (node) => {
13261
13535
  const parent = node.parent;
13262
13536
  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");
@@ -13273,14 +13547,111 @@ var require_zod_form_validation_default = createRule({
13273
13547
  }
13274
13548
  return null;
13275
13549
  };
13550
+ const containingStatement = (node) => {
13551
+ let current = node;
13552
+ while (current.parent !== void 0) {
13553
+ const parent = current.parent;
13554
+ if (parent.type === AST_NODE_TYPES53.BlockStatement || parent.type === AST_NODE_TYPES53.Program) {
13555
+ return current;
13556
+ }
13557
+ current = parent;
13558
+ }
13559
+ return null;
13560
+ };
13561
+ const zodParseMethod = (call) => {
13562
+ const callee = call.callee;
13563
+ return callee.type === AST_NODE_TYPES53.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES53.Identifier ? callee.property.name : null;
13564
+ };
13565
+ const hasConditionalAncestorBeforeStatement = (node, statement) => {
13566
+ let current = node.parent;
13567
+ while (current !== void 0 && current !== statement) {
13568
+ if (current.type === AST_NODE_TYPES53.LogicalExpression || current.type === AST_NODE_TYPES53.ConditionalExpression) {
13569
+ return true;
13570
+ }
13571
+ current = current.parent;
13572
+ }
13573
+ return false;
13574
+ };
13575
+ const isAwaitedBeforeStatement = (node, statement) => {
13576
+ let current = node.parent;
13577
+ while (current !== void 0 && current !== statement) {
13578
+ if (current.type === AST_NODE_TYPES53.AwaitExpression) return true;
13579
+ current = current.parent;
13580
+ }
13581
+ return false;
13582
+ };
13583
+ const guaranteedValidationStatement = (declarator, reference) => {
13584
+ const parse2 = zodParseAncestor(reference);
13585
+ if (parse2 === null) return null;
13586
+ const declarationStatement = containingStatement(declarator);
13587
+ const validationStatement = containingStatement(parse2);
13588
+ if (declarationStatement === null || validationStatement === null || declarationStatement.parent !== validationStatement.parent || validationStatement.range[0] <= declarationStatement.range[1] || hasConditionalAncestorBeforeStatement(parse2, validationStatement)) {
13589
+ return null;
13590
+ }
13591
+ if (validationStatement.type !== AST_NODE_TYPES53.VariableDeclaration && validationStatement.type !== AST_NODE_TYPES53.ExpressionStatement) {
13592
+ return null;
13593
+ }
13594
+ const method = zodParseMethod(parse2);
13595
+ if (method === "parse") return validationStatement;
13596
+ if (method === "parseAsync" && isAwaitedBeforeStatement(parse2, validationStatement)) {
13597
+ return validationStatement;
13598
+ }
13599
+ return null;
13600
+ };
13601
+ const isSafePrevalidationInspection = (identifier) => {
13602
+ const parent = identifier.parent;
13603
+ if (parent.type === AST_NODE_TYPES53.UnaryExpression && parent.operator === "typeof") {
13604
+ return true;
13605
+ }
13606
+ if (parent.type !== AST_NODE_TYPES53.BinaryExpression || parent.left !== identifier) {
13607
+ return false;
13608
+ }
13609
+ if (parent.operator === "instanceof") {
13610
+ return parent.right.type === AST_NODE_TYPES53.Identifier && (parent.right.name === "File" || parent.right.name === "Blob");
13611
+ }
13612
+ 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");
13613
+ };
13614
+ const statementWithinBlock = (node, block) => {
13615
+ let current = node;
13616
+ while (current.parent !== void 0 && current.parent !== block) {
13617
+ current = current.parent;
13618
+ }
13619
+ return current.parent === block ? current : null;
13620
+ };
13276
13621
  const bindingIsValidated = (declarator) => {
13277
13622
  const variable = context.sourceCode.getDeclaredVariables(declarator)[0];
13278
13623
  if (variable === void 0) return false;
13279
- return variable.references.some(
13280
- (ref) => hasZodParseAncestor(ref.identifier) || isInstanceofNarrowing(ref.identifier)
13624
+ const references = variable.references.filter((reference) => !reference.isWriteOnly()).map((reference) => reference.identifier).filter(
13625
+ (identifier) => identifier.type === AST_NODE_TYPES53.Identifier
13281
13626
  );
13627
+ if (references.length === 0) return false;
13628
+ if (references.some(isInstanceofNarrowing)) return true;
13629
+ const validationStatements = references.map((reference) => guaranteedValidationStatement(declarator, reference)).filter(
13630
+ (statement) => statement !== null
13631
+ );
13632
+ const declarationStatement = containingStatement(declarator);
13633
+ const declarationBlock = declarationStatement?.parent;
13634
+ return references.every((reference) => {
13635
+ if (zodParseAncestor(reference) !== null || isSafePrevalidationInspection(reference)) {
13636
+ return true;
13637
+ }
13638
+ if (declarationBlock === void 0) return false;
13639
+ const useStatement = statementWithinBlock(reference, declarationBlock);
13640
+ return useStatement !== null && validationStatements.some(
13641
+ (statement) => statement.range[1] < useStatement.range[0]
13642
+ );
13643
+ });
13282
13644
  };
13283
13645
  return {
13646
+ ImportDeclaration(node) {
13647
+ if (!isZodModule(node.source.value)) return;
13648
+ for (const specifier of node.specifiers) {
13649
+ 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")) {
13650
+ const binding = resolvedBinding(specifier.local);
13651
+ if (binding !== null) zodBindings.add(binding);
13652
+ }
13653
+ }
13654
+ },
13284
13655
  CallExpression(node) {
13285
13656
  if (!isFormDataGetCall(node)) return;
13286
13657
  if (hasZodParseAncestor(node) || isInstanceofNarrowing(node)) return;
@@ -13360,7 +13731,7 @@ var store_insert_requires_on_conflict_default = createRule({
13360
13731
  });
13361
13732
 
13362
13733
  // src/rules/stepdown.ts
13363
- import { AST_NODE_TYPES as AST_NODE_TYPES54, ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
13734
+ import { AST_NODE_TYPES as AST_NODE_TYPES54, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
13364
13735
  var stepdownDocumentation = {
13365
13736
  summary: "Place a private helper below its sole direct same-scope caller.",
13366
13737
  rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
@@ -13553,7 +13924,7 @@ function methodName(node) {
13553
13924
  return !node.computed && node.key.type === AST_NODE_TYPES54.Identifier ? node.key.name : null;
13554
13925
  }
13555
13926
  function referencedMethod(context, node, classVariables) {
13556
- const objectVariable = node.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils13.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
13927
+ const objectVariable = node.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils15.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
13557
13928
  const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
13558
13929
  if (node.object.type !== AST_NODE_TYPES54.ThisExpression && !isClassReference) return null;
13559
13930
  if (node.property.type === AST_NODE_TYPES54.PrivateIdentifier) return `#${node.property.name}`;
@@ -13606,11 +13977,11 @@ function classScope(context, node, computedReferenceNames) {
13606
13977
  const pinned = /* @__PURE__ */ new Set();
13607
13978
  const classVariables = /* @__PURE__ */ new Set();
13608
13979
  if (node.id !== null) {
13609
- const internal = ASTUtils13.findVariable(context.sourceCode.getScope(node), node.id.name);
13980
+ const internal = ASTUtils15.findVariable(context.sourceCode.getScope(node), node.id.name);
13610
13981
  if (internal !== null) classVariables.add(internal);
13611
13982
  }
13612
13983
  if (node.type === AST_NODE_TYPES54.ClassExpression && node.parent.type === AST_NODE_TYPES54.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES54.Identifier) {
13613
- const outer = ASTUtils13.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
13984
+ const outer = ASTUtils15.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
13614
13985
  if (outer !== null) classVariables.add(outer);
13615
13986
  }
13616
13987
  for (const method of methods) {
@@ -13646,7 +14017,7 @@ function classScope(context, node, computedReferenceNames) {
13646
14017
  return;
13647
14018
  }
13648
14019
  if (binding.type !== AST_NODE_TYPES54.Identifier) return;
13649
- const variable = ASTUtils13.findVariable(context.sourceCode.getScope(binding), binding.name);
14020
+ const variable = ASTUtils15.findVariable(context.sourceCode.getScope(binding), binding.name);
13650
14021
  if (variable !== null) {
13651
14022
  methodClassVariables.add(variable);
13652
14023
  methodAliases.add(variable);
@@ -13676,7 +14047,7 @@ function classScope(context, node, computedReferenceNames) {
13676
14047
  return;
13677
14048
  }
13678
14049
  if (!privateNames.has(target)) return;
13679
- const objectVariable = current.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils13.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
14050
+ const objectVariable = current.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils15.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
13680
14051
  if (objectVariable !== null && methodAliases.has(objectVariable)) {
13681
14052
  pinned.add(target);
13682
14053
  return;
@@ -13760,7 +14131,7 @@ var stepdown_default = createRule({
13760
14131
  // src/rules/zod-naming-convention.ts
13761
14132
  import {
13762
14133
  AST_NODE_TYPES as AST_NODE_TYPES55,
13763
- ASTUtils as ASTUtils14
14134
+ ASTUtils as ASTUtils16
13764
14135
  } from "@typescript-eslint/utils";
13765
14136
  var zodNamingConventionDocumentation = {
13766
14137
  summary: "Enforce a consistent Zod schema naming convention \u2014 a `Z` prefix (`ZUser`) or a `Schema` suffix (`userSchema`); both are accepted by default.",
@@ -13847,7 +14218,7 @@ var zod_naming_convention_default = createRule({
13847
14218
  const acceptsSchemaWord = convention !== "prefix";
13848
14219
  const zodBindings = /* @__PURE__ */ new Set();
13849
14220
  function resolvedBinding(identifier) {
13850
- return ASTUtils14.findVariable(
14221
+ return ASTUtils16.findVariable(
13851
14222
  context.sourceCode.getScope(identifier),
13852
14223
  identifier.name
13853
14224
  );
@@ -14043,7 +14414,7 @@ var rules = {
14043
14414
  };
14044
14415
  var meta = {
14045
14416
  name: "@sarj/eslint-plugin",
14046
- version: "15.1.0"
14417
+ version: "15.3.0"
14047
14418
  };
14048
14419
  var applicationOnlyRules = [
14049
14420
  "no-restricted-library-load",