@sarj/eslint-plugin 15.17.8 → 15.17.9

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
@@ -2455,8 +2455,51 @@ var no_duplicate_lifecycle_refresh_listeners_default = createRule({
2455
2455
  }
2456
2456
  });
2457
2457
 
2458
+ // src/rules/_exported-next-config-property.ts
2459
+ import { ASTUtils as ASTUtils4 } from "@typescript-eslint/utils";
2460
+ function unwrap(node) {
2461
+ while (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression") node = node.expression;
2462
+ return node;
2463
+ }
2464
+ function exportedNextConfigProperty(sourceCode, path) {
2465
+ const resolve2 = (input, seen = /* @__PURE__ */ new Set()) => {
2466
+ const node = unwrap(input);
2467
+ if (node.type !== "Identifier") return node;
2468
+ if (seen.has(node)) return null;
2469
+ seen.add(node);
2470
+ const binding = ASTUtils4.findVariable(sourceCode.getScope(node), node.name);
2471
+ const definition = binding?.defs.length === 1 ? binding.defs[0] : void 0;
2472
+ if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some((reference) => reference.identifier !== node && reference.init !== true)) return null;
2473
+ return resolve2(definition.node.init, seen);
2474
+ };
2475
+ let exported = null;
2476
+ for (const statement of sourceCode.ast.body) {
2477
+ if (statement.type === "ExportDefaultDeclaration") exported = statement.declaration;
2478
+ if (statement.type !== "ExpressionStatement" || statement.expression.type !== "AssignmentExpression" || statement.expression.operator !== "=" || sourceCode.ast.body.length !== 1) continue;
2479
+ const assignment = statement.expression;
2480
+ const left = assignment.left;
2481
+ if (left.type !== "MemberExpression" || left.computed || left.object.type !== "Identifier" || left.object.name !== "module" || left.property.type !== "Identifier" || left.property.name !== "exports") continue;
2482
+ const binding = ASTUtils4.findVariable(sourceCode.getScope(left.object), "module");
2483
+ if (binding === null || binding.defs.length === 0) exported = assignment.right;
2484
+ }
2485
+ if (exported === null) return null;
2486
+ let current = resolve2(exported);
2487
+ let selected = null;
2488
+ for (const name of path) {
2489
+ if (current?.type !== "ObjectExpression" || current.properties.some((property) => property.type !== "Property" || property.computed || property.kind !== "init")) return null;
2490
+ selected = null;
2491
+ for (const property of current.properties) {
2492
+ if (property.type !== "Property") continue;
2493
+ const key = property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? property.key.value : null;
2494
+ if (key === name) selected = property;
2495
+ }
2496
+ if (selected === null) return null;
2497
+ current = resolve2(selected.value);
2498
+ }
2499
+ return selected;
2500
+ }
2501
+
2458
2502
  // src/rules/no-dangerously-allow-svg.ts
2459
- import "@typescript-eslint/utils";
2460
2503
  var NEXT_CONFIG_RE = /(?:^|\/)next\.config\.[cm]?[jt]s$/;
2461
2504
  var NO_DANGEROUSLY_ALLOW_SVG_DOCUMENTATION = {
2462
2505
  summary: "Next.js image configuration enables unsanitized SVG rendering",
@@ -2464,7 +2507,7 @@ var NO_DANGEROUSLY_ALLOW_SVG_DOCUMENTATION = {
2464
2507
  remediation: "Keep dangerouslyAllowSVG disabled. If SVG delivery is unavoidable, use a separately reviewed asset path with restrictive Content-Disposition and Content-Security-Policy headers.",
2465
2508
  category: "security",
2466
2509
  limitations: [
2467
- "Only a literal true assigned to dangerouslyAllowSVG in a next.config source file is reported; computed or imported configuration is intentionally not inferred."
2510
+ "Only a literal true in the effective images property of a directly exported object, unescaped const alias, or isolated module.exports object is reported. Wrappers, factories, spreads, computed keys and mutations are not inferred."
2468
2511
  ],
2469
2512
  examples: [
2470
2513
  {
@@ -2487,11 +2530,6 @@ var NO_DANGEROUSLY_ALLOW_SVG_DOCUMENTATION = {
2487
2530
  }
2488
2531
  ]
2489
2532
  };
2490
- function propertyName(node) {
2491
- if (!node.computed && node.key.type === "Identifier") return node.key.name;
2492
- if (node.key.type === "Literal" && typeof node.key.value === "string") return node.key.value;
2493
- return null;
2494
- }
2495
2533
  var no_dangerously_allow_svg_default = createRule({
2496
2534
  name: "no-dangerously-allow-svg",
2497
2535
  documentation: NO_DANGEROUSLY_ALLOW_SVG_DOCUMENTATION,
@@ -2507,8 +2545,9 @@ var no_dangerously_allow_svg_default = createRule({
2507
2545
  create(context) {
2508
2546
  if (!NEXT_CONFIG_RE.test(context.filename.replaceAll("\\", "/"))) return {};
2509
2547
  return {
2510
- Property(node) {
2511
- if (propertyName(node) === "dangerouslyAllowSVG" && node.value.type === "Literal" && node.value.value === true) {
2548
+ "Program:exit"() {
2549
+ const node = exportedNextConfigProperty(context.sourceCode, ["images", "dangerouslyAllowSVG"]);
2550
+ if (node !== null && node.value.type === "Literal" && node.value.value === true) {
2512
2551
  context.report({ node, messageId: "noDangerouslyAllowSvg" });
2513
2552
  }
2514
2553
  }
@@ -3117,7 +3156,7 @@ function subtreeMatches(stmt, predicate, descendIntoFunctions = false) {
3117
3156
  visit(stmt);
3118
3157
  return found;
3119
3158
  }
3120
- function unwrap(expr) {
3159
+ function unwrap2(expr) {
3121
3160
  let current = expr;
3122
3161
  while (current.type === AST_NODE_TYPES12.ChainExpression || current.type === AST_NODE_TYPES12.TSNonNullExpression) {
3123
3162
  current = current.expression;
@@ -3145,7 +3184,7 @@ var hasThrowingCallOrNew = (node) => subtreeMatches(
3145
3184
  (n) => n.type === AST_NODE_TYPES12.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES12.NewExpression && !isPureNew(n)
3146
3185
  );
3147
3186
  function isBareCallStatement(stmt) {
3148
- return stmt.type === AST_NODE_TYPES12.ExpressionStatement && unwrap(stmt.expression).type === AST_NODE_TYPES12.CallExpression;
3187
+ return stmt.type === AST_NODE_TYPES12.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES12.CallExpression;
3149
3188
  }
3150
3189
  function isSimpleCatchFinallyOrchestration(node) {
3151
3190
  const handler = node.handler;
@@ -3248,8 +3287,8 @@ function handlerEndsByHandingOff(handler) {
3248
3287
  return last.type === AST_NODE_TYPES12.ExpressionStatement && unwrapAwait(last.expression).type === AST_NODE_TYPES12.CallExpression;
3249
3288
  }
3250
3289
  function unwrapAwait(expr) {
3251
- const inner = unwrap(expr);
3252
- return inner.type === AST_NODE_TYPES12.AwaitExpression ? unwrap(inner.argument) : inner;
3290
+ const inner = unwrap2(expr);
3291
+ return inner.type === AST_NODE_TYPES12.AwaitExpression ? unwrap2(inner.argument) : inner;
3253
3292
  }
3254
3293
  function handlerMentionsCaughtBinding(handler) {
3255
3294
  const names = caughtBindingNames(handler);
@@ -3574,7 +3613,7 @@ var NO_HAND_ROLLED_SPINNER_DOCUMENTATION = {
3574
3613
  rationale: "One-off loading indicators duplicate a shared primitive and let accessibility and styling diverge.",
3575
3614
  remediation: "Render the design-system Spinner component instead.",
3576
3615
  category: "maintainability",
3577
- limitations: ["Only static className values on div and span elements are inspected; tests, stories, generated files, and the design-system implementation are excluded."],
3616
+ limitations: ["Only effective static className values on div and span elements are inspected; a later spread makes the value unknown. Tests, stories, generated files, and the design-system implementation are excluded."],
3578
3617
  examples: [
3579
3618
  { 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 },
3580
3619
  { 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 }
@@ -3636,10 +3675,10 @@ var no_hand_rolled_spinner_default = createRule({
3636
3675
  if (node.name.type !== AST_NODE_TYPES14.JSXIdentifier || node.name.name !== "div" && node.name.name !== "span") {
3637
3676
  return;
3638
3677
  }
3639
- const classNameAttribute = node.attributes.find(
3640
- (attribute) => attribute.type === AST_NODE_TYPES14.JSXAttribute && attribute.name.type === AST_NODE_TYPES14.JSXIdentifier && attribute.name.name === "className"
3678
+ const classNameAttribute = node.attributes.toReversed().find(
3679
+ (attribute) => attribute.type === AST_NODE_TYPES14.JSXSpreadAttribute || attribute.type === AST_NODE_TYPES14.JSXAttribute && attribute.name.type === AST_NODE_TYPES14.JSXIdentifier && attribute.name.name === "className"
3641
3680
  );
3642
- if (classNameAttribute === void 0) return;
3681
+ if (classNameAttribute?.type !== AST_NODE_TYPES14.JSXAttribute) return;
3643
3682
  const className = staticClassName(classNameAttribute);
3644
3683
  if (className === null) return;
3645
3684
  const classes = className.split(/\s+/u);
@@ -3882,15 +3921,15 @@ var no_insecure_random_id_default = createRule({
3882
3921
  });
3883
3922
 
3884
3923
  // src/rules/no-json-stringify-error.ts
3885
- import "@typescript-eslint/utils";
3924
+ import { ASTUtils as ASTUtils5 } from "@typescript-eslint/utils";
3886
3925
  var NO_JSON_STRINGIFY_ERROR_DOCUMENTATION = {
3887
- summary: "Disallow `JSON.stringify` on an Error value; it yields `{}` because `message`/`stack` are non-enumerable.",
3926
+ summary: "Avoid generic JSON serialization that can omit native Error details.",
3888
3927
  rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
3889
3928
  remediation: "Serialize explicit error fields or use an error-aware serializer.",
3890
3929
  category: "correctness",
3891
- limitations: ["The rule uses local catch-binding and constructor provenance rather than type information."],
3930
+ limitations: ["The rule uses stable local catch bindings and unshadowed built-in constructors, not runtime type information. Custom replacers are left to their serializer contract; a catch value is not guaranteed to be an Error."],
3892
3931
  examples: [
3893
- { 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 },
3932
+ { id: "explicit-error-message", title: "Narrow an unknown catch value before selecting fields", outcome: "no-match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err instanceof Error ? err.message : String(err) }); }" }], focusPath: "src/report.ts", expectedCount: 0, public: true },
3894
3933
  { 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 }
3895
3934
  ]
3896
3935
  };
@@ -3922,34 +3961,17 @@ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
3922
3961
  "URIError"
3923
3962
  ]);
3924
3963
  function identifierIsProvenError(identifier, scope) {
3925
- if (isCatchBinding(scope, identifier.name)) return true;
3926
- let current = scope;
3927
- while (current !== null && !current.set.has(identifier.name)) {
3928
- current = current.upper;
3929
- }
3930
- const variable = current?.set.get(identifier.name);
3931
- if (variable === void 0 || variable.defs.length !== 1) return false;
3964
+ const variable = ASTUtils5.findVariable(scope, identifier.name);
3965
+ if (variable === null || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
3932
3966
  const definition = variable.defs[0];
3967
+ if (definition?.type === "CatchClause") return true;
3933
3968
  if (definition?.type !== "Variable") return false;
3934
3969
  const initializer = definition.node.init;
3935
- return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && variable.references.every(
3936
- (reference) => !reference.isWrite() || reference.init === true
3937
- );
3970
+ return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && isGlobalIdentifier(initializer.callee.name, scope);
3938
3971
  }
3939
- function isCatchBinding(scope, name) {
3940
- let current = scope;
3941
- while (current) {
3942
- const variable = current.set.get(name);
3943
- if (variable) {
3944
- for (const def of variable.defs) {
3945
- if (def.type === "CatchClause") {
3946
- return true;
3947
- }
3948
- }
3949
- }
3950
- current = current.upper;
3951
- }
3952
- return false;
3972
+ function isGlobalIdentifier(name, scope) {
3973
+ const binding = ASTUtils5.findVariable(scope, name);
3974
+ return binding === null || binding.defs.length === 0;
3953
3975
  }
3954
3976
  function positiveErrorSubject(test) {
3955
3977
  return instanceofErrorSubject(test) ?? typeGuardSubject(test);
@@ -4057,7 +4079,7 @@ function expressionSuggestsError(expression, scope) {
4057
4079
  return identifierIsProvenError(expression, scope);
4058
4080
  }
4059
4081
  if (expression.type === "NewExpression" && expression.callee.type === "Identifier") {
4060
- return BUILTIN_ERROR_CONSTRUCTORS.has(expression.callee.name);
4082
+ return BUILTIN_ERROR_CONSTRUCTORS.has(expression.callee.name) && isGlobalIdentifier(expression.callee.name, scope);
4061
4083
  }
4062
4084
  return expression.type === "MemberExpression" && memberSuggestsError(expression, scope);
4063
4085
  }
@@ -4080,11 +4102,11 @@ var no_json_stringify_error_default = createRule({
4080
4102
  meta: {
4081
4103
  type: "problem",
4082
4104
  docs: {
4083
- description: "Disallow `JSON.stringify` on an Error value; it yields `{}` because `message`/`stack` are non-enumerable."
4105
+ description: "Avoid generic JSON serialization that can omit native Error details."
4084
4106
  },
4085
4107
  schema: [],
4086
4108
  messages: {
4087
- noJsonStringifyError: "`JSON.stringify` on an Error yields `{}` because `message`/`stack` are non-enumerable. Log `err.message` / `err.stack`, or use a proper error serializer."
4109
+ noJsonStringifyError: "Generic `JSON.stringify` can omit non-enumerable Error details such as message and stack. Serialize explicit fields or use an error-aware serializer."
4088
4110
  }
4089
4111
  },
4090
4112
  defaultOptions: [],
@@ -4099,6 +4121,9 @@ var no_json_stringify_error_default = createRule({
4099
4121
  return;
4100
4122
  }
4101
4123
  const scope = context.sourceCode.getScope(firstArg);
4124
+ if (!isGlobalIdentifier("JSON", scope)) return;
4125
+ const replacer = node.arguments[1];
4126
+ if (replacer !== void 0 && !(replacer.type === "Literal" && replacer.value === null) && !(replacer.type === "Identifier" && replacer.name === "undefined" && isGlobalIdentifier("undefined", scope))) return;
4102
4127
  const unsafeValue = directLiteralValues(firstArg).find(
4103
4128
  (value) => expressionSuggestsError(value, scope) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
4104
4129
  );
@@ -4456,8 +4481,8 @@ function privateMemberFixes(context, services, owner, members, removePrivateKeyw
4456
4481
  return;
4457
4482
  }
4458
4483
  if (node.type !== AST_NODE_TYPES16.MemberExpression) return;
4459
- const propertyName8 = node.property.type === AST_NODE_TYPES16.Identifier || node.property.type === AST_NODE_TYPES16.PrivateIdentifier ? node.property.name : node.property.type === AST_NODE_TYPES16.Literal && typeof node.property.value === "string" ? node.property.value : null;
4460
- if (propertyName8 !== name) return;
4484
+ const propertyName6 = node.property.type === AST_NODE_TYPES16.Identifier || node.property.type === AST_NODE_TYPES16.PrivateIdentifier ? node.property.name : node.property.type === AST_NODE_TYPES16.Literal && typeof node.property.value === "string" ? node.property.value : null;
4485
+ if (propertyName6 !== name) return;
4461
4486
  const propertySymbol = symbolAt(services, checker, node.property);
4462
4487
  if (node.computed || node.property.type !== AST_NODE_TYPES16.Identifier || node.object.type !== AST_NODE_TYPES16.ThisExpression || enclosingClass(node) !== owner || !symbols.some((symbol) => sameSymbol(symbol, propertySymbol))) {
4463
4488
  unsafe = true;
@@ -4620,7 +4645,7 @@ var interface_contract_members_private_default = createRule({
4620
4645
  });
4621
4646
 
4622
4647
  // src/rules/no-log-only-catch.ts
4623
- import { AST_NODE_TYPES as AST_NODE_TYPES18, ASTUtils as ASTUtils4 } from "@typescript-eslint/utils";
4648
+ import { AST_NODE_TYPES as AST_NODE_TYPES18, ASTUtils as ASTUtils6 } from "@typescript-eslint/utils";
4624
4649
 
4625
4650
  // src/rules/_logging.ts
4626
4651
  import "@typescript-eslint/utils";
@@ -4792,7 +4817,7 @@ function seededFallbackHandled(tryStatement, scope) {
4792
4817
  if (previous.declarations.length !== 1 || declarator === void 0) return false;
4793
4818
  if (declarator.id.type !== AST_NODE_TYPES18.Identifier) return false;
4794
4819
  if (declarator.init == null || !isSeedValue(declarator.init)) return false;
4795
- const variable = ASTUtils4.findVariable(scope, declarator.id.name);
4820
+ const variable = ASTUtils6.findVariable(scope, declarator.id.name);
4796
4821
  if (variable === null) return false;
4797
4822
  const [tryStart, tryEnd] = tryStatement.block.range;
4798
4823
  let writtenInTry = false;
@@ -4897,7 +4922,7 @@ var no_log_only_catch_default = createRule({
4897
4922
  });
4898
4923
 
4899
4924
  // src/rules/no-bare-return-from-test-catch.ts
4900
- import { AST_NODE_TYPES as AST_NODE_TYPES19, ASTUtils as ASTUtils5 } from "@typescript-eslint/utils";
4925
+ import { AST_NODE_TYPES as AST_NODE_TYPES19, ASTUtils as ASTUtils7 } from "@typescript-eslint/utils";
4901
4926
  var NO_BARE_RETURN_FROM_TEST_CATCH_DOCUMENTATION = {
4902
4927
  summary: "Disallow a bare return from a test catch block when it skips a later assertion.",
4903
4928
  rationale: "The caught failure turns into a passing test without executing the assertion that follows it.",
@@ -4922,7 +4947,7 @@ function staticMemberName2(node) {
4922
4947
  return null;
4923
4948
  }
4924
4949
  function importedName3(identifier, context, modules) {
4925
- const variable = ASTUtils5.findVariable(context.sourceCode.getScope(identifier), identifier.name);
4950
+ const variable = ASTUtils7.findVariable(context.sourceCode.getScope(identifier), identifier.name);
4926
4951
  if (variable === null || variable.defs.length === 0) return identifier.name;
4927
4952
  for (const definition of variable.defs) {
4928
4953
  if (definition.node.type !== AST_NODE_TYPES19.ImportSpecifier) continue;
@@ -5062,7 +5087,7 @@ var ADAPTER_BASENAME_RE = /(?:^|[-_.])adapters?(?:[-_.]|$)/i;
5062
5087
  var API_BOUNDARY_IMPORT_RE = /(?:^|[/_.-])(?:api|client|sdk|contract|generated)(?:$|[/_.-])/i;
5063
5088
  var SNAKE_CASE_RE = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/;
5064
5089
  var LOWER_CAMEL_CASE_RE = /^[a-z][A-Za-z0-9]*$/;
5065
- function propertyName2(node) {
5090
+ function propertyName(node) {
5066
5091
  return node.type === AST_NODE_TYPES20.Identifier ? node.name : null;
5067
5092
  }
5068
5093
  function memberName3(node) {
@@ -5112,7 +5137,7 @@ var no_bespoke_api_case_conversion_default = createRule({
5112
5137
  return {
5113
5138
  Property(node) {
5114
5139
  if (node.computed || node.method || node.shorthand) return;
5115
- const key = propertyName2(node.key);
5140
+ const key = propertyName(node.key);
5116
5141
  const value = memberName3(node.value);
5117
5142
  if (key === null || value === null || !isDirectCaseTranslation(key, value)) return;
5118
5143
  const wireName = SNAKE_CASE_RE.test(key) ? key : value;
@@ -5306,7 +5331,7 @@ var no_vague_suppression_description_default = createRule({
5306
5331
  });
5307
5332
 
5308
5333
  // src/rules/no-generic-single-export-module.ts
5309
- import { AST_NODE_TYPES as AST_NODE_TYPES22, ASTUtils as ASTUtils6 } from "@typescript-eslint/utils";
5334
+ import { AST_NODE_TYPES as AST_NODE_TYPES22, ASTUtils as ASTUtils8 } from "@typescript-eslint/utils";
5310
5335
  var NO_GENERIC_SINGLE_EXPORT_MODULE_DOCUMENTATION = {
5311
5336
  summary: "Disallow generic module stems when one runtime export already names the responsibility.",
5312
5337
  rationale: "A generic filename hides the sole exported responsibility and makes navigation less descriptive.",
@@ -5431,8 +5456,8 @@ function typeOnlyBindings(program) {
5431
5456
  }
5432
5457
  return new Set([...names].filter((name) => !runtimeNames.has(name)));
5433
5458
  }
5434
- function isGlobalIdentifier(context, node) {
5435
- const variable = ASTUtils6.findVariable(context.sourceCode.getScope(node), node.name);
5459
+ function isGlobalIdentifier2(context, node) {
5460
+ const variable = ASTUtils8.findVariable(context.sourceCode.getScope(node), node.name);
5436
5461
  return variable === null || variable.defs.length === 0;
5437
5462
  }
5438
5463
  function isConventionalFrameworkUtility(filename, exported) {
@@ -5463,11 +5488,11 @@ var no_generic_single_export_module_default = createRule({
5463
5488
  return {
5464
5489
  CallExpression(node) {
5465
5490
  const first = node.arguments[0];
5466
- if (first?.type === AST_NODE_TYPES22.Identifier && first.name === "exports" && isGlobalIdentifier(context, first) && node.callee.type === AST_NODE_TYPES22.MemberExpression && node.callee.object.type === AST_NODE_TYPES22.Identifier && node.callee.object.name === "Object" && isGlobalIdentifier(context, node.callee.object) && memberPropertyName(node.callee) !== null && CJS_OBJECT_EXPORT_METHODS.has(memberPropertyName(node.callee))) hasCommonJsExport = true;
5491
+ if (first?.type === AST_NODE_TYPES22.Identifier && first.name === "exports" && isGlobalIdentifier2(context, first) && node.callee.type === AST_NODE_TYPES22.MemberExpression && node.callee.object.type === AST_NODE_TYPES22.Identifier && node.callee.object.name === "Object" && isGlobalIdentifier2(context, node.callee.object) && memberPropertyName(node.callee) !== null && CJS_OBJECT_EXPORT_METHODS.has(memberPropertyName(node.callee))) hasCommonJsExport = true;
5467
5492
  },
5468
5493
  MemberExpression(node) {
5469
- if (node.object.type === AST_NODE_TYPES22.Identifier && node.object.name === "exports" && isGlobalIdentifier(context, node.object)) hasCommonJsExport = true;
5470
- if (node.object.type === AST_NODE_TYPES22.Identifier && node.object.name === "module" && isGlobalIdentifier(context, node.object) && memberPropertyName(node) === "exports") hasCommonJsExport = true;
5494
+ if (node.object.type === AST_NODE_TYPES22.Identifier && node.object.name === "exports" && isGlobalIdentifier2(context, node.object)) hasCommonJsExport = true;
5495
+ if (node.object.type === AST_NODE_TYPES22.Identifier && node.object.name === "module" && isGlobalIdentifier2(context, node.object) && memberPropertyName(node) === "exports") hasCommonJsExport = true;
5471
5496
  },
5472
5497
  "Program:exit"(program) {
5473
5498
  if (hasCommonJsExport) return;
@@ -5905,7 +5930,6 @@ var no_positional_tuple_return_default = createRule({
5905
5930
  });
5906
5931
 
5907
5932
  // src/rules/no-production-browser-source-maps.ts
5908
- import "@typescript-eslint/utils";
5909
5933
  var NEXT_CONFIG_RE2 = /(?:^|\/)next\.config\.[cm]?[jt]s$/;
5910
5934
  var NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION = {
5911
5935
  summary: "Next.js production browser source maps expose application source",
@@ -5913,7 +5937,7 @@ var NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION = {
5913
5937
  remediation: "Leave productionBrowserSourceMaps disabled and upload private source maps directly to the error-monitoring service during the build.",
5914
5938
  category: "security",
5915
5939
  limitations: [
5916
- "Only a literal true assigned in a next.config source file is reported; computed or imported configuration is intentionally not inferred."
5940
+ "Only a literal true in the effective property of a directly exported object, unescaped const alias, or isolated module.exports object is reported. Wrappers, factories, spreads, computed keys and mutations are not inferred."
5917
5941
  ],
5918
5942
  examples: [
5919
5943
  {
@@ -5936,11 +5960,6 @@ var NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION = {
5936
5960
  }
5937
5961
  ]
5938
5962
  };
5939
- function propertyName3(node) {
5940
- if (!node.computed && node.key.type === "Identifier") return node.key.name;
5941
- if (node.key.type === "Literal" && typeof node.key.value === "string") return node.key.value;
5942
- return null;
5943
- }
5944
5963
  var no_production_browser_source_maps_default = createRule({
5945
5964
  name: "no-production-browser-source-maps",
5946
5965
  documentation: NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION,
@@ -5956,8 +5975,9 @@ var no_production_browser_source_maps_default = createRule({
5956
5975
  create(context) {
5957
5976
  if (!NEXT_CONFIG_RE2.test(context.filename.replaceAll("\\", "/"))) return {};
5958
5977
  return {
5959
- Property(node) {
5960
- if (propertyName3(node) === "productionBrowserSourceMaps" && node.value.type === "Literal" && node.value.value === true) {
5978
+ "Program:exit"() {
5979
+ const node = exportedNextConfigProperty(context.sourceCode, ["productionBrowserSourceMaps"]);
5980
+ if (node !== null && node.value.type === "Literal" && node.value.value === true) {
5961
5981
  context.report({ node, messageId: "noProductionBrowserSourceMaps" });
5962
5982
  }
5963
5983
  }
@@ -5966,13 +5986,13 @@ var no_production_browser_source_maps_default = createRule({
5966
5986
  });
5967
5987
 
5968
5988
  // src/rules/no-raw-env.ts
5969
- import "@typescript-eslint/utils";
5989
+ import { ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
5970
5990
  var NO_RAW_ENV_DOCUMENTATION = {
5971
5991
  summary: "Disallow direct `process.env` and `import.meta.env` reads outside validated boundaries.",
5972
5992
  rationale: "Raw environment reads are untyped and defer invalid configuration failures until use.",
5973
5993
  remediation: "Validate environment values at startup and import the typed configuration object.",
5974
5994
  category: "correctness",
5975
- limitations: ["Host markers, assignment targets, tests, scripts, build config, and validated boundaries are excluded."],
5995
+ limitations: ["Host markers, assignment targets, tests, scripts, build config, and recognized validation-boundary files are policy exemptions. A validation call does not prove every export is validated; assignment-target exemptions also include compound writes that read the previous value."],
5976
5996
  examples: [
5977
5997
  { id: "validated-environment", title: "Read validated configuration", outcome: "no-match", files: [{ path: "src/database.ts", source: "import { env } from './env.js'; const url = env.DATABASE_URL;" }], focusPath: "src/database.ts", expectedCount: 0, public: true },
5978
5998
  { id: "raw-environment-read", title: "Do not read raw configuration", outcome: "match", files: [{ path: "src/database.ts", source: "const url = process.env.DATABASE_URL;" }], focusPath: "src/database.ts", expectedCount: 1, public: true }
@@ -5980,10 +6000,6 @@ var NO_RAW_ENV_DOCUMENTATION = {
5980
6000
  };
5981
6001
  var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
5982
6002
  var ENV_BOUNDARY_FILE_RE = /(^|[\\/])(?:env|client-env|server-env|client-settings|server-settings)\.[cm]?[jt]sx?$/;
5983
- var ENV_VALIDATION_MARKER_RE = /\bcreateEnv\s*\(|\bz\.object\s*\(|\.(?:safeParse|parse)\s*\(/;
5984
- function isValidatedEnvBoundary(filename, sourceText) {
5985
- return ENV_BOUNDARY_FILE_RE.test(filename.replaceAll("\\", "/")) && ENV_VALIDATION_MARKER_RE.test(sourceText);
5986
- }
5987
6003
  function isProcessEnv(node) {
5988
6004
  return !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
5989
6005
  }
@@ -6038,30 +6054,43 @@ var no_raw_env_default = createRule({
6038
6054
  defaultOptions: [],
6039
6055
  create(context) {
6040
6056
  const filename = context.filename;
6041
- if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/")) || isValidatedEnvBoundary(filename, context.sourceCode.text)) {
6057
+ if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/"))) {
6042
6058
  return {};
6043
6059
  }
6060
+ const reads = [];
6061
+ const boundaryFile = ENV_BOUNDARY_FILE_RE.test(filename.replaceAll("\\", "/"));
6062
+ let hasValidationCall = false;
6044
6063
  return {
6064
+ CallExpression(node) {
6065
+ if (!boundaryFile) return;
6066
+ const callee = node.callee;
6067
+ if (callee.type === "Identifier" && callee.name === "createEnv" || callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier" && (["parse", "safeParse"].includes(callee.property.name) || callee.property.name === "object" && callee.object.type === "Identifier" && callee.object.name === "z")) hasValidationCall = true;
6068
+ },
6045
6069
  MemberExpression(node) {
6070
+ if (isProcessEnv(node) && node.object.type === "Identifier") {
6071
+ const binding = ASTUtils9.findVariable(context.sourceCode.getScope(node), node.object.name);
6072
+ if (binding !== null && binding.defs.length > 0 && !binding.defs.every((definition) => definition.type === "ImportBinding" && definition.parent.type === "ImportDeclaration" && ["node:process", "process"].includes(definition.parent.source.value) && ["ImportDefaultSpecifier", "ImportNamespaceSpecifier"].includes(definition.node.type))) return;
6073
+ }
6046
6074
  if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isExemptVariableAccess(node) && !isWriteTarget(node) && !isWholeEnvSpread(node)) {
6047
- context.report({
6048
- node,
6049
- messageId: "noRawEnv"
6050
- });
6075
+ reads.push(node);
6051
6076
  }
6077
+ },
6078
+ "Program:exit"() {
6079
+ if (boundaryFile && hasValidationCall) return;
6080
+ for (const node of reads) context.report({ node, messageId: "noRawEnv" });
6052
6081
  }
6053
6082
  };
6054
6083
  }
6055
6084
  });
6056
6085
 
6057
6086
  // src/rules/no-raw-fetch-outside-clients.ts
6058
- import { AST_NODE_TYPES as AST_NODE_TYPES24, ASTUtils as ASTUtils7 } from "@typescript-eslint/utils";
6087
+ import { AST_NODE_TYPES as AST_NODE_TYPES24, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
6059
6088
  var NO_RAW_FETCH_OUTSIDE_CLIENTS_DOCUMENTATION = {
6060
6089
  summary: "Disallow calling the global `fetch` outside the client layer; route outbound HTTP through a client module that owns retry, timeout and status handling.",
6061
6090
  rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
6062
6091
  remediation: "Move the request into a client module and call that abstraction from application code.",
6063
6092
  category: "architecture",
6064
- limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded. Configure the same literal Next.js basePath here and on prefer-server-actions so one rule owns each internal mutation."],
6093
+ limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded. Configure the same literal Next.js basePath here and on prefer-server-actions so one rule owns each internal mutation.", "Effect and pre-signed-transfer exemptions use recognized syntax and naming conventions, not complete React or URL provenance. Those conservative exclusions are recall limitations, not evidence that every excluded request satisfies transport policy."],
6065
6094
  examples: [
6066
6095
  { id: "client-call", title: "Use a client abstraction", outcome: "no-match", files: [{ path: "src/routes/handler.ts", source: "const response = await billingClient.getInvoice(id);" }], focusPath: "src/routes/handler.ts", expectedCount: 0, public: true },
6067
6096
  { id: "raw-fetch", title: "Do not call global fetch here", outcome: "match", files: [{ path: "src/routes/handler.ts", source: "const response = await fetch('/api/invoices');" }], focusPath: "src/routes/handler.ts", expectedCount: 1, public: true }
@@ -6243,7 +6272,7 @@ var no_raw_fetch_outside_clients_default = createRule({
6243
6272
  internalApiPrefixes.push(`${options.basePath}/api`);
6244
6273
  }
6245
6274
  function resolvesToGlobal(identifier) {
6246
- const variable = ASTUtils7.findVariable(
6275
+ const variable = ASTUtils10.findVariable(
6247
6276
  context.sourceCode.getScope(identifier),
6248
6277
  identifier.name
6249
6278
  );
@@ -6252,17 +6281,18 @@ var no_raw_fetch_outside_clients_default = createRule({
6252
6281
  function resolveNode2(node) {
6253
6282
  if (node === void 0) return null;
6254
6283
  if (node.type !== AST_NODE_TYPES24.Identifier) return node;
6255
- const variable = ASTUtils7.findVariable(
6284
+ const variable = ASTUtils10.findVariable(
6256
6285
  context.sourceCode.getScope(node),
6257
6286
  node.name
6258
6287
  );
6259
6288
  if (variable?.defs.length !== 1) return node;
6260
6289
  const definition = variable.defs[0];
6261
- return definition?.type === "Variable" && definition.node.init !== null ? definition.node.init : node;
6290
+ return definition?.type === "Variable" && definition.parent.kind === "const" && definition.node.init !== null && !variable.references.some((reference) => reference.isWrite() && reference.init !== true) && !(definition.node.init.type === AST_NODE_TYPES24.ObjectExpression && variable.references.some((reference) => reference.identifier !== node && reference.init !== true)) ? definition.node.init : node;
6262
6291
  }
6263
6292
  function propertyValue(node, name) {
6264
6293
  if (node?.type !== AST_NODE_TYPES24.ObjectExpression) return null;
6265
- for (const property of node.properties) {
6294
+ if (node.properties.some((property) => property.type !== AST_NODE_TYPES24.Property || property.computed)) return null;
6295
+ for (const property of [...node.properties].reverse()) {
6266
6296
  if (property.type !== AST_NODE_TYPES24.Property || property.computed) continue;
6267
6297
  const key = property.key;
6268
6298
  const keyName = key.type === AST_NODE_TYPES24.Identifier ? key.name : key.type === AST_NODE_TYPES24.Literal && typeof key.value === "string" ? key.value : null;
@@ -6330,7 +6360,7 @@ var no_raw_fetch_outside_clients_default = createRule({
6330
6360
  });
6331
6361
 
6332
6362
  // src/rules/no-restricted-library-load.ts
6333
- import { AST_NODE_TYPES as AST_NODE_TYPES25, ASTUtils as ASTUtils8 } from "@typescript-eslint/utils";
6363
+ import { AST_NODE_TYPES as AST_NODE_TYPES25, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
6334
6364
  var NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION = {
6335
6365
  summary: "Apply a configured library-replacement policy to literal dynamic imports, CommonJS loads, and TypeScript import-equals declarations.",
6336
6366
  rationale: "Runtime module loads can bypass the replacement policy enforced for static imports.",
@@ -6403,7 +6433,7 @@ var no_restricted_library_load_default = createRule({
6403
6433
  });
6404
6434
  }
6405
6435
  function isUnshadowedRequire(node) {
6406
- const variable = ASTUtils8.findVariable(
6436
+ const variable = ASTUtils11.findVariable(
6407
6437
  context.sourceCode.getScope(node),
6408
6438
  node.name
6409
6439
  );
@@ -6435,7 +6465,7 @@ var no_restricted_library_load_default = createRule({
6435
6465
  });
6436
6466
 
6437
6467
  // src/rules/no-router-refresh-polling.ts
6438
- import { AST_NODE_TYPES as AST_NODE_TYPES26, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
6468
+ import { AST_NODE_TYPES as AST_NODE_TYPES26, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
6439
6469
  var NO_ROUTER_REFRESH_POLLING_DOCUMENTATION = {
6440
6470
  summary: "Do not poll by calling a Next.js router's refresh method from a timer.",
6441
6471
  rationale: "A route refresh refetches and rerenders the whole route on every tick instead of loading the named resource that changed.",
@@ -6443,8 +6473,8 @@ var NO_ROUTER_REFRESH_POLLING_DOCUMENTATION = {
6443
6473
  category: "performance",
6444
6474
  limitations: ["Only router bindings created from next/navigation useRouter and direct setInterval or window.setInterval callbacks are inspected; generated and test files are excluded."],
6445
6475
  examples: [
6446
- { id: "poll-named-action", title: "Poll a named action", outcome: "no-match", files: [{ path: "src/status.tsx", source: 'import { useRouter } from "next/navigation"; const router = useRouter(); setInterval(() => fetchStatus(), POLLING_INTERVAL_MS);' }], focusPath: "src/status.tsx", expectedCount: 0, public: true },
6447
- { id: "poll-router-refresh", title: "Do not poll the whole route", outcome: "match", files: [{ path: "src/status.tsx", source: 'import { useRouter } from "next/navigation"; const router = useRouter(); setInterval(() => router.refresh(), POLLING_INTERVAL_MS);' }], focusPath: "src/status.tsx", expectedCount: 1, public: true }
6476
+ { id: "poll-named-action", title: "Poll a named resource", outcome: "no-match", files: [{ path: "src/status.tsx", source: '"use client"; import { useEffect } from "react"; function Status() { useEffect(() => { const timer = setInterval(() => fetchStatus(), POLLING_INTERVAL_MS); return () => clearInterval(timer); }, []); return null; }' }], focusPath: "src/status.tsx", expectedCount: 0, public: true },
6477
+ { id: "poll-router-refresh", title: "Do not poll the whole route", outcome: "match", files: [{ path: "src/status.tsx", source: '"use client"; import { useEffect } from "react"; import { useRouter } from "next/navigation"; function Status() { const router = useRouter(); useEffect(() => { const timer = setInterval(() => router.refresh(), POLLING_INTERVAL_MS); return () => clearInterval(timer); }, [router]); return null; }' }], focusPath: "src/status.tsx", expectedCount: 1, public: true }
6448
6478
  ]
6449
6479
  };
6450
6480
  function importedName4(node) {
@@ -6454,6 +6484,7 @@ function enclosingIntervalCallback(sourceCode, node) {
6454
6484
  const ancestors = sourceCode.getAncestors(node);
6455
6485
  for (let index = ancestors.length - 1; index >= 0; index -= 1) {
6456
6486
  const ancestor = ancestors[index];
6487
+ if (ancestor?.type === AST_NODE_TYPES26.FunctionDeclaration) return null;
6457
6488
  if (ancestor?.type !== AST_NODE_TYPES26.ArrowFunctionExpression && ancestor?.type !== AST_NODE_TYPES26.FunctionExpression) continue;
6458
6489
  const parent = ancestor.parent;
6459
6490
  return parent.type === AST_NODE_TYPES26.CallExpression && parent.arguments[0] === ancestor && isIntervalCallee(sourceCode, parent.callee) ? ancestor : null;
@@ -6464,7 +6495,7 @@ function isIntervalCallee(sourceCode, node) {
6464
6495
  return node.type === AST_NODE_TYPES26.Identifier && node.name === "setInterval" && isUnshadowedGlobal2(sourceCode, node) || node.type === AST_NODE_TYPES26.MemberExpression && !node.computed && node.object.type === AST_NODE_TYPES26.Identifier && (node.object.name === "window" || node.object.name === "globalThis") && isUnshadowedGlobal2(sourceCode, node.object) && node.property.type === AST_NODE_TYPES26.Identifier && node.property.name === "setInterval";
6465
6496
  }
6466
6497
  function isUnshadowedGlobal2(sourceCode, node) {
6467
- const variable = ASTUtils9.findVariable(sourceCode.getScope(node), node.name);
6498
+ const variable = ASTUtils12.findVariable(sourceCode.getScope(node), node.name);
6468
6499
  return variable === null || variable.defs.length === 0;
6469
6500
  }
6470
6501
  var no_router_refresh_polling_default = createRule({
@@ -6487,25 +6518,25 @@ var no_router_refresh_polling_default = createRule({
6487
6518
  if (node.source.value !== "next/navigation") return;
6488
6519
  for (const specifier of node.specifiers) {
6489
6520
  if (specifier.type === AST_NODE_TYPES26.ImportSpecifier && importedName4(specifier) === "useRouter") {
6490
- const variable = ASTUtils9.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
6521
+ const variable = ASTUtils12.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
6491
6522
  if (variable !== null) routerHooks.add(variable);
6492
6523
  }
6493
6524
  }
6494
6525
  },
6495
6526
  VariableDeclarator(node) {
6496
6527
  if (node.id.type === AST_NODE_TYPES26.Identifier && node.init?.type === AST_NODE_TYPES26.CallExpression && node.init.callee.type === AST_NODE_TYPES26.Identifier) {
6497
- const hook = ASTUtils9.findVariable(context.sourceCode.getScope(node.init.callee), node.init.callee.name);
6498
- const router = ASTUtils9.findVariable(context.sourceCode.getScope(node.id), node.id.name);
6528
+ const hook = ASTUtils12.findVariable(context.sourceCode.getScope(node.init.callee), node.init.callee.name);
6529
+ const router = ASTUtils12.findVariable(context.sourceCode.getScope(node.id), node.id.name);
6499
6530
  if (hook !== null && router !== null && routerHooks.has(hook)) routers.add(router);
6500
6531
  }
6501
6532
  },
6502
6533
  CallExpression(node) {
6503
6534
  if (node.callee.type !== AST_NODE_TYPES26.MemberExpression || node.callee.computed || node.callee.object.type !== AST_NODE_TYPES26.Identifier || node.callee.property.type !== AST_NODE_TYPES26.Identifier || node.callee.property.name !== "refresh") return;
6504
- const router = ASTUtils9.findVariable(
6535
+ const router = ASTUtils12.findVariable(
6505
6536
  context.sourceCode.getScope(node.callee.object),
6506
6537
  node.callee.object.name
6507
6538
  );
6508
- if (router === null || !routers.has(router)) return;
6539
+ if (router === null || !routers.has(router) || router.references.some((reference) => reference.isWrite() && reference.init !== true)) return;
6509
6540
  const callback = enclosingIntervalCallback(context.sourceCode, node);
6510
6541
  if (callback !== null && !reportedCallbacks.has(callback)) {
6511
6542
  reportedCallbacks.add(callback);
@@ -7191,11 +7222,11 @@ function isAuthSecretName(identifier) {
7191
7222
  var NO_SECRET_IN_LOG_DOCUMENTATION = {
7192
7223
  summary: "Disallow passing a secret-named value or a raw request/response blob to a logging call; both leak to log sinks. Redact or omit.",
7193
7224
  rationale: "Logs are widely retained and distributed, so credentials and raw bodies can become durable data leaks.",
7194
- remediation: "Omit the value or log an explicitly redacted, truncated, or derived non-sensitive field.",
7225
+ remediation: "Omit the value, log allowlisted non-sensitive context, or use an approved redactor; truncation alone is not a safety guarantee.",
7195
7226
  category: "security",
7196
- limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers."],
7227
+ limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers. Name-based exemptions are policy heuristics, not proof that a value is safely redacted."],
7197
7228
  examples: [
7198
- { id: "redacted-secret", title: "Log an explicitly redacted value", outcome: "no-match", files: [{ path: "src/auth.ts", source: "logger.info('auth', { tokenPrefix });" }], focusPath: "src/auth.ts", expectedCount: 0, public: true },
7229
+ { id: "redacted-secret", title: "Log non-sensitive context instead of the secret", outcome: "no-match", files: [{ path: "src/auth.ts", source: "logger.info('auth', { requestId });" }], focusPath: "src/auth.ts", expectedCount: 0, public: true },
7199
7230
  { id: "logged-secret", title: "Do not send a secret to logs", outcome: "match", files: [{ path: "src/auth.ts", source: "logger.error('auth failed', { token });" }], focusPath: "src/auth.ts", expectedCount: 1, public: true }
7200
7231
  ]
7201
7232
  };
@@ -7247,13 +7278,14 @@ var LOG_INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
7247
7278
  var REDACTION_RE = /prefix|suffix|redact|mask|hash|hint|_len|length/i;
7248
7279
  var WHOLE_TOKEN_REDACTION_MARKERS = /* @__PURE__ */ new Set(["tag"]);
7249
7280
  function isSecretKeyword(name) {
7250
- if (REDACTION_RE.test(name)) {
7251
- return false;
7252
- }
7253
- if (tokenize(name).some((tok) => WHOLE_TOKEN_REDACTION_MARKERS.has(tok))) {
7254
- return false;
7255
- }
7256
- return isSecretName(name, LOG_INNOCUOUS_WORDS);
7281
+ return !hasRedactionMarker(name) && isSecretName(name, LOG_INNOCUOUS_WORDS);
7282
+ }
7283
+ function hasRedactionMarker(name) {
7284
+ return REDACTION_RE.test(name) || tokenize(name).some((tok) => WHOLE_TOKEN_REDACTION_MARKERS.has(tok));
7285
+ }
7286
+ function valueName(node) {
7287
+ if (node.type === "Identifier") return node.name;
7288
+ return node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier" ? node.property.name : null;
7257
7289
  }
7258
7290
  function isRawSecretValue(prop) {
7259
7291
  if (prop.shorthand) {
@@ -7337,8 +7369,8 @@ var no_secret_in_log_default = createRule({
7337
7369
  }
7338
7370
  ],
7339
7371
  messages: {
7340
- noSecretInLog: "Secret `{{name}}` passed to a logging call leaks it to log sinks. Redact (e.g. `{{name}}Prefix: {{name}}.slice(0, 6)`) or omit it.",
7341
- noRawBodyInLog: "Raw `{{name}}` passed to a logging call. Request/response blobs carry PII and often echo credentials back, and log sinks have no retention policy. Log a derived value instead (a status, `{{name}}.id`, a length, a truncated issue list) or pass it through a redactor (`redact({{name}})`)."
7372
+ noSecretInLog: "Secret-like `{{name}}` passed to a logging call. Omit it or use an approved redactor; a prefix can expose an entire short secret.",
7373
+ noRawBodyInLog: "Raw `{{name}}` passed to a logging call. Request/response bodies can contain personal data or credentials. Log allowlisted non-sensitive context or use an approved redactor."
7342
7374
  }
7343
7375
  },
7344
7376
  defaultOptions: [{}],
@@ -7346,7 +7378,7 @@ var no_secret_in_log_default = createRule({
7346
7378
  const matcher = createLogMatcher(loggingOptions);
7347
7379
  const blobArmApplies = !isTestFile(context.filename);
7348
7380
  function reportSecretArgument(arg) {
7349
- const name = arg.type === "Identifier" ? arg.name : arg.type === "MemberExpression" && !arg.computed && arg.property.type === "Identifier" ? arg.property.name : null;
7381
+ const name = valueName(arg);
7350
7382
  if (name === null || !isSecretKeyword(name)) {
7351
7383
  return false;
7352
7384
  }
@@ -7355,10 +7387,13 @@ var no_secret_in_log_default = createRule({
7355
7387
  }
7356
7388
  function reportSecretProperty(prop) {
7357
7389
  const keyName = propertyKeyName2(prop);
7358
- if (keyName === null || !isSecretKeyword(keyName) || !isRawSecretValue(prop)) {
7390
+ const value = valueName(prop.value);
7391
+ if (value !== null && hasRedactionMarker(value)) return false;
7392
+ const name = value !== null && isSecretKeyword(value) ? value : keyName;
7393
+ if (name === null || !isSecretKeyword(name) || !isRawSecretValue(prop)) {
7359
7394
  return false;
7360
7395
  }
7361
- context.report({ node: prop, messageId: "noSecretInLog", data: { name: keyName } });
7396
+ context.report({ node: prop, messageId: "noSecretInLog", data: { name } });
7362
7397
  return true;
7363
7398
  }
7364
7399
  function reportRawBlob(node, value) {
@@ -8829,9 +8864,48 @@ var RESULT_FILLER = /* @__PURE__ */ new Set([
8829
8864
  "the",
8830
8865
  "value"
8831
8866
  ]);
8832
- function hasVacuousTypedTag(text) {
8833
- const tags = typedTags(text);
8834
- return tags.length > 0 && tags.some(isVacuousTag);
8867
+ function parameterTarget(parameter) {
8868
+ if (parameter.type === "TSParameterProperty") return parameterTarget(parameter.parameter);
8869
+ return parameter.type === "AssignmentPattern" ? parameter.left : parameter;
8870
+ }
8871
+ function documentedSignature(sourceCode, comment) {
8872
+ const before = sourceCode.getTokenBefore(comment);
8873
+ if (before?.loc.end.line === comment.loc.start.line) return null;
8874
+ const token = sourceCode.getTokenAfter(comment);
8875
+ if (token === null || token.loc.start.line !== comment.loc.end.line + 1) return null;
8876
+ let node = sourceCode.getNodeByRangeIndex(token.range[0]);
8877
+ while (node !== null && node.type !== "Program" && node.type !== "BlockStatement" && node.type !== "ClassBody") {
8878
+ const signature = functionSignature(node);
8879
+ if (signature !== null) {
8880
+ return signature.returnType !== void 0 && signature.params.every((parameter) => {
8881
+ const target = parameterTarget(parameter);
8882
+ return "typeAnnotation" in target && target.typeAnnotation != null;
8883
+ }) ? signature : null;
8884
+ }
8885
+ node = node.parent ?? null;
8886
+ }
8887
+ return null;
8888
+ }
8889
+ function functionSignature(node) {
8890
+ switch (node.type) {
8891
+ case "ExportNamedDeclaration":
8892
+ case "ExportDefaultDeclaration":
8893
+ return node.declaration === null ? null : functionSignature(node.declaration);
8894
+ case "FunctionDeclaration":
8895
+ case "FunctionExpression":
8896
+ case "ArrowFunctionExpression":
8897
+ case "TSDeclareFunction":
8898
+ case "TSMethodSignature":
8899
+ return node;
8900
+ case "MethodDefinition":
8901
+ return node.value;
8902
+ case "VariableDeclaration": {
8903
+ const init = node.declarations.length === 1 ? node.declarations[0]?.init : null;
8904
+ return init?.type === "ArrowFunctionExpression" || init?.type === "FunctionExpression" ? init : null;
8905
+ }
8906
+ default:
8907
+ return null;
8908
+ }
8835
8909
  }
8836
8910
  function typedTags(text) {
8837
8911
  const tags = [];
@@ -8845,26 +8919,37 @@ function typedTags(text) {
8845
8919
  }
8846
8920
  }
8847
8921
  return tags.map(({ kind, payload }) => {
8848
- let rest = payload.replace(/^\{[^}\n]+\}\s*/u, "").trim();
8922
+ const typeMatch = /^\{([^}\n]+)\}\s*/u.exec(payload);
8923
+ const explicitType = typeMatch?.[1]?.trim() ?? null;
8924
+ let rest = payload.slice(typeMatch?.[0].length ?? 0).trim();
8849
8925
  if (!PARAM_TAGS2.has(kind)) {
8850
- return { kind, name: null, description: rest.replace(/^-\s*/u, "").trim() };
8926
+ return { kind, name: null, description: rest.replace(/^-\s+/u, "").trim(), explicitType };
8851
8927
  }
8852
- const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s*|\s+)?(.*)$/u.exec(rest);
8853
- if (match === null) return { kind, name: null, description: "" };
8854
- const rawName = (match[1] ?? "").replace(/^\[/u, "").replace(/\]$/u, "").split("=")[0] ?? "";
8928
+ const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s+|\s+)?(.*)$/u.exec(rest);
8929
+ const rawName = match?.[1] ?? "";
8930
+ if (match === null || !/^[A-Za-z_$][\w$]*$/u.test(rawName)) return { kind, name: null, description: rest, explicitType };
8855
8931
  rest = (match[2] ?? "").trim();
8856
- return { kind, name: rawName, description: rest };
8932
+ return { kind, name: rawName, description: rest, explicitType };
8857
8933
  });
8858
8934
  }
8859
- function isVacuousTag(tag) {
8935
+ function isVacuousTag(tag, signature, sourceCode) {
8936
+ let annotation = signature.returnType;
8937
+ if (PARAM_TAGS2.has(tag.kind)) {
8938
+ const parameter = signature.params.map(parameterTarget).find((node) => node.type === "Identifier" && node.name === tag.name);
8939
+ if (parameter?.type !== "Identifier") return false;
8940
+ annotation = parameter.typeAnnotation;
8941
+ }
8942
+ if (annotation === void 0) return false;
8943
+ if (tag.explicitType !== null && (!/^(?:string|number|boolean|bigint|symbol|unknown|never|void|null|undefined)$/u.test(tag.explicitType) || sourceCode.getText(annotation.typeAnnotation) !== tag.explicitType)) return false;
8944
+ if (/[^\p{L}\p{M}\s.,]/u.test(tag.description)) return false;
8860
8945
  const description = words(tag.description).map(canonicalWord);
8861
- if (description.length === 0) return true;
8946
+ if (description.length === 0) return tag.description.length === 0;
8862
8947
  if (tag.name === null) return description.every((word) => RESULT_FILLER.has(word));
8863
8948
  const nameWords2 = new Set(words(tag.name).map(canonicalWord));
8864
8949
  return description.every((word) => PARAMETER_FILLER.has(word) || nameWords2.has(word));
8865
8950
  }
8866
8951
  function words(text) {
8867
- return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[a-z][a-z0-9]*/gu) ?? [];
8952
+ return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[\p{L}\p{M}][\p{L}\p{M}\p{N}]*/gu) ?? [];
8868
8953
  }
8869
8954
  function canonicalWord(word) {
8870
8955
  if (["identifier", "identifiers", "ids"].includes(word)) return "id";
@@ -8876,7 +8961,7 @@ var NO_TYPED_DOC_SECTIONS_DOCUMENTATION = {
8876
8961
  rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
8877
8962
  remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
8878
8963
  category: "maintainability",
8879
- limitations: ["Description-free or name-restating parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
8964
+ limitations: ["Description-free or name-restating tags require the adjacent explicitly typed signature and a corresponding parameter name. Optional/defaulted or nested parameter tags and unproven explicit JSDoc types are preserved."],
8880
8965
  examples: [
8881
8966
  {
8882
8967
  id: "behavioral-documentation",
@@ -8914,7 +8999,8 @@ var no_typed_doc_sections_default = createRule({
8914
8999
  return {
8915
9000
  Program() {
8916
9001
  for (const group of proseGroups(context.filename, context.sourceCode, true)) {
8917
- if (group.hasTypedTags && hasVacuousTypedTag(group.text) && documentsTypedFunction(context.sourceCode, group.comment)) {
9002
+ const signature = documentedSignature(context.sourceCode, group.comment);
9003
+ if (group.hasTypedTags && signature !== null && typedTags(group.text).some((tag) => isVacuousTag(tag, signature, context.sourceCode))) {
8918
9004
  context.report({ node: group.comment, messageId: "typedSection" });
8919
9005
  }
8920
9006
  }
@@ -8924,7 +9010,7 @@ var no_typed_doc_sections_default = createRule({
8924
9010
  });
8925
9011
 
8926
9012
  // src/rules/no-trailing-value-narration.ts
8927
- import "@typescript-eslint/utils";
9013
+ import { AST_TOKEN_TYPES as AST_TOKEN_TYPES2 } from "@typescript-eslint/utils";
8928
9014
  var NO_TRAILING_VALUE_NARRATION_DOCUMENTATION = {
8929
9015
  summary: "Flag a trailing comment that repeats the line's numeric value only to name its unit.",
8930
9016
  rationale: "A repeated value can disagree with the expression after either the code or comment changes.",
@@ -8932,7 +9018,7 @@ var NO_TRAILING_VALUE_NARRATION_DOCUMENTATION = {
8932
9018
  category: "maintainability",
8933
9019
  autofix: "suggestion",
8934
9020
  aliases: ["trailing-value-narration"],
8935
- limitations: ["Only trailing comments with numeric values and recognized unit words are inspected."],
9021
+ limitations: ["Only attached declaration, property, or assignment values containing numeric tokens and comments with recognized unit words are inspected. Deletion requires a numeric literal and the same unit on its owner. Constraints, additional prose, unknown expressions on unit-bearing owners, and cross-unit annotations are preserved; conversions are not evaluated."],
8936
9022
  examples: [
8937
9023
  {
8938
9024
  id: "explain-constraint",
@@ -8955,7 +9041,7 @@ var NO_TRAILING_VALUE_NARRATION_DOCUMENTATION = {
8955
9041
  ]
8956
9042
  };
8957
9043
  var NUMBER_RE = /(?<![\w.])(\d+(?:\.\d+)?)(?![\w.])/g;
8958
- var WORD_RE3 = /[A-Za-z]+(?:'[a-z]+)?|\d+(?:\.\d+)?/g;
9044
+ var WORD_RE3 = /[\p{L}\p{M}]+(?:'[\p{L}\p{M}]+)?|\d+(?:\.\d+)?/gu;
8959
9045
  var UNIT_WORDS = /* @__PURE__ */ new Set([
8960
9046
  "bytes",
8961
9047
  "characters",
@@ -9017,9 +9103,9 @@ var STOPWORDS3 = /* @__PURE__ */ new Set([
9017
9103
  ]);
9018
9104
  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;
9019
9105
  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;
9020
- function narratesValue(body2, code) {
9106
+ function narratesValue(body2, code, codeNumbers) {
9021
9107
  if (body2.length === 0 || DIRECTIVE_RE5.test(body2) || hasExternalReference(body2)) return false;
9022
- const codeNumbers = numbersIn(code);
9108
+ if (/[^\p{L}\p{M}\p{N}\s.,:()_]/u.test(body2)) return false;
9023
9109
  if (codeNumbers.size === 0) return false;
9024
9110
  const words2 = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
9025
9111
  if (words2.length === 0) return false;
@@ -9039,8 +9125,36 @@ function narratesValue(body2, code) {
9039
9125
  function numbersIn(text) {
9040
9126
  return new Set(text.match(NUMBER_RE) ?? []);
9041
9127
  }
9042
- function nameAlreadyCarriesUnit(code) {
9043
- return (code.match(/[A-Za-z_$][\w$]*/gu) ?? []).some((identifier) => UNIT_NAME_SUFFIX_RE.test(identifier));
9128
+ function canonicalUnit(word) {
9129
+ switch (word) {
9130
+ case "milliseconds":
9131
+ return "ms";
9132
+ case "sec":
9133
+ case "secs":
9134
+ case "second":
9135
+ case "seconds":
9136
+ return "s";
9137
+ case "mins":
9138
+ case "minute":
9139
+ case "minutes":
9140
+ return "min";
9141
+ case "hr":
9142
+ case "hrs":
9143
+ case "hours":
9144
+ return "hour";
9145
+ case "days":
9146
+ return "day";
9147
+ case "bytes":
9148
+ return "byte";
9149
+ default:
9150
+ return word;
9151
+ }
9152
+ }
9153
+ function identifierUnit(node) {
9154
+ if (node.type === "MemberExpression" && !node.computed) return identifierUnit(node.property);
9155
+ if (node.type !== "Identifier") return null;
9156
+ const suffix = UNIT_NAME_SUFFIX_RE.exec(node.name)?.[0];
9157
+ return suffix === void 0 ? null : canonicalUnit(suffix.replace(/^_/u, "").toLowerCase());
9044
9158
  }
9045
9159
  var no_trailing_value_narration_default = createRule({
9046
9160
  name: "no-trailing-value-narration",
@@ -9054,7 +9168,7 @@ var no_trailing_value_narration_default = createRule({
9054
9168
  schema: [],
9055
9169
  messages: {
9056
9170
  deleteNarration: "Trailing comment restates the literal and the identifier already names its unit \u2014 delete the comment so it cannot drift.",
9057
- narratesValue: "Trailing comment restates the literal on this line \u2014 put the unit in the name (STALE_TIME_MS) so it cannot drift.",
9171
+ narratesValue: "Consider putting the unit in the name if this comment only narrates the value; keep conversion details and constraints.",
9058
9172
  removeNarration: "Delete the redundant trailing narration."
9059
9173
  }
9060
9174
  },
@@ -9079,15 +9193,46 @@ var no_trailing_value_narration_default = createRule({
9079
9193
  }
9080
9194
  return false;
9081
9195
  }
9196
+ function attachedValue(comment) {
9197
+ let token = sourceCode.getTokenBefore(comment);
9198
+ if (token?.value === ";" || token?.value === ",") token = sourceCode.getTokenBefore(token);
9199
+ if (token === null) return null;
9200
+ let node = sourceCode.getNodeByRangeIndex(token.range[0]);
9201
+ while (node !== null && node.type !== "Program") {
9202
+ if (node.range[1] <= comment.range[0]) {
9203
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier" && node.init !== null) {
9204
+ return { name: node.id, value: node.init };
9205
+ }
9206
+ if (node.type === "Property" && !node.computed && node.kind === "init" && node.parent.type === "ObjectExpression") {
9207
+ return { name: node.key, value: node.value };
9208
+ }
9209
+ if (node.type === "PropertyDefinition" && !node.computed && node.value !== null) {
9210
+ return { name: node.key, value: node.value };
9211
+ }
9212
+ if (node.type === "AssignmentExpression" && node.operator === "=") {
9213
+ if (node.left.type !== "Identifier" && (node.left.type !== "MemberExpression" || node.left.computed)) return null;
9214
+ return { name: node.left, value: node.right };
9215
+ }
9216
+ }
9217
+ node = node.parent ?? null;
9218
+ }
9219
+ return null;
9220
+ }
9082
9221
  return {
9083
9222
  Program() {
9084
9223
  for (const comment of sourceCode.getAllComments()) {
9085
9224
  if (!isTrailing(comment) || isInsideBrackets(comment)) continue;
9086
- const line = sourceCode.lines[comment.loc.start.line - 1] ?? "";
9087
- const code = line.slice(0, comment.loc.start.column);
9225
+ const attached = attachedValue(comment);
9226
+ if (attached === null) continue;
9227
+ const code = `${sourceCode.getText(attached.name)} ${sourceCode.getText(attached.value)}`;
9228
+ const codeNumbers = new Set(sourceCode.getTokens(attached.value).filter((token) => token.type === AST_TOKEN_TYPES2.Numeric).flatMap((token) => [...numbersIn(token.value)]));
9088
9229
  const body2 = comment.value.replace(/^\*+/, "").replace(/\*+$/, "").trim();
9089
- if (narratesValue(body2, code)) {
9090
- const canDelete = nameAlreadyCarriesUnit(code);
9230
+ if (narratesValue(body2, code, codeNumbers)) {
9231
+ const namedUnit = identifierUnit(attached.name);
9232
+ if (namedUnit !== null && (attached.value.type !== "Literal" || typeof attached.value.value !== "number")) continue;
9233
+ const units = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase()).filter((word) => UNIT_WORDS.has(word));
9234
+ if (namedUnit !== null && !units.every((word) => canonicalUnit(word) === namedUnit)) continue;
9235
+ const canDelete = namedUnit !== null;
9091
9236
  const removal = canDelete ? trailingCommentRemovalRange(sourceCode.text, comment) : null;
9092
9237
  context.report({
9093
9238
  node: comment,
@@ -9107,10 +9252,10 @@ var no_trailing_value_narration_default = createRule({
9107
9252
  });
9108
9253
 
9109
9254
  // src/rules/no-declaration-comment-wall.ts
9110
- import { AST_NODE_TYPES as AST_NODE_TYPES36, AST_TOKEN_TYPES as AST_TOKEN_TYPES3 } from "@typescript-eslint/utils";
9255
+ import { AST_NODE_TYPES as AST_NODE_TYPES36, AST_TOKEN_TYPES as AST_TOKEN_TYPES4 } from "@typescript-eslint/utils";
9111
9256
 
9112
9257
  // src/rules/_comment-wall.ts
9113
- import { AST_NODE_TYPES as AST_NODE_TYPES35, AST_TOKEN_TYPES as AST_TOKEN_TYPES2 } from "@typescript-eslint/utils";
9258
+ import { AST_NODE_TYPES as AST_NODE_TYPES35, AST_TOKEN_TYPES as AST_TOKEN_TYPES3 } from "@typescript-eslint/utils";
9114
9259
  var WALL_DEFAULTS = {
9115
9260
  // Below three rows "a wall" is not a fair description of what the reader sees.
9116
9261
  minCommentedMembers: 3,
@@ -9176,7 +9321,7 @@ function commentBody(comment) {
9176
9321
  return comment.value.replace(/^\*+/, "").replace(/^[ \t]*\*[ \t]?/gm, "").trim();
9177
9322
  }
9178
9323
  function hasJsDocTag(comment) {
9179
- return comment.type === AST_TOKEN_TYPES2.Block && comment.value.startsWith("*") && /(?:^|\s)@[A-Za-z][\w-]*\b/u.test(commentBody(comment));
9324
+ return comment.type === AST_TOKEN_TYPES3.Block && comment.value.startsWith("*") && /(?:^|\s)@[A-Za-z][\w-]*\b/u.test(commentBody(comment));
9180
9325
  }
9181
9326
  function carriesValue(body2) {
9182
9327
  return isProtected(body2) || VALUE_TAG_RE2.test(body2) || DEFAULT_RE.test(body2) || DIGIT_RE.test(body2) || UNIT_WORD_RE.test(body2) || EXAMPLE_RE.test(body2) || BANNER_RE.test(body2) || NON_ASCII_LETTER_RE2.test(body2);
@@ -9312,7 +9457,7 @@ var no_declaration_comment_wall_default = createRule({
9312
9457
  const before = sourceCode.getTokenBefore(lead, { includeComments: false });
9313
9458
  if (before === null || before.loc.end.line < lead.loc.start.line) {
9314
9459
  const previousLine = endingOn.get(lead.loc.start.line - 1);
9315
- if (lead.type === AST_TOKEN_TYPES3.Line && previousLine?.type === AST_TOKEN_TYPES3.Line && previousLine.loc.start.column === lead.loc.start.column) {
9460
+ if (lead.type === AST_TOKEN_TYPES4.Line && previousLine?.type === AST_TOKEN_TYPES4.Line && previousLine.loc.start.column === lead.loc.start.column) {
9316
9461
  return void 0;
9317
9462
  }
9318
9463
  return lead;
@@ -9382,10 +9527,10 @@ var no_declaration_comment_wall_default = createRule({
9382
9527
  import { AST_NODE_TYPES as AST_NODE_TYPES37 } from "@typescript-eslint/utils";
9383
9528
  var NO_UNION_IN_COMMENT_DOCUMENTATION = {
9384
9529
  summary: "Flag a comment that lists a `string` field's allowed values instead of the type listing them.",
9385
- rationale: "A comment cannot prevent callers from supplying strings outside the listed set, and the list can drift from runtime behavior.",
9386
- remediation: "Move the allowed values into a string-literal union and remove the redundant comment.",
9387
- category: "correctness",
9388
- limitations: ["Only bare quoted-value lists attached to supported string declarations and schema-builder fields are inspected."],
9530
+ rationale: "A broad string annotation does not express a closed set documented beside it.",
9531
+ remediation: "If the list is exhaustive, express it as a string-literal union; keep examples and runtime constraints documented separately.",
9532
+ category: "maintainability",
9533
+ limitations: ["Only bare quoted-value lists directly attached to explicitly annotated string declarations are inspected. Unknown schema builders and runtime validation are not inferred."],
9389
9534
  examples: [
9390
9535
  {
9391
9536
  id: "literal-union",
@@ -9412,16 +9557,6 @@ var LITERAL = String.raw`(?:'[^'\n]*'|"[^"\n]*"|\`[^\`\n]*\`)`;
9412
9557
  var LEAD_IN_RE2 = /^(?:one of|either|values?|allowed(?: values)?|options?|possible(?: values)?)\s*[:=-]?\s*/i;
9413
9558
  var UNION_BODY_RE = new RegExp(String.raw`^${LITERAL}(?:\s*[|,/]\s*${LITERAL})+\.?$`);
9414
9559
  var LITERAL_G = new RegExp(LITERAL, "g");
9415
- var STRING_BUILDERS = /* @__PURE__ */ new Set([
9416
- "char",
9417
- "citext",
9418
- "longtext",
9419
- "mediumtext",
9420
- "string",
9421
- "text",
9422
- "tinytext",
9423
- "varchar"
9424
- ]);
9425
9560
  function isBareString(node) {
9426
9561
  if (node === void 0) return false;
9427
9562
  switch (node.type) {
@@ -9446,12 +9581,6 @@ function targetOf(node) {
9446
9581
  if (name === null || !isBareString(node.typeAnnotation?.typeAnnotation)) return null;
9447
9582
  return { node, name };
9448
9583
  }
9449
- case AST_NODE_TYPES37.Property: {
9450
- const name = node.computed || node.shorthand ? null : nameOf(node.key);
9451
- const callee = rootCallee(node.value);
9452
- if (name === null || callee === null || !STRING_BUILDERS.has(callee)) return null;
9453
- return { node, name };
9454
- }
9455
9584
  case AST_NODE_TYPES37.VariableDeclarator: {
9456
9585
  if (node.id.type !== AST_NODE_TYPES37.Identifier) return null;
9457
9586
  if (!isBareString(node.id.typeAnnotation?.typeAnnotation)) return null;
@@ -9461,24 +9590,6 @@ function targetOf(node) {
9461
9590
  return null;
9462
9591
  }
9463
9592
  }
9464
- function rootCallee(node) {
9465
- let current = node;
9466
- for (let hops = 0; current != null && hops < 12; hops += 1) {
9467
- switch (current.type) {
9468
- case AST_NODE_TYPES37.CallExpression:
9469
- current = current.callee;
9470
- break;
9471
- case AST_NODE_TYPES37.MemberExpression:
9472
- current = current.object;
9473
- break;
9474
- case AST_NODE_TYPES37.Identifier:
9475
- return current.name;
9476
- default:
9477
- return null;
9478
- }
9479
- }
9480
- return null;
9481
- }
9482
9593
  function nameOf(key) {
9483
9594
  if (key.type === AST_NODE_TYPES37.Identifier) return key.name;
9484
9595
  if (key.type === AST_NODE_TYPES37.Literal && typeof key.value === "string") return key.value;
@@ -9503,7 +9614,7 @@ var no_union_in_comment_default = createRule({
9503
9614
  },
9504
9615
  schema: [],
9505
9616
  messages: {
9506
- unionInComment: 'This comment is a type \u2014 `{{name}}` still accepts every string, so the set it lists is enforced by nobody. Make it a string-literal union ("{{first}}" | \u2026) and delete the comment.'
9617
+ unionInComment: 'The annotation for `{{name}}` accepts arbitrary strings. If this list is exhaustive, express it as a string-literal union ("{{first}}" | \u2026); retain separate runtime constraints or examples.'
9507
9618
  }
9508
9619
  },
9509
9620
  defaultOptions: [],
@@ -9528,7 +9639,11 @@ var no_union_in_comment_default = createRule({
9528
9639
  }
9529
9640
  for (let node = anchor; node != null && node.type !== AST_NODE_TYPES37.Program; node = node.parent) {
9530
9641
  const target = targetOf(node);
9531
- if (target !== null) return target;
9642
+ if (target !== null) {
9643
+ const follows = target.node.range[1] <= comment.range[0] && target.node.loc.end.line === comment.loc.start.line;
9644
+ const precedes = comment.range[1] <= target.node.range[0] && comment.loc.end.line + 1 === target.node.loc.start.line;
9645
+ return follows || precedes ? target : null;
9646
+ }
9532
9647
  }
9533
9648
  return null;
9534
9649
  }
@@ -9541,8 +9656,6 @@ var no_union_in_comment_default = createRule({
9541
9656
  if (literals === null) continue;
9542
9657
  const target = annotated(comment);
9543
9658
  if (target === null) continue;
9544
- const declaration = sourceCode.getText(target.node);
9545
- if (literals.every((literal) => declaration.includes(literal))) continue;
9546
9659
  context.report({
9547
9660
  node: comment,
9548
9661
  messageId: "unionInComment",
@@ -9555,13 +9668,14 @@ var no_union_in_comment_default = createRule({
9555
9668
  });
9556
9669
 
9557
9670
  // src/rules/no-type-member-comment-wall.ts
9558
- import { AST_NODE_TYPES as AST_NODE_TYPES38, AST_TOKEN_TYPES as AST_TOKEN_TYPES4 } from "@typescript-eslint/utils";
9671
+ import { AST_NODE_TYPES as AST_NODE_TYPES38, AST_TOKEN_TYPES as AST_TOKEN_TYPES5 } from "@typescript-eslint/utils";
9672
+ var BEHAVIORAL_RELATION_RE = /\b(?:not|no|never|only|must|shall|should|may|can|could|will|would|required|optional|if|unless|when|before|after|until|while|without|instead|true|false|null|undefined)\b|[<>=!]/iu;
9559
9673
  var NO_TYPE_MEMBER_COMMENT_WALL_DOCUMENTATION = {
9560
9674
  summary: "Flag an object type whose member comments mostly re-spell the members' own names and types.",
9561
9675
  rationale: "Repetitive member comments add scanning cost while hiding the comments that describe facts absent from the type.",
9562
9676
  remediation: "Delete comments that restate member names or types and keep comments that add constraints or behavior.",
9563
9677
  category: "maintainability",
9564
- limitations: ["Only interface and type-literal bodies meeting the configured comment-count and restatement-ratio thresholds are reported."],
9678
+ limitations: ["Only interface and type-literal bodies meeting the configured comment-count and restatement-ratio thresholds are reported. Negation, requirements, conditional relations, and fixed-value contracts are not counted as restatements. The novel-word threshold is a review heuristic, not proof that a comment contains no useful contract."],
9565
9679
  examples: [
9566
9680
  {
9567
9681
  id: "uncommented-members",
@@ -9596,7 +9710,7 @@ var no_type_member_comment_wall_default = createRule({
9596
9710
  },
9597
9711
  schema: [WALL_SCHEMA],
9598
9712
  messages: {
9599
- commentWall: "{{restated}} of this type's {{commented}} member comments only re-spell names and types \u2014 delete them; if a row still needs narration, improve its name or type. Keep constraints and rationale."
9713
+ commentWall: "{{restated}} of this type's {{commented}} member comments appear to repeat names and types \u2014 review them for removal or clearer naming. Keep constraints and rationale."
9600
9714
  }
9601
9715
  },
9602
9716
  defaultOptions: [WALL_DEFAULTS],
@@ -9620,7 +9734,7 @@ var no_type_member_comment_wall_default = createRule({
9620
9734
  const before = sourceCode.getTokenBefore(lead, { includeComments: false });
9621
9735
  if (before === null || before.loc.end.line < lead.loc.start.line) {
9622
9736
  const previousLine = endingOn.get(lead.loc.start.line - 1);
9623
- if (lead.type === AST_TOKEN_TYPES4.Line && previousLine?.type === AST_TOKEN_TYPES4.Line && previousLine.loc.start.column === lead.loc.start.column) {
9737
+ if (lead.type === AST_TOKEN_TYPES5.Line && previousLine?.type === AST_TOKEN_TYPES5.Line && previousLine.loc.start.column === lead.loc.start.column) {
9624
9738
  return void 0;
9625
9739
  }
9626
9740
  return lead;
@@ -9654,7 +9768,7 @@ var no_type_member_comment_wall_default = createRule({
9654
9768
  claimed.add(comment);
9655
9769
  commented += 1;
9656
9770
  const body2 = commentBody(comment);
9657
- if (body2.length === 0 || hasJsDocTag(comment) || carriesValue(body2) || isTagsOnly(body2)) {
9771
+ if (body2.length === 0 || hasJsDocTag(comment) || carriesValue(body2) || isTagsOnly(body2) || BEHAVIORAL_RELATION_RE.test(body2)) {
9658
9772
  continue;
9659
9773
  }
9660
9774
  if (novelWords(body2, knownTokens(sourceCode.getText(member))) <= options.maxNovelWords) {
@@ -9684,9 +9798,9 @@ import { AST_NODE_TYPES as AST_NODE_TYPES39 } from "@typescript-eslint/utils";
9684
9798
  var NO_UNNECESSARY_USE_CLIENT_DOCUMENTATION = {
9685
9799
  summary: "Flag `'use client'` files with no hooks or event handlers \u2014 they could be RSC.",
9686
9800
  rationale: "An unnecessary client boundary sends the component and its transitive dependencies to the browser without using client-only behavior.",
9687
- remediation: "Remove the directive, or keep it only when the module uses a supported client-side API or boundary dependency.",
9801
+ remediation: "Review whether the directive can be removed after checking transitive client requirements and intended export boundaries; local syntax alone does not prove server compatibility.",
9688
9802
  category: "performance",
9689
- limitations: ["Client need is inferred from recognized hooks, handlers, browser globals, exports, classes, and known client-only imports."],
9803
+ limitations: ["Client need is inferred from recognized hooks, handlers, browser globals, exports, classes, and known client-only imports. Unknown side-effect imports preserve the boundary; arbitrary transitive runtime requirements are not inspected."],
9690
9804
  examples: [
9691
9805
  {
9692
9806
  id: "interactive-component",
@@ -9766,7 +9880,7 @@ var subtreeReadsImportedBinding = (node, imported) => {
9766
9880
  return false;
9767
9881
  };
9768
9882
  var isUseClientDirective = (node) => {
9769
- return node.type === AST_NODE_TYPES39.ExpressionStatement && node.expression.type === AST_NODE_TYPES39.Literal && node.expression.value === "use client";
9883
+ return node.type === AST_NODE_TYPES39.ExpressionStatement && node.directive === "use client";
9770
9884
  };
9771
9885
  var isGlobalReference = (node, context) => {
9772
9886
  if (!BROWSER_GLOBALS.has(node.name)) return false;
@@ -9832,7 +9946,7 @@ var no_unnecessary_use_client_default = createRule({
9832
9946
  return {
9833
9947
  Program(node) {
9834
9948
  for (const stmt of node.body) {
9835
- if (stmt.type !== AST_NODE_TYPES39.ExpressionStatement) break;
9949
+ if (stmt.type !== AST_NODE_TYPES39.ExpressionStatement || stmt.directive === void 0) break;
9836
9950
  if (isUseClientDirective(stmt)) {
9837
9951
  directiveNode = stmt;
9838
9952
  break;
@@ -9853,6 +9967,7 @@ var no_unnecessary_use_client_default = createRule({
9853
9967
  if (directiveNode === null) return;
9854
9968
  if (typeof node.source.value !== "string") return;
9855
9969
  const source = node.source.value;
9970
+ if (node.importKind !== "type" && node.specifiers.length === 0) hasClientIndicator = true;
9856
9971
  if (CLIENT_ONLY_PACKAGES_REGEX.test(source) || CLIENT_REQUIRED_MODULES.has(source)) {
9857
9972
  hasClientIndicator = true;
9858
9973
  }
@@ -9922,7 +10037,7 @@ var no_unnecessary_use_client_default = createRule({
9922
10037
  // src/rules/no-unsafe-mock-casting.ts
9923
10038
  import {
9924
10039
  AST_NODE_TYPES as AST_NODE_TYPES40,
9925
- ASTUtils as ASTUtils10
10040
+ ASTUtils as ASTUtils13
9926
10041
  } from "@typescript-eslint/utils";
9927
10042
  var MOCK_TYPE_NAMES = /* @__PURE__ */ new Set([
9928
10043
  "Mock",
@@ -9988,7 +10103,7 @@ var no_unsafe_mock_casting_default = createRule({
9988
10103
  const directBindings = /* @__PURE__ */ new Set();
9989
10104
  const namespaceBindings = /* @__PURE__ */ new Set();
9990
10105
  function resolve2(identifier) {
9991
- return ASTUtils10.findVariable(
10106
+ return ASTUtils13.findVariable(
9992
10107
  context.sourceCode.getScope(identifier),
9993
10108
  identifier.name
9994
10109
  );
@@ -10038,7 +10153,7 @@ var no_unsafe_mock_casting_default = createRule({
10038
10153
  import {
10039
10154
  ESLintUtils as ESLintUtils3,
10040
10155
  AST_NODE_TYPES as AST_NODE_TYPES41,
10041
- ASTUtils as ASTUtils11
10156
+ ASTUtils as ASTUtils14
10042
10157
  } from "@typescript-eslint/utils";
10043
10158
  import * as ts2 from "typescript";
10044
10159
  var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
@@ -10084,9 +10199,9 @@ function isIgnoredFile(filename, sourceText) {
10084
10199
  function isZodModule2(source) {
10085
10200
  return /(^|[/@-])zod([/-]|$)/.test(source);
10086
10201
  }
10087
- function unwrap2(node) {
10202
+ function unwrap3(node) {
10088
10203
  if (node.type === AST_NODE_TYPES41.TSAsExpression || node.type === AST_NODE_TYPES41.TSSatisfiesExpression) {
10089
- return unwrap2(node.expression);
10204
+ return unwrap3(node.expression);
10090
10205
  }
10091
10206
  return node;
10092
10207
  }
@@ -10148,7 +10263,7 @@ var no_zod_native_enum_default = createRule({
10148
10263
  const zodImportedBindings = /* @__PURE__ */ new Map();
10149
10264
  const zodNamespaceBindings = /* @__PURE__ */ new Set();
10150
10265
  function resolvedBinding(identifier) {
10151
- return ASTUtils11.findVariable(
10266
+ return ASTUtils14.findVariable(
10152
10267
  sourceCode.getScope(identifier),
10153
10268
  identifier.name
10154
10269
  );
@@ -10198,7 +10313,7 @@ var no_zod_native_enum_default = createRule({
10198
10313
  if (argument === void 0 || argument.type === AST_NODE_TYPES41.SpreadElement) {
10199
10314
  return;
10200
10315
  }
10201
- const arg = unwrap2(argument);
10316
+ const arg = unwrap3(argument);
10202
10317
  if (arg.type !== AST_NODE_TYPES41.Identifier) return;
10203
10318
  const isEnum = resolvesToLocalEnum(arg, sourceCode.getScope(arg)) || services !== null && resolvesToImportedEnum(arg, services);
10204
10319
  if (isEnum) {
@@ -10214,7 +10329,7 @@ var no_zod_native_enum_default = createRule({
10214
10329
  });
10215
10330
 
10216
10331
  // src/rules/test-loops-over-literal-cases.ts
10217
- import { AST_NODE_TYPES as AST_NODE_TYPES42, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
10332
+ import { AST_NODE_TYPES as AST_NODE_TYPES42, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
10218
10333
  var TEST_LOOPS_OVER_LITERAL_CASES_DOCUMENTATION = {
10219
10334
  summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
10220
10335
  rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
@@ -10377,7 +10492,7 @@ var test_loops_over_literal_cases_default = createRule({
10377
10492
  return {};
10378
10493
  }
10379
10494
  const isFrameworkIdentifier = (identifier, modules) => {
10380
- const variable = ASTUtils12.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10495
+ const variable = ASTUtils15.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10381
10496
  if (variable === null || variable.defs.length === 0) return true;
10382
10497
  return variable.defs.some((definition) => {
10383
10498
  let current = definition.node;
@@ -10511,17 +10626,18 @@ var test_phase_label_comment_default = createRule({
10511
10626
  // src/rules/prefer-constant-time-secret-compare.ts
10512
10627
  import { AST_NODE_TYPES as AST_NODE_TYPES44 } from "@typescript-eslint/utils";
10513
10628
  var PREFER_CONSTANT_TIME_SECRET_COMPARE_DOCUMENTATION = {
10514
- summary: "Disallow `===`/`!==` on a secret-like value; short-circuiting comparison leaks the secret through timing. Use a constant-time compare.",
10515
- rationale: "Ordinary equality stops at the first differing byte, allowing repeated measurements to reveal secret material.",
10629
+ summary: "Prefer a supported constant-time comparison primitive for secret-like values.",
10630
+ rationale: "Ordinary equality offers no constant-time guarantee for comparing secrets.",
10516
10631
  remediation: "Compare equal-length cryptographic digests with a constant-time comparison primitive.",
10517
10632
  category: "security",
10518
- limitations: ["Secret-like values are identified conservatively from their names; test files and public sentinel comparisons are excluded."],
10633
+ limitations: ["This is name-based analysis, not proof of runtime sensitivity. Ambiguous token names require an authentication or cryptographic qualifier; test files and public sentinel comparisons are excluded."],
10519
10634
  examples: [
10520
- { id: "constant-time-compare", title: "Use a constant-time comparison", outcome: "no-match", files: [{ path: "src/auth.ts", source: "if (await constantTimeEqual(presentedToken, expectedToken)) { allow(); }" }], focusPath: "src/auth.ts", expectedCount: 0, public: true },
10521
- { id: "secret-equality", title: "Do not compare secrets with equality", outcome: "match", files: [{ path: "src/auth.ts", source: "if (presentedToken === expectedToken) { allow(); }" }], focusPath: "src/auth.ts", expectedCount: 1, public: true }
10635
+ { id: "constant-time-compare", title: "Use a constant-time comparison", outcome: "no-match", files: [{ path: "src/auth.ts", source: "if (await constantTimeEqual(presentedAccessToken, expectedAccessToken)) { allow(); }" }], focusPath: "src/auth.ts", expectedCount: 0, public: true },
10636
+ { id: "secret-equality", title: "Do not compare authentication tokens with equality", outcome: "match", files: [{ path: "src/auth.ts", source: "if (presentedAccessToken === expectedAccessToken) { allow(); }" }], focusPath: "src/auth.ts", expectedCount: 1, public: true }
10522
10637
  ]
10523
10638
  };
10524
10639
  var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
10640
+ var AUTH_TOKEN_QUALIFIERS = /* @__PURE__ */ new Set(["access", "refresh", "session", "admin", "csrf", "xsrf", "auth", "authentication", "signing", "api"]);
10525
10641
  var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
10526
10642
  var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
10527
10643
  var SENTINEL_PREFIX_RE = /^(skip|sentinel|empty|none|missing|unset|placeholder|dummy|fake|example|noop)[A-Z]/;
@@ -10559,7 +10675,9 @@ function isSecretOperand(node) {
10559
10675
  return node.expressions.some((expression) => isSecretOperand(expression));
10560
10676
  }
10561
10677
  const name = operandName(node);
10562
- return name !== null && isAuthSecretName(name);
10678
+ if (name === null || !isAuthSecretName(name)) return false;
10679
+ const words2 = tokenize(name);
10680
+ return !words2.includes("token") || words2.some((word) => AUTH_TOKEN_QUALIFIERS.has(word) || word !== "token" && SECRET_WORDS.has(word));
10563
10681
  }
10564
10682
  function secretNameOf(node) {
10565
10683
  if (node.type === AST_NODE_TYPES44.TemplateLiteral) {
@@ -10579,11 +10697,11 @@ var prefer_constant_time_secret_compare_default = createRule({
10579
10697
  meta: {
10580
10698
  type: "problem",
10581
10699
  docs: {
10582
- description: "Disallow `===`/`!==` on a secret-like value; short-circuiting comparison leaks the secret through timing. Use a constant-time compare."
10700
+ description: "Prefer a supported constant-time comparison primitive for secret-like values."
10583
10701
  },
10584
10702
  schema: [],
10585
10703
  messages: {
10586
- preferConstantTimeSecretCompare: "`{{operator}}` on secret `{{name}}` short-circuits on the first differing byte and leaks it through timing. Compare constant-time instead (`crypto.subtle.timingSafeEqual` over equal-length SHA-256 digests)."
10704
+ preferConstantTimeSecretCompare: "`{{operator}}` on secret-like `{{name}}` is not guaranteed constant-time. Use a constant-time comparison primitive supported by the target runtime and handle its input-length requirements."
10587
10705
  }
10588
10706
  },
10589
10707
  defaultOptions: [],
@@ -10869,13 +10987,14 @@ var prefer_discriminated_union_default = createRule({
10869
10987
  });
10870
10988
 
10871
10989
  // src/rules/prefer-input-group-search.ts
10872
- import { AST_NODE_TYPES as AST_NODE_TYPES47 } from "@typescript-eslint/utils";
10990
+ import { AST_NODE_TYPES as AST_NODE_TYPES47, ASTUtils as ASTUtils16 } from "@typescript-eslint/utils";
10873
10991
  var PREFER_INPUT_GROUP_SEARCH_DOCUMENTATION = {
10874
10992
  summary: "Require search icons and shared Input controls in the same visual wrapper to use InputGroup.",
10875
10993
  rationale: "The shared compound control provides consistent spacing, focus behavior, and accessible composition.",
10876
10994
  remediation: "Compose the search icon and field with InputGroup, InputGroupAddon, and InputGroupInput.",
10877
10995
  category: "style",
10878
10996
  limitations: [
10997
+ "Opposite branches of the same conditional expression and icons with explicit interaction handlers are excluded; arbitrary component behavior is not inferred.",
10879
10998
  "Only Search and Input bindings imported from the recognized shared modules are paired.",
10880
10999
  "The file must import InputGroup, proving that the repository has adopted that optional primitive."
10881
11000
  ],
@@ -10927,10 +11046,26 @@ function nearestEligibleCommonAncestor(search, input, inputGroupNames) {
10927
11046
  return null;
10928
11047
  }
10929
11048
  function isActionIcon(search, wrapper) {
11049
+ if (hasInteraction(search.node)) return true;
10930
11050
  return jsxAncestors(search).some((ancestor) => {
10931
11051
  if (ancestor === wrapper) return false;
10932
11052
  const name = elementName(ancestor.openingElement);
10933
- return name === "a" || name === "button";
11053
+ return name === "a" || name === "button" || hasInteraction(ancestor.openingElement);
11054
+ });
11055
+ }
11056
+ function hasInteraction(node) {
11057
+ return node.attributes.some(
11058
+ (attribute) => attribute.type === AST_NODE_TYPES47.JSXAttribute && attribute.name.type === AST_NODE_TYPES47.JSXIdentifier && /^(?:on[A-Z]|href$)/u.test(attribute.name.name)
11059
+ );
11060
+ }
11061
+ function mutuallyExclusive(left, right) {
11062
+ return left.ancestors.some((ancestor, index) => {
11063
+ if (ancestor.type !== AST_NODE_TYPES47.ConditionalExpression) return false;
11064
+ const otherIndex = right.ancestors.indexOf(ancestor);
11065
+ if (otherIndex < 0) return false;
11066
+ const leftBranch = left.ancestors[index + 1];
11067
+ const rightBranch = right.ancestors[otherIndex + 1];
11068
+ return leftBranch === ancestor.consequent && rightBranch === ancestor.alternate || leftBranch === ancestor.alternate && rightBranch === ancestor.consequent;
10934
11069
  });
10935
11070
  }
10936
11071
  var prefer_input_group_search_default = createRule({
@@ -10955,6 +11090,7 @@ var prefer_input_group_search_default = createRule({
10955
11090
  const searches = [];
10956
11091
  return {
10957
11092
  ImportDeclaration(node) {
11093
+ if (node.importKind === "type") return;
10958
11094
  const source = String(node.source.value);
10959
11095
  if (source === "lucide-react") {
10960
11096
  for (const exported of SEARCH_EXPORTS) {
@@ -10975,6 +11111,8 @@ var prefer_input_group_search_default = createRule({
10975
11111
  JSXOpeningElement(node) {
10976
11112
  const name = elementName(node);
10977
11113
  if (name === null) return;
11114
+ const binding = ASTUtils16.findVariable(context.sourceCode.getScope(node), name);
11115
+ if (binding?.defs.length !== 1 || binding.defs[0]?.node.type !== AST_NODE_TYPES47.ImportSpecifier || binding.defs[0].node.importKind === "type") return;
10978
11116
  const occurrence = {
10979
11117
  ancestors: context.sourceCode.getAncestors(node),
10980
11118
  node
@@ -10988,6 +11126,7 @@ var prefer_input_group_search_default = createRule({
10988
11126
  for (const search of searches) {
10989
11127
  if (isWithinInputGroup(search, inputGroupNames)) continue;
10990
11128
  for (const input of inputs) {
11129
+ if (mutuallyExclusive(search, input)) continue;
10991
11130
  if (isWithinInputGroup(input, inputGroupNames)) continue;
10992
11131
  const wrapper = nearestEligibleCommonAncestor(
10993
11132
  search,
@@ -11012,7 +11151,7 @@ var prefer_input_group_search_default = createRule({
11012
11151
 
11013
11152
  // src/rules/prefer-millisecond-control-duration-schema.ts
11014
11153
  import {
11015
- ASTUtils as ASTUtils13,
11154
+ ASTUtils as ASTUtils17,
11016
11155
  AST_NODE_TYPES as AST_NODE_TYPES48
11017
11156
  } from "@typescript-eslint/utils";
11018
11157
  var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
@@ -11081,7 +11220,7 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11081
11220
  const zodNamespaces = /* @__PURE__ */ new Set();
11082
11221
  const objectFactories = /* @__PURE__ */ new Set();
11083
11222
  function binding(identifier) {
11084
- return ASTUtils13.findVariable(context.sourceCode.getScope(identifier), identifier.name);
11223
+ return ASTUtils17.findVariable(context.sourceCode.getScope(identifier), identifier.name);
11085
11224
  }
11086
11225
  function record(target, identifier) {
11087
11226
  const variable = binding(identifier);
@@ -11128,7 +11267,7 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11128
11267
  });
11129
11268
 
11130
11269
  // src/rules/prefer-immutable-module-constant.ts
11131
- import { AST_NODE_TYPES as AST_NODE_TYPES49, ASTUtils as ASTUtils14 } from "@typescript-eslint/utils";
11270
+ import { AST_NODE_TYPES as AST_NODE_TYPES49, ASTUtils as ASTUtils18 } from "@typescript-eslint/utils";
11132
11271
  var PREFER_IMMUTABLE_MODULE_CONSTANT_DOCUMENTATION = {
11133
11272
  summary: "Require module-level constant collections to expose readonly state.",
11134
11273
  rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
@@ -11281,7 +11420,7 @@ var prefer_immutable_module_constant_default = createRule({
11281
11420
  create(context) {
11282
11421
  const sourceCode = context.sourceCode;
11283
11422
  const isUnshadowedGlobal3 = (identifier) => {
11284
- const variable = ASTUtils14.findVariable(sourceCode.getScope(identifier), identifier.name);
11423
+ const variable = ASTUtils18.findVariable(sourceCode.getScope(identifier), identifier.name);
11285
11424
  return variable === null || variable.defs.length === 0;
11286
11425
  };
11287
11426
  if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
@@ -11381,6 +11520,7 @@ var PREFER_SHADCN_PRIMITIVES_DOCUMENTATION = {
11381
11520
  remediation: "Replace the raw visible control with the corresponding shared shadcn component.",
11382
11521
  category: "style",
11383
11522
  limitations: [
11523
+ "Native multiple selects and controls with possibly enabled hidden attributes are excluded. Unknown JSX spreads can hide controls; visual equivalence is not inferred.",
11384
11524
  "Hidden and file inputs, unassociated labels, and non-control semantic elements are excluded.",
11385
11525
  "Tests and the shared components/ui primitive implementation tree are excluded.",
11386
11526
  "Package-local project detection is opt-in and fails closed unless components.json, one unambiguous tsconfig/jsconfig alias, the exact primitive module, and its expected export all exist."
@@ -11668,6 +11808,7 @@ function isStaticallyAssociatedLabel(node) {
11668
11808
  return node.parent.type === AST_NODE_TYPES50.JSXElement && containsLabelableElement(node.parent);
11669
11809
  }
11670
11810
  function replacementFor(node, element) {
11811
+ if (element === "select" && mayHaveBooleanAttribute(node, "multiple")) return null;
11671
11812
  if (element !== "input") return RAW_PRIMITIVES[element];
11672
11813
  const typeAttribute = effectiveAttribute(node, "type");
11673
11814
  if (typeAttribute.kind === "unknown") return null;
@@ -11682,6 +11823,14 @@ function replacementFor(node, element) {
11682
11823
  if (AMBIGUOUS_INPUT_TYPES.has(inputType)) return null;
11683
11824
  return RAW_PRIMITIVES.input;
11684
11825
  }
11826
+ function mayHaveBooleanAttribute(node, name) {
11827
+ for (const attribute of node.attributes.toReversed()) {
11828
+ if (attribute.type === AST_NODE_TYPES50.JSXSpreadAttribute) return true;
11829
+ if (attribute.name.type !== AST_NODE_TYPES50.JSXIdentifier || attribute.name.name !== name) continue;
11830
+ return !(attribute.value?.type === AST_NODE_TYPES50.JSXExpressionContainer && attribute.value.expression.type === AST_NODE_TYPES50.Literal && attribute.value.expression.value === false);
11831
+ }
11832
+ return false;
11833
+ }
11685
11834
  var prefer_shadcn_primitives_default = createRule({
11686
11835
  name: "prefer-shadcn-primitives",
11687
11836
  documentation: PREFER_SHADCN_PRIMITIVES_DOCUMENTATION,
@@ -11727,6 +11876,9 @@ var prefer_shadcn_primitives_default = createRule({
11727
11876
  JSXOpeningElement(node) {
11728
11877
  const element = rawElementName(node);
11729
11878
  if (element === null) return;
11879
+ if (mayHaveBooleanAttribute(node, "hidden") || context.sourceCode.getAncestors(node).some(
11880
+ (ancestor) => ancestor.type === AST_NODE_TYPES50.JSXElement && mayHaveBooleanAttribute(ancestor.openingElement, "hidden")
11881
+ )) return;
11730
11882
  if (element === "label" && !isStaticallyAssociatedLabel(node)) return;
11731
11883
  const replacement = replacementFor(node, element);
11732
11884
  if (replacement === null) return;
@@ -11812,9 +11964,9 @@ function isIgnoredFile2(filename, sourceText) {
11812
11964
  function isLocalFixtureFile(filename) {
11813
11965
  return isTestFile(filename) || isStoryFile(filename);
11814
11966
  }
11815
- function unwrap3(node) {
11967
+ function unwrap4(node) {
11816
11968
  if (node.type === AST_NODE_TYPES51.TSAsExpression || node.type === AST_NODE_TYPES51.TSSatisfiesExpression || node.type === AST_NODE_TYPES51.TSNonNullExpression) {
11817
- return unwrap3(node.expression);
11969
+ return unwrap4(node.expression);
11818
11970
  }
11819
11971
  return node;
11820
11972
  }
@@ -11826,7 +11978,7 @@ function isLiteralOnly(node, depth) {
11826
11978
  if (depth > MAX_LITERAL_DEPTH) {
11827
11979
  return false;
11828
11980
  }
11829
- const inner = unwrap3(node);
11981
+ const inner = unwrap4(node);
11830
11982
  switch (inner.type) {
11831
11983
  case AST_NODE_TYPES51.Literal: {
11832
11984
  return !(isRegexLiteral(inner) && HAS_STATEFUL_FLAG_RE.test(inner.regex.flags));
@@ -11883,7 +12035,7 @@ function classify(init, checkRegex) {
11883
12035
  if (node.arguments.length !== 1 || arg === void 0 || arg.type === AST_NODE_TYPES51.SpreadElement) {
11884
12036
  return null;
11885
12037
  }
11886
- const entries = unwrap3(arg);
12038
+ const entries = unwrap4(arg);
11887
12039
  if (entries.type !== AST_NODE_TYPES51.ArrayExpression) {
11888
12040
  return null;
11889
12041
  }
@@ -11892,9 +12044,9 @@ function classify(init, checkRegex) {
11892
12044
  return null;
11893
12045
  }
11894
12046
  function unwrapObjectFreeze(node) {
11895
- const inner = unwrap3(node);
12047
+ const inner = unwrap4(node);
11896
12048
  if (inner.type === AST_NODE_TYPES51.CallExpression && inner.callee.type === AST_NODE_TYPES51.MemberExpression && !inner.callee.computed && inner.callee.object.type === AST_NODE_TYPES51.Identifier && inner.callee.object.name === "Object" && inner.callee.property.type === AST_NODE_TYPES51.Identifier && inner.callee.property.name === "freeze" && inner.arguments.length === 1 && inner.arguments[0] !== void 0 && inner.arguments[0].type !== AST_NODE_TYPES51.SpreadElement) {
11897
- return unwrap3(inner.arguments[0]);
12049
+ return unwrap4(inner.arguments[0]);
11898
12050
  }
11899
12051
  return inner;
11900
12052
  }
@@ -12427,7 +12579,7 @@ var prefer_module_level_schema_default = createRule({
12427
12579
  // src/rules/prefer-module-level-refined-schema.ts
12428
12580
  import {
12429
12581
  AST_NODE_TYPES as AST_NODE_TYPES53,
12430
- ASTUtils as ASTUtils15
12582
+ ASTUtils as ASTUtils19
12431
12583
  } from "@typescript-eslint/utils";
12432
12584
  var BENCHMARK_PATH_RE = /(^|[/\\])(?:benchmarks?|bench)[/\\]/;
12433
12585
  var FACTORIES = /* @__PURE__ */ new Set([
@@ -12698,7 +12850,7 @@ var prefer_module_level_refined_schema_default = createRule({
12698
12850
  return {};
12699
12851
  const zodBindings = /* @__PURE__ */ new Set();
12700
12852
  function resolvedBinding(identifier) {
12701
- return ASTUtils15.findVariable(
12853
+ return ASTUtils19.findVariable(
12702
12854
  context.sourceCode.getScope(identifier),
12703
12855
  identifier.name
12704
12856
  );
@@ -12799,7 +12951,7 @@ var prefer_module_level_refined_schema_default = createRule({
12799
12951
  // src/rules/prefer-multi-value-zod-literal.ts
12800
12952
  import {
12801
12953
  AST_NODE_TYPES as AST_NODE_TYPES54,
12802
- ASTUtils as ASTUtils16
12954
+ ASTUtils as ASTUtils20
12803
12955
  } from "@typescript-eslint/utils";
12804
12956
  var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
12805
12957
  summary: "Use the Zod 4 multi-value literal API instead of a union of literal schemas.",
@@ -12846,7 +12998,7 @@ function isStaticPrimitive(node, context) {
12846
12998
  if (node.type === AST_NODE_TYPES54.TemplateLiteral && node.expressions.length === 0)
12847
12999
  return true;
12848
13000
  if (node.type === AST_NODE_TYPES54.Identifier && node.name === "undefined") {
12849
- const binding = ASTUtils16.findVariable(
13001
+ const binding = ASTUtils20.findVariable(
12850
13002
  context.sourceCode.getScope(node),
12851
13003
  node.name
12852
13004
  );
@@ -12883,7 +13035,7 @@ var prefer_multi_value_zod_literal_default = createRule({
12883
13035
  const zodBindings = /* @__PURE__ */ new Set();
12884
13036
  const zod4Bindings = /* @__PURE__ */ new Set();
12885
13037
  function resolvedBinding(identifier) {
12886
- return ASTUtils16.findVariable(
13038
+ return ASTUtils20.findVariable(
12887
13039
  context.sourceCode.getScope(identifier),
12888
13040
  identifier.name
12889
13041
  );
@@ -13001,10 +13153,10 @@ var PREFER_NAMED_COMPLEX_RETURN_TYPE_DOCUMENTATION = {
13001
13153
  { id: "inline-result", title: "Do not inline a multi-state result", outcome: "match", files: [{ path: "src/queue.ts", source: "export function claim(): { state: 'idle' } | { state: 'waiting'; retryAt: number } | { state: 'claimed'; id: string } { return { state: 'idle' }; }" }], focusPath: "src/queue.ts", expectedCount: 1, public: true }
13002
13154
  ]
13003
13155
  };
13004
- function unwrap4(node) {
13156
+ function unwrap5(node) {
13005
13157
  if (node.type === AST_NODE_TYPES56.TSTypeReference && node.typeArguments?.params.length === 1) {
13006
13158
  const [inner] = node.typeArguments.params;
13007
- if (inner !== void 0) return unwrap4(inner);
13159
+ if (inner !== void 0) return unwrap5(inner);
13008
13160
  }
13009
13161
  return node;
13010
13162
  }
@@ -13015,10 +13167,10 @@ function report(context, node) {
13015
13167
  }
13016
13168
  }
13017
13169
  function isComplex(node) {
13018
- const type = unwrap4(node);
13170
+ const type = unwrap5(node);
13019
13171
  if (type.type === AST_NODE_TYPES56.TSTypeLiteral) return type.members.length >= 3;
13020
13172
  if (type.type !== AST_NODE_TYPES56.TSUnionType || type.types.length < 3) return false;
13021
- return type.types.every((member) => unwrap4(member).type === AST_NODE_TYPES56.TSTypeLiteral);
13173
+ return type.types.every((member) => unwrap5(member).type === AST_NODE_TYPES56.TSTypeLiteral);
13022
13174
  }
13023
13175
  var prefer_named_complex_return_type_default = createRule({
13024
13176
  name: "prefer-named-complex-return-type",
@@ -13045,7 +13197,7 @@ var prefer_named_complex_return_type_default = createRule({
13045
13197
  });
13046
13198
 
13047
13199
  // src/rules/prefer-native-random-uuid.ts
13048
- import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils17 } from "@typescript-eslint/utils";
13200
+ import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils21 } from "@typescript-eslint/utils";
13049
13201
  var PREFER_NATIVE_RANDOM_UUID_DOCUMENTATION = {
13050
13202
  summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
13051
13203
  rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
@@ -13081,7 +13233,7 @@ var prefer_native_random_uuid_default = createRule({
13081
13233
  const directBindings = /* @__PURE__ */ new Set();
13082
13234
  const namespaceBindings = /* @__PURE__ */ new Set();
13083
13235
  function resolve2(identifier) {
13084
- return ASTUtils17.findVariable(context.sourceCode.getScope(identifier), identifier.name);
13236
+ return ASTUtils21.findVariable(context.sourceCode.getScope(identifier), identifier.name);
13085
13237
  }
13086
13238
  function record(identifier, destination) {
13087
13239
  const variable = resolve2(identifier);
@@ -13144,7 +13296,7 @@ var prefer_native_random_uuid_default = createRule({
13144
13296
  });
13145
13297
 
13146
13298
  // src/rules/prefer-node-crypto-hash.ts
13147
- import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils18 } from "@typescript-eslint/utils";
13299
+ import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils22 } from "@typescript-eslint/utils";
13148
13300
  var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
13149
13301
  summary: "Prefer the modern one-shot node:crypto hash API when streaming state is unnecessary.",
13150
13302
  rationale: "A createHash-update-digest chain allocates mutable streaming state for a single in-memory value; Node's built-in hash function expresses the one-shot operation directly and can use its optimized fast path.",
@@ -13184,7 +13336,7 @@ function isUnshadowedBuiltinIdentifier(identifier, resolve2) {
13184
13336
  const variable = resolve2(identifier);
13185
13337
  return variable === null || variable.defs.length === 0;
13186
13338
  }
13187
- function propertyName4(node) {
13339
+ function propertyName2(node) {
13188
13340
  if (!node.computed && node.key.type === AST_NODE_TYPES58.Identifier) return node.key.name;
13189
13341
  if (node.key.type === AST_NODE_TYPES58.Literal && typeof node.key.value === "string") {
13190
13342
  return node.key.value;
@@ -13200,7 +13352,7 @@ var prefer_node_crypto_hash_default = createRule({
13200
13352
  const directBindings = /* @__PURE__ */ new Set();
13201
13353
  const namespaceBindings = /* @__PURE__ */ new Set();
13202
13354
  function resolve2(identifier) {
13203
- return ASTUtils18.findVariable(
13355
+ return ASTUtils22.findVariable(
13204
13356
  context.sourceCode.getScope(identifier),
13205
13357
  identifier.name
13206
13358
  );
@@ -13230,7 +13382,7 @@ var prefer_node_crypto_hash_default = createRule({
13230
13382
  }
13231
13383
  if (node.id.type !== AST_NODE_TYPES58.ObjectPattern) return;
13232
13384
  for (const property of node.id.properties) {
13233
- if (property.type === AST_NODE_TYPES58.Property && propertyName4(property) === "createHash" && property.value.type === AST_NODE_TYPES58.Identifier) {
13385
+ if (property.type === AST_NODE_TYPES58.Property && propertyName2(property) === "createHash" && property.value.type === AST_NODE_TYPES58.Identifier) {
13234
13386
  record(property.value, directBindings);
13235
13387
  }
13236
13388
  }
@@ -13312,7 +13464,7 @@ function isFsLoader(node) {
13312
13464
  function isFsSpecifier(node) {
13313
13465
  return node.type === AST_NODE_TYPES59.Literal && (node.value === "node:fs" || node.value === "fs");
13314
13466
  }
13315
- function propertyName5(node) {
13467
+ function propertyName3(node) {
13316
13468
  if (!node.computed && node.key.type === AST_NODE_TYPES59.Identifier) return node.key.name;
13317
13469
  if (node.key.type === AST_NODE_TYPES59.Literal && typeof node.key.value === "string") return node.key.value;
13318
13470
  return null;
@@ -13363,7 +13515,7 @@ var prefer_node_fs_promises_default = createRule({
13363
13515
  if (node.id.type !== AST_NODE_TYPES59.ObjectPattern) return;
13364
13516
  const synchronousImports = node.id.properties.flatMap((property) => {
13365
13517
  if (property.type !== AST_NODE_TYPES59.Property) return [];
13366
- const name = propertyName5(property);
13518
+ const name = propertyName3(property);
13367
13519
  return name?.endsWith("Sync") === true ? [name] : [];
13368
13520
  });
13369
13521
  if (synchronousImports.length > 0) {
@@ -13387,7 +13539,7 @@ var prefer_node_fs_promises_default = createRule({
13387
13539
  });
13388
13540
 
13389
13541
  // src/rules/prefer-non-nullable-collection.ts
13390
- import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils19 } from "@typescript-eslint/utils";
13542
+ import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils23 } from "@typescript-eslint/utils";
13391
13543
  var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
13392
13544
  summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
13393
13545
  rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
@@ -13400,7 +13552,7 @@ var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
13400
13552
  ]
13401
13553
  };
13402
13554
  var ARRAY_TYPE_NAMES = /* @__PURE__ */ new Set(["Array", "ReadonlyArray"]);
13403
- function propertyName6(node) {
13555
+ function propertyName4(node) {
13404
13556
  const key = node.key;
13405
13557
  if (node.computed) return null;
13406
13558
  if (key.type === AST_NODE_TYPES60.Identifier) return key.name;
@@ -13413,7 +13565,7 @@ function isArrayType(node) {
13413
13565
  }
13414
13566
  function nullableProperty(node) {
13415
13567
  if (node.optional) return null;
13416
- const name = propertyName6(node);
13568
+ const name = propertyName4(node);
13417
13569
  const annotation = node.typeAnnotation?.typeAnnotation;
13418
13570
  if (name === null || annotation?.type !== AST_NODE_TYPES60.TSUnionType) return null;
13419
13571
  const concrete = annotation.types.filter(
@@ -13517,14 +13669,14 @@ function directlyCoalesced(node) {
13517
13669
  return parent?.type === AST_NODE_TYPES60.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
13518
13670
  }
13519
13671
  function identifierIsOnlyCoalesced(context, binding, fn) {
13520
- const variable = ASTUtils19.findVariable(context.sourceCode.getScope(binding), binding.name);
13672
+ const variable = ASTUtils23.findVariable(context.sourceCode.getScope(binding), binding.name);
13521
13673
  if (variable === null || variable.references.length === 0) return false;
13522
13674
  return variable.references.every(
13523
13675
  (reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
13524
13676
  );
13525
13677
  }
13526
13678
  function memberIsOnlyCoalesced(context, object, property, fn) {
13527
- const variable = ASTUtils19.findVariable(context.sourceCode.getScope(object), object.name);
13679
+ const variable = ASTUtils23.findVariable(context.sourceCode.getScope(object), object.name);
13528
13680
  if (variable === null) return false;
13529
13681
  const accesses = variable.references.flatMap((reference) => {
13530
13682
  if (!belongsToFunction(reference.identifier, fn)) return [null];
@@ -13623,7 +13775,7 @@ var prefer_non_nullable_collection_default = createRule({
13623
13775
  context.report({
13624
13776
  node,
13625
13777
  messageId: "preferNonNullableCollection",
13626
- data: { name: propertyName6(node) ?? "collection" }
13778
+ data: { name: propertyName4(node) ?? "collection" }
13627
13779
  });
13628
13780
  }
13629
13781
  }
@@ -13634,7 +13786,7 @@ var prefer_non_nullable_collection_default = createRule({
13634
13786
  // src/rules/prefer-nullish-filter-predicate.ts
13635
13787
  import {
13636
13788
  AST_NODE_TYPES as AST_NODE_TYPES61,
13637
- ASTUtils as ASTUtils20,
13789
+ ASTUtils as ASTUtils24,
13638
13790
  ESLintUtils as ESLintUtils5
13639
13791
  } from "@typescript-eslint/utils";
13640
13792
  import ts3 from "typescript";
@@ -13680,7 +13832,7 @@ var PREFER_NULLISH_FILTER_PREDICATE_DOCUMENTATION = {
13680
13832
  ]
13681
13833
  };
13682
13834
  function isUnshadowedBoolean(node, context) {
13683
- const variable = ASTUtils20.findVariable(context.sourceCode.getScope(node), node.name);
13835
+ const variable = ASTUtils24.findVariable(context.sourceCode.getScope(node), node.name);
13684
13836
  return variable === null || variable.defs.length === 0;
13685
13837
  }
13686
13838
  function isBuiltinArrayFilter(node, services) {
@@ -13739,7 +13891,7 @@ function isProvablyTruthy(type, checker) {
13739
13891
  }
13740
13892
  function availableParameterName(node, context) {
13741
13893
  for (const name of ["value", "item", "element", "candidate"]) {
13742
- if (ASTUtils20.findVariable(context.sourceCode.getScope(node), name) === null) return name;
13894
+ if (ASTUtils24.findVariable(context.sourceCode.getScope(node), name) === null) return name;
13743
13895
  }
13744
13896
  return null;
13745
13897
  }
@@ -13793,7 +13945,7 @@ var prefer_nullish_filter_predicate_default = createRule({
13793
13945
 
13794
13946
  // src/rules/prefer-await-in-async-return.ts
13795
13947
  import {
13796
- ASTUtils as ASTUtils21,
13948
+ ASTUtils as ASTUtils25,
13797
13949
  ESLintUtils as ESLintUtils6,
13798
13950
  AST_NODE_TYPES as AST_NODE_TYPES62
13799
13951
  } from "@typescript-eslint/utils";
@@ -13907,13 +14059,13 @@ var prefer_await_in_async_return_default = createRule({
13907
14059
  if (services === null) return {};
13908
14060
  const frameworkLoaders = /* @__PURE__ */ new Set();
13909
14061
  const rememberFrameworkLoader = (identifier) => {
13910
- const variable = ASTUtils21.findVariable(context.sourceCode.getScope(identifier), identifier.name);
14062
+ const variable = ASTUtils25.findVariable(context.sourceCode.getScope(identifier), identifier.name);
13911
14063
  if (variable !== null) frameworkLoaders.add(variable);
13912
14064
  };
13913
14065
  const isFrameworkLoaderCallback = (owner) => {
13914
14066
  const parent = owner.parent;
13915
14067
  if (parent.type !== AST_NODE_TYPES62.CallExpression || parent.arguments[0] !== owner || parent.callee.type !== AST_NODE_TYPES62.Identifier) return false;
13916
- const variable = ASTUtils21.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
14068
+ const variable = ASTUtils25.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
13917
14069
  return variable !== null && frameworkLoaders.has(variable);
13918
14070
  };
13919
14071
  return {
@@ -13955,7 +14107,7 @@ var PREFER_SCHEMA_FOR_API_PAYLOAD_DOCUMENTATION = {
13955
14107
  { id: "unvalidated-payload", title: "Do not trust response JSON directly", outcome: "match", files: [{ path: "src/client.ts", source: "async function load(response) { const body = await response.json(); return body.id; }" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
13956
14108
  ]
13957
14109
  };
13958
- var unwrap5 = (node) => {
14110
+ var unwrap6 = (node) => {
13959
14111
  let current = node;
13960
14112
  while (current !== null && current !== void 0) {
13961
14113
  if (current.type === AST_NODE_TYPES63.TSAsExpression || current.type === AST_NODE_TYPES63.TSTypeAssertion || current.type === AST_NODE_TYPES63.TSNonNullExpression || current.type === AST_NODE_TYPES63.TSSatisfiesExpression) {
@@ -13974,23 +14126,23 @@ var PROMISE_CHAIN_METHODS = /* @__PURE__ */ new Set([
13974
14126
  "finally"
13975
14127
  ]);
13976
14128
  var isSchemaParseReference = (node) => {
13977
- const inner = unwrap5(node);
14129
+ const inner = unwrap6(node);
13978
14130
  return inner !== null && inner.type === AST_NODE_TYPES63.MemberExpression && !inner.computed && inner.property.type === AST_NODE_TYPES63.Identifier && (inner.property.name === "parse" || inner.property.name === "safeParse");
13979
14131
  };
13980
14132
  var isRawPayloadSource = (node, isKnownLocalText) => {
13981
- let current = unwrap5(node);
14133
+ let current = unwrap6(node);
13982
14134
  if (current === null) return false;
13983
14135
  if (current.type === AST_NODE_TYPES63.AwaitExpression) {
13984
- current = unwrap5(current.argument);
14136
+ current = unwrap6(current.argument);
13985
14137
  }
13986
14138
  if (current === null || current.type !== AST_NODE_TYPES63.CallExpression) {
13987
14139
  return false;
13988
14140
  }
13989
- const callee = unwrap5(current.callee);
14141
+ const callee = unwrap6(current.callee);
13990
14142
  if (callee === null || callee.type !== AST_NODE_TYPES63.MemberExpression) {
13991
14143
  return false;
13992
14144
  }
13993
- const property = unwrap5(callee.property);
14145
+ const property = unwrap6(callee.property);
13994
14146
  if (property === null || property.type !== AST_NODE_TYPES63.Identifier) {
13995
14147
  return false;
13996
14148
  }
@@ -14000,17 +14152,17 @@ var isRawPayloadSource = (node, isKnownLocalText) => {
14000
14152
  if (PROMISE_CHAIN_METHODS.has(property.name)) {
14001
14153
  return !current.arguments.some(isSchemaParseReference) && isRawPayloadSource(callee.object);
14002
14154
  }
14003
- const object = unwrap5(callee.object);
14155
+ const object = unwrap6(callee.object);
14004
14156
  return property.name === "parse" && object !== null && object.type === AST_NODE_TYPES63.Identifier && object.name === "JSON" && !isLocalFileRead(current.arguments[0]) && isKnownLocalText?.(current.arguments[0]) !== true;
14005
14157
  };
14006
14158
  var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
14007
14159
  var isDirectLocalFileRead = (node) => {
14008
- let current = unwrap5(node);
14160
+ let current = unwrap6(node);
14009
14161
  if (current?.type === AST_NODE_TYPES63.AwaitExpression) {
14010
- current = unwrap5(current.argument);
14162
+ current = unwrap6(current.argument);
14011
14163
  }
14012
14164
  if (current?.type !== AST_NODE_TYPES63.CallExpression) return false;
14013
- const callee = unwrap5(current.callee);
14165
+ const callee = unwrap6(current.callee);
14014
14166
  const name = callee?.type === AST_NODE_TYPES63.Identifier ? callee.name : callee?.type === AST_NODE_TYPES63.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES63.Identifier ? callee.property.name : null;
14015
14167
  return name !== null && FILE_READ_RE.test(name);
14016
14168
  };
@@ -14250,7 +14402,7 @@ var isGuardTestPosition = (node) => {
14250
14402
  return false;
14251
14403
  };
14252
14404
  var unvalidatedVariableRef = (node, scope, tracked) => {
14253
- const unwrapped = unwrap5(node);
14405
+ const unwrapped = unwrap6(node);
14254
14406
  if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES63.Identifier) {
14255
14407
  return null;
14256
14408
  }
@@ -14279,7 +14431,7 @@ var prefer_schema_for_api_payload_default = createRule({
14279
14431
  const aliasGroups = /* @__PURE__ */ new Map();
14280
14432
  const localFileTextVariables = /* @__PURE__ */ new Set();
14281
14433
  const localFileTextRef = (node, scope) => {
14282
- const unwrapped = unwrap5(node);
14434
+ const unwrapped = unwrap6(node);
14283
14435
  if (unwrapped?.type !== AST_NODE_TYPES63.Identifier) return null;
14284
14436
  const variable = findVariable2(scope, unwrapped.name);
14285
14437
  return variable !== null && localFileTextVariables.has(variable) ? variable : null;
@@ -14415,7 +14567,7 @@ var prefer_schema_for_api_payload_default = createRule({
14415
14567
  const scope = context.sourceCode.getScope(node);
14416
14568
  for (const arg of node.arguments) {
14417
14569
  if (arg.type === AST_NODE_TYPES63.SpreadElement) continue;
14418
- const unwrapped = unwrap5(arg);
14570
+ const unwrapped = unwrap6(arg);
14419
14571
  if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES63.Identifier) {
14420
14572
  continue;
14421
14573
  }
@@ -14427,7 +14579,7 @@ var prefer_schema_for_api_payload_default = createRule({
14427
14579
  if (isInsideAssertion(node)) return;
14428
14580
  if (isValidationRead(node)) return;
14429
14581
  const scope = context.sourceCode.getScope(node);
14430
- const obj = unwrap5(node.object);
14582
+ const obj = unwrap6(node.object);
14431
14583
  if (isRawPayloadSource(
14432
14584
  obj,
14433
14585
  (candidate2) => localFileTextRef(candidate2, scope) !== null
@@ -14632,6 +14784,7 @@ var PREFER_SEMANTIC_COLORS_DOCUMENTATION = {
14632
14784
  remediation: "Replace raw palette and literal colors with the closest semantic design-system token or CSS variable.",
14633
14785
  category: "style",
14634
14786
  limitations: [
14787
+ "Class-composition helper objects use literal keys as class fragments; cva/tv configuration objects retain value traversal. Computed keys are not resolved. URL payloads are not color literals.",
14635
14788
  "Email, PDF, video-rendering, print-only, icon artwork, masks, gradients, stories, and explicitly configured non-token projects have targeted exclusions.",
14636
14789
  "Opaque-foreground checks are opt-in and require both a same-variant semantic background class and its package-local declared foreground token."
14637
14790
  ],
@@ -15036,7 +15189,7 @@ var prefer_semantic_colors_default = createRule({
15036
15189
  });
15037
15190
  }
15038
15191
  };
15039
- const checkClassNode = (node) => {
15192
+ const checkClassNode = (node, objectKeys = false) => {
15040
15193
  if (node === null) return;
15041
15194
  switch (node.type) {
15042
15195
  case AST_NODE_TYPES66.Literal:
@@ -15047,27 +15200,35 @@ var prefer_semantic_colors_default = createRule({
15047
15200
  break;
15048
15201
  case AST_NODE_TYPES66.ArrayExpression:
15049
15202
  for (const element of node.elements) {
15050
- if (element !== null && element.type !== AST_NODE_TYPES66.SpreadElement) checkClassNode(element);
15203
+ if (element !== null && element.type !== AST_NODE_TYPES66.SpreadElement) checkClassNode(element, objectKeys);
15051
15204
  }
15052
15205
  break;
15053
15206
  case AST_NODE_TYPES66.ObjectExpression:
15054
15207
  for (const property of node.properties) {
15055
- if (property.type === AST_NODE_TYPES66.Property) checkClassNode(property.value);
15208
+ if (property.type !== AST_NODE_TYPES66.Property) continue;
15209
+ if (objectKeys) {
15210
+ if (property.value.type === AST_NODE_TYPES66.Literal && !property.value.value && !("regex" in property.value)) continue;
15211
+ if (!property.computed && property.key.type === AST_NODE_TYPES66.Literal) {
15212
+ checkClassNode(property.key);
15213
+ }
15214
+ } else {
15215
+ checkClassNode(property.value);
15216
+ }
15056
15217
  }
15057
15218
  break;
15058
15219
  case AST_NODE_TYPES66.ConditionalExpression:
15059
- checkClassNode(node.consequent);
15060
- checkClassNode(node.alternate);
15220
+ checkClassNode(node.consequent, objectKeys);
15221
+ checkClassNode(node.alternate, objectKeys);
15061
15222
  break;
15062
15223
  case AST_NODE_TYPES66.LogicalExpression:
15063
- checkClassNode(node.right);
15224
+ checkClassNode(node.right, objectKeys);
15064
15225
  break;
15065
15226
  default:
15066
15227
  break;
15067
15228
  }
15068
15229
  };
15069
15230
  const checkColorValueNode = (node) => {
15070
- if (node.type === AST_NODE_TYPES66.Literal && typeof node.value === "string" && RAW_COLOR_VALUE_RE.test(node.value) && !CSS_VAR_REFERENCE_RE.test(node.value)) {
15231
+ if (node.type === AST_NODE_TYPES66.Literal && typeof node.value === "string" && RAW_COLOR_VALUE_RE.test(node.value.replace(/url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/giu, "")) && !CSS_VAR_REFERENCE_RE.test(node.value)) {
15071
15232
  report2(node, "inlineColor", { value: node.value });
15072
15233
  }
15073
15234
  };
@@ -15087,7 +15248,9 @@ var prefer_semantic_colors_default = createRule({
15087
15248
  }
15088
15249
  if (node.callee.type === AST_NODE_TYPES66.Identifier && CLASS_FNS.has(node.callee.name)) {
15089
15250
  for (const arg of node.arguments) {
15090
- if (arg.type !== AST_NODE_TYPES66.SpreadElement) checkClassNode(arg);
15251
+ if (arg.type !== AST_NODE_TYPES66.SpreadElement) {
15252
+ checkClassNode(arg, node.callee.name !== "cva" && node.callee.name !== "tv");
15253
+ }
15091
15254
  }
15092
15255
  }
15093
15256
  },
@@ -15131,13 +15294,13 @@ var prefer_semantic_colors_default = createRule({
15131
15294
  });
15132
15295
 
15133
15296
  // src/rules/prefer-server-actions.ts
15134
- import "@typescript-eslint/utils";
15297
+ import { ASTUtils as ASTUtils26 } from "@typescript-eslint/utils";
15135
15298
  var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
15136
15299
  summary: "Prefer Next.js Server Actions over same-origin API mutations.",
15137
- rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
15138
- remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
15300
+ rationale: "Server Actions can remove a hand-written internal API wrapper while retaining typed application calls. Client invocations still cross a network and serialization boundary.",
15301
+ remediation: "Consider a Server Action for application-owned mutations; preserve authorization, input validation and any public API consumers.",
15139
15302
  category: "architecture",
15140
- limitations: ["Only statically recognizable /api/ mutations, including one explicitly configured literal deployment base path, in use-client modules are reported; server boundaries and route handlers are excluded."],
15303
+ limitations: ["Only statically recognizable /api/ mutations through global fetch or proven Axios imports/instances in use-client modules are reported. Custom wrapper provenance, mutated configuration and unknown option overrides are not inferred; server boundaries and route handlers are excluded."],
15141
15304
  examples: [
15142
15305
  { 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 },
15143
15306
  { 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 }
@@ -15163,21 +15326,39 @@ function resolvesToGlobalFetch(context, identifier) {
15163
15326
  function resolveNode(node, context) {
15164
15327
  if (!node) return null;
15165
15328
  if (node.type !== "Identifier") return node;
15166
- let scope = getScope(context, node);
15167
- while (scope) {
15168
- const variable = scope.set.get(node.name);
15169
- if (variable && variable.defs.length === 1) {
15170
- const def = variable.defs[0];
15171
- if (def && def.type === "Variable") {
15172
- const declarator = def.node;
15173
- if (declarator.type === "VariableDeclarator" && declarator.init) {
15174
- return declarator.init;
15175
- }
15176
- }
15177
- }
15178
- scope = scope.upper;
15179
- }
15180
- return node;
15329
+ const variable = ASTUtils26.findVariable(getScope(context, node), node.name);
15330
+ const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
15331
+ if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || variable?.references.some((reference) => reference.isWrite() && reference.init !== true)) return node;
15332
+ if (definition.node.init.type === "ObjectExpression" && variable?.references.some((reference) => reference.identifier !== node && reference.init !== true)) return node;
15333
+ return definition.node.init;
15334
+ }
15335
+ function isAxiosClient(node, context, seen = /* @__PURE__ */ new Set()) {
15336
+ if (node.type !== "Identifier" || seen.has(node)) return false;
15337
+ seen.add(node);
15338
+ const variable = ASTUtils26.findVariable(getScope(context, node), node.name);
15339
+ const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
15340
+ if (definition === void 0 || variable?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
15341
+ if (variable?.references.some((reference) => {
15342
+ if (reference.init === true) return false;
15343
+ const identifier = reference.identifier;
15344
+ const parent = identifier.parent;
15345
+ if (parent.type === "CallExpression" && parent.callee === identifier) return false;
15346
+ return parent.type !== "MemberExpression" || parent.object !== identifier || parent.computed || parent.parent.type !== "CallExpression" || parent.parent.callee !== parent;
15347
+ })) return false;
15348
+ if (definition.type === "ImportBinding") {
15349
+ const declaration = definition.parent;
15350
+ return declaration.type === "ImportDeclaration" && declaration.source.value === "axios" && declaration.importKind !== "type" && (definition.node.type === "ImportDefaultSpecifier" || definition.node.type === "ImportSpecifier" && definition.node.importKind !== "type" && (definition.node.imported.type === "Identifier" ? definition.node.imported.name : definition.node.imported.value) === "default");
15351
+ }
15352
+ if (definition.type !== "Variable" || definition.parent.kind !== "const") return false;
15353
+ const init = definition.node.init;
15354
+ return init?.type === "CallExpression" && init.arguments.length <= 1 && hasLocalAxiosOptions(init.arguments[0], context) && init.callee.type === "MemberExpression" && !init.callee.computed && init.callee.property.type === "Identifier" && init.callee.property.name === "create" && isAxiosClient(init.callee.object, context, seen);
15355
+ }
15356
+ function hasLocalAxiosOptions(node, context) {
15357
+ if (node === void 0) return true;
15358
+ const options = resolveNode(node, context);
15359
+ return options?.type === "ObjectExpression" && options.properties.every(
15360
+ (property) => property.type === "Property" && !property.computed && property.kind === "init" && !["baseURL", "adapter"].includes(property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : "baseURL")
15361
+ );
15181
15362
  }
15182
15363
  function isApiUrl(node, context, apiPrefixes) {
15183
15364
  const resolved = resolveNode(node, context);
@@ -15238,7 +15419,8 @@ function isFunctionArgument(node, context) {
15238
15419
  }
15239
15420
  function getPropertyNode(objNode, propName2) {
15240
15421
  if (!objNode || objNode.type !== "ObjectExpression") return null;
15241
- for (const prop of objNode.properties) {
15422
+ if (objNode.properties.some((property) => property.type === "SpreadElement" || property.computed)) return null;
15423
+ for (const prop of [...objNode.properties].reverse()) {
15242
15424
  if (prop.type !== "Property") continue;
15243
15425
  let keyName = null;
15244
15426
  if (prop.key.type === "Identifier" && !prop.computed) {
@@ -15276,7 +15458,7 @@ var prefer_server_actions_default = createRule({
15276
15458
  }
15277
15459
  ],
15278
15460
  messages: {
15279
- preferServerAction: "Mutation against a same-origin API route \u2014 prefer a Next.js Server Action for type-safety and to avoid the JSON round-trip."
15461
+ preferServerAction: "This client mutation targets a same-origin API route. Consider a Server Action to remove the hand-written API wrapper; retain authorization and validation, since the call still crosses a network boundary."
15280
15462
  }
15281
15463
  },
15282
15464
  defaultOptions: [{}],
@@ -15320,9 +15502,11 @@ var prefer_server_actions_default = createRule({
15320
15502
  }
15321
15503
  }
15322
15504
  }
15323
- } else if (node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && !node.callee.computed) {
15505
+ } else if (node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && !node.callee.computed && isAxiosClient(node.callee.object, context)) {
15324
15506
  const methodName2 = node.callee.property.name.toLowerCase();
15325
15507
  if (AXIOS_MUTATION_METHODS.has(methodName2)) {
15508
+ const config = node.arguments[methodName2 === "delete" ? 1 : 2];
15509
+ if (!hasLocalAxiosOptions(config, context)) return;
15326
15510
  const urlArg = node.arguments[0];
15327
15511
  const hasHandlerArg = node.arguments.some(
15328
15512
  (arg) => arg.type !== "SpreadElement" && isFunctionArgument(arg, context)
@@ -15331,11 +15515,12 @@ var prefer_server_actions_default = createRule({
15331
15515
  isMutation = true;
15332
15516
  }
15333
15517
  }
15334
- } else if (node.callee.type === "Identifier" && (node.callee.name === "axios" || node.callee.name === "request")) {
15518
+ } else if (node.callee.type === "Identifier" && isAxiosClient(node.callee, context)) {
15335
15519
  const firstArg = node.arguments[0];
15336
15520
  if (firstArg && firstArg.type !== "SpreadElement") {
15337
15521
  const configArg = resolveNode(firstArg, context);
15338
15522
  if (configArg && configArg.type === "ObjectExpression") {
15523
+ if (!hasLocalAxiosOptions(firstArg, context)) return;
15339
15524
  const urlNode = getPropertyNode(configArg, "url");
15340
15525
  const methodNode = getPropertyNode(configArg, "method");
15341
15526
  if (urlNode && isApiUrl(urlNode, context, apiPrefixes) && methodNode && isMutationMethod(methodNode, context)) {
@@ -15619,7 +15804,7 @@ var prefer_whole_object_assertion_default = createRule({
15619
15804
  });
15620
15805
 
15621
15806
  // src/rules/repeated-static-call-cases.ts
15622
- import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils22 } from "@typescript-eslint/utils";
15807
+ import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils27 } from "@typescript-eslint/utils";
15623
15808
  var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
15624
15809
  summary: "Report three or more consecutive literal call assertions that should be independently named test cases.",
15625
15810
  rationale: "Copy-pasted cases obscure the input table and stop later cases from being reported after the first failure.",
@@ -15645,7 +15830,7 @@ function staticMemberName5(node) {
15645
15830
  return null;
15646
15831
  }
15647
15832
  function importedName6(identifier, context, modules) {
15648
- const variable = ASTUtils22.findVariable(context.sourceCode.getScope(identifier), identifier.name);
15833
+ const variable = ASTUtils27.findVariable(context.sourceCode.getScope(identifier), identifier.name);
15649
15834
  if (variable === null || variable.defs.length === 0) return identifier.name;
15650
15835
  for (const definition of variable.defs) {
15651
15836
  if (definition.node.type !== AST_NODE_TYPES68.ImportSpecifier) continue;
@@ -16223,11 +16408,11 @@ var prefer_zod_infer_default = createRule({
16223
16408
  continue;
16224
16409
  }
16225
16410
  const key = member.key;
16226
- const propertyName8 = key.type === AST_NODE_TYPES69.Identifier ? key.name : key.type === AST_NODE_TYPES69.Literal && typeof key.value === "string" ? key.value : null;
16227
- if (propertyName8 === null) {
16411
+ const propertyName6 = key.type === AST_NODE_TYPES69.Identifier ? key.name : key.type === AST_NODE_TYPES69.Literal && typeof key.value === "string" ? key.value : null;
16412
+ if (propertyName6 === null) {
16228
16413
  continue;
16229
16414
  }
16230
- const propertyTokens = nameTokens(propertyName8);
16415
+ const propertyTokens = nameTokens(propertyName6);
16231
16416
  if (propertyTokens.length < 2) {
16232
16417
  continue;
16233
16418
  }
@@ -16242,7 +16427,7 @@ var prefer_zod_infer_default = createRule({
16242
16427
  node: annotation,
16243
16428
  owner,
16244
16429
  ownerName,
16245
- propertyName: propertyName8,
16430
+ propertyName: propertyName6,
16246
16431
  propertyTokens
16247
16432
  });
16248
16433
  }
@@ -16646,12 +16831,13 @@ var require_assert_never_default = createRule({
16646
16831
  });
16647
16832
 
16648
16833
  // src/rules/require-fetch-timeout.ts
16649
- import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils23 } from "@typescript-eslint/utils";
16834
+ import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils28 } from "@typescript-eslint/utils";
16650
16835
  var REQUIRE_FETCH_TIMEOUT_DOCUMENTATION = {
16651
- summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
16836
+ summary: "Require an explicit abort signal on locally analyzable global fetch calls.",
16652
16837
  rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
16653
16838
  remediation: "Pass an abort signal, such as `AbortSignal.timeout(ms)`, in the fetch init.",
16654
16839
  category: "correctness",
16840
+ limitations: ["Signal presence establishes an explicit cancellation path, not a guaranteed timeout. Forwarded Request objects can carry an existing signal."],
16655
16841
  examples: [
16656
16842
  { id: "bounded-fetch", title: "Bound the request", outcome: "no-match", files: [{ path: "src/client.ts", source: "await fetch(url, { signal: AbortSignal.timeout(5000) });" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
16657
16843
  { id: "unbounded-fetch", title: "Do not leave fetch unbounded", outcome: "match", files: [{ path: "src/client.ts", source: "await fetch('https://api.example.com/items');" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
@@ -16697,7 +16883,7 @@ var require_fetch_timeout_default = createRule({
16697
16883
  meta: {
16698
16884
  type: "problem",
16699
16885
  docs: {
16700
- description: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever."
16886
+ description: "Require an explicit abort signal on locally analyzable global fetch calls."
16701
16887
  },
16702
16888
  schema: [
16703
16889
  {
@@ -16713,7 +16899,7 @@ var require_fetch_timeout_default = createRule({
16713
16899
  }
16714
16900
  ],
16715
16901
  messages: {
16716
- missingSignal: "This `fetch()` has no abort `signal` \u2014 a stalled upstream will hang it forever. Pass `{ signal: AbortSignal.timeout(ms) }` or a signal from an AbortController."
16902
+ missingSignal: "This `fetch()` has no explicit abort signal. Pass `AbortSignal.timeout(ms)` for a deadline, or an owner-managed signal for cancellation."
16717
16903
  }
16718
16904
  },
16719
16905
  defaultOptions: [{}],
@@ -16727,7 +16913,7 @@ var require_fetch_timeout_default = createRule({
16727
16913
  }
16728
16914
  function resolvesToGlobal(identifier) {
16729
16915
  const scope = context.sourceCode.getScope(identifier);
16730
- const variable = ASTUtils23.findVariable(scope, identifier.name);
16916
+ const variable = ASTUtils28.findVariable(scope, identifier.name);
16731
16917
  return variable === null || variable.defs.length === 0;
16732
16918
  }
16733
16919
  function isGlobalFetchCall2(callee) {
@@ -16737,7 +16923,7 @@ var require_fetch_timeout_default = createRule({
16737
16923
  return callee.type === AST_NODE_TYPES71.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES71.Identifier && callee.property.name === "fetch" && callee.object.type === AST_NODE_TYPES71.Identifier && GLOBAL_OBJECTS2.has(callee.object.name) && resolvesToGlobal(callee.object);
16738
16924
  }
16739
16925
  function localConstInitProvablyLacksSignal(identifier) {
16740
- const variable = ASTUtils23.findVariable(
16926
+ const variable = ASTUtils28.findVariable(
16741
16927
  context.sourceCode.getScope(identifier),
16742
16928
  identifier.name
16743
16929
  );
@@ -16756,13 +16942,23 @@ var require_fetch_timeout_default = createRule({
16756
16942
  }
16757
16943
  return true;
16758
16944
  }
16945
+ function isForwardedRequest(argument) {
16946
+ let value = argument;
16947
+ if (value.type === AST_NODE_TYPES71.Identifier) {
16948
+ const binding = ASTUtils28.findVariable(context.sourceCode.getScope(value), value.name);
16949
+ const definition = binding?.defs.length === 1 ? binding.defs[0] : void 0;
16950
+ if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
16951
+ value = definition.node.init;
16952
+ }
16953
+ return value.type === AST_NODE_TYPES71.NewExpression && value.callee.type === AST_NODE_TYPES71.Identifier && value.callee.name === "Request" && resolvesToGlobal(value.callee);
16954
+ }
16759
16955
  return {
16760
16956
  CallExpression(node) {
16761
16957
  if (!isGlobalFetchCall2(node.callee)) {
16762
16958
  return;
16763
16959
  }
16764
16960
  const [first, init] = node.arguments;
16765
- if (node.arguments.length === 1 && first !== void 0 && !isInlineUrl(first, resolvesToGlobal)) {
16961
+ if (first !== void 0 && (node.arguments.length === 1 && !isInlineUrl(first, resolvesToGlobal) || isForwardedRequest(first))) {
16766
16962
  return;
16767
16963
  }
16768
16964
  if (init === void 0 || initProvablyLacksSignal(init) || init.type === AST_NODE_TYPES71.Identifier && localConstInitProvablyLacksSignal(init)) {
@@ -17775,7 +17971,7 @@ function isStaticValue(node) {
17775
17971
  }
17776
17972
  return false;
17777
17973
  }
17778
- function propertyName7(property) {
17974
+ function propertyName5(property) {
17779
17975
  if (property.computed) return null;
17780
17976
  if (property.key.type === AST_NODE_TYPES75.Identifier) return property.key.name;
17781
17977
  return typeof property.key.value === "string" ? property.key.value : null;
@@ -17812,7 +18008,7 @@ var require_static_next_matcher_default = createRule({
17812
18008
  continue;
17813
18009
  }
17814
18010
  for (const property of config.properties) {
17815
- if (property.type !== AST_NODE_TYPES75.Property || propertyName7(property) !== "matcher" || property.value.type === AST_NODE_TYPES75.AssignmentPattern) {
18011
+ if (property.type !== AST_NODE_TYPES75.Property || propertyName5(property) !== "matcher" || property.value.type === AST_NODE_TYPES75.AssignmentPattern) {
17816
18012
  continue;
17817
18013
  }
17818
18014
  if (!isStaticValue(property.value)) {
@@ -17826,21 +18022,21 @@ var require_static_next_matcher_default = createRule({
17826
18022
  });
17827
18023
 
17828
18024
  // src/rules/require-use-form-default-values.ts
17829
- import { ASTUtils as ASTUtils24 } from "@typescript-eslint/utils";
18025
+ import { ASTUtils as ASTUtils29 } from "@typescript-eslint/utils";
17830
18026
  var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
17831
- summary: "react-hook-form useForm call without defaultValues",
18027
+ summary: "react-hook-form useForm call without explicit initial or reactive values",
17832
18028
  rationale: "Without an explicit initial value, fields can change from uncontrolled to controlled as data arrives, reset behavior becomes ambiguous, and the form's initial shape no longer documents the values users can edit.",
17833
- remediation: "Pass an object with a defaultValues property to useForm; use empty strings, nulls, or schema-appropriate values deliberately for every controlled field.",
18029
+ remediation: "Provide defaultValues for initial state, or values when reactive external state owns initialization; choose schema-appropriate values for controlled fields.",
17834
18030
  category: "correctness",
17835
18031
  limitations: [
17836
- "Only direct calls to a scope-resolved useForm value imported from react-hook-form are checked; wrapper hooks and computed option objects are intentionally not inferred."
18032
+ "Only direct calls to a scope-resolved useForm value imported from react-hook-form are checked; reactive values are accepted, while wrapper hooks, spreads and computed option objects are intentionally not inferred."
17837
18033
  ],
17838
18034
  examples: [
17839
18035
  {
17840
18036
  id: "form-with-initial-values",
17841
18037
  title: "Give the form an explicit initial shape",
17842
18038
  outcome: "no-match",
17843
- files: [{ path: "profile-form.tsx", source: "import { useForm } from 'react-hook-form';\nconst form = useForm({ defaultValues: { name: '' } });\n" }],
18039
+ files: [{ path: "profile-form.tsx", source: "'use client'; import { useForm } from 'react-hook-form'; function ProfileForm() { const form = useForm({ defaultValues: { name: '' } }); return <input {...form.register('name')} />; }" }],
17844
18040
  focusPath: "profile-form.tsx",
17845
18041
  expectedCount: 0,
17846
18042
  public: true
@@ -17849,16 +18045,16 @@ var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
17849
18045
  id: "form-without-initial-values",
17850
18046
  title: "Do not leave form initialization implicit",
17851
18047
  outcome: "match",
17852
- files: [{ path: "profile-form.tsx", source: "import { useForm } from 'react-hook-form';\nconst form = useForm({ mode: 'onChange' });\n" }],
18048
+ files: [{ path: "profile-form.tsx", source: "'use client'; import { useForm } from 'react-hook-form'; function ProfileForm() { const form = useForm({ mode: 'onChange' }); return <input {...form.register('name')} />; }" }],
17853
18049
  focusPath: "profile-form.tsx",
17854
18050
  expectedCount: 1,
17855
18051
  public: true
17856
18052
  }
17857
18053
  ]
17858
18054
  };
17859
- function hasDefaultValues(options) {
18055
+ function hasInitializationOrUnknownOptions(options) {
17860
18056
  return options?.type === "ObjectExpression" && options.properties.some(
17861
- (property) => property.type === "Property" && !property.computed && (property.key.type === "Identifier" && property.key.name === "defaultValues" || property.key.type === "Literal" && property.key.value === "defaultValues")
18057
+ (property) => property.type === "SpreadElement" || property.computed || (property.key.type === "Identifier" && ["defaultValues", "values"].includes(property.key.name) || property.key.type === "Literal" && ["defaultValues", "values"].includes(String(property.key.value)))
17862
18058
  );
17863
18059
  }
17864
18060
  var require_use_form_default_values_default = createRule({
@@ -17869,7 +18065,7 @@ var require_use_form_default_values_default = createRule({
17869
18065
  docs: { description: REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION.summary },
17870
18066
  schema: [],
17871
18067
  messages: {
17872
- requireUseFormDefaultValues: "Pass explicit defaultValues to useForm so fields have a stable initial shape and reset behavior."
18068
+ requireUseFormDefaultValues: "Provide defaultValues or reactive values to useForm so controlled fields have an explicit initial shape."
17873
18069
  }
17874
18070
  },
17875
18071
  defaultOptions: [],
@@ -17880,15 +18076,15 @@ var require_use_form_default_values_default = createRule({
17880
18076
  if (node.source.value !== "react-hook-form") return;
17881
18077
  for (const specifier of node.specifiers) {
17882
18078
  if (specifier.type !== "ImportSpecifier" || (specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value) !== "useForm") continue;
17883
- const variable = ASTUtils24.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
18079
+ const variable = ASTUtils29.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
17884
18080
  if (variable) importedHooks.add(variable);
17885
18081
  }
17886
18082
  },
17887
18083
  CallExpression(node) {
17888
18084
  if (node.callee.type !== "Identifier") return;
17889
- const variable = ASTUtils24.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
18085
+ const variable = ASTUtils29.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
17890
18086
  const options = node.arguments[0];
17891
- if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" || hasDefaultValues(options)) return;
18087
+ if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" || hasInitializationOrUnknownOptions(options)) return;
17892
18088
  context.report({ node, messageId: "requireUseFormDefaultValues" });
17893
18089
  }
17894
18090
  };
@@ -17901,10 +18097,10 @@ var ACTION_MODULE_RE = /(?:^|\/)app\/.*\/(?:actions|[^/]+-actions)\.[cm]?[jt]s$/
17901
18097
  var REQUIRE_USE_SERVER_IN_ACTIONS_FILE_DOCUMENTATION = {
17902
18098
  summary: "route action module missing the use server directive",
17903
18099
  rationale: "An exported async function is not callable as a Server Action merely because its file is named actions.ts. Without the module directive, a client import can fail or pull server-only implementation details across the client boundary.",
17904
- remediation: "Put 'use server' at the start of the route action module.",
18100
+ remediation: "Use a leading module directive for an action-only module, or retain a function-level directive for an inline Server Action. Do not turn mixed non-action exports into a Server Action module.",
17905
18101
  category: "correctness",
17906
18102
  limitations: [
17907
- "Only exported async functions in actions.ts or *-actions.ts below an app directory are checked; other naming schemes and inline Server Actions are intentionally outside the rule."
18103
+ "Only direct named exports of async declarations or initialized functions in actions.ts or *-actions.ts below an app directory are checked. Detached/default exports and other naming schemes are not inferred; functions with their own directive are accepted."
17908
18104
  ],
17909
18105
  examples: [
17910
18106
  {
@@ -17929,9 +18125,10 @@ var REQUIRE_USE_SERVER_IN_ACTIONS_FILE_DOCUMENTATION = {
17929
18125
  };
17930
18126
  function isExportedAsyncFunction(node) {
17931
18127
  const declaration = node.declaration;
17932
- if (declaration?.type === "FunctionDeclaration") return declaration.async;
18128
+ const unmarked = (fn) => fn.async && !(fn.body?.type === "BlockStatement" && fn.body.body.some((statement) => statement.type === "ExpressionStatement" && statement.directive === "use server"));
18129
+ if (declaration?.type === "FunctionDeclaration") return unmarked(declaration);
17933
18130
  return declaration?.type === "VariableDeclaration" && declaration.declarations.some(
17934
- (item) => item.init?.type === "ArrowFunctionExpression" || item.init?.type === "FunctionExpression" ? item.init.async : false
18131
+ (item) => item.init?.type === "ArrowFunctionExpression" || item.init?.type === "FunctionExpression" ? unmarked(item.init) : false
17935
18132
  );
17936
18133
  }
17937
18134
  var require_use_server_in_actions_file_default = createRule({
@@ -17964,7 +18161,7 @@ var require_use_server_in_actions_file_default = createRule({
17964
18161
  // src/rules/require-zod-form-validation.ts
17965
18162
  import {
17966
18163
  AST_NODE_TYPES as AST_NODE_TYPES76,
17967
- ASTUtils as ASTUtils25
18164
+ ASTUtils as ASTUtils30
17968
18165
  } from "@typescript-eslint/utils";
17969
18166
  var REQUIRE_ZOD_FORM_VALIDATION_DOCUMENTATION = {
17970
18167
  summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
@@ -18032,7 +18229,7 @@ var require_zod_form_validation_default = createRule({
18032
18229
  return {};
18033
18230
  }
18034
18231
  const zodBindings = /* @__PURE__ */ new Set();
18035
- const resolvedBinding = (identifier) => ASTUtils25.findVariable(
18232
+ const resolvedBinding = (identifier) => ASTUtils30.findVariable(
18036
18233
  context.sourceCode.getScope(identifier),
18037
18234
  identifier.name
18038
18235
  );
@@ -18329,7 +18526,7 @@ var store_insert_requires_on_conflict_default = createRule({
18329
18526
  });
18330
18527
 
18331
18528
  // src/rules/stepdown.ts
18332
- import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils26 } from "@typescript-eslint/utils";
18529
+ import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils31 } from "@typescript-eslint/utils";
18333
18530
  var STEPDOWN_DOCUMENTATION = {
18334
18531
  summary: "Place a private helper below its sole direct same-scope caller.",
18335
18532
  rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
@@ -18534,7 +18731,7 @@ function methodName(node) {
18534
18731
  return !node.computed && node.key.type === AST_NODE_TYPES77.Identifier ? node.key.name : null;
18535
18732
  }
18536
18733
  function referencedMethod(context, node, classVariables) {
18537
- const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils26.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
18734
+ const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils31.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
18538
18735
  const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
18539
18736
  if (node.object.type !== AST_NODE_TYPES77.ThisExpression && !isClassReference) return null;
18540
18737
  if (node.property.type === AST_NODE_TYPES77.PrivateIdentifier) return `#${node.property.name}`;
@@ -18587,11 +18784,11 @@ function classScope(context, node, computedReferenceNames) {
18587
18784
  const pinned = /* @__PURE__ */ new Set();
18588
18785
  const classVariables = /* @__PURE__ */ new Set();
18589
18786
  if (node.id !== null) {
18590
- const internal = ASTUtils26.findVariable(context.sourceCode.getScope(node), node.id.name);
18787
+ const internal = ASTUtils31.findVariable(context.sourceCode.getScope(node), node.id.name);
18591
18788
  if (internal !== null) classVariables.add(internal);
18592
18789
  }
18593
18790
  if (node.type === AST_NODE_TYPES77.ClassExpression && node.parent.type === AST_NODE_TYPES77.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES77.Identifier) {
18594
- const outer = ASTUtils26.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
18791
+ const outer = ASTUtils31.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
18595
18792
  if (outer !== null) classVariables.add(outer);
18596
18793
  }
18597
18794
  for (const method of methods) {
@@ -18627,7 +18824,7 @@ function classScope(context, node, computedReferenceNames) {
18627
18824
  return;
18628
18825
  }
18629
18826
  if (binding.type !== AST_NODE_TYPES77.Identifier) return;
18630
- const variable = ASTUtils26.findVariable(context.sourceCode.getScope(binding), binding.name);
18827
+ const variable = ASTUtils31.findVariable(context.sourceCode.getScope(binding), binding.name);
18631
18828
  if (variable !== null) {
18632
18829
  methodClassVariables.add(variable);
18633
18830
  methodAliases.add(variable);
@@ -18657,7 +18854,7 @@ function classScope(context, node, computedReferenceNames) {
18657
18854
  return;
18658
18855
  }
18659
18856
  if (!privateNames.has(target)) return;
18660
- const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils26.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
18857
+ const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils31.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
18661
18858
  if (objectVariable !== null && methodAliases.has(objectVariable)) {
18662
18859
  pinned.add(target);
18663
18860
  return;
@@ -18856,14 +19053,14 @@ function staticMemberName7(node) {
18856
19053
  if (node.computed && node.property.type === AST_NODE_TYPES78.Literal && typeof node.property.value === "string") return node.property.value;
18857
19054
  return null;
18858
19055
  }
18859
- function unwrap6(node) {
18860
- if (node.type === AST_NODE_TYPES78.AwaitExpression) return unwrap6(node.argument);
18861
- if (node.type === AST_NODE_TYPES78.ChainExpression) return unwrap6(node.expression);
18862
- if (node.type === AST_NODE_TYPES78.TSAsExpression || node.type === AST_NODE_TYPES78.TSNonNullExpression || node.type === AST_NODE_TYPES78.TSTypeAssertion) return unwrap6(node.expression);
19056
+ function unwrap7(node) {
19057
+ if (node.type === AST_NODE_TYPES78.AwaitExpression) return unwrap7(node.argument);
19058
+ if (node.type === AST_NODE_TYPES78.ChainExpression) return unwrap7(node.expression);
19059
+ if (node.type === AST_NODE_TYPES78.TSAsExpression || node.type === AST_NODE_TYPES78.TSNonNullExpression || node.type === AST_NODE_TYPES78.TSTypeAssertion) return unwrap7(node.expression);
18863
19060
  return node;
18864
19061
  }
18865
19062
  function stringValue(node) {
18866
- const current = unwrap6(node);
19063
+ const current = unwrap7(node);
18867
19064
  if (current.type === AST_NODE_TYPES78.Literal && typeof current.value === "string") return current.value;
18868
19065
  if (current.type === AST_NODE_TYPES78.TemplateLiteral && current.expressions.length === 0) return current.quasis[0]?.value.cooked ?? null;
18869
19066
  return null;
@@ -18872,7 +19069,7 @@ function importSource(node) {
18872
19069
  return typeof node.source.value === "string" ? node.source.value : null;
18873
19070
  }
18874
19071
  function requireSource(node) {
18875
- const current = unwrap6(node);
19072
+ const current = unwrap7(node);
18876
19073
  if (current.type !== AST_NODE_TYPES78.CallExpression || current.callee.type !== AST_NODE_TYPES78.Identifier || current.callee.name !== "require" || current.arguments.length !== 1 || current.arguments[0]?.type === AST_NODE_TYPES78.SpreadElement) return null;
18877
19074
  return stringValue(current.arguments[0]);
18878
19075
  }
@@ -18910,7 +19107,7 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
18910
19107
  return /* @__PURE__ */ new Set();
18911
19108
  };
18912
19109
  const sourcePath = (node) => {
18913
- const current = unwrap6(node);
19110
+ const current = unwrap7(node);
18914
19111
  const value = stringValue(current);
18915
19112
  if (value !== null) return sourceSuffixRe.test(value);
18916
19113
  if (current.type === AST_NODE_TYPES78.Identifier) return visible("paths", current.name);
@@ -18925,37 +19122,37 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
18925
19122
  return false;
18926
19123
  };
18927
19124
  const rawRead = (node) => {
18928
- const current = unwrap6(node);
19125
+ const current = unwrap7(node);
18929
19126
  if (current.type !== AST_NODE_TYPES78.CallExpression || current.arguments.length === 0) return false;
18930
- const callee = unwrap6(current.callee);
19127
+ const callee = unwrap7(current.callee);
18931
19128
  if (callee.type === AST_NODE_TYPES78.Identifier) {
18932
19129
  return visible("fsReaders", callee.name) && sourcePath(current.arguments[0]);
18933
19130
  }
18934
19131
  if (callee.type !== AST_NODE_TYPES78.MemberExpression) return false;
18935
19132
  const name2 = staticMemberName7(callee);
18936
- const object = unwrap6(callee.object);
19133
+ const object = unwrap7(callee.object);
18937
19134
  return name2 !== null && FS_READERS.has(name2) && object.type === AST_NODE_TYPES78.Identifier && visible("fsObjects", object.name) && sourcePath(current.arguments[0]);
18938
19135
  };
18939
19136
  const rawOrigins = (node) => {
18940
- const current = unwrap6(node);
19137
+ const current = unwrap7(node);
18941
19138
  if (current.type === AST_NODE_TYPES78.Identifier) return visibleRawOrigins(current.name);
18942
19139
  if (rawRead(current)) return /* @__PURE__ */ new Set([`${current.range[0]}:${current.range[1]}`]);
18943
19140
  if (current.type === AST_NODE_TYPES78.BinaryExpression && current.operator === "+") return /* @__PURE__ */ new Set([...rawOrigins(current.left), ...rawOrigins(current.right)]);
18944
19141
  if (current.type === AST_NODE_TYPES78.MemberExpression && staticMemberName7(current) === "length") return rawOrigins(current.object);
18945
19142
  if (current.type !== AST_NODE_TYPES78.CallExpression) return /* @__PURE__ */ new Set();
18946
- const callee = unwrap6(current.callee);
19143
+ const callee = unwrap7(current.callee);
18947
19144
  if (callee.type !== AST_NODE_TYPES78.MemberExpression) return /* @__PURE__ */ new Set();
18948
19145
  const name2 = staticMemberName7(callee);
18949
19146
  return name2 !== null && TEXT_TRANSFORMS.has(name2) ? rawOrigins(callee.object) : /* @__PURE__ */ new Set();
18950
19147
  };
18951
19148
  const evidenceOrigins = (node) => {
18952
- const current = unwrap6(node);
19149
+ const current = unwrap7(node);
18953
19150
  const direct = rawOrigins(current);
18954
19151
  if (direct.size > 0) return direct;
18955
19152
  if (current.type === AST_NODE_TYPES78.BinaryExpression || current.type === AST_NODE_TYPES78.LogicalExpression) return /* @__PURE__ */ new Set([...evidenceOrigins(current.left), ...evidenceOrigins(current.right)]);
18956
19153
  if (current.type === AST_NODE_TYPES78.UnaryExpression) return evidenceOrigins(current.argument);
18957
19154
  if (current.type !== AST_NODE_TYPES78.CallExpression) return /* @__PURE__ */ new Set();
18958
- const callee = unwrap6(current.callee);
19155
+ const callee = unwrap7(current.callee);
18959
19156
  if (callee.type !== AST_NODE_TYPES78.MemberExpression) return /* @__PURE__ */ new Set();
18960
19157
  const name2 = staticMemberName7(callee);
18961
19158
  if (name2 !== null && TEXT_PREDICATES.has(name2)) return rawOrigins(callee.object);
@@ -18963,15 +19160,15 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
18963
19160
  return /* @__PURE__ */ new Set();
18964
19161
  };
18965
19162
  const rawAssertionOrigins = (node) => {
18966
- const callee = unwrap6(node.callee);
19163
+ const callee = unwrap7(node.callee);
18967
19164
  if (callee.type === AST_NODE_TYPES78.Identifier && callee.name === "assert") {
18968
19165
  return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
18969
19166
  }
18970
19167
  if (callee.type !== AST_NODE_TYPES78.MemberExpression) return /* @__PURE__ */ new Set();
18971
19168
  const matcher = staticMemberName7(callee);
18972
19169
  if (matcher === null) return /* @__PURE__ */ new Set();
18973
- let receiver = unwrap6(callee.object);
18974
- while (receiver.type === AST_NODE_TYPES78.MemberExpression && EXPECT_MODIFIERS2.has(staticMemberName7(receiver) ?? "")) receiver = unwrap6(receiver.object);
19170
+ let receiver = unwrap7(callee.object);
19171
+ while (receiver.type === AST_NODE_TYPES78.MemberExpression && EXPECT_MODIFIERS2.has(staticMemberName7(receiver) ?? "")) receiver = unwrap7(receiver.object);
18975
19172
  if (receiver.type === AST_NODE_TYPES78.CallExpression && receiver.callee.type === AST_NODE_TYPES78.Identifier && receiver.callee.name === "expect") {
18976
19173
  if (!EXPECT_MATCHERS.has(matcher)) return /* @__PURE__ */ new Set();
18977
19174
  return new Set([...receiver.arguments, ...node.arguments].flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
@@ -18980,7 +19177,7 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
18980
19177
  return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
18981
19178
  };
18982
19179
  const rawRegexExtractionOrigins = (node) => {
18983
- const callee = unwrap6(node.callee);
19180
+ const callee = unwrap7(node.callee);
18984
19181
  if (callee.type !== AST_NODE_TYPES78.MemberExpression || staticMemberName7(callee) !== "matchAll" || node.arguments.length !== 1) return /* @__PURE__ */ new Set();
18985
19182
  const argument = node.arguments[0];
18986
19183
  if (argument?.type !== AST_NODE_TYPES78.Literal || !(argument.value instanceof RegExp)) return /* @__PURE__ */ new Set();
@@ -19003,11 +19200,11 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19003
19200
  }
19004
19201
  };
19005
19202
  const sourceCollection = (node) => {
19006
- const current = unwrap6(node);
19203
+ const current = unwrap7(node);
19007
19204
  return current.type === AST_NODE_TYPES78.ArrayExpression && current.elements.length > 0 && current.elements.every((element) => element !== null && element.type !== AST_NODE_TYPES78.SpreadElement && sourcePath(element));
19008
19205
  };
19009
19206
  const declaredNames2 = (node) => {
19010
- const current = unwrap6(node);
19207
+ const current = unwrap7(node);
19011
19208
  if (current.type === AST_NODE_TYPES78.Identifier) return [current.name];
19012
19209
  if (current.type === AST_NODE_TYPES78.AssignmentPattern) return declaredNames2(current.left);
19013
19210
  if (current.type === AST_NODE_TYPES78.RestElement) return declaredNames2(current.argument);
@@ -19059,7 +19256,7 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19059
19256
  if (node.left.type === AST_NODE_TYPES78.Identifier) declare(node.left.name, { path: sourcePath(node.right), rawOrigins: rawOrigins(node.right) });
19060
19257
  },
19061
19258
  ForOfStatement(node) {
19062
- const right = unwrap6(node.right);
19259
+ const right = unwrap7(node.right);
19063
19260
  const collection = right.type === AST_NODE_TYPES78.Identifier && visible("collections", right.name);
19064
19261
  const left = node.left.type === AST_NODE_TYPES78.VariableDeclaration ? node.left.declarations[0]?.id : node.left;
19065
19262
  if (collection && left?.type === AST_NODE_TYPES78.Identifier) declare(left.name, { path: true });
@@ -19092,7 +19289,8 @@ var SOLE_EXPORT_MATCHES_FILENAME_DOCUMENTATION = {
19092
19289
  category: "maintainability",
19093
19290
  limitations: [
19094
19291
  "Framework entrypoints, generic stems covered by no-generic-single-export-module, tests, generated files, anonymous defaults, CommonJS, and re-exports are excluded.",
19095
- "The rule compares the primary filename stem and preserves conventional suffixes such as .server or .worker."
19292
+ "The rule compares the primary filename stem and preserves a single private underscore prefix and conventional suffixes such as .server or .worker.",
19293
+ "Exported destructuring patterns are excluded rather than undercounted as public exports."
19096
19294
  ],
19097
19295
  examples: [
19098
19296
  { id: "matching-class", title: "Match a class and module", outcome: "no-match", files: [{ path: "src/artifact-store.ts", source: "export class ArtifactStore {}" }], focusPath: "src/artifact-store.ts", expectedCount: 0, public: true },
@@ -19148,7 +19346,7 @@ var sole_export_matches_filename_default = createRule({
19148
19346
  create(context) {
19149
19347
  const fileStem = stem3(context.filename);
19150
19348
  const normalizedFilename = context.filename.replaceAll("\\", "/");
19151
- if (EXCLUDED_STEMS.has(fileStem) || normalizedFilename.includes("/pages/") || context.filename.endsWith(".d.ts") || isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
19349
+ if (EXCLUDED_STEMS.has(fileStem) || /(?:^|\/)app\/(?:.*\/)?(?:global-)?error\.[jt]sx?$/u.test(normalizedFilename) || normalizedFilename.includes("/pages/") || context.filename.endsWith(".d.ts") || isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
19152
19350
  return {
19153
19351
  "Program:exit"(program) {
19154
19352
  const exports = [];
@@ -19167,6 +19365,7 @@ var sole_export_matches_filename_default = createRule({
19167
19365
  publicExports.add(declaration.id.name);
19168
19366
  }
19169
19367
  if (declaration?.type === AST_NODE_TYPES79.VariableDeclaration) {
19368
+ if (declaration.declarations.some((item) => item.id.type !== AST_NODE_TYPES79.Identifier)) return;
19170
19369
  for (const item of declaration.declarations) {
19171
19370
  if (item.id.type === AST_NODE_TYPES79.Identifier) publicExports.add(item.id.name);
19172
19371
  }
@@ -19189,8 +19388,12 @@ var sole_export_matches_filename_default = createRule({
19189
19388
  if (unique.size !== 1 || publicExports.size !== 1) return;
19190
19389
  const only = [...unique.values()][0];
19191
19390
  if (only === void 0) return;
19192
- const expected = kebabCase(only.name);
19193
- if (expected === "" || expected === fileStem.toLowerCase()) return;
19391
+ if (only.name === "onRouterTransitionStart" && /(?:^|\/)instrumentation-client\.[jt]s$/u.test(normalizedFilename)) return;
19392
+ if (only.name === "collections" && /(?:^|\/)src\/content\.config\.(?:ts|js|mjs)$/u.test(normalizedFilename) && program.body.some((statement) => statement.type === AST_NODE_TYPES79.ImportDeclaration && statement.source.value === "astro:content")) return;
19393
+ const exportedStem = kebabCase(only.name);
19394
+ if (exportedStem === "") return;
19395
+ const expected = `${fileStem.startsWith("_") ? "_" : ""}${exportedStem}`;
19396
+ if (expected === fileStem.toLowerCase()) return;
19194
19397
  context.report({ node: only.node, messageId: "matchSoleExport", data: { exported: only.name, expected } });
19195
19398
  }
19196
19399
  };
@@ -19238,7 +19441,7 @@ var iac_source_coupled_test_default = createSourceCoupledRule(
19238
19441
  // src/rules/require-pascal-case-zod-schema-name.ts
19239
19442
  import {
19240
19443
  AST_NODE_TYPES as AST_NODE_TYPES80,
19241
- ASTUtils as ASTUtils27
19444
+ ASTUtils as ASTUtils32
19242
19445
  } from "@typescript-eslint/utils";
19243
19446
  var REQUIRE_PASCAL_CASE_ZOD_SCHEMA_NAME_DOCUMENTATION = {
19244
19447
  summary: "Require confirmed module-level Zod schema contracts to use PascalCase with a `Schema` suffix.",
@@ -19439,7 +19642,7 @@ var require_pascal_case_zod_schema_name_default = createRule({
19439
19642
  const zodBindings = /* @__PURE__ */ new Set();
19440
19643
  const schemaBindings = /* @__PURE__ */ new Set();
19441
19644
  function resolvedBinding(identifier) {
19442
- return ASTUtils27.findVariable(
19645
+ return ASTUtils32.findVariable(
19443
19646
  context.sourceCode.getScope(identifier),
19444
19647
  identifier.name
19445
19648
  );
@@ -19684,7 +19887,7 @@ var RULES = {
19684
19887
  };
19685
19888
  var meta = {
19686
19889
  name: "@sarj/eslint-plugin",
19687
- version: "15.17.8"
19890
+ version: "15.17.9"
19688
19891
  };
19689
19892
  var APPLICATION_ONLY_RULES = [];
19690
19893
  var LIBRARY_IMPORT_POLICY = ["error", {