@sarj/eslint-plugin 19.18.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-E2O4LN7G.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 = {
@@ -6441,6 +6441,57 @@ var PRODUCTION_DERIVED_TEST_CASES_DOCUMENTATION = {
6441
6441
  ]
6442
6442
  };
6443
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
+
6444
6495
  // src/docs/repeated-static-call-cases.ts
6445
6496
  var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
6446
6497
  summary: "Review three or more consecutive static-input call assertions as potential named cases.",
@@ -7578,6 +7629,7 @@ var RULE_DOCUMENTATION = {
7578
7629
  "prefer-zod-infer": PREFER_ZOD_INFER_DOCUMENTATION,
7579
7630
  "prefer-zod-parse-output-type": PREFER_ZOD_PARSE_OUTPUT_TYPE_DOCUMENTATION,
7580
7631
  "production-derived-test-cases": PRODUCTION_DERIVED_TEST_CASES_DOCUMENTATION,
7632
+ "react-prefer-updater-function": REACT_PREFER_UPDATER_FUNCTION_DOCUMENTATION,
7581
7633
  "repeated-static-call-cases": REPEATED_STATIC_CALL_CASES_DOCUMENTATION,
7582
7634
  "require-assert-never": REQUIRE_ASSERT_NEVER_DOCUMENTATION,
7583
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
  },
@@ -31917,8 +31920,62 @@ var production_derived_test_cases_default = defineRule({
31917
31920
  }
31918
31921
  });
31919
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
+
31920
31977
  // src/rules/repeated-static-call-cases.ts
31921
- var NAME131 = "repeated-static-call-cases";
31978
+ var NAME132 = "repeated-static-call-cases";
31922
31979
  var TEST_MODULES2 = /* @__PURE__ */ new Set(["@jest/globals", "@playwright/test", "bun:test", "node:test", "vitest"]);
31923
31980
  var TEST_NAMES3 = /* @__PURE__ */ new Set(["it", "test"]);
31924
31981
  var TEST_MODIFIERS5 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
@@ -32042,7 +32099,7 @@ var repeated_static_call_cases_default = defineRule({
32042
32099
  type: "suggestion",
32043
32100
  docs: {
32044
32101
  description: "Review three or more consecutive static-input call assertions as potential named cases.",
32045
- url: docsUrl(NAME131)
32102
+ url: docsUrl(NAME132)
32046
32103
  },
32047
32104
  schema: [],
32048
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." }
@@ -32185,7 +32242,7 @@ function isPrimitiveReturn(node, annotation) {
32185
32242
  }
32186
32243
 
32187
32244
  // src/rules/require-assert-never.ts
32188
- var NAME132 = "require-assert-never";
32245
+ var NAME133 = "require-assert-never";
32189
32246
  function isRuntimeHandlingStatement(statement) {
32190
32247
  if (statement.type === "EmptyStatement") return false;
32191
32248
  if (statement.type === "BreakStatement") return statement.label !== null;
@@ -32255,7 +32312,7 @@ var require_assert_never_default = defineRule({
32255
32312
  fixable: "code",
32256
32313
  docs: {
32257
32314
  description: "Require a never witness in empty switch defaults so discriminated unions remain exhaustive at compile time.",
32258
- url: docsUrl(NAME132)
32315
+ url: docsUrl(NAME133)
32259
32316
  },
32260
32317
  schema: [],
32261
32318
  messages: {
@@ -32299,7 +32356,7 @@ var require_assert_never_default = defineRule({
32299
32356
  });
32300
32357
 
32301
32358
  // src/rules/require-button-accessible-name.ts
32302
- var NAME133 = "require-button-accessible-name";
32359
+ var NAME134 = "require-button-accessible-name";
32303
32360
  var DELEGATING_PROPS = /* @__PURE__ */ new Set(["render", "asChild"]);
32304
32361
  function delegatesRendering(opening) {
32305
32362
  return opening.attributes.some(
@@ -32312,7 +32369,7 @@ var require_button_accessible_name_default = defineRule({
32312
32369
  type: "problem",
32313
32370
  docs: {
32314
32371
  description: "Require an accessible name on statically unnamed native or configured JSX buttons.",
32315
- url: docsUrl(NAME133)
32372
+ url: docsUrl(NAME134)
32316
32373
  },
32317
32374
  schema: [
32318
32375
  {
@@ -32366,7 +32423,7 @@ var require_button_accessible_name_default = defineRule({
32366
32423
  });
32367
32424
 
32368
32425
  // src/rules/require-camelcase-properties.ts
32369
- var NAME134 = "require-camelcase-properties";
32426
+ var NAME135 = "require-camelcase-properties";
32370
32427
  var SNAKE_CASE_RE2 = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/u;
32371
32428
  function blocksRename(member3, name) {
32372
32429
  if (member3.type === "TSIndexSignature" || "computed" in member3 && member3.computed) return true;
@@ -32380,7 +32437,7 @@ var require_camelcase_properties_default = defineRule({
32380
32437
  fixable: "code",
32381
32438
  docs: {
32382
32439
  description: "Require unquoted lower snake_case TypeScript properties and dot access to use camelCase.",
32383
- url: docsUrl(NAME134)
32440
+ url: docsUrl(NAME135)
32384
32441
  },
32385
32442
  schema: [],
32386
32443
  defaultOptions: [],
@@ -32444,7 +32501,7 @@ var require_camelcase_properties_default = defineRule({
32444
32501
  });
32445
32502
 
32446
32503
  // src/rules/require-constrained-schema-integer.ts
32447
- var NAME135 = "require-constrained-schema-integer";
32504
+ var NAME136 = "require-constrained-schema-integer";
32448
32505
  var FIELD_MINIMUMS = /* @__PURE__ */ new Map([["limit", 1], ["version", 0]]);
32449
32506
  var NUMBER_FACTORIES = /* @__PURE__ */ new Set(["number", "int", "int32", "uint32"]);
32450
32507
  var OBJECT_FACTORIES = /* @__PURE__ */ new Set(["object", "strictObject", "looseObject"]);
@@ -32486,7 +32543,7 @@ var require_constrained_schema_integer_default = defineRule({
32486
32543
  type: "suggestion",
32487
32544
  docs: {
32488
32545
  description: "Declare integer and lower-bound validation for numeric limit and version schema fields.",
32489
- url: docsUrl(NAME135)
32546
+ url: docsUrl(NAME136)
32490
32547
  },
32491
32548
  schema: [],
32492
32549
  messages: { constrainInteger: "Declare `{{field}}` as an integer with a lower bound of {{minimum}} or greater, or reuse its named domain schema." }
@@ -32965,14 +33022,14 @@ function closedNominalInheritanceFix(sourceCode2, file, call, consumer, implemen
32965
33022
  }
32966
33023
 
32967
33024
  // src/rules/require-explicit-contract-implementation.ts
32968
- var NAME136 = "require-explicit-contract-implementation";
33025
+ var NAME137 = "require-explicit-contract-implementation";
32969
33026
  var require_explicit_contract_implementation_default = defineRule({
32970
33027
  meta: {
32971
33028
  type: "problem",
32972
33029
  fixable: "code",
32973
33030
  docs: {
32974
33031
  description: "Require nominal inheritance when an injected abstract class is rejected by an instanceof guard.",
32975
- url: docsUrl(NAME136)
33032
+ url: docsUrl(NAME137)
32976
33033
  },
32977
33034
  schema: [],
32978
33035
  messages: {
@@ -33663,7 +33720,7 @@ function abstractContractOperations(node, functionAliases) {
33663
33720
  }
33664
33721
 
33665
33722
  // src/rules/require-explicit-service-contract.ts
33666
- var NAME137 = "require-explicit-service-contract";
33723
+ var NAME138 = "require-explicit-service-contract";
33667
33724
  var JAVASCRIPT_FILE_RE2 = /\.(?:js|jsx|mjs|cjs)$/iu;
33668
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;
33669
33726
  var NON_SERVICE_SUFFIX = /(?:Router|Middleware|Builder)$/u;
@@ -33679,7 +33736,7 @@ var require_explicit_service_contract_default = defineRule({
33679
33736
  type: "problem",
33680
33737
  docs: {
33681
33738
  description: "Require explicit contracts for classes that invoke retained collaborators.",
33682
- url: docsUrl(NAME137)
33739
+ url: docsUrl(NAME138)
33683
33740
  },
33684
33741
  schema: [],
33685
33742
  fixable: "code",
@@ -33715,7 +33772,7 @@ var require_explicit_service_contract_default = defineRule({
33715
33772
  });
33716
33773
 
33717
33774
  // src/rules/require-fetch-timeout.ts
33718
- var NAME138 = "require-fetch-timeout";
33775
+ var NAME139 = "require-fetch-timeout";
33719
33776
  function emptySignal(expression, resolvesToGlobal) {
33720
33777
  const value = unwrapExpression(expression);
33721
33778
  if (value.type === "Literal" && value.value === null)
@@ -33777,7 +33834,7 @@ var require_fetch_timeout_default = defineRule({
33777
33834
  type: "problem",
33778
33835
  docs: {
33779
33836
  description: "Require an explicit abort signal on locally analyzable global fetch calls.",
33780
- url: docsUrl(NAME138)
33837
+ url: docsUrl(NAME139)
33781
33838
  },
33782
33839
  schema: [
33783
33840
  {
@@ -33869,13 +33926,13 @@ var require_fetch_timeout_default = defineRule({
33869
33926
  });
33870
33927
 
33871
33928
  // src/rules/require-ime-safe-enter-handler.ts
33872
- var NAME139 = "require-ime-safe-enter-handler";
33929
+ var NAME140 = "require-ime-safe-enter-handler";
33873
33930
  var require_ime_safe_enter_handler_default = defineRule({
33874
33931
  meta: {
33875
33932
  type: "problem",
33876
33933
  docs: {
33877
33934
  description: "Preserve IME composition before Enter submits or blurs a native text control.",
33878
- url: docsUrl(NAME139)
33935
+ url: docsUrl(NAME140)
33879
33936
  },
33880
33937
  schema: [],
33881
33938
  messages: {
@@ -33951,7 +34008,7 @@ function nativeAction(node, event) {
33951
34008
  }
33952
34009
 
33953
34010
  // src/rules/require-interface-for-exported-class.ts
33954
- var NAME140 = "require-interface-for-exported-class";
34011
+ var NAME141 = "require-interface-for-exported-class";
33955
34012
  var JAVASCRIPT_FILE_RE3 = /\.(?:js|jsx|mjs|cjs)$/iu;
33956
34013
  function isPublicInstanceMember2(member3) {
33957
34014
  return !member3.static && member3.accessibility !== "private" && member3.accessibility !== "protected" && member3.key.type !== "PrivateIdentifier";
@@ -34012,7 +34069,7 @@ var require_interface_for_exported_class_default = defineRule({
34012
34069
  type: "suggestion",
34013
34070
  docs: {
34014
34071
  description: "Require exported concrete classes with public behavior to declare a contract.",
34015
- url: docsUrl(NAME140)
34072
+ url: docsUrl(NAME141)
34016
34073
  },
34017
34074
  schema: [],
34018
34075
  fixable: "code",
@@ -34054,7 +34111,7 @@ var require_interface_for_exported_class_default = defineRule({
34054
34111
  });
34055
34112
 
34056
34113
  // src/rules/require-jwt-lifetime.ts
34057
- var NAME141 = "require-jwt-lifetime";
34114
+ var NAME142 = "require-jwt-lifetime";
34058
34115
  function isJoseValueImport(declaration) {
34059
34116
  return declaration.type === "ImportDeclaration" && declaration.importKind !== "type" && declaration.source.value === "jose";
34060
34117
  }
@@ -34063,7 +34120,7 @@ var require_jwt_lifetime_default = defineRule({
34063
34120
  type: "problem",
34064
34121
  docs: {
34065
34122
  description: "Require an expiration claim or a maximum token age when returning a verified JWT.",
34066
- url: docsUrl(NAME141)
34123
+ url: docsUrl(NAME142)
34067
34124
  },
34068
34125
  schema: [],
34069
34126
  messages: { lifetime: "Require the JWT exp claim or set maxTokenAge before returning this verification result." }
@@ -34122,7 +34179,7 @@ var require_jwt_lifetime_default = defineRule({
34122
34179
  });
34123
34180
 
34124
34181
  // src/rules/require-pascal-case-zod-schema-name.ts
34125
- var NAME142 = "require-pascal-case-zod-schema-name";
34182
+ var NAME143 = "require-pascal-case-zod-schema-name";
34126
34183
  var PASCAL_SCHEMA_NAME_RE = /^[A-Z][A-Za-z0-9]*Schema$/;
34127
34184
  var BENCHMARK_PATH_RE2 = /(^|[\\/])(?:benchmarks?|bench)[\\/]/;
34128
34185
  var NON_SCHEMA_TERMINALS2 = /* @__PURE__ */ new Set([
@@ -34293,7 +34350,7 @@ var require_pascal_case_zod_schema_name_default = defineRule({
34293
34350
  fixable: "code",
34294
34351
  docs: {
34295
34352
  description: "Require confirmed module-level Zod schema contracts to use PascalCase with a `Schema` suffix.",
34296
- url: docsUrl(NAME142)
34353
+ url: docsUrl(NAME143)
34297
34354
  },
34298
34355
  schema: [],
34299
34356
  defaultOptions: [],
@@ -34403,7 +34460,7 @@ var require_pascal_case_zod_schema_name_default = defineRule({
34403
34460
  });
34404
34461
 
34405
34462
  // src/rules/require-port-for-service.ts
34406
- var NAME143 = "require-port-for-service";
34463
+ var NAME144 = "require-port-for-service";
34407
34464
  var HTTP_TRANSPORT_TYPE_RE = /^(?:KyInstance|AxiosInstance|Session)$/;
34408
34465
  var TRANSPORT_WRAPPER_NAME_RE = /(?:Client$|^Http[A-Z])/;
34409
34466
  var ERROR_VALUE_NAME_RE = /Error$/;
@@ -34537,7 +34594,7 @@ var require_port_for_service_default = defineRule({
34537
34594
  type: "suggestion",
34538
34595
  docs: {
34539
34596
  description: "Advise when an exported service with injected collaborators has public methods not covered by its declared ports.",
34540
- url: docsUrl(NAME143)
34597
+ url: docsUrl(NAME144)
34541
34598
  },
34542
34599
  schema: [],
34543
34600
  fixable: "code",
@@ -34577,7 +34634,7 @@ var require_port_for_service_default = defineRule({
34577
34634
  });
34578
34635
 
34579
34636
  // src/rules/require-sql-access-class.ts
34580
- var NAME144 = "require-sql-access-class";
34637
+ var NAME145 = "require-sql-access-class";
34581
34638
  var DIRECT_EXECUTION_METHODS = /* @__PURE__ */ new Set([
34582
34639
  "all",
34583
34640
  "batch",
@@ -34775,7 +34832,7 @@ var require_sql_access_class_default = defineRule({
34775
34832
  type: "suggestion",
34776
34833
  docs: {
34777
34834
  description: "Keep SQL reads and writes inside a class that receives its database dependency.",
34778
- url: docsUrl(NAME144)
34835
+ url: docsUrl(NAME145)
34779
34836
  },
34780
34837
  schema: [],
34781
34838
  messages: {
@@ -34847,7 +34904,7 @@ function staticMatcherFix(sourceCode2, property) {
34847
34904
  }
34848
34905
 
34849
34906
  // src/rules/require-static-next-matcher.ts
34850
- var NAME145 = "require-static-next-matcher";
34907
+ var NAME146 = "require-static-next-matcher";
34851
34908
  var NEXT_ENTRY_FILE = /(?:^|[/\\])(?:middleware|proxy)\.[cm]?[jt]sx?$/u;
34852
34909
  function isStaticValue(node) {
34853
34910
  const value = unwrapExpression(node);
@@ -34879,7 +34936,7 @@ var require_static_next_matcher_default = defineRule({
34879
34936
  fixable: "code",
34880
34937
  docs: {
34881
34938
  description: "Require Next.js middleware and proxy matcher configuration to contain only build-time literals.",
34882
- url: docsUrl(NAME145)
34939
+ url: docsUrl(NAME146)
34883
34940
  },
34884
34941
  schema: [],
34885
34942
  messages: {
@@ -34923,7 +34980,7 @@ var require_static_next_matcher_default = defineRule({
34923
34980
  });
34924
34981
 
34925
34982
  // src/rules/require-svg-accessible-name.ts
34926
- var NAME146 = "require-svg-accessible-name";
34983
+ var NAME147 = "require-svg-accessible-name";
34927
34984
  var DECORATIVE_ROLES = /* @__PURE__ */ new Set(["none", "presentation"]);
34928
34985
  var NAME_ATTRIBUTES = ["aria-label", "aria-labelledby"];
34929
34986
  var NO_ICON_MODULES = [];
@@ -34932,7 +34989,7 @@ var require_svg_accessible_name_default = defineRule({
34932
34989
  type: "problem",
34933
34990
  docs: {
34934
34991
  description: "Require a name or explicit decorative semantics on inline SVG elements.",
34935
- url: docsUrl(NAME146)
34992
+ url: docsUrl(NAME147)
34936
34993
  },
34937
34994
  schema: [],
34938
34995
  messages: {
@@ -34964,7 +35021,7 @@ var require_svg_accessible_name_default = defineRule({
34964
35021
  });
34965
35022
 
34966
35023
  // src/rules/require-use-form-default-values.ts
34967
- var NAME147 = "require-use-form-default-values";
35024
+ var NAME148 = "require-use-form-default-values";
34968
35025
  var UNSAFE_FORM_MEMBERS = /* @__PURE__ */ new Set(["__defineGetter__", "__defineSetter__", "__proto__", "valueOf"]);
34969
35026
  function staticPropertyName2(property) {
34970
35027
  return property.computed ? null : getPropertyName(property);
@@ -35007,7 +35064,7 @@ var require_use_form_default_values_default = defineRule({
35007
35064
  type: "problem",
35008
35065
  docs: {
35009
35066
  description: "Require explicit form-level initialization or a field default for directly bound Controller fields.",
35010
- url: docsUrl(NAME147)
35067
+ url: docsUrl(NAME148)
35011
35068
  },
35012
35069
  schema: [],
35013
35070
  messages: { requireUseFormDefaultValues: "Add non-undefined defaultValues/values to the associated useForm call or non-undefined defaultValue to this field." }
@@ -35146,7 +35203,7 @@ var require_use_form_default_values_default = defineRule({
35146
35203
  });
35147
35204
 
35148
35205
  // src/rules/require-use-server-in-actions-file.ts
35149
- var NAME148 = "require-use-server-in-actions-file";
35206
+ var NAME149 = "require-use-server-in-actions-file";
35150
35207
  var ACTION_MODULE_RE = /(?:^|\/)app\/.*\/(?:actions|[^/]+-actions)\.[cm]?[jt]s$/u;
35151
35208
  function isUseServerDirective2(statement) {
35152
35209
  return statement.type === "ExpressionStatement" && statement.directive === "use server";
@@ -35167,7 +35224,7 @@ var require_use_server_in_actions_file_default = defineRule({
35167
35224
  type: "problem",
35168
35225
  docs: {
35169
35226
  description: "route action module missing the use server directive",
35170
- url: docsUrl(NAME148)
35227
+ url: docsUrl(NAME149)
35171
35228
  },
35172
35229
  schema: [],
35173
35230
  messages: {
@@ -35193,7 +35250,7 @@ var require_use_server_in_actions_file_default = defineRule({
35193
35250
  });
35194
35251
 
35195
35252
  // src/rules/require-zod-form-validation.ts
35196
- var NAME149 = "require-zod-form-validation";
35253
+ var NAME150 = "require-zod-form-validation";
35197
35254
  var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
35198
35255
  "parse",
35199
35256
  "safeParse",
@@ -35252,7 +35309,7 @@ var require_zod_form_validation_default = defineRule({
35252
35309
  type: "problem",
35253
35310
  docs: {
35254
35311
  description: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
35255
- url: docsUrl(NAME149)
35312
+ url: docsUrl(NAME150)
35256
35313
  },
35257
35314
  schema: [],
35258
35315
  messages: {
@@ -35540,7 +35597,7 @@ function declaresFramework(metadata, name) {
35540
35597
  }
35541
35598
 
35542
35599
  // src/rules/sole-export-matches-filename.ts
35543
- var NAME150 = "sole-export-matches-filename";
35600
+ var NAME151 = "sole-export-matches-filename";
35544
35601
  var EXCLUDED_STEMS = /* @__PURE__ */ new Set(["global", "index", "types"]);
35545
35602
  function stem3(filename) {
35546
35603
  const base = filename.replaceAll("\\", "/").split("/").at(-1) ?? "";
@@ -35554,7 +35611,7 @@ var sole_export_matches_filename_default = defineRule({
35554
35611
  type: "suggestion",
35555
35612
  docs: {
35556
35613
  description: "Make a module filename reflect its sole named public runtime export.",
35557
- url: docsUrl(NAME150)
35614
+ url: docsUrl(NAME151)
35558
35615
  },
35559
35616
  schema: [],
35560
35617
  messages: {
@@ -35608,7 +35665,7 @@ var source_coupled_test_default = createSourceCoupledRule(
35608
35665
  );
35609
35666
 
35610
35667
  // src/rules/stepdown.ts
35611
- function isFunction(node) {
35668
+ function isFunction2(node) {
35612
35669
  return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
35613
35670
  }
35614
35671
  function isErasedExpression2(node) {
@@ -35631,7 +35688,7 @@ function wrappedExpression(node) {
35631
35688
  function enclosingFunction2(node) {
35632
35689
  let current = parentOf(node);
35633
35690
  while (current !== null) {
35634
- if (isFunction(current)) return current;
35691
+ if (isFunction2(current)) return current;
35635
35692
  current = parentOf(current);
35636
35693
  }
35637
35694
  return void 0;
@@ -35890,7 +35947,7 @@ function isDeferredModuleStatement(statement) {
35890
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") {
35891
35948
  return true;
35892
35949
  }
35893
- 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)));
35894
35951
  }
35895
35952
  function exportedNames(program) {
35896
35953
  const names = /* @__PURE__ */ new Set();
@@ -35931,7 +35988,7 @@ function moduleDefinitions(program) {
35931
35988
  }
35932
35989
  function constFunctionDefinition(declarator) {
35933
35990
  const initializer = declarator.init === null ? null : unwrapExpression4(declarator.init);
35934
- if (declarator.id.type !== "Identifier" || initializer === null || !isFunction(initializer)) return [];
35991
+ if (declarator.id.type !== "Identifier" || initializer === null || !isFunction2(initializer)) return [];
35935
35992
  const { name } = declarator.id;
35936
35993
  return [{ key: name, name, node: declarator, functionNode: initializer, bindingNode: declarator }];
35937
35994
  }
@@ -36010,7 +36067,7 @@ function referencedPropertyName(node) {
36010
36067
  }
36011
36068
  function walk3(node, visitorKeys, visit2, nestedFunction = false) {
36012
36069
  visit2(node, nestedFunction);
36013
- const nested = nestedFunction || isFunction(node) || node.type === "ClassDeclaration" || node.type === "ClassExpression";
36070
+ const nested = nestedFunction || isFunction2(node) || node.type === "ClassDeclaration" || node.type === "ClassExpression";
36014
36071
  forEachAstChild(node, visitorKeys, (child) => {
36015
36072
  walk3(child, visitorKeys, visit2, nested);
36016
36073
  });
@@ -36031,7 +36088,7 @@ function classScope(state, node, computedReferences, privateBandOnly) {
36031
36088
  const visitorKeys = state.sourceCode.visitorKeys;
36032
36089
  const methods = node.body.body.flatMap((member3) => member3.type === "MethodDefinition" ? [member3] : []);
36033
36090
  const counts = classMethodCounts(node.body, methods);
36034
- const implementations = methods.filter((method) => isFunction(method.value));
36091
+ const implementations = methods.filter((method) => isFunction2(method.value));
36035
36092
  const implementationCounts = classMethodCounts(node.body, implementations);
36036
36093
  const scopeDefinitions = implementations.flatMap((method) => {
36037
36094
  const name = methodName(method);
@@ -36069,7 +36126,7 @@ function classScope(state, node, computedReferences, privateBandOnly) {
36069
36126
  return;
36070
36127
  }
36071
36128
  const methodFunction = method.value;
36072
- if (!isFunction(methodFunction) || methodFunction.body === null) return;
36129
+ if (!isFunction2(methodFunction) || methodFunction.body === null) return;
36073
36130
  const body3 = methodFunction.body;
36074
36131
  const caller = methodKey(name, method.static);
36075
36132
  const receivers = new Map(classReceivers);
@@ -36297,7 +36354,7 @@ var stepdown_default = defineRule({
36297
36354
  });
36298
36355
 
36299
36356
  // src/rules/store-insert-requires-on-conflict.ts
36300
- var NAME151 = "store-insert-requires-on-conflict";
36357
+ var NAME152 = "store-insert-requires-on-conflict";
36301
36358
  var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
36302
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;
36303
36360
  var REPLAY_CONTRACT_NAME = /(?:enqueue|ensure|migrate|recordOnce|schedule|seed|upsert|getOrCreate|createIfAbsent|insertIfAbsent)/i;
@@ -36326,7 +36383,7 @@ var store_insert_requires_on_conflict_default = defineRule({
36326
36383
  type: "problem",
36327
36384
  docs: {
36328
36385
  description: "Review conflict handling for embedded inserts in replay-named callables.",
36329
- url: docsUrl(NAME151)
36386
+ url: docsUrl(NAME152)
36330
36387
  },
36331
36388
  schema: [],
36332
36389
  messages: {
@@ -36351,7 +36408,7 @@ var store_insert_requires_on_conflict_default = defineRule({
36351
36408
  });
36352
36409
 
36353
36410
  // src/rules/test-loops-over-literal-cases.ts
36354
- var NAME152 = "test-loops-over-literal-cases";
36411
+ var NAME153 = "test-loops-over-literal-cases";
36355
36412
  var TEST_CALLERS3 = /* @__PURE__ */ new Set(["it", "test"]);
36356
36413
  var TEST_MODIFIERS6 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
36357
36414
  var ASSERTION_ROOTS = /* @__PURE__ */ new Set([
@@ -36447,7 +36504,7 @@ var test_loops_over_literal_cases_default = defineRule({
36447
36504
  type: "suggestion",
36448
36505
  docs: {
36449
36506
  description: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
36450
- url: docsUrl(NAME152)
36507
+ url: docsUrl(NAME153)
36451
36508
  },
36452
36509
  schema: [],
36453
36510
  messages: {
@@ -36511,7 +36568,7 @@ var test_loops_over_literal_cases_default = defineRule({
36511
36568
  });
36512
36569
 
36513
36570
  // src/rules/test-phase-label-comment.ts
36514
- var NAME153 = "test-phase-label-comment";
36571
+ var NAME154 = "test-phase-label-comment";
36515
36572
  var PHASE_WORD = String.raw`arrange|act|assert(?:ion)?s?|given|when|then|exercise|execute|verif(?:y|ication)|cleanup|prepare|sanity(?:\s+check)?`;
36516
36573
  var PHASE_RE = new RegExp(
36517
36574
  String.raw`^[-=~*_#.\s]{0,40}(?:${PHASE_WORD})(?:\s*(?:[/&+,|]|->|and)\s*(?:${PHASE_WORD}))*[-=~*_#.\s:;!–—]{0,40}$`,
@@ -36539,7 +36596,7 @@ var test_phase_label_comment_default = defineRule({
36539
36596
  fixable: "code",
36540
36597
  docs: {
36541
36598
  description: "Tests must not use bare Arrange, Act, Assert, Given, When, or Then phase comments.",
36542
- url: docsUrl(NAME153)
36599
+ url: docsUrl(NAME154)
36543
36600
  },
36544
36601
  schema: [],
36545
36602
  messages: { removeLabel: "Bare test phase label \u2014 delete it and let the test names and helpers carry the structure." }
@@ -36722,6 +36779,7 @@ var RULES = {
36722
36779
  "prefer-zod-infer": prefer_zod_infer_default,
36723
36780
  "prefer-zod-parse-output-type": prefer_zod_parse_output_type_default,
36724
36781
  "production-derived-test-cases": production_derived_test_cases_default,
36782
+ "react-prefer-updater-function": react_prefer_updater_function_default,
36725
36783
  "repeated-static-call-cases": repeated_static_call_cases_default,
36726
36784
  "require-assert-never": require_assert_never_default,
36727
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-E2O4LN7G.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.18.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",