@sarj/eslint-plugin 19.17.0 → 19.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/catalog.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  publicDocumentation,
3
3
  ruleSpec,
4
4
  src_default
5
- } from "./chunk-4USNCXB3.js";
5
+ } from "./chunk-UI4ADSAA.js";
6
6
 
7
7
  // src/docs/directive-disable-enable-pair.ts
8
8
  var DIRECTIVE_DISABLE_ENABLE_PAIR_DOCUMENTATION = {
@@ -5118,11 +5118,13 @@ var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
5118
5118
  summary: "Preserve producer-proven tag/payload and Boolean state relationships in return types.",
5119
5119
  defaultLevel: "warning",
5120
5120
  rationale: "A flat object with optional branch data loses the relationship between a tag and its payload. Callers cannot narrow to the required fields, and the type permits incomplete or contradictory combinations.",
5121
- remediation: "If the variants are mutually exclusive, represent each with a literal tag and its required payload. Preserve shared fields on every member, including payload value types and nullability. For a producer returning a subset of a broader wire contract, give the producer a precise domain return type after validation.",
5121
+ remediation: "If the variants are mutually exclusive, represent each with a literal tag and its required payload. For predicates over one current state, consider returning that authoritative state instead of storing multiple derived flags. Preserve every valid state, independent dimension, shared field, payload value type and nullability. For a producer returning a subset of a broader wire contract, give the producer a precise domain return type after validation.",
5122
5122
  category: "correctness",
5123
5123
  autofix: "safe",
5124
5124
  limitations: [
5125
- "Names, Boolean counts and optional-field counts never establish a finding. A producer's literal writes establish either tag-specific payload presence or a pair of required Boolean fields with two or three distinct combinations, with both fields varying. Boolean members may use boolean, true | false or one unmerged nongeneric local alias of either. All four combinations, constant dimensions, optional/unknown dimensions and expression-valued flags are excluded. At most eight Boolean fields are compared. Only one relationship is reported per contract; payload evidence takes precedence. Independently nullable payloads do not erase Boolean evidence.",
5125
+ "Names, Boolean counts and optional-field counts never establish a finding. A producer's literal writes establish either tag-specific payload presence or a pair of required Boolean fields with two or three distinct combinations, with both fields varying. Boolean members may use boolean, true | false or one unmerged nongeneric local alias of either. All four combinations, constant dimensions and optional/unknown dimensions are excluded. Expression-valued flags require the separate typed evidence described below. At most eight Boolean fields are compared. Only one relationship is reported per contract; existing literal/payload evidence takes precedence. Independently nullable payloads do not erase Boolean evidence.",
5126
+ "With type services enabled, a synchronous nongeneric producer with exactly one own return, a final direct object return and a supported flat local contract also exposes strict equality predicates over the same earlier local const binding. Native types at both reads must be finite domains of at most sixteen string/number literals containing both distinct singleton comparison values. Literal constants and direct named-import/local enum members are supported; enum declarations and members are resolved by symbol identity and raw runtime literal values, including renamed imports and reexports. At most 128 candidate pairs per file are analyzed; exceeding the bound abstains from all typed findings in that file. No automatic fix is offered for these projections.",
5127
+ "The const snapshot may be destructured or copied from another primitive binding, passed to callbacks, or read by closures without losing its value. Mutable bindings, parameters and module captures, property reads/getters, open/nullable/object/symbol domains, async producers, multiple own returns, generic/inherited/imported contracts and dynamic eval abstain. Nested callback returns do not belong to the producer. Enum namespace writes, aliases, escapes, computed accesses, calls and merged declarations in the analyzed file abstain. Cross-file enum declarations can supply type/constant identity; external mutation, dishonest declarations/assertions and runtime monkeypatching are not modeled. The analysis proves a relationship under those type and mutation assumptions, not domain intent or a whole-program ownership guarantee.",
5126
5128
  "Producer analysis requires a finite literal tag, every declared tag syntactically witnessed, exhaustive simple return paths without literal constant conditions, plain object returns, and consistent optional-field presence for each tag. It resolves only unmerged local aliases/interfaces without inheritance or generics, one local alias of a literal tag domain, and the built-in Promise around async returns. Callable or opaque own then properties abstain for async producers because Promise assimilation can replace the fulfilled value. Inherited then methods and external prototype mutation are not modeled. This is syntax-only evidence of field presence; explicit undefined, void and null payloads do not establish payload requirements, while expression value types are not inferred. Type-only assertion/satisfies/non-null wrappers, literal computed string keys and a sole-use const immediately before its return are recognized. Const alias chains, observation, mutation and dynamic eval abstain. Dynamic returns, spreads, dynamic computed keys, accessors, loops and try statements are deliberately skipped.",
5127
5129
  "A switch producer is supported only for a non-overloaded function declaration when its sole body statement switches on an unchanged direct string or number parameter, has unique same-kind literal labels and a default, and every nonempty case body is a sole object return or throw, optionally inside sole blocks. Empty labels may share the next terminating body; nested conditions, conditional returns, statement prefixes, breaks, other fallthrough, captured/closed inputs, dynamic eval and switches under preceding control flow abstain. Broad parameter domains establish input witnesses for each label and the default; this does not infer branch-specific runtime effects or expression value types. Path visits, depth and returned objects are bounded conservatively.",
5128
5130
  "These are refactoring advisories, not proven defects or proof that other writers cannot produce more states. Historical observations, partial results, unknown states and independently completing operations may need the broader contract. Review all readers and writers before migrating a shared contract; preserve every valid combination. A union of existing literal flag pairs is the smallest type-only replacement; a named disposition is a reviewed domain migration.",
@@ -5138,6 +5140,8 @@ var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
5138
5140
  { id: "explicit-produced-variants", scenarioId: "produced-variants", title: "Give each produced variant its payload", outcome: "no-match", files: [{ path: "src/job.ts", source: 'function job(done: boolean): { phase: "queued"; ticket: string } | { phase: "complete" } { return done ? { phase: "complete" } : { phase: "queued", ticket: "t" }; }' }], focusPath: "src/job.ts", expectedCount: 0, public: true },
5139
5141
  { id: "flat-switch-producer", scenarioId: "switch-producer", title: "A complete dispatcher preserves each returned variant", outcome: "match", files: [{ path: "src/job.ts", source: 'function job(command: string): { phase: "queued" | "complete"; ticket?: string } { switch (command) { case "start": return { phase: "queued", ticket: "t" }; default: return { phase: "complete" }; } }' }], focusPath: "src/job.ts", expectedCount: 1, public: true },
5140
5142
  { id: "explicit-switch-variants", scenarioId: "switch-producer", title: "An explicit dispatcher result supports payload narrowing", outcome: "no-match", files: [{ path: "src/job.ts", source: 'function job(command: string): { phase: "queued"; ticket: string } | { phase: "complete" } { switch (command) { case "start": return { phase: "queued", ticket: "t" }; default: return { phase: "complete" }; } }' }], focusPath: "src/job.ts", expectedCount: 0, public: true },
5143
+ { id: "immutable-state-projections", scenarioId: "state-projection", title: "Two predicates over one immutable phase cannot both be true", outcome: "match", files: [{ path: "src/projection.ts", source: 'type Phase = "ready" | "waiting" | "unknown"; declare function snapshot(): Phase; function state(): { ready: boolean; waiting: boolean } { const phase = snapshot(); return { ready: phase === "ready", waiting: phase === "waiting" }; }' }], focusPath: "src/projection.ts", expectedCount: 1, public: true },
5144
+ { id: "authoritative-state", scenarioId: "state-projection", title: "Return the phase while preserving the independent timeout dimension", outcome: "no-match", files: [{ path: "src/projection.ts", source: 'type Phase = "ready" | "waiting" | "reconnecting" | "unknown"; declare function snapshot(): Phase; function state(timeout: boolean): { phase: Phase; timeout: boolean } { const phase = snapshot(); return { phase, timeout }; }' }], focusPath: "src/projection.ts", expectedCount: 0, public: true },
5141
5145
  {
5142
5146
  id: "safe-autofix",
5143
5147
  title: "Preserve an unused private producer's exact existing variants",
@@ -6437,6 +6441,57 @@ var PRODUCTION_DERIVED_TEST_CASES_DOCUMENTATION = {
6437
6441
  ]
6438
6442
  };
6439
6443
 
6444
+ // src/docs/react-prefer-updater-function.ts
6445
+ var CART = "import { useState } from 'react'; export function Cart({ load }: { load: () => Promise<string> }) { const [items, setItems] = useState<string[]>([]); const add = async () => { const item = await load(); ";
6446
+ var CART_END = ' }; return <button type="button" onClick={add}>{items.length}</button>; }';
6447
+ var RENDER_PHASE = "import { useState } from 'react'; export function Panel({ request }: { request: boolean }) { const [dialog, setDialog] = useState({ open: request, request }); if (dialog.request !== request) { setDialog({ open: request || dialog.open, request }); } return <p>{String(dialog.open)}</p>; }";
6448
+ var REACT_PREFER_UPDATER_FUNCTION_DOCUMENTATION = {
6449
+ defaultLevel: "warning",
6450
+ summary: "Use the updater form when the next React state is built from the current state.",
6451
+ rationale: "A state variable holds the snapshot from the render that created the callback. After an await, in a timer, promise callback, subscription or effect, or when two updates run before React re-renders, that snapshot is stale. `setItems([...items, item])` then drops whatever another update added in the meantime. An updater receives the latest queued state, so each update builds on the one before it.",
6452
+ remediation: "Pass an updater instead. Rewrite `setItems([...items, item])` as `setItems((previous) => [...previous, item])`, and read `previous` wherever the argument read the state or a local derived from it. Compute IDs, timestamps and other impure values before the call, because React runs updaters twice in Strict Mode development builds.",
6453
+ category: "correctness",
6454
+ limitations: [
6455
+ "The rule is advisory and has no automatic fix. In a synchronous event handler that updates once, the snapshot is still current, so the rewrite keeps today's behavior and protects the handler against a later await or a second update.",
6456
+ "The rule pairs a setter with its state through one array destructure of a React useState call, including renamed, default and namespace React imports, and resolves every read by scope. It ignores setters reached through props, aliases or custom hooks that return state tuples. It also ignores object-destructured state, useReducer dispatch and reassigned bindings.",
6457
+ "The rule reports only state reads that the setter argument itself evaluates. It ignores reads inside nested functions, type-only reads, a bare identity write and next values first stored in a local. It also ignores setter calls made directly in the function that declares the state, because they run during render, where the state belongs to the render in progress."
6458
+ ],
6459
+ references: [
6460
+ "https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state",
6461
+ "https://react.dev/learn/queueing-a-series-of-state-updates",
6462
+ "https://bun.com/docs/lint/rules/bun/react-prefer-updater-function"
6463
+ ],
6464
+ examples: [
6465
+ {
6466
+ id: "snapshot-after-await",
6467
+ title: "Append to the render snapshot after an await",
6468
+ outcome: "match",
6469
+ files: [{ path: "src/cart.tsx", source: `${CART}setItems([...items, item]);${CART_END}` }],
6470
+ focusPath: "src/cart.tsx",
6471
+ expectedCount: 1,
6472
+ public: true
6473
+ },
6474
+ {
6475
+ id: "updater-after-await",
6476
+ title: "Append to the latest state through an updater",
6477
+ outcome: "no-match",
6478
+ files: [{ path: "src/cart.tsx", source: `${CART}setItems((previous) => [...previous, item]);${CART_END}` }],
6479
+ focusPath: "src/cart.tsx",
6480
+ expectedCount: 0,
6481
+ public: true
6482
+ },
6483
+ {
6484
+ id: "render-phase-update",
6485
+ title: "Adjust state from the render in progress",
6486
+ outcome: "no-match",
6487
+ files: [{ path: "src/panel.tsx", source: RENDER_PHASE }],
6488
+ focusPath: "src/panel.tsx",
6489
+ expectedCount: 0,
6490
+ public: false
6491
+ }
6492
+ ]
6493
+ };
6494
+
6440
6495
  // src/docs/repeated-static-call-cases.ts
6441
6496
  var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
6442
6497
  summary: "Review three or more consecutive static-input call assertions as potential named cases.",
@@ -7574,6 +7629,7 @@ var RULE_DOCUMENTATION = {
7574
7629
  "prefer-zod-infer": PREFER_ZOD_INFER_DOCUMENTATION,
7575
7630
  "prefer-zod-parse-output-type": PREFER_ZOD_PARSE_OUTPUT_TYPE_DOCUMENTATION,
7576
7631
  "production-derived-test-cases": PRODUCTION_DERIVED_TEST_CASES_DOCUMENTATION,
7632
+ "react-prefer-updater-function": REACT_PREFER_UPDATER_FUNCTION_DOCUMENTATION,
7577
7633
  "repeated-static-call-cases": REPEATED_STATIC_CALL_CASES_DOCUMENTATION,
7578
7634
  "require-assert-never": REQUIRE_ASSERT_NEVER_DOCUMENTATION,
7579
7635
  "require-button-accessible-name": REQUIRE_BUTTON_ACCESSIBLE_NAME_DOCUMENTATION,
@@ -16467,6 +16467,9 @@ var VIEWS = /* @__PURE__ */ new WeakMap();
16467
16467
  var DIRECT_EVAL = /* @__PURE__ */ new WeakMap();
16468
16468
  var REACT_IMPORTS = /* @__PURE__ */ new WeakMap();
16469
16469
  var PARAMETER_BINDINGS = /* @__PURE__ */ new WeakMap();
16470
+ function importsReactValue(source) {
16471
+ return source.ast.body.some((statement) => statement.type === "ImportDeclaration" && statement.source.value === "react" && statement.importKind !== "type");
16472
+ }
16470
16473
  function renderFunction(node) {
16471
16474
  let parent = parentOf(node);
16472
16475
  while (parent !== null) {
@@ -16644,7 +16647,7 @@ var no_readonly_prop_state_default = defineRule({
16644
16647
  tsrs = null;
16645
16648
  pending = [];
16646
16649
  const source = context.sourceCode;
16647
- if (isGeneratedFile({ filename: context.filename, cwd: context.cwd }, source.text) || isTestFile({ filename: context.filename, cwd: context.cwd }) || !source.ast.body.some((statement) => statement.type === "ImportDeclaration" && statement.source.value === "react" && statement.importKind !== "type")) return false;
16650
+ if (isGeneratedFile({ filename: context.filename, cwd: context.cwd }, source.text) || isTestFile({ filename: context.filename, cwd: context.cwd }) || !importsReactValue(source)) return false;
16648
16651
  tsrs = tsrsServices(context);
16649
16652
  return true;
16650
16653
  },
@@ -23411,6 +23414,94 @@ function literalTag(node) {
23411
23414
  return typeof node.value === "string" || typeof node.value === "number" || typeof node.value === "boolean" ? node.value : void 0;
23412
23415
  }
23413
23416
 
23417
+ // src/rules/_discriminated-union-projections.ts
23418
+ function projectedBooleanPairs(owner, returned2, source) {
23419
+ if (owner.async || owner.generator || isPresent(owner.typeParameters) || owner.body?.type !== "BlockStatement" || parentOf(returned2) !== owner.body || owner.body.body.at(-1) !== returned2 || returned2.argument?.type !== "ObjectExpression") return [];
23420
+ const contract = returnContract(owner, source);
23421
+ const shape = contract === void 0 ? void 0 : objectContract(contract.members);
23422
+ if (shape === void 0 || contract === void 0) return [];
23423
+ const properties2 = returnedProperties([returned2.argument], shape)?.[0];
23424
+ if (properties2 === void 0) return [];
23425
+ const fields2 = [...shape.members].filter(([, member3]) => !member3.optional && booleanType(member3.typeAnnotation?.typeAnnotation, source));
23426
+ if (fields2.length > 8) return [];
23427
+ const comparisons = [];
23428
+ for (const [field] of fields2) {
23429
+ const value = properties2.get(field);
23430
+ if (value?.type !== "BinaryExpression" || value.operator !== "===" || value.left.type !== "Identifier" || !constantSyntax(value.right, source)) continue;
23431
+ const binding3 = localSnapshot(value.left, owner, source);
23432
+ if (binding3 !== null) comparisons.push({ field, binding: binding3, left: value.left, right: value.right });
23433
+ }
23434
+ return comparisons.flatMap((left, index) => comparisons.slice(index + 1).filter((right) => right.binding === left.binding).map((right) => ({ reportNode: contract.reportNode, left, right })));
23435
+ }
23436
+ function localSnapshot(identifier, owner, source) {
23437
+ const variable = findVariable(source.getScope(identifier), identifier.name);
23438
+ const declaration = variable?.defs[0]?.node;
23439
+ if (variable === null || variable.defs.length !== 1 || declaration?.type !== "VariableDeclarator" || !isConstDeclaration(parentOf(declaration)) || !isPresent(declaration.init) || enclosingFunction(declaration) !== owner || declaration.range[1] >= identifier.range[0] || !stableValueBinding(variable)) return null;
23440
+ return variable;
23441
+ }
23442
+ function constantSyntax(node, source) {
23443
+ if (node.type === "Literal") return typeof node.value === "string" || typeof node.value === "number" && Number.isFinite(node.value);
23444
+ if (node.type !== "MemberExpression" || node.computed || node.optional || node.object.type !== "Identifier" || node.property.type !== "Identifier") return false;
23445
+ const variable = findVariable(source.getScope(node.object), node.object.name);
23446
+ const definition = variable?.defs[0]?.node;
23447
+ if (variable === null || variable.defs.length !== 1 || definition?.type !== "TSEnumDeclaration" && definition?.type !== "ImportSpecifier" || !stableValueBinding(variable)) return false;
23448
+ return variable.references.every((reference) => {
23449
+ if (typeOnlyReference(reference)) return true;
23450
+ const member3 = parentOf(reference.identifier);
23451
+ if (member3?.type !== "MemberExpression" || member3.object !== reference.identifier || member3.computed || member3.optional || writtenMember(member3)) return false;
23452
+ const use = parentOf(member3);
23453
+ return !((use?.type === "CallExpression" || use?.type === "NewExpression") && use.callee === member3);
23454
+ });
23455
+ }
23456
+ function enumConstant(node, native) {
23457
+ if (node.type !== "MemberExpression") return false;
23458
+ const root = native.node(node.object);
23459
+ const member3 = native.node(node.property);
23460
+ let symbol = root === void 0 ? void 0 : native.checker.getSymbolAtLocation(root);
23461
+ if (symbol === void 0 || member3 === void 0) return false;
23462
+ if ((symbol.flags & native.sync.SymbolFlags.Alias) !== 0) symbol = native.checker.getAliasedSymbol(symbol);
23463
+ const declarations = symbol.declarations;
23464
+ if (declarations.length !== 1 || declarations[0]?.kind !== native.ast.SyntaxKind.EnumDeclaration) return false;
23465
+ const selected = native.checker.getSymbolAtLocation(member3)?.declarations;
23466
+ return selected?.length === 1 && selected[0]?.kind === native.ast.SyntaxKind.EnumMember;
23467
+ }
23468
+ function projectedBooleanEvidence(file, candidates2) {
23469
+ const nodes = candidates2.flatMap(({ left, right }) => [left.left, right.left, left.right, right.right]);
23470
+ const types = file.typesOf(nodes);
23471
+ const unions = types.flatMap((type) => type !== void 0 && type.isUnionType() ? [type] : []);
23472
+ const lists = file.batch(unions.map((type) => type.getTypes.gen()));
23473
+ let index = 0;
23474
+ const domains = types.map((type) => type === void 0 ? void 0 : primitiveValues(type.isUnionType() ? lists[index++] ?? [] : [type], file));
23475
+ const native = nativeTypes(file);
23476
+ const constants = /* @__PURE__ */ new Map();
23477
+ function constant(node, value) {
23478
+ if (node.type === "Literal") return node.value === value;
23479
+ let valid = constants.get(node);
23480
+ if (valid === void 0) {
23481
+ valid = enumConstant(node, native);
23482
+ constants.set(node, valid);
23483
+ }
23484
+ return valid;
23485
+ }
23486
+ return candidates2.map(({ left, right }, ordinal) => {
23487
+ const [a, b, x, y] = domains.slice(ordinal * 4, ordinal * 4 + 4);
23488
+ const first = x?.[0];
23489
+ const second = y?.[0];
23490
+ return a !== void 0 && b !== void 0 && x?.length === 1 && y?.length === 1 && first !== void 0 && second !== void 0 && first !== second && constant(left.right, first) && constant(right.right, second) && a.includes(first) && a.includes(second) && b.includes(first) && b.includes(second);
23491
+ });
23492
+ }
23493
+ function primitiveValues(types, file) {
23494
+ if (types.length < 1 || types.length > 16) return void 0;
23495
+ const values = [];
23496
+ for (const type of types) {
23497
+ if ((type.flags & (file.flags.StringLiteral | file.flags.NumberLiteral)) === 0) return void 0;
23498
+ const value = type.value;
23499
+ if (typeof value !== "string" && !(typeof value === "number" && Number.isFinite(value))) return void 0;
23500
+ values.push(value);
23501
+ }
23502
+ return values;
23503
+ }
23504
+
23414
23505
  // src/rules/prefer-discriminated-union.ts
23415
23506
  var NAME99 = "prefer-discriminated-union";
23416
23507
  var prefer_discriminated_union_default = defineRule({
@@ -23424,12 +23515,14 @@ var prefer_discriminated_union_default = defineRule({
23424
23515
  schema: [],
23425
23516
  messages: {
23426
23517
  preferProducedVariants: "This producer returns tag-specific payloads for `{{tag}}`, but its flat return contract makes them optional. If these variants define the domain, use a discriminated union with each variant's required payload, or give this producer a precise return type separate from the broader contract.",
23427
- preferProducedState: "This producer returns a restricted set of literal combinations for `{{fields}}`, while its Boolean fields permit all four. Review a union preserving every returned combination, or a typed disposition if these are domain phases. Other writers and readers of a shared contract require review."
23518
+ preferProducedState: "This producer returns a restricted set of literal combinations for `{{fields}}`, while its Boolean fields permit all four. Review a union preserving every returned combination, or a typed disposition if these are domain phases. Other writers and readers of a shared contract require review.",
23519
+ preferProjectedState: "This producer compares the same immutable primitive snapshot with distinct state values for `{{fields}}`, but its Boolean return contract also permits true/true. Review returning the authoritative state or preserving every produced combination in a union. Keep independent dimensions and unknown states; other writers and readers of a shared contract require review."
23428
23520
  }
23429
23521
  },
23430
23522
  createOnce(context) {
23431
23523
  const reportedContracts = /* @__PURE__ */ new Set();
23432
23524
  const producers = [];
23525
+ const returns = /* @__PURE__ */ new Map();
23433
23526
  function rememberProducer(node) {
23434
23527
  producers.push(node);
23435
23528
  }
@@ -23449,6 +23542,7 @@ var prefer_discriminated_union_default = defineRule({
23449
23542
  before() {
23450
23543
  reportedContracts.clear();
23451
23544
  producers.length = 0;
23545
+ returns.clear();
23452
23546
  const file = { filename: context.filename, cwd: context.cwd };
23453
23547
  if (isTestFile(file) || isGeneratedFile(file, context.sourceCode.text)) return false;
23454
23548
  return void 0;
@@ -23456,8 +23550,28 @@ var prefer_discriminated_union_default = defineRule({
23456
23550
  ArrowFunctionExpression: rememberProducer,
23457
23551
  FunctionDeclaration: rememberProducer,
23458
23552
  FunctionExpression: rememberProducer,
23553
+ ReturnStatement(node) {
23554
+ const owner = enclosingFunction(node);
23555
+ if (owner !== null) returns.set(owner, returns.has(owner) ? null : node);
23556
+ },
23459
23557
  "Program:exit"() {
23460
23558
  for (const producer of producers) checkProducer(producer);
23559
+ if (hasIdentifierNamed(context.sourceCode, "eval")) return;
23560
+ const candidates2 = [];
23561
+ for (const producer of producers) {
23562
+ const returned2 = returns.get(producer);
23563
+ if (!isPresent(returned2)) continue;
23564
+ candidates2.push(...projectedBooleanPairs(producer, returned2, context.sourceCode).filter(({ reportNode }) => !reportedContracts.has(reportNode)));
23565
+ if (candidates2.length > 128) return;
23566
+ }
23567
+ if (candidates2.length === 0) return;
23568
+ const evidence = tsrsServices(context)?.run((file) => projectedBooleanEvidence(file, candidates2));
23569
+ if (!isPresent(evidence)) return;
23570
+ candidates2.forEach(({ reportNode, left, right }, index) => {
23571
+ if (evidence[index] !== true || reportedContracts.has(reportNode)) return;
23572
+ reportedContracts.add(reportNode);
23573
+ context.report({ node: reportNode, messageId: "preferProjectedState", data: { fields: [left.field, right.field].join("`, `") } });
23574
+ });
23461
23575
  }
23462
23576
  };
23463
23577
  }
@@ -31806,8 +31920,62 @@ var production_derived_test_cases_default = defineRule({
31806
31920
  }
31807
31921
  });
31808
31922
 
31923
+ // src/rules/react-prefer-updater-function.ts
31924
+ var NAME131 = "react-prefer-updater-function";
31925
+ function stateForSetter(source, setter) {
31926
+ const declarator = setter.defs[0]?.node;
31927
+ if (setter.defs.length !== 1 || declarator?.type !== "VariableDeclarator" || declarator.id.type !== "ArrayPattern" || declarator.init === null) return null;
31928
+ const [value, update] = declarator.id.elements;
31929
+ const call = unwrapExpression(declarator.init);
31930
+ if (value?.type !== "Identifier" || update?.type !== "Identifier" || !setter.identifiers.includes(update) || call.type !== "CallExpression" || !reactHook(source, call.callee, "useState") || !stableValueBinding(setter)) return null;
31931
+ const state = source.getDeclaredVariables(declarator).find((variable) => variable.identifiers.includes(value));
31932
+ return state !== void 0 && stableValueBinding(state) ? { state, owner: enclosingFunction(declarator) } : null;
31933
+ }
31934
+ function readsStateEagerly(state, argument) {
31935
+ return state.references.some((reference) => reference.isRead() && !typeOnlyReference(reference) && evaluatedBy(reference.identifier, argument));
31936
+ }
31937
+ function evaluatedBy(node, argument) {
31938
+ for (let current = node; current !== null && !isFunction(current); current = parentOf(current)) {
31939
+ if (current === argument) return true;
31940
+ }
31941
+ return false;
31942
+ }
31943
+ function isFunction(node) {
31944
+ return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration";
31945
+ }
31946
+ var react_prefer_updater_function_default = defineRule({
31947
+ meta: {
31948
+ type: "problem",
31949
+ docs: { description: "Use the updater form when the next React state is built from the current state.", url: docsUrl(NAME131) },
31950
+ schema: [],
31951
+ messages: {
31952
+ preferUpdaterFunction: "`{{setter}}` builds the next state from the `{{state}}` snapshot, which is stale after an await, in timers and effects, or when two updates run before a re-render. Pass an updater: `{{setter}}((previous) => ...)`, reading `previous` wherever the argument read `{{state}}` or a value derived from it. Compute impure values such as IDs or timestamps before the call so the updater stays pure."
31953
+ }
31954
+ },
31955
+ createOnce(context) {
31956
+ return {
31957
+ before() {
31958
+ const source = context.sourceCode;
31959
+ return !isGeneratedFile({ filename: context.filename, cwd: context.cwd }, source.text) && importsReactValue(source);
31960
+ },
31961
+ CallExpression(node) {
31962
+ const [argument] = node.arguments;
31963
+ const callee = unwrapExpression(node.callee);
31964
+ if (node.arguments.length !== 1 || argument === void 0 || argument.type === "SpreadElement" || callee.type !== "Identifier") return;
31965
+ const value = unwrapExpression(argument);
31966
+ if (value.type === "Identifier" || isFunction(value)) return;
31967
+ const source = context.sourceCode;
31968
+ const setter = findVariable(source.getScope(callee), callee.name);
31969
+ const binding3 = setter === null ? null : stateForSetter(source, setter);
31970
+ if (setter === null || binding3 === null || enclosingFunction(node) === binding3.owner || !readsStateEagerly(binding3.state, argument)) return;
31971
+ context.report({ node, messageId: "preferUpdaterFunction", data: { setter: setter.name, state: binding3.state.name } });
31972
+ }
31973
+ };
31974
+ }
31975
+ });
31976
+
31809
31977
  // src/rules/repeated-static-call-cases.ts
31810
- var NAME131 = "repeated-static-call-cases";
31978
+ var NAME132 = "repeated-static-call-cases";
31811
31979
  var TEST_MODULES2 = /* @__PURE__ */ new Set(["@jest/globals", "@playwright/test", "bun:test", "node:test", "vitest"]);
31812
31980
  var TEST_NAMES3 = /* @__PURE__ */ new Set(["it", "test"]);
31813
31981
  var TEST_MODIFIERS5 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
@@ -31931,7 +32099,7 @@ var repeated_static_call_cases_default = defineRule({
31931
32099
  type: "suggestion",
31932
32100
  docs: {
31933
32101
  description: "Review three or more consecutive static-input call assertions as potential named cases.",
31934
- url: docsUrl(NAME131)
32102
+ url: docsUrl(NAME132)
31935
32103
  },
31936
32104
  schema: [],
31937
32105
  messages: { repeatedStaticCallCases: "These {{count}} consecutive assertions repeat a call with static inputs. If independent, use named parameterized cases or subtests; preserve ordered scenarios as one test." }
@@ -32074,7 +32242,7 @@ function isPrimitiveReturn(node, annotation) {
32074
32242
  }
32075
32243
 
32076
32244
  // src/rules/require-assert-never.ts
32077
- var NAME132 = "require-assert-never";
32245
+ var NAME133 = "require-assert-never";
32078
32246
  function isRuntimeHandlingStatement(statement) {
32079
32247
  if (statement.type === "EmptyStatement") return false;
32080
32248
  if (statement.type === "BreakStatement") return statement.label !== null;
@@ -32144,7 +32312,7 @@ var require_assert_never_default = defineRule({
32144
32312
  fixable: "code",
32145
32313
  docs: {
32146
32314
  description: "Require a never witness in empty switch defaults so discriminated unions remain exhaustive at compile time.",
32147
- url: docsUrl(NAME132)
32315
+ url: docsUrl(NAME133)
32148
32316
  },
32149
32317
  schema: [],
32150
32318
  messages: {
@@ -32188,7 +32356,7 @@ var require_assert_never_default = defineRule({
32188
32356
  });
32189
32357
 
32190
32358
  // src/rules/require-button-accessible-name.ts
32191
- var NAME133 = "require-button-accessible-name";
32359
+ var NAME134 = "require-button-accessible-name";
32192
32360
  var DELEGATING_PROPS = /* @__PURE__ */ new Set(["render", "asChild"]);
32193
32361
  function delegatesRendering(opening) {
32194
32362
  return opening.attributes.some(
@@ -32201,7 +32369,7 @@ var require_button_accessible_name_default = defineRule({
32201
32369
  type: "problem",
32202
32370
  docs: {
32203
32371
  description: "Require an accessible name on statically unnamed native or configured JSX buttons.",
32204
- url: docsUrl(NAME133)
32372
+ url: docsUrl(NAME134)
32205
32373
  },
32206
32374
  schema: [
32207
32375
  {
@@ -32255,7 +32423,7 @@ var require_button_accessible_name_default = defineRule({
32255
32423
  });
32256
32424
 
32257
32425
  // src/rules/require-camelcase-properties.ts
32258
- var NAME134 = "require-camelcase-properties";
32426
+ var NAME135 = "require-camelcase-properties";
32259
32427
  var SNAKE_CASE_RE2 = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/u;
32260
32428
  function blocksRename(member3, name) {
32261
32429
  if (member3.type === "TSIndexSignature" || "computed" in member3 && member3.computed) return true;
@@ -32269,7 +32437,7 @@ var require_camelcase_properties_default = defineRule({
32269
32437
  fixable: "code",
32270
32438
  docs: {
32271
32439
  description: "Require unquoted lower snake_case TypeScript properties and dot access to use camelCase.",
32272
- url: docsUrl(NAME134)
32440
+ url: docsUrl(NAME135)
32273
32441
  },
32274
32442
  schema: [],
32275
32443
  defaultOptions: [],
@@ -32333,7 +32501,7 @@ var require_camelcase_properties_default = defineRule({
32333
32501
  });
32334
32502
 
32335
32503
  // src/rules/require-constrained-schema-integer.ts
32336
- var NAME135 = "require-constrained-schema-integer";
32504
+ var NAME136 = "require-constrained-schema-integer";
32337
32505
  var FIELD_MINIMUMS = /* @__PURE__ */ new Map([["limit", 1], ["version", 0]]);
32338
32506
  var NUMBER_FACTORIES = /* @__PURE__ */ new Set(["number", "int", "int32", "uint32"]);
32339
32507
  var OBJECT_FACTORIES = /* @__PURE__ */ new Set(["object", "strictObject", "looseObject"]);
@@ -32375,7 +32543,7 @@ var require_constrained_schema_integer_default = defineRule({
32375
32543
  type: "suggestion",
32376
32544
  docs: {
32377
32545
  description: "Declare integer and lower-bound validation for numeric limit and version schema fields.",
32378
- url: docsUrl(NAME135)
32546
+ url: docsUrl(NAME136)
32379
32547
  },
32380
32548
  schema: [],
32381
32549
  messages: { constrainInteger: "Declare `{{field}}` as an integer with a lower bound of {{minimum}} or greater, or reuse its named domain schema." }
@@ -32854,14 +33022,14 @@ function closedNominalInheritanceFix(sourceCode2, file, call, consumer, implemen
32854
33022
  }
32855
33023
 
32856
33024
  // src/rules/require-explicit-contract-implementation.ts
32857
- var NAME136 = "require-explicit-contract-implementation";
33025
+ var NAME137 = "require-explicit-contract-implementation";
32858
33026
  var require_explicit_contract_implementation_default = defineRule({
32859
33027
  meta: {
32860
33028
  type: "problem",
32861
33029
  fixable: "code",
32862
33030
  docs: {
32863
33031
  description: "Require nominal inheritance when an injected abstract class is rejected by an instanceof guard.",
32864
- url: docsUrl(NAME136)
33032
+ url: docsUrl(NAME137)
32865
33033
  },
32866
33034
  schema: [],
32867
33035
  messages: {
@@ -33552,7 +33720,7 @@ function abstractContractOperations(node, functionAliases) {
33552
33720
  }
33553
33721
 
33554
33722
  // src/rules/require-explicit-service-contract.ts
33555
- var NAME137 = "require-explicit-service-contract";
33723
+ var NAME138 = "require-explicit-service-contract";
33556
33724
  var JAVASCRIPT_FILE_RE2 = /\.(?:js|jsx|mjs|cjs)$/iu;
33557
33725
  var BOUNDARY_SUFFIX = /(?:Service|ServiceImpl|Store|Repository|Provider|Executor|Enqueuer|Runner|Platform|Manager|ManagerImpl|Detector|Control|Refresher|Reader|Settlement|Retry|Stop|Catalog|DAO)$/u;
33558
33726
  var NON_SERVICE_SUFFIX = /(?:Router|Middleware|Builder)$/u;
@@ -33568,7 +33736,7 @@ var require_explicit_service_contract_default = defineRule({
33568
33736
  type: "problem",
33569
33737
  docs: {
33570
33738
  description: "Require explicit contracts for classes that invoke retained collaborators.",
33571
- url: docsUrl(NAME137)
33739
+ url: docsUrl(NAME138)
33572
33740
  },
33573
33741
  schema: [],
33574
33742
  fixable: "code",
@@ -33604,7 +33772,7 @@ var require_explicit_service_contract_default = defineRule({
33604
33772
  });
33605
33773
 
33606
33774
  // src/rules/require-fetch-timeout.ts
33607
- var NAME138 = "require-fetch-timeout";
33775
+ var NAME139 = "require-fetch-timeout";
33608
33776
  function emptySignal(expression, resolvesToGlobal) {
33609
33777
  const value = unwrapExpression(expression);
33610
33778
  if (value.type === "Literal" && value.value === null)
@@ -33666,7 +33834,7 @@ var require_fetch_timeout_default = defineRule({
33666
33834
  type: "problem",
33667
33835
  docs: {
33668
33836
  description: "Require an explicit abort signal on locally analyzable global fetch calls.",
33669
- url: docsUrl(NAME138)
33837
+ url: docsUrl(NAME139)
33670
33838
  },
33671
33839
  schema: [
33672
33840
  {
@@ -33758,13 +33926,13 @@ var require_fetch_timeout_default = defineRule({
33758
33926
  });
33759
33927
 
33760
33928
  // src/rules/require-ime-safe-enter-handler.ts
33761
- var NAME139 = "require-ime-safe-enter-handler";
33929
+ var NAME140 = "require-ime-safe-enter-handler";
33762
33930
  var require_ime_safe_enter_handler_default = defineRule({
33763
33931
  meta: {
33764
33932
  type: "problem",
33765
33933
  docs: {
33766
33934
  description: "Preserve IME composition before Enter submits or blurs a native text control.",
33767
- url: docsUrl(NAME139)
33935
+ url: docsUrl(NAME140)
33768
33936
  },
33769
33937
  schema: [],
33770
33938
  messages: {
@@ -33840,7 +34008,7 @@ function nativeAction(node, event) {
33840
34008
  }
33841
34009
 
33842
34010
  // src/rules/require-interface-for-exported-class.ts
33843
- var NAME140 = "require-interface-for-exported-class";
34011
+ var NAME141 = "require-interface-for-exported-class";
33844
34012
  var JAVASCRIPT_FILE_RE3 = /\.(?:js|jsx|mjs|cjs)$/iu;
33845
34013
  function isPublicInstanceMember2(member3) {
33846
34014
  return !member3.static && member3.accessibility !== "private" && member3.accessibility !== "protected" && member3.key.type !== "PrivateIdentifier";
@@ -33901,7 +34069,7 @@ var require_interface_for_exported_class_default = defineRule({
33901
34069
  type: "suggestion",
33902
34070
  docs: {
33903
34071
  description: "Require exported concrete classes with public behavior to declare a contract.",
33904
- url: docsUrl(NAME140)
34072
+ url: docsUrl(NAME141)
33905
34073
  },
33906
34074
  schema: [],
33907
34075
  fixable: "code",
@@ -33943,7 +34111,7 @@ var require_interface_for_exported_class_default = defineRule({
33943
34111
  });
33944
34112
 
33945
34113
  // src/rules/require-jwt-lifetime.ts
33946
- var NAME141 = "require-jwt-lifetime";
34114
+ var NAME142 = "require-jwt-lifetime";
33947
34115
  function isJoseValueImport(declaration) {
33948
34116
  return declaration.type === "ImportDeclaration" && declaration.importKind !== "type" && declaration.source.value === "jose";
33949
34117
  }
@@ -33952,7 +34120,7 @@ var require_jwt_lifetime_default = defineRule({
33952
34120
  type: "problem",
33953
34121
  docs: {
33954
34122
  description: "Require an expiration claim or a maximum token age when returning a verified JWT.",
33955
- url: docsUrl(NAME141)
34123
+ url: docsUrl(NAME142)
33956
34124
  },
33957
34125
  schema: [],
33958
34126
  messages: { lifetime: "Require the JWT exp claim or set maxTokenAge before returning this verification result." }
@@ -34011,7 +34179,7 @@ var require_jwt_lifetime_default = defineRule({
34011
34179
  });
34012
34180
 
34013
34181
  // src/rules/require-pascal-case-zod-schema-name.ts
34014
- var NAME142 = "require-pascal-case-zod-schema-name";
34182
+ var NAME143 = "require-pascal-case-zod-schema-name";
34015
34183
  var PASCAL_SCHEMA_NAME_RE = /^[A-Z][A-Za-z0-9]*Schema$/;
34016
34184
  var BENCHMARK_PATH_RE2 = /(^|[\\/])(?:benchmarks?|bench)[\\/]/;
34017
34185
  var NON_SCHEMA_TERMINALS2 = /* @__PURE__ */ new Set([
@@ -34182,7 +34350,7 @@ var require_pascal_case_zod_schema_name_default = defineRule({
34182
34350
  fixable: "code",
34183
34351
  docs: {
34184
34352
  description: "Require confirmed module-level Zod schema contracts to use PascalCase with a `Schema` suffix.",
34185
- url: docsUrl(NAME142)
34353
+ url: docsUrl(NAME143)
34186
34354
  },
34187
34355
  schema: [],
34188
34356
  defaultOptions: [],
@@ -34292,7 +34460,7 @@ var require_pascal_case_zod_schema_name_default = defineRule({
34292
34460
  });
34293
34461
 
34294
34462
  // src/rules/require-port-for-service.ts
34295
- var NAME143 = "require-port-for-service";
34463
+ var NAME144 = "require-port-for-service";
34296
34464
  var HTTP_TRANSPORT_TYPE_RE = /^(?:KyInstance|AxiosInstance|Session)$/;
34297
34465
  var TRANSPORT_WRAPPER_NAME_RE = /(?:Client$|^Http[A-Z])/;
34298
34466
  var ERROR_VALUE_NAME_RE = /Error$/;
@@ -34426,7 +34594,7 @@ var require_port_for_service_default = defineRule({
34426
34594
  type: "suggestion",
34427
34595
  docs: {
34428
34596
  description: "Advise when an exported service with injected collaborators has public methods not covered by its declared ports.",
34429
- url: docsUrl(NAME143)
34597
+ url: docsUrl(NAME144)
34430
34598
  },
34431
34599
  schema: [],
34432
34600
  fixable: "code",
@@ -34466,7 +34634,7 @@ var require_port_for_service_default = defineRule({
34466
34634
  });
34467
34635
 
34468
34636
  // src/rules/require-sql-access-class.ts
34469
- var NAME144 = "require-sql-access-class";
34637
+ var NAME145 = "require-sql-access-class";
34470
34638
  var DIRECT_EXECUTION_METHODS = /* @__PURE__ */ new Set([
34471
34639
  "all",
34472
34640
  "batch",
@@ -34664,7 +34832,7 @@ var require_sql_access_class_default = defineRule({
34664
34832
  type: "suggestion",
34665
34833
  docs: {
34666
34834
  description: "Keep SQL reads and writes inside a class that receives its database dependency.",
34667
- url: docsUrl(NAME144)
34835
+ url: docsUrl(NAME145)
34668
34836
  },
34669
34837
  schema: [],
34670
34838
  messages: {
@@ -34736,7 +34904,7 @@ function staticMatcherFix(sourceCode2, property) {
34736
34904
  }
34737
34905
 
34738
34906
  // src/rules/require-static-next-matcher.ts
34739
- var NAME145 = "require-static-next-matcher";
34907
+ var NAME146 = "require-static-next-matcher";
34740
34908
  var NEXT_ENTRY_FILE = /(?:^|[/\\])(?:middleware|proxy)\.[cm]?[jt]sx?$/u;
34741
34909
  function isStaticValue(node) {
34742
34910
  const value = unwrapExpression(node);
@@ -34768,7 +34936,7 @@ var require_static_next_matcher_default = defineRule({
34768
34936
  fixable: "code",
34769
34937
  docs: {
34770
34938
  description: "Require Next.js middleware and proxy matcher configuration to contain only build-time literals.",
34771
- url: docsUrl(NAME145)
34939
+ url: docsUrl(NAME146)
34772
34940
  },
34773
34941
  schema: [],
34774
34942
  messages: {
@@ -34812,7 +34980,7 @@ var require_static_next_matcher_default = defineRule({
34812
34980
  });
34813
34981
 
34814
34982
  // src/rules/require-svg-accessible-name.ts
34815
- var NAME146 = "require-svg-accessible-name";
34983
+ var NAME147 = "require-svg-accessible-name";
34816
34984
  var DECORATIVE_ROLES = /* @__PURE__ */ new Set(["none", "presentation"]);
34817
34985
  var NAME_ATTRIBUTES = ["aria-label", "aria-labelledby"];
34818
34986
  var NO_ICON_MODULES = [];
@@ -34821,7 +34989,7 @@ var require_svg_accessible_name_default = defineRule({
34821
34989
  type: "problem",
34822
34990
  docs: {
34823
34991
  description: "Require a name or explicit decorative semantics on inline SVG elements.",
34824
- url: docsUrl(NAME146)
34992
+ url: docsUrl(NAME147)
34825
34993
  },
34826
34994
  schema: [],
34827
34995
  messages: {
@@ -34853,7 +35021,7 @@ var require_svg_accessible_name_default = defineRule({
34853
35021
  });
34854
35022
 
34855
35023
  // src/rules/require-use-form-default-values.ts
34856
- var NAME147 = "require-use-form-default-values";
35024
+ var NAME148 = "require-use-form-default-values";
34857
35025
  var UNSAFE_FORM_MEMBERS = /* @__PURE__ */ new Set(["__defineGetter__", "__defineSetter__", "__proto__", "valueOf"]);
34858
35026
  function staticPropertyName2(property) {
34859
35027
  return property.computed ? null : getPropertyName(property);
@@ -34896,7 +35064,7 @@ var require_use_form_default_values_default = defineRule({
34896
35064
  type: "problem",
34897
35065
  docs: {
34898
35066
  description: "Require explicit form-level initialization or a field default for directly bound Controller fields.",
34899
- url: docsUrl(NAME147)
35067
+ url: docsUrl(NAME148)
34900
35068
  },
34901
35069
  schema: [],
34902
35070
  messages: { requireUseFormDefaultValues: "Add non-undefined defaultValues/values to the associated useForm call or non-undefined defaultValue to this field." }
@@ -35035,7 +35203,7 @@ var require_use_form_default_values_default = defineRule({
35035
35203
  });
35036
35204
 
35037
35205
  // src/rules/require-use-server-in-actions-file.ts
35038
- var NAME148 = "require-use-server-in-actions-file";
35206
+ var NAME149 = "require-use-server-in-actions-file";
35039
35207
  var ACTION_MODULE_RE = /(?:^|\/)app\/.*\/(?:actions|[^/]+-actions)\.[cm]?[jt]s$/u;
35040
35208
  function isUseServerDirective2(statement) {
35041
35209
  return statement.type === "ExpressionStatement" && statement.directive === "use server";
@@ -35056,7 +35224,7 @@ var require_use_server_in_actions_file_default = defineRule({
35056
35224
  type: "problem",
35057
35225
  docs: {
35058
35226
  description: "route action module missing the use server directive",
35059
- url: docsUrl(NAME148)
35227
+ url: docsUrl(NAME149)
35060
35228
  },
35061
35229
  schema: [],
35062
35230
  messages: {
@@ -35082,7 +35250,7 @@ var require_use_server_in_actions_file_default = defineRule({
35082
35250
  });
35083
35251
 
35084
35252
  // src/rules/require-zod-form-validation.ts
35085
- var NAME149 = "require-zod-form-validation";
35253
+ var NAME150 = "require-zod-form-validation";
35086
35254
  var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
35087
35255
  "parse",
35088
35256
  "safeParse",
@@ -35141,7 +35309,7 @@ var require_zod_form_validation_default = defineRule({
35141
35309
  type: "problem",
35142
35310
  docs: {
35143
35311
  description: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
35144
- url: docsUrl(NAME149)
35312
+ url: docsUrl(NAME150)
35145
35313
  },
35146
35314
  schema: [],
35147
35315
  messages: {
@@ -35429,7 +35597,7 @@ function declaresFramework(metadata, name) {
35429
35597
  }
35430
35598
 
35431
35599
  // src/rules/sole-export-matches-filename.ts
35432
- var NAME150 = "sole-export-matches-filename";
35600
+ var NAME151 = "sole-export-matches-filename";
35433
35601
  var EXCLUDED_STEMS = /* @__PURE__ */ new Set(["global", "index", "types"]);
35434
35602
  function stem3(filename) {
35435
35603
  const base = filename.replaceAll("\\", "/").split("/").at(-1) ?? "";
@@ -35443,7 +35611,7 @@ var sole_export_matches_filename_default = defineRule({
35443
35611
  type: "suggestion",
35444
35612
  docs: {
35445
35613
  description: "Make a module filename reflect its sole named public runtime export.",
35446
- url: docsUrl(NAME150)
35614
+ url: docsUrl(NAME151)
35447
35615
  },
35448
35616
  schema: [],
35449
35617
  messages: {
@@ -35497,7 +35665,7 @@ var source_coupled_test_default = createSourceCoupledRule(
35497
35665
  );
35498
35666
 
35499
35667
  // src/rules/stepdown.ts
35500
- function isFunction(node) {
35668
+ function isFunction2(node) {
35501
35669
  return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
35502
35670
  }
35503
35671
  function isErasedExpression2(node) {
@@ -35520,7 +35688,7 @@ function wrappedExpression(node) {
35520
35688
  function enclosingFunction2(node) {
35521
35689
  let current = parentOf(node);
35522
35690
  while (current !== null) {
35523
- if (isFunction(current)) return current;
35691
+ if (isFunction2(current)) return current;
35524
35692
  current = parentOf(current);
35525
35693
  }
35526
35694
  return void 0;
@@ -35779,7 +35947,7 @@ function isDeferredModuleStatement(statement) {
35779
35947
  if (node === null || node.type === "FunctionDeclaration" || node.type === "TSDeclareFunction" || node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration" || node.type === "ImportDeclaration" || node.type === "ExportAllDeclaration" || node.type === "EmptyStatement") {
35780
35948
  return true;
35781
35949
  }
35782
- return node.type === "VariableDeclaration" && node.declarations.every((declaration) => declaration.id.type === "Identifier" && declaration.init !== null && isFunction(unwrapExpression4(declaration.init)));
35950
+ return node.type === "VariableDeclaration" && node.declarations.every((declaration) => declaration.id.type === "Identifier" && declaration.init !== null && isFunction2(unwrapExpression4(declaration.init)));
35783
35951
  }
35784
35952
  function exportedNames(program) {
35785
35953
  const names = /* @__PURE__ */ new Set();
@@ -35820,7 +35988,7 @@ function moduleDefinitions(program) {
35820
35988
  }
35821
35989
  function constFunctionDefinition(declarator) {
35822
35990
  const initializer = declarator.init === null ? null : unwrapExpression4(declarator.init);
35823
- if (declarator.id.type !== "Identifier" || initializer === null || !isFunction(initializer)) return [];
35991
+ if (declarator.id.type !== "Identifier" || initializer === null || !isFunction2(initializer)) return [];
35824
35992
  const { name } = declarator.id;
35825
35993
  return [{ key: name, name, node: declarator, functionNode: initializer, bindingNode: declarator }];
35826
35994
  }
@@ -35899,7 +36067,7 @@ function referencedPropertyName(node) {
35899
36067
  }
35900
36068
  function walk3(node, visitorKeys, visit2, nestedFunction = false) {
35901
36069
  visit2(node, nestedFunction);
35902
- const nested = nestedFunction || isFunction(node) || node.type === "ClassDeclaration" || node.type === "ClassExpression";
36070
+ const nested = nestedFunction || isFunction2(node) || node.type === "ClassDeclaration" || node.type === "ClassExpression";
35903
36071
  forEachAstChild(node, visitorKeys, (child) => {
35904
36072
  walk3(child, visitorKeys, visit2, nested);
35905
36073
  });
@@ -35920,7 +36088,7 @@ function classScope(state, node, computedReferences, privateBandOnly) {
35920
36088
  const visitorKeys = state.sourceCode.visitorKeys;
35921
36089
  const methods = node.body.body.flatMap((member3) => member3.type === "MethodDefinition" ? [member3] : []);
35922
36090
  const counts = classMethodCounts(node.body, methods);
35923
- const implementations = methods.filter((method) => isFunction(method.value));
36091
+ const implementations = methods.filter((method) => isFunction2(method.value));
35924
36092
  const implementationCounts = classMethodCounts(node.body, implementations);
35925
36093
  const scopeDefinitions = implementations.flatMap((method) => {
35926
36094
  const name = methodName(method);
@@ -35958,7 +36126,7 @@ function classScope(state, node, computedReferences, privateBandOnly) {
35958
36126
  return;
35959
36127
  }
35960
36128
  const methodFunction = method.value;
35961
- if (!isFunction(methodFunction) || methodFunction.body === null) return;
36129
+ if (!isFunction2(methodFunction) || methodFunction.body === null) return;
35962
36130
  const body3 = methodFunction.body;
35963
36131
  const caller = methodKey(name, method.static);
35964
36132
  const receivers = new Map(classReceivers);
@@ -36186,7 +36354,7 @@ var stepdown_default = defineRule({
36186
36354
  });
36187
36355
 
36188
36356
  // src/rules/store-insert-requires-on-conflict.ts
36189
- var NAME151 = "store-insert-requires-on-conflict";
36357
+ var NAME152 = "store-insert-requires-on-conflict";
36190
36358
  var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
36191
36359
  var CONFLICT_HANDLED = /\bON\s+CONFLICT\b|\bON\s+DUPLICATE\s+KEY\b|\bINSERT\s+OR\s+(?:IGNORE|REPLACE)\b|\bINSERT\b[\s\S]*?\bSELECT\b[\s\S]*?\bWHERE\s+NOT\s+EXISTS\b/i;
36192
36360
  var REPLAY_CONTRACT_NAME = /(?:enqueue|ensure|migrate|recordOnce|schedule|seed|upsert|getOrCreate|createIfAbsent|insertIfAbsent)/i;
@@ -36215,7 +36383,7 @@ var store_insert_requires_on_conflict_default = defineRule({
36215
36383
  type: "problem",
36216
36384
  docs: {
36217
36385
  description: "Review conflict handling for embedded inserts in replay-named callables.",
36218
- url: docsUrl(NAME151)
36386
+ url: docsUrl(NAME152)
36219
36387
  },
36220
36388
  schema: [],
36221
36389
  messages: {
@@ -36240,7 +36408,7 @@ var store_insert_requires_on_conflict_default = defineRule({
36240
36408
  });
36241
36409
 
36242
36410
  // src/rules/test-loops-over-literal-cases.ts
36243
- var NAME152 = "test-loops-over-literal-cases";
36411
+ var NAME153 = "test-loops-over-literal-cases";
36244
36412
  var TEST_CALLERS3 = /* @__PURE__ */ new Set(["it", "test"]);
36245
36413
  var TEST_MODIFIERS6 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
36246
36414
  var ASSERTION_ROOTS = /* @__PURE__ */ new Set([
@@ -36336,7 +36504,7 @@ var test_loops_over_literal_cases_default = defineRule({
36336
36504
  type: "suggestion",
36337
36505
  docs: {
36338
36506
  description: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
36339
- url: docsUrl(NAME152)
36507
+ url: docsUrl(NAME153)
36340
36508
  },
36341
36509
  schema: [],
36342
36510
  messages: {
@@ -36400,7 +36568,7 @@ var test_loops_over_literal_cases_default = defineRule({
36400
36568
  });
36401
36569
 
36402
36570
  // src/rules/test-phase-label-comment.ts
36403
- var NAME153 = "test-phase-label-comment";
36571
+ var NAME154 = "test-phase-label-comment";
36404
36572
  var PHASE_WORD = String.raw`arrange|act|assert(?:ion)?s?|given|when|then|exercise|execute|verif(?:y|ication)|cleanup|prepare|sanity(?:\s+check)?`;
36405
36573
  var PHASE_RE = new RegExp(
36406
36574
  String.raw`^[-=~*_#.\s]{0,40}(?:${PHASE_WORD})(?:\s*(?:[/&+,|]|->|and)\s*(?:${PHASE_WORD}))*[-=~*_#.\s:;!–—]{0,40}$`,
@@ -36428,7 +36596,7 @@ var test_phase_label_comment_default = defineRule({
36428
36596
  fixable: "code",
36429
36597
  docs: {
36430
36598
  description: "Tests must not use bare Arrange, Act, Assert, Given, When, or Then phase comments.",
36431
- url: docsUrl(NAME153)
36599
+ url: docsUrl(NAME154)
36432
36600
  },
36433
36601
  schema: [],
36434
36602
  messages: { removeLabel: "Bare test phase label \u2014 delete it and let the test names and helpers carry the structure." }
@@ -36611,6 +36779,7 @@ var RULES = {
36611
36779
  "prefer-zod-infer": prefer_zod_infer_default,
36612
36780
  "prefer-zod-parse-output-type": prefer_zod_parse_output_type_default,
36613
36781
  "production-derived-test-cases": production_derived_test_cases_default,
36782
+ "react-prefer-updater-function": react_prefer_updater_function_default,
36614
36783
  "repeated-static-call-cases": repeated_static_call_cases_default,
36615
36784
  "require-assert-never": require_assert_never_default,
36616
36785
  "require-button-accessible-name": require_button_accessible_name_default,
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  src_default
3
- } from "./chunk-4USNCXB3.js";
3
+ } from "./chunk-UI4ADSAA.js";
4
4
  export {
5
5
  src_default as default
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "19.17.0",
3
+ "version": "19.19.0",
4
4
  "packageManager": "npm@12.2.1",
5
5
  "description": "Custom Oxlint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",