@sarj/eslint-plugin 15.4.0 → 15.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -607,6 +607,9 @@ function isAssertionStatement(statement) {
607
607
  const expression = statement.expression;
608
608
  return expression.type === import_utils3.AST_NODE_TYPES.CallExpression && isAssertionCall(expression);
609
609
  }
610
+ function isTypeOnlyContractStatement(statement) {
611
+ return statement.type === import_utils3.AST_NODE_TYPES.TSTypeAliasDeclaration || statement.type === import_utils3.AST_NODE_TYPES.TSInterfaceDeclaration;
612
+ }
610
613
  function normalizedLiteral(node) {
611
614
  if ("regex" in node) {
612
615
  return ["Literal", "regex", node.regex.pattern, node.regex.flags];
@@ -664,7 +667,7 @@ var duplicate_test_body_default = createRule({
664
667
  return;
665
668
  }
666
669
  const body2 = candidate.body;
667
- if (body2.body.type !== import_utils3.AST_NODE_TYPES.BlockStatement || body2.body.body.length < MIN_STATEMENTS || body2.body.body.every(isAssertionStatement)) {
670
+ if (body2.body.type !== import_utils3.AST_NODE_TYPES.BlockStatement || body2.body.body.length < MIN_STATEMENTS || body2.body.body.every(isAssertionStatement) || body2.body.body.some(isTypeOnlyContractStatement)) {
668
671
  return;
669
672
  }
670
673
  const comments = context.sourceCode.getCommentsInside(body2.body).map((comment) => [comment.type, comment.value]);
@@ -3559,7 +3562,11 @@ var NUMBER_METHODS = /* @__PURE__ */ new Set([
3559
3562
  "lt",
3560
3563
  "lte",
3561
3564
  "max",
3562
- "min"
3565
+ "min",
3566
+ "negative",
3567
+ "nonnegative",
3568
+ "nonpositive",
3569
+ "positive"
3563
3570
  ]);
3564
3571
  var LENGTH_METHODS = /* @__PURE__ */ new Set(["length", "max", "min"]);
3565
3572
  var RESHAPING_METHODS = /* @__PURE__ */ new Set([
@@ -3696,13 +3703,29 @@ var no_impossible_zod_literal_bounds_default = createRule({
3696
3703
  let upper = null;
3697
3704
  for (const { method, node } of chain.calls) {
3698
3705
  if (!allowed.has(method)) return null;
3699
- const value = finiteNumber(node.arguments[0]);
3706
+ const semantic = method === "positive" ? { exclusive: true, lower: true, value: 0 } : method === "nonnegative" ? { exclusive: false, lower: true, value: 0 } : method === "negative" ? { exclusive: true, lower: false, value: 0 } : method === "nonpositive" ? { exclusive: false, lower: false, value: 0 } : null;
3707
+ const value = semantic?.value ?? finiteNumber(node.arguments[0]);
3700
3708
  if (value === null) return null;
3701
3709
  if (chain.kind !== "number" && (!Number.isInteger(value) || value < 0)) {
3702
3710
  return null;
3703
3711
  }
3704
3712
  const label = `${method}(${String(value)})`;
3705
- if (method === "length") {
3713
+ if (semantic !== null) {
3714
+ if (node.arguments.length !== 0) return null;
3715
+ if (semantic.lower) {
3716
+ lower = strongerLower(lower, {
3717
+ exclusive: semantic.exclusive,
3718
+ label: `${method}()`,
3719
+ value
3720
+ });
3721
+ } else {
3722
+ upper = strongerUpper(upper, {
3723
+ exclusive: semantic.exclusive,
3724
+ label: `${method}()`,
3725
+ value
3726
+ });
3727
+ }
3728
+ } else if (method === "length") {
3706
3729
  lower = strongerLower(lower, { exclusive: false, label, value });
3707
3730
  upper = strongerUpper(upper, { exclusive: false, label, value });
3708
3731
  } else if (method === "gt" || method === "gte" || method === "min") {
@@ -6815,6 +6838,13 @@ function nearestEnclosingFunction(node) {
6815
6838
  }
6816
6839
  return null;
6817
6840
  }
6841
+ function isImmediatelyConsumedSleep(node) {
6842
+ const parent = node.parent;
6843
+ if (parent?.type === import_utils34.AST_NODE_TYPES.AwaitExpression || parent?.type === import_utils34.AST_NODE_TYPES.ReturnStatement || parent?.type === import_utils34.AST_NODE_TYPES.ExpressionStatement) {
6844
+ return true;
6845
+ }
6846
+ return parent?.type === import_utils34.AST_NODE_TYPES.ArrowFunctionExpression && parent.body === node;
6847
+ }
6818
6848
  function isTestBody(fn) {
6819
6849
  const call = fn.parent;
6820
6850
  if (call?.type !== import_utils34.AST_NODE_TYPES.CallExpression || !call.arguments.some((argument) => argument === fn)) {
@@ -6857,6 +6887,9 @@ var no_sleep_in_test_body_default = createRule({
6857
6887
  return {};
6858
6888
  }
6859
6889
  const report = (node) => {
6890
+ if (!isImmediatelyConsumedSleep(node)) {
6891
+ return;
6892
+ }
6860
6893
  const enclosing = nearestEnclosingFunction(node);
6861
6894
  if (enclosing === null || !isTestBody(enclosing)) {
6862
6895
  return;
@@ -7154,6 +7187,27 @@ function isSmallStaticForLoop(node) {
7154
7187
  const iterations = node.test.right.value - declaration.init.value + (node.test.operator === "<=" ? 1 : 0);
7155
7188
  return iterations >= 0 && iterations <= 8;
7156
7189
  }
7190
+ function isStringSeededReduce(node) {
7191
+ if (node.callee.type !== "MemberExpression" || node.callee.computed || node.callee.property.type !== "Identifier" || node.callee.property.name !== "reduce" || node.arguments.length !== 2) {
7192
+ return false;
7193
+ }
7194
+ const [callback, initial] = node.arguments;
7195
+ if (node.callee.object.type === "ArrayExpression" && node.callee.object.elements.length <= 8) {
7196
+ return false;
7197
+ }
7198
+ if (callback === void 0 || initial === void 0 || callback.type === "SpreadElement" || initial.type === "SpreadElement" || callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression" || callback.params[0]?.type !== "Identifier" || !isStringLiteralInit(initial)) {
7199
+ return false;
7200
+ }
7201
+ const accumulator = callback.params[0].name;
7202
+ if (callback.body.type !== "BlockStatement") {
7203
+ return isConcatOntoTarget(callback.body, accumulator);
7204
+ }
7205
+ if (callback.body.body.length !== 1 || callback.body.body[0]?.type !== "ReturnStatement") {
7206
+ return false;
7207
+ }
7208
+ const returned = callback.body.body[0].argument;
7209
+ return returned !== null && isConcatOntoTarget(returned, accumulator);
7210
+ }
7157
7211
  var no_string_concat_in_loop_default = createRule({
7158
7212
  name: "no-string-concat-in-loop",
7159
7213
  documentation: noStringConcatInLoopDocumentation,
@@ -7164,7 +7218,8 @@ var no_string_concat_in_loop_default = createRule({
7164
7218
  },
7165
7219
  schema: [],
7166
7220
  messages: {
7167
- noStringConcatInLoop: 'Avoid building a string with `+=` inside a loop \u2014 this is O(n^2). Push the parts onto an array and use `arr.join("")` after the loop.'
7221
+ noStringConcatInLoop: 'Avoid building a string with `+=` inside a loop \u2014 this is O(n^2). Push the parts onto an array and use `arr.join("")` after the loop.',
7222
+ noStringReduce: "Avoid concatenating a growing string in `reduce` \u2014 this is O(n^2). Map the fragments and join them once instead."
7168
7223
  }
7169
7224
  },
7170
7225
  defaultOptions: [],
@@ -7174,6 +7229,11 @@ var no_string_concat_in_loop_default = createRule({
7174
7229
  }
7175
7230
  const reported = /* @__PURE__ */ new WeakMap();
7176
7231
  return {
7232
+ CallExpression(node) {
7233
+ if (isStringSeededReduce(node)) {
7234
+ context.report({ node, messageId: "noStringReduce" });
7235
+ }
7236
+ },
7177
7237
  AssignmentExpression(node) {
7178
7238
  if (node.left.type !== "Identifier") {
7179
7239
  return;
@@ -7278,6 +7338,9 @@ function isLiteral(node) {
7278
7338
  return false;
7279
7339
  }
7280
7340
  }
7341
+ function isStructuralLiteral(node) {
7342
+ return node.type === import_utils37.AST_NODE_TYPES.ArrayExpression || node.type === import_utils37.AST_NODE_TYPES.ObjectExpression;
7343
+ }
7281
7344
  function expectOperand(callee) {
7282
7345
  const receiver = callee.object;
7283
7346
  if (receiver.type !== import_utils37.AST_NODE_TYPES.CallExpression || receiver.callee.type !== import_utils37.AST_NODE_TYPES.Identifier || receiver.callee.name !== "expect" || receiver.arguments.length !== 1) {
@@ -7334,6 +7397,9 @@ var no_tautological_expect_default = createRule({
7334
7397
  if (!EQUALITY_MATCHERS.has(matcher) || node.arguments.length !== 1 || expected === void 0 || !isLiteral(expected)) {
7335
7398
  return;
7336
7399
  }
7400
+ if (matcher === "toBe" && (isStructuralLiteral(operand) || isStructuralLiteral(expected))) {
7401
+ return;
7402
+ }
7337
7403
  if (context.sourceCode.getText(operand) !== context.sourceCode.getText(expected)) {
7338
7404
  return;
7339
7405
  }
@@ -7847,7 +7913,13 @@ var no_declaration_comment_wall_default = createRule({
7847
7913
  const lead = ownsItsLine ? endingOn.get(member.loc.start.line - 1) : void 0;
7848
7914
  if (lead !== void 0) {
7849
7915
  const before = sourceCode.getTokenBefore(lead, { includeComments: false });
7850
- if (before === null || before.loc.end.line < lead.loc.start.line) return lead;
7916
+ if (before === null || before.loc.end.line < lead.loc.start.line) {
7917
+ const previousLine = endingOn.get(lead.loc.start.line - 1);
7918
+ if (lead.type === import_utils40.AST_TOKEN_TYPES.Line && previousLine?.type === import_utils40.AST_TOKEN_TYPES.Line && previousLine.loc.start.column === lead.loc.start.column) {
7919
+ return void 0;
7920
+ }
7921
+ return lead;
7922
+ }
7851
7923
  }
7852
7924
  const trail = startingOn.get(member.loc.end.line);
7853
7925
  return trail !== void 0 && trail.range[0] > member.range[0] ? trail : void 0;
@@ -8149,7 +8221,13 @@ var no_type_member_comment_wall_default = createRule({
8149
8221
  const lead = ownsItsLine ? endingOn.get(member.loc.start.line - 1) : void 0;
8150
8222
  if (lead !== void 0) {
8151
8223
  const before = sourceCode.getTokenBefore(lead, { includeComments: false });
8152
- if (before === null || before.loc.end.line < lead.loc.start.line) return lead;
8224
+ if (before === null || before.loc.end.line < lead.loc.start.line) {
8225
+ const previousLine = endingOn.get(lead.loc.start.line - 1);
8226
+ if (lead.type === import_utils42.AST_TOKEN_TYPES.Line && previousLine?.type === import_utils42.AST_TOKEN_TYPES.Line && previousLine.loc.start.column === lead.loc.start.column) {
8227
+ return void 0;
8228
+ }
8229
+ return lead;
8230
+ }
8153
8231
  }
8154
8232
  const trail = startingOn.get(member.loc.end.line);
8155
8233
  return trail !== void 0 && trail.range[0] > member.range[0] ? trail : void 0;
@@ -8446,6 +8524,10 @@ var no_unnecessary_use_client_default = createRule({
8446
8524
  var import_utils44 = require("@typescript-eslint/utils");
8447
8525
  var MOCK_TYPE_NAMES = /* @__PURE__ */ new Set([
8448
8526
  "Mock",
8527
+ "Mocked",
8528
+ "MockedClass",
8529
+ "MockedFunction",
8530
+ "MockedObject",
8449
8531
  "MockInstance",
8450
8532
  "SpyInstance"
8451
8533
  ]);
@@ -8689,9 +8771,9 @@ var no_zod_native_enum_default = createRule({
8689
8771
  }
8690
8772
  function isZodMemberCall(node, api) {
8691
8773
  const callee = node.callee;
8692
- if (callee.type === import_utils45.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.object.type === import_utils45.AST_NODE_TYPES.Identifier && callee.property.type === import_utils45.AST_NODE_TYPES.Identifier) {
8774
+ if (callee.type === import_utils45.AST_NODE_TYPES.MemberExpression && callee.object.type === import_utils45.AST_NODE_TYPES.Identifier && (callee.property.type === import_utils45.AST_NODE_TYPES.Identifier && !callee.computed && callee.property.name === api || callee.computed && callee.property.type === import_utils45.AST_NODE_TYPES.Literal && callee.property.value === api)) {
8693
8775
  const binding = resolvedBinding(callee.object);
8694
- return binding !== null && zodNamespaceBindings.has(binding) && callee.property.name === api;
8776
+ return binding !== null && zodNamespaceBindings.has(binding);
8695
8777
  }
8696
8778
  if (callee.type === import_utils45.AST_NODE_TYPES.Identifier) {
8697
8779
  const binding = resolvedBinding(callee);
@@ -9535,6 +9617,27 @@ var prefer_immutable_module_constant_default = createRule({
9535
9617
  }
9536
9618
  const exportedNames2 = /* @__PURE__ */ new Set();
9537
9619
  const typeAliases2 = /* @__PURE__ */ new Map();
9620
+ const mutatesThroughConstAlias = (root) => {
9621
+ const pending = [root];
9622
+ const seen = /* @__PURE__ */ new Set();
9623
+ while (pending.length > 0) {
9624
+ const variable = pending.pop();
9625
+ if (variable === void 0 || seen.has(variable)) continue;
9626
+ seen.add(variable);
9627
+ for (const reference of variable.references) {
9628
+ const identifier = reference.identifier;
9629
+ if (identifier.type !== import_utils51.AST_NODE_TYPES.Identifier) continue;
9630
+ if (referenceMutates(identifier, isUnshadowedGlobal)) return true;
9631
+ const declarator = identifier.parent;
9632
+ if (declarator.type !== import_utils51.AST_NODE_TYPES.VariableDeclarator || declarator.init !== identifier || declarator.id.type !== import_utils51.AST_NODE_TYPES.Identifier || declarator.parent.type !== import_utils51.AST_NODE_TYPES.VariableDeclaration || declarator.parent.kind !== "const") {
9633
+ continue;
9634
+ }
9635
+ const alias = sourceCode.getDeclaredVariables(declarator)[0];
9636
+ if (alias !== void 0) pending.push(alias);
9637
+ }
9638
+ }
9639
+ return false;
9640
+ };
9538
9641
  return {
9539
9642
  Program(node) {
9540
9643
  for (const statement of node.body) {
@@ -9575,9 +9678,7 @@ var prefer_immutable_module_constant_default = createRule({
9575
9678
  return;
9576
9679
  }
9577
9680
  const variable = sourceCode.getDeclaredVariables(node)[0];
9578
- if (!directlyExported && !exportedNames2.has(node.id.name) && variable?.references.some(
9579
- (reference) => reference.identifier.type === import_utils51.AST_NODE_TYPES.Identifier && referenceMutates(reference.identifier, isUnshadowedGlobal)
9580
- ) === true) {
9681
+ if (!directlyExported && !exportedNames2.has(node.id.name) && variable !== void 0 && mutatesThroughConstAlias(variable)) {
9581
9682
  return;
9582
9683
  }
9583
9684
  context.report({
@@ -10356,6 +10457,10 @@ var prefer_module_level_schema_default = createRule({
10356
10457
  }
10357
10458
  for (const definition of resolved.defs) {
10358
10459
  if (definition.type === "ImportBinding") {
10460
+ const parent = reference.identifier.parent;
10461
+ if (parent?.type === import_utils54.AST_NODE_TYPES.CallExpression && parent.callee === reference.identifier && !zodNamespaces.has(reference.identifier.name)) {
10462
+ return false;
10463
+ }
10359
10464
  continue;
10360
10465
  }
10361
10466
  if (definition.node.type === import_utils54.AST_NODE_TYPES.VariableDeclarator && definition.node.parent.type === import_utils54.AST_NODE_TYPES.VariableDeclaration && definition.node.parent.kind !== "const") {
@@ -12187,10 +12292,7 @@ var SHAPE_PRESERVING_METHODS = /* @__PURE__ */ new Set([
12187
12292
  ]);
12188
12293
  var OPTIONAL_MODIFIERS = /* @__PURE__ */ new Set([
12189
12294
  "optional",
12190
- "nullish",
12191
- "default",
12192
- "prefault",
12193
- "catch"
12295
+ "nullish"
12194
12296
  ]);
12195
12297
  var NULLABLE_MODIFIERS = /* @__PURE__ */ new Set(["nullable", "nullish"]);
12196
12298
  var RESHAPING_MODIFIERS = /* @__PURE__ */ new Set([
@@ -13459,6 +13561,7 @@ var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
13459
13561
  "parseAsync",
13460
13562
  "safeParseAsync"
13461
13563
  ]);
13564
+ var FORM_VALUE_METHODS = /* @__PURE__ */ new Set(["get", "getAll"]);
13462
13565
  var zodReceiverRoot = (node) => {
13463
13566
  let current = node;
13464
13567
  while (true) {
@@ -13533,7 +13636,7 @@ var require_zod_form_validation_default = createRule({
13533
13636
  };
13534
13637
  const isFormSourceIdentifier = (node) => {
13535
13638
  if (node.type !== import_utils66.AST_NODE_TYPES.Identifier) return false;
13536
- if (/formdata/i.test(node.name)) return true;
13639
+ const conventionalName = /formdata/i.test(node.name);
13537
13640
  let scope = context.sourceCode.getScope(node);
13538
13641
  while (scope !== null) {
13539
13642
  const variable = scope.set.get(node.name);
@@ -13542,16 +13645,16 @@ var require_zod_form_validation_default = createRule({
13542
13645
  if (def !== void 0 && def.type === "Variable" && def.node.type === import_utils66.AST_NODE_TYPES.VariableDeclarator && def.node.init !== null) {
13543
13646
  return isFormDataMethodCall(def.node.init);
13544
13647
  }
13545
- return false;
13648
+ return def?.type === "Parameter" && conventionalName;
13546
13649
  }
13547
13650
  scope = scope.upper;
13548
13651
  }
13549
- return false;
13652
+ return conventionalName;
13550
13653
  };
13551
13654
  const isFormDataGetCall = (node) => {
13552
13655
  const callee = node.callee;
13553
13656
  if (callee.type !== import_utils66.AST_NODE_TYPES.MemberExpression) return false;
13554
- if (callee.property.type !== import_utils66.AST_NODE_TYPES.Identifier || callee.property.name !== "get") {
13657
+ if (callee.property.type !== import_utils66.AST_NODE_TYPES.Identifier || !FORM_VALUE_METHODS.has(callee.property.name)) {
13555
13658
  return false;
13556
13659
  }
13557
13660
  return isFormSourceIdentifier(callee.object);
@@ -13645,6 +13748,40 @@ var require_zod_form_validation_default = createRule({
13645
13748
  }
13646
13749
  return ["===", "!==", "==", "!="].includes(parent.operator) && (parent.right.type === import_utils66.AST_NODE_TYPES.Literal && parent.right.value === null || parent.right.type === import_utils66.AST_NODE_TYPES.Identifier && parent.right.name === "undefined");
13647
13750
  };
13751
+ const isDescendantOf = (node, ancestor) => {
13752
+ let current = node;
13753
+ while (current !== void 0 && current !== null) {
13754
+ if (current === ancestor) return true;
13755
+ current = current.parent;
13756
+ }
13757
+ return false;
13758
+ };
13759
+ const blockTerminates = (node) => {
13760
+ if (node.type === import_utils66.AST_NODE_TYPES.ReturnStatement || node.type === import_utils66.AST_NODE_TYPES.ThrowStatement) {
13761
+ return true;
13762
+ }
13763
+ if (node.type !== import_utils66.AST_NODE_TYPES.BlockStatement || node.body.length === 0) return false;
13764
+ const last = node.body.at(-1);
13765
+ return last !== void 0 && blockTerminates(last);
13766
+ };
13767
+ const narrowingIf = (identifier) => {
13768
+ const comparison = identifier.parent;
13769
+ if (comparison?.type !== import_utils66.AST_NODE_TYPES.BinaryExpression || comparison.operator !== "instanceof" || comparison.left !== identifier || comparison.right.type !== import_utils66.AST_NODE_TYPES.Identifier || comparison.right.name !== "File" && comparison.right.name !== "Blob") {
13770
+ return null;
13771
+ }
13772
+ const maybeNegation = comparison.parent;
13773
+ const negated = maybeNegation?.type === import_utils66.AST_NODE_TYPES.UnaryExpression && maybeNegation.operator === "!";
13774
+ const test = negated ? maybeNegation : comparison;
13775
+ const branch = test.parent;
13776
+ return branch?.type === import_utils66.AST_NODE_TYPES.IfStatement && branch.test === test ? { branch, positive: !negated } : null;
13777
+ };
13778
+ const useDominatedByNarrowing = (use, narrowings) => narrowings.some(({ branch, positive }) => {
13779
+ if (positive) return isDescendantOf(use, branch.consequent);
13780
+ if (!blockTerminates(branch.consequent)) return false;
13781
+ const branchStatement = containingStatement(branch);
13782
+ const useStatement = containingStatement(use);
13783
+ return branchStatement !== null && useStatement !== null && branchStatement.parent === useStatement.parent && branchStatement.range[1] < useStatement.range[0];
13784
+ });
13648
13785
  const statementWithinBlock = (node, block) => {
13649
13786
  let current = node;
13650
13787
  while (current.parent !== void 0 && current.parent !== block) {
@@ -13659,14 +13796,16 @@ var require_zod_form_validation_default = createRule({
13659
13796
  (identifier) => identifier.type === import_utils66.AST_NODE_TYPES.Identifier
13660
13797
  );
13661
13798
  if (references.length === 0) return false;
13662
- if (references.some(isInstanceofNarrowing)) return true;
13799
+ const narrowings = references.map(narrowingIf).filter(
13800
+ (value) => value !== null
13801
+ );
13663
13802
  const validationStatements = references.map((reference) => guaranteedValidationStatement(declarator, reference)).filter(
13664
13803
  (statement) => statement !== null
13665
13804
  );
13666
13805
  const declarationStatement = containingStatement(declarator);
13667
13806
  const declarationBlock = declarationStatement?.parent;
13668
13807
  return references.every((reference) => {
13669
- if (zodParseAncestor(reference) !== null || isSafePrevalidationInspection(reference)) {
13808
+ if (zodParseAncestor(reference) !== null || isSafePrevalidationInspection(reference) || useDominatedByNarrowing(reference, narrowings)) {
13670
13809
  return true;
13671
13810
  }
13672
13811
  if (declarationBlock === void 0) return false;
@@ -14196,7 +14335,13 @@ var NON_SCHEMA_TERMINALS = /* @__PURE__ */ new Set([
14196
14335
  "safeDecodeAsync",
14197
14336
  "toJSONSchema",
14198
14337
  "registry",
14199
- "implement"
14338
+ "implement",
14339
+ "flattenError",
14340
+ "formatError",
14341
+ "isNullable",
14342
+ "isOptional",
14343
+ "prettifyError",
14344
+ "treeifyError"
14200
14345
  ]);
14201
14346
  var terminalMethodName = (callee) => !callee.computed && callee.property.type === import_utils69.AST_NODE_TYPES.Identifier ? callee.property.name : null;
14202
14347
  var calleeChainRoot = (node) => {
@@ -14445,7 +14590,7 @@ var rules = {
14445
14590
  };
14446
14591
  var meta = {
14447
14592
  name: "@sarj/eslint-plugin",
14448
- version: "15.4.0"
14593
+ version: "15.5.0"
14449
14594
  };
14450
14595
  var applicationOnlyRules = [
14451
14596
  "no-restricted-library-load",
package/dist/index.d.cts CHANGED
@@ -225,7 +225,7 @@ declare const rules: {
225
225
  readonly "no-silent-promise-catch": DocumentedRule<readonly [], "silentCatch">;
226
226
  readonly "no-sleep-in-test-body": DocumentedRule<readonly [], "noSleepInTestBody">;
227
227
  readonly "no-storage-in-stateless-modules": DocumentedRule<readonly [RuleOptions$3?], "storageInStatelessModule">;
228
- readonly "no-string-concat-in-loop": DocumentedRule<readonly [], "noStringConcatInLoop">;
228
+ readonly "no-string-concat-in-loop": DocumentedRule<readonly [], "noStringConcatInLoop" | "noStringReduce">;
229
229
  readonly "no-tautological-expect": DocumentedRule<readonly [], "tautologicalComparison" | "tautologicalMatcher">;
230
230
  readonly "no-typed-doc-sections": DocumentedRule<readonly [], "typedSection">;
231
231
  readonly "no-trailing-value-narration": DocumentedRule<readonly [], "deleteNarration" | "narratesValue">;
@@ -415,7 +415,7 @@ type FlatPreset = {
415
415
  declare const plugin: {
416
416
  readonly meta: {
417
417
  readonly name: "@sarj/eslint-plugin";
418
- readonly version: "15.4.0";
418
+ readonly version: "15.5.0";
419
419
  };
420
420
  readonly rules: {
421
421
  readonly "duplicate-test-body": DocumentedRule<readonly [], "duplicateTestBody">;
@@ -455,7 +455,7 @@ declare const plugin: {
455
455
  readonly "no-silent-promise-catch": DocumentedRule<readonly [], "silentCatch">;
456
456
  readonly "no-sleep-in-test-body": DocumentedRule<readonly [], "noSleepInTestBody">;
457
457
  readonly "no-storage-in-stateless-modules": DocumentedRule<readonly [RuleOptions$3?], "storageInStatelessModule">;
458
- readonly "no-string-concat-in-loop": DocumentedRule<readonly [], "noStringConcatInLoop">;
458
+ readonly "no-string-concat-in-loop": DocumentedRule<readonly [], "noStringConcatInLoop" | "noStringReduce">;
459
459
  readonly "no-tautological-expect": DocumentedRule<readonly [], "tautologicalComparison" | "tautologicalMatcher">;
460
460
  readonly "no-typed-doc-sections": DocumentedRule<readonly [], "typedSection">;
461
461
  readonly "no-trailing-value-narration": DocumentedRule<readonly [], "deleteNarration" | "narratesValue">;
package/dist/index.d.ts CHANGED
@@ -225,7 +225,7 @@ declare const rules: {
225
225
  readonly "no-silent-promise-catch": DocumentedRule<readonly [], "silentCatch">;
226
226
  readonly "no-sleep-in-test-body": DocumentedRule<readonly [], "noSleepInTestBody">;
227
227
  readonly "no-storage-in-stateless-modules": DocumentedRule<readonly [RuleOptions$3?], "storageInStatelessModule">;
228
- readonly "no-string-concat-in-loop": DocumentedRule<readonly [], "noStringConcatInLoop">;
228
+ readonly "no-string-concat-in-loop": DocumentedRule<readonly [], "noStringConcatInLoop" | "noStringReduce">;
229
229
  readonly "no-tautological-expect": DocumentedRule<readonly [], "tautologicalComparison" | "tautologicalMatcher">;
230
230
  readonly "no-typed-doc-sections": DocumentedRule<readonly [], "typedSection">;
231
231
  readonly "no-trailing-value-narration": DocumentedRule<readonly [], "deleteNarration" | "narratesValue">;
@@ -415,7 +415,7 @@ type FlatPreset = {
415
415
  declare const plugin: {
416
416
  readonly meta: {
417
417
  readonly name: "@sarj/eslint-plugin";
418
- readonly version: "15.4.0";
418
+ readonly version: "15.5.0";
419
419
  };
420
420
  readonly rules: {
421
421
  readonly "duplicate-test-body": DocumentedRule<readonly [], "duplicateTestBody">;
@@ -455,7 +455,7 @@ declare const plugin: {
455
455
  readonly "no-silent-promise-catch": DocumentedRule<readonly [], "silentCatch">;
456
456
  readonly "no-sleep-in-test-body": DocumentedRule<readonly [], "noSleepInTestBody">;
457
457
  readonly "no-storage-in-stateless-modules": DocumentedRule<readonly [RuleOptions$3?], "storageInStatelessModule">;
458
- readonly "no-string-concat-in-loop": DocumentedRule<readonly [], "noStringConcatInLoop">;
458
+ readonly "no-string-concat-in-loop": DocumentedRule<readonly [], "noStringConcatInLoop" | "noStringReduce">;
459
459
  readonly "no-tautological-expect": DocumentedRule<readonly [], "tautologicalComparison" | "tautologicalMatcher">;
460
460
  readonly "no-typed-doc-sections": DocumentedRule<readonly [], "typedSection">;
461
461
  readonly "no-trailing-value-narration": DocumentedRule<readonly [], "deleteNarration" | "narratesValue">;
package/dist/index.js CHANGED
@@ -564,6 +564,9 @@ function isAssertionStatement(statement) {
564
564
  const expression = statement.expression;
565
565
  return expression.type === AST_NODE_TYPES2.CallExpression && isAssertionCall(expression);
566
566
  }
567
+ function isTypeOnlyContractStatement(statement) {
568
+ return statement.type === AST_NODE_TYPES2.TSTypeAliasDeclaration || statement.type === AST_NODE_TYPES2.TSInterfaceDeclaration;
569
+ }
567
570
  function normalizedLiteral(node) {
568
571
  if ("regex" in node) {
569
572
  return ["Literal", "regex", node.regex.pattern, node.regex.flags];
@@ -621,7 +624,7 @@ var duplicate_test_body_default = createRule({
621
624
  return;
622
625
  }
623
626
  const body2 = candidate.body;
624
- if (body2.body.type !== AST_NODE_TYPES2.BlockStatement || body2.body.body.length < MIN_STATEMENTS || body2.body.body.every(isAssertionStatement)) {
627
+ if (body2.body.type !== AST_NODE_TYPES2.BlockStatement || body2.body.body.length < MIN_STATEMENTS || body2.body.body.every(isAssertionStatement) || body2.body.body.some(isTypeOnlyContractStatement)) {
625
628
  return;
626
629
  }
627
630
  const comments = context.sourceCode.getCommentsInside(body2.body).map((comment) => [comment.type, comment.value]);
@@ -3521,7 +3524,11 @@ var NUMBER_METHODS = /* @__PURE__ */ new Set([
3521
3524
  "lt",
3522
3525
  "lte",
3523
3526
  "max",
3524
- "min"
3527
+ "min",
3528
+ "negative",
3529
+ "nonnegative",
3530
+ "nonpositive",
3531
+ "positive"
3525
3532
  ]);
3526
3533
  var LENGTH_METHODS = /* @__PURE__ */ new Set(["length", "max", "min"]);
3527
3534
  var RESHAPING_METHODS = /* @__PURE__ */ new Set([
@@ -3658,13 +3665,29 @@ var no_impossible_zod_literal_bounds_default = createRule({
3658
3665
  let upper = null;
3659
3666
  for (const { method, node } of chain.calls) {
3660
3667
  if (!allowed.has(method)) return null;
3661
- const value = finiteNumber(node.arguments[0]);
3668
+ const semantic = method === "positive" ? { exclusive: true, lower: true, value: 0 } : method === "nonnegative" ? { exclusive: false, lower: true, value: 0 } : method === "negative" ? { exclusive: true, lower: false, value: 0 } : method === "nonpositive" ? { exclusive: false, lower: false, value: 0 } : null;
3669
+ const value = semantic?.value ?? finiteNumber(node.arguments[0]);
3662
3670
  if (value === null) return null;
3663
3671
  if (chain.kind !== "number" && (!Number.isInteger(value) || value < 0)) {
3664
3672
  return null;
3665
3673
  }
3666
3674
  const label = `${method}(${String(value)})`;
3667
- if (method === "length") {
3675
+ if (semantic !== null) {
3676
+ if (node.arguments.length !== 0) return null;
3677
+ if (semantic.lower) {
3678
+ lower = strongerLower(lower, {
3679
+ exclusive: semantic.exclusive,
3680
+ label: `${method}()`,
3681
+ value
3682
+ });
3683
+ } else {
3684
+ upper = strongerUpper(upper, {
3685
+ exclusive: semantic.exclusive,
3686
+ label: `${method}()`,
3687
+ value
3688
+ });
3689
+ }
3690
+ } else if (method === "length") {
3668
3691
  lower = strongerLower(lower, { exclusive: false, label, value });
3669
3692
  upper = strongerUpper(upper, { exclusive: false, label, value });
3670
3693
  } else if (method === "gt" || method === "gte" || method === "min") {
@@ -6777,6 +6800,13 @@ function nearestEnclosingFunction(node) {
6777
6800
  }
6778
6801
  return null;
6779
6802
  }
6803
+ function isImmediatelyConsumedSleep(node) {
6804
+ const parent = node.parent;
6805
+ if (parent?.type === AST_NODE_TYPES25.AwaitExpression || parent?.type === AST_NODE_TYPES25.ReturnStatement || parent?.type === AST_NODE_TYPES25.ExpressionStatement) {
6806
+ return true;
6807
+ }
6808
+ return parent?.type === AST_NODE_TYPES25.ArrowFunctionExpression && parent.body === node;
6809
+ }
6780
6810
  function isTestBody(fn) {
6781
6811
  const call = fn.parent;
6782
6812
  if (call?.type !== AST_NODE_TYPES25.CallExpression || !call.arguments.some((argument) => argument === fn)) {
@@ -6819,6 +6849,9 @@ var no_sleep_in_test_body_default = createRule({
6819
6849
  return {};
6820
6850
  }
6821
6851
  const report = (node) => {
6852
+ if (!isImmediatelyConsumedSleep(node)) {
6853
+ return;
6854
+ }
6822
6855
  const enclosing = nearestEnclosingFunction(node);
6823
6856
  if (enclosing === null || !isTestBody(enclosing)) {
6824
6857
  return;
@@ -7116,6 +7149,27 @@ function isSmallStaticForLoop(node) {
7116
7149
  const iterations = node.test.right.value - declaration.init.value + (node.test.operator === "<=" ? 1 : 0);
7117
7150
  return iterations >= 0 && iterations <= 8;
7118
7151
  }
7152
+ function isStringSeededReduce(node) {
7153
+ if (node.callee.type !== "MemberExpression" || node.callee.computed || node.callee.property.type !== "Identifier" || node.callee.property.name !== "reduce" || node.arguments.length !== 2) {
7154
+ return false;
7155
+ }
7156
+ const [callback, initial] = node.arguments;
7157
+ if (node.callee.object.type === "ArrayExpression" && node.callee.object.elements.length <= 8) {
7158
+ return false;
7159
+ }
7160
+ if (callback === void 0 || initial === void 0 || callback.type === "SpreadElement" || initial.type === "SpreadElement" || callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression" || callback.params[0]?.type !== "Identifier" || !isStringLiteralInit(initial)) {
7161
+ return false;
7162
+ }
7163
+ const accumulator = callback.params[0].name;
7164
+ if (callback.body.type !== "BlockStatement") {
7165
+ return isConcatOntoTarget(callback.body, accumulator);
7166
+ }
7167
+ if (callback.body.body.length !== 1 || callback.body.body[0]?.type !== "ReturnStatement") {
7168
+ return false;
7169
+ }
7170
+ const returned = callback.body.body[0].argument;
7171
+ return returned !== null && isConcatOntoTarget(returned, accumulator);
7172
+ }
7119
7173
  var no_string_concat_in_loop_default = createRule({
7120
7174
  name: "no-string-concat-in-loop",
7121
7175
  documentation: noStringConcatInLoopDocumentation,
@@ -7126,7 +7180,8 @@ var no_string_concat_in_loop_default = createRule({
7126
7180
  },
7127
7181
  schema: [],
7128
7182
  messages: {
7129
- noStringConcatInLoop: 'Avoid building a string with `+=` inside a loop \u2014 this is O(n^2). Push the parts onto an array and use `arr.join("")` after the loop.'
7183
+ noStringConcatInLoop: 'Avoid building a string with `+=` inside a loop \u2014 this is O(n^2). Push the parts onto an array and use `arr.join("")` after the loop.',
7184
+ noStringReduce: "Avoid concatenating a growing string in `reduce` \u2014 this is O(n^2). Map the fragments and join them once instead."
7130
7185
  }
7131
7186
  },
7132
7187
  defaultOptions: [],
@@ -7136,6 +7191,11 @@ var no_string_concat_in_loop_default = createRule({
7136
7191
  }
7137
7192
  const reported = /* @__PURE__ */ new WeakMap();
7138
7193
  return {
7194
+ CallExpression(node) {
7195
+ if (isStringSeededReduce(node)) {
7196
+ context.report({ node, messageId: "noStringReduce" });
7197
+ }
7198
+ },
7139
7199
  AssignmentExpression(node) {
7140
7200
  if (node.left.type !== "Identifier") {
7141
7201
  return;
@@ -7240,6 +7300,9 @@ function isLiteral(node) {
7240
7300
  return false;
7241
7301
  }
7242
7302
  }
7303
+ function isStructuralLiteral(node) {
7304
+ return node.type === AST_NODE_TYPES27.ArrayExpression || node.type === AST_NODE_TYPES27.ObjectExpression;
7305
+ }
7243
7306
  function expectOperand(callee) {
7244
7307
  const receiver = callee.object;
7245
7308
  if (receiver.type !== AST_NODE_TYPES27.CallExpression || receiver.callee.type !== AST_NODE_TYPES27.Identifier || receiver.callee.name !== "expect" || receiver.arguments.length !== 1) {
@@ -7296,6 +7359,9 @@ var no_tautological_expect_default = createRule({
7296
7359
  if (!EQUALITY_MATCHERS.has(matcher) || node.arguments.length !== 1 || expected === void 0 || !isLiteral(expected)) {
7297
7360
  return;
7298
7361
  }
7362
+ if (matcher === "toBe" && (isStructuralLiteral(operand) || isStructuralLiteral(expected))) {
7363
+ return;
7364
+ }
7299
7365
  if (context.sourceCode.getText(operand) !== context.sourceCode.getText(expected)) {
7300
7366
  return;
7301
7367
  }
@@ -7609,7 +7675,7 @@ var no_trailing_value_narration_default = createRule({
7609
7675
  });
7610
7676
 
7611
7677
  // src/rules/no-declaration-comment-wall.ts
7612
- import { AST_NODE_TYPES as AST_NODE_TYPES29 } from "@typescript-eslint/utils";
7678
+ import { AST_NODE_TYPES as AST_NODE_TYPES29, AST_TOKEN_TYPES } from "@typescript-eslint/utils";
7613
7679
 
7614
7680
  // src/rules/_comment-wall.ts
7615
7681
  import { AST_NODE_TYPES as AST_NODE_TYPES28 } from "@typescript-eslint/utils";
@@ -7809,7 +7875,13 @@ var no_declaration_comment_wall_default = createRule({
7809
7875
  const lead = ownsItsLine ? endingOn.get(member.loc.start.line - 1) : void 0;
7810
7876
  if (lead !== void 0) {
7811
7877
  const before = sourceCode.getTokenBefore(lead, { includeComments: false });
7812
- if (before === null || before.loc.end.line < lead.loc.start.line) return lead;
7878
+ if (before === null || before.loc.end.line < lead.loc.start.line) {
7879
+ const previousLine = endingOn.get(lead.loc.start.line - 1);
7880
+ if (lead.type === AST_TOKEN_TYPES.Line && previousLine?.type === AST_TOKEN_TYPES.Line && previousLine.loc.start.column === lead.loc.start.column) {
7881
+ return void 0;
7882
+ }
7883
+ return lead;
7884
+ }
7813
7885
  }
7814
7886
  const trail = startingOn.get(member.loc.end.line);
7815
7887
  return trail !== void 0 && trail.range[0] > member.range[0] ? trail : void 0;
@@ -8048,7 +8120,7 @@ var no_union_in_comment_default = createRule({
8048
8120
  });
8049
8121
 
8050
8122
  // src/rules/no-type-member-comment-wall.ts
8051
- import { AST_NODE_TYPES as AST_NODE_TYPES31 } from "@typescript-eslint/utils";
8123
+ import { AST_NODE_TYPES as AST_NODE_TYPES31, AST_TOKEN_TYPES as AST_TOKEN_TYPES2 } from "@typescript-eslint/utils";
8052
8124
  var noTypeMemberCommentWallDocumentation = {
8053
8125
  summary: "Flag an object type whose member comments mostly re-spell the members' own names and types.",
8054
8126
  rationale: "Repetitive member comments add scanning cost while hiding the comments that describe facts absent from the type.",
@@ -8111,7 +8183,13 @@ var no_type_member_comment_wall_default = createRule({
8111
8183
  const lead = ownsItsLine ? endingOn.get(member.loc.start.line - 1) : void 0;
8112
8184
  if (lead !== void 0) {
8113
8185
  const before = sourceCode.getTokenBefore(lead, { includeComments: false });
8114
- if (before === null || before.loc.end.line < lead.loc.start.line) return lead;
8186
+ if (before === null || before.loc.end.line < lead.loc.start.line) {
8187
+ const previousLine = endingOn.get(lead.loc.start.line - 1);
8188
+ if (lead.type === AST_TOKEN_TYPES2.Line && previousLine?.type === AST_TOKEN_TYPES2.Line && previousLine.loc.start.column === lead.loc.start.column) {
8189
+ return void 0;
8190
+ }
8191
+ return lead;
8192
+ }
8115
8193
  }
8116
8194
  const trail = startingOn.get(member.loc.end.line);
8117
8195
  return trail !== void 0 && trail.range[0] > member.range[0] ? trail : void 0;
@@ -8411,6 +8489,10 @@ import {
8411
8489
  } from "@typescript-eslint/utils";
8412
8490
  var MOCK_TYPE_NAMES = /* @__PURE__ */ new Set([
8413
8491
  "Mock",
8492
+ "Mocked",
8493
+ "MockedClass",
8494
+ "MockedFunction",
8495
+ "MockedObject",
8414
8496
  "MockInstance",
8415
8497
  "SpyInstance"
8416
8498
  ]);
@@ -8658,9 +8740,9 @@ var no_zod_native_enum_default = createRule({
8658
8740
  }
8659
8741
  function isZodMemberCall(node, api) {
8660
8742
  const callee = node.callee;
8661
- if (callee.type === AST_NODE_TYPES34.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES34.Identifier && callee.property.type === AST_NODE_TYPES34.Identifier) {
8743
+ if (callee.type === AST_NODE_TYPES34.MemberExpression && callee.object.type === AST_NODE_TYPES34.Identifier && (callee.property.type === AST_NODE_TYPES34.Identifier && !callee.computed && callee.property.name === api || callee.computed && callee.property.type === AST_NODE_TYPES34.Literal && callee.property.value === api)) {
8662
8744
  const binding = resolvedBinding(callee.object);
8663
- return binding !== null && zodNamespaceBindings.has(binding) && callee.property.name === api;
8745
+ return binding !== null && zodNamespaceBindings.has(binding);
8664
8746
  }
8665
8747
  if (callee.type === AST_NODE_TYPES34.Identifier) {
8666
8748
  const binding = resolvedBinding(callee);
@@ -9504,6 +9586,27 @@ var prefer_immutable_module_constant_default = createRule({
9504
9586
  }
9505
9587
  const exportedNames2 = /* @__PURE__ */ new Set();
9506
9588
  const typeAliases2 = /* @__PURE__ */ new Map();
9589
+ const mutatesThroughConstAlias = (root) => {
9590
+ const pending = [root];
9591
+ const seen = /* @__PURE__ */ new Set();
9592
+ while (pending.length > 0) {
9593
+ const variable = pending.pop();
9594
+ if (variable === void 0 || seen.has(variable)) continue;
9595
+ seen.add(variable);
9596
+ for (const reference of variable.references) {
9597
+ const identifier = reference.identifier;
9598
+ if (identifier.type !== AST_NODE_TYPES39.Identifier) continue;
9599
+ if (referenceMutates(identifier, isUnshadowedGlobal)) return true;
9600
+ const declarator = identifier.parent;
9601
+ if (declarator.type !== AST_NODE_TYPES39.VariableDeclarator || declarator.init !== identifier || declarator.id.type !== AST_NODE_TYPES39.Identifier || declarator.parent.type !== AST_NODE_TYPES39.VariableDeclaration || declarator.parent.kind !== "const") {
9602
+ continue;
9603
+ }
9604
+ const alias = sourceCode.getDeclaredVariables(declarator)[0];
9605
+ if (alias !== void 0) pending.push(alias);
9606
+ }
9607
+ }
9608
+ return false;
9609
+ };
9507
9610
  return {
9508
9611
  Program(node) {
9509
9612
  for (const statement of node.body) {
@@ -9544,9 +9647,7 @@ var prefer_immutable_module_constant_default = createRule({
9544
9647
  return;
9545
9648
  }
9546
9649
  const variable = sourceCode.getDeclaredVariables(node)[0];
9547
- if (!directlyExported && !exportedNames2.has(node.id.name) && variable?.references.some(
9548
- (reference) => reference.identifier.type === AST_NODE_TYPES39.Identifier && referenceMutates(reference.identifier, isUnshadowedGlobal)
9549
- ) === true) {
9650
+ if (!directlyExported && !exportedNames2.has(node.id.name) && variable !== void 0 && mutatesThroughConstAlias(variable)) {
9550
9651
  return;
9551
9652
  }
9552
9653
  context.report({
@@ -10325,6 +10426,10 @@ var prefer_module_level_schema_default = createRule({
10325
10426
  }
10326
10427
  for (const definition of resolved.defs) {
10327
10428
  if (definition.type === "ImportBinding") {
10429
+ const parent = reference.identifier.parent;
10430
+ if (parent?.type === AST_NODE_TYPES42.CallExpression && parent.callee === reference.identifier && !zodNamespaces.has(reference.identifier.name)) {
10431
+ return false;
10432
+ }
10328
10433
  continue;
10329
10434
  }
10330
10435
  if (definition.node.type === AST_NODE_TYPES42.VariableDeclarator && definition.node.parent.type === AST_NODE_TYPES42.VariableDeclaration && definition.node.parent.kind !== "const") {
@@ -12156,10 +12261,7 @@ var SHAPE_PRESERVING_METHODS = /* @__PURE__ */ new Set([
12156
12261
  ]);
12157
12262
  var OPTIONAL_MODIFIERS = /* @__PURE__ */ new Set([
12158
12263
  "optional",
12159
- "nullish",
12160
- "default",
12161
- "prefault",
12162
- "catch"
12264
+ "nullish"
12163
12265
  ]);
12164
12266
  var NULLABLE_MODIFIERS = /* @__PURE__ */ new Set(["nullable", "nullish"]);
12165
12267
  var RESHAPING_MODIFIERS = /* @__PURE__ */ new Set([
@@ -13434,6 +13536,7 @@ var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
13434
13536
  "parseAsync",
13435
13537
  "safeParseAsync"
13436
13538
  ]);
13539
+ var FORM_VALUE_METHODS = /* @__PURE__ */ new Set(["get", "getAll"]);
13437
13540
  var zodReceiverRoot = (node) => {
13438
13541
  let current = node;
13439
13542
  while (true) {
@@ -13508,7 +13611,7 @@ var require_zod_form_validation_default = createRule({
13508
13611
  };
13509
13612
  const isFormSourceIdentifier = (node) => {
13510
13613
  if (node.type !== AST_NODE_TYPES53.Identifier) return false;
13511
- if (/formdata/i.test(node.name)) return true;
13614
+ const conventionalName = /formdata/i.test(node.name);
13512
13615
  let scope = context.sourceCode.getScope(node);
13513
13616
  while (scope !== null) {
13514
13617
  const variable = scope.set.get(node.name);
@@ -13517,16 +13620,16 @@ var require_zod_form_validation_default = createRule({
13517
13620
  if (def !== void 0 && def.type === "Variable" && def.node.type === AST_NODE_TYPES53.VariableDeclarator && def.node.init !== null) {
13518
13621
  return isFormDataMethodCall(def.node.init);
13519
13622
  }
13520
- return false;
13623
+ return def?.type === "Parameter" && conventionalName;
13521
13624
  }
13522
13625
  scope = scope.upper;
13523
13626
  }
13524
- return false;
13627
+ return conventionalName;
13525
13628
  };
13526
13629
  const isFormDataGetCall = (node) => {
13527
13630
  const callee = node.callee;
13528
13631
  if (callee.type !== AST_NODE_TYPES53.MemberExpression) return false;
13529
- if (callee.property.type !== AST_NODE_TYPES53.Identifier || callee.property.name !== "get") {
13632
+ if (callee.property.type !== AST_NODE_TYPES53.Identifier || !FORM_VALUE_METHODS.has(callee.property.name)) {
13530
13633
  return false;
13531
13634
  }
13532
13635
  return isFormSourceIdentifier(callee.object);
@@ -13620,6 +13723,40 @@ var require_zod_form_validation_default = createRule({
13620
13723
  }
13621
13724
  return ["===", "!==", "==", "!="].includes(parent.operator) && (parent.right.type === AST_NODE_TYPES53.Literal && parent.right.value === null || parent.right.type === AST_NODE_TYPES53.Identifier && parent.right.name === "undefined");
13622
13725
  };
13726
+ const isDescendantOf = (node, ancestor) => {
13727
+ let current = node;
13728
+ while (current !== void 0 && current !== null) {
13729
+ if (current === ancestor) return true;
13730
+ current = current.parent;
13731
+ }
13732
+ return false;
13733
+ };
13734
+ const blockTerminates = (node) => {
13735
+ if (node.type === AST_NODE_TYPES53.ReturnStatement || node.type === AST_NODE_TYPES53.ThrowStatement) {
13736
+ return true;
13737
+ }
13738
+ if (node.type !== AST_NODE_TYPES53.BlockStatement || node.body.length === 0) return false;
13739
+ const last = node.body.at(-1);
13740
+ return last !== void 0 && blockTerminates(last);
13741
+ };
13742
+ const narrowingIf = (identifier) => {
13743
+ const comparison = identifier.parent;
13744
+ if (comparison?.type !== AST_NODE_TYPES53.BinaryExpression || comparison.operator !== "instanceof" || comparison.left !== identifier || comparison.right.type !== AST_NODE_TYPES53.Identifier || comparison.right.name !== "File" && comparison.right.name !== "Blob") {
13745
+ return null;
13746
+ }
13747
+ const maybeNegation = comparison.parent;
13748
+ const negated = maybeNegation?.type === AST_NODE_TYPES53.UnaryExpression && maybeNegation.operator === "!";
13749
+ const test = negated ? maybeNegation : comparison;
13750
+ const branch = test.parent;
13751
+ return branch?.type === AST_NODE_TYPES53.IfStatement && branch.test === test ? { branch, positive: !negated } : null;
13752
+ };
13753
+ const useDominatedByNarrowing = (use, narrowings) => narrowings.some(({ branch, positive }) => {
13754
+ if (positive) return isDescendantOf(use, branch.consequent);
13755
+ if (!blockTerminates(branch.consequent)) return false;
13756
+ const branchStatement = containingStatement(branch);
13757
+ const useStatement = containingStatement(use);
13758
+ return branchStatement !== null && useStatement !== null && branchStatement.parent === useStatement.parent && branchStatement.range[1] < useStatement.range[0];
13759
+ });
13623
13760
  const statementWithinBlock = (node, block) => {
13624
13761
  let current = node;
13625
13762
  while (current.parent !== void 0 && current.parent !== block) {
@@ -13634,14 +13771,16 @@ var require_zod_form_validation_default = createRule({
13634
13771
  (identifier) => identifier.type === AST_NODE_TYPES53.Identifier
13635
13772
  );
13636
13773
  if (references.length === 0) return false;
13637
- if (references.some(isInstanceofNarrowing)) return true;
13774
+ const narrowings = references.map(narrowingIf).filter(
13775
+ (value) => value !== null
13776
+ );
13638
13777
  const validationStatements = references.map((reference) => guaranteedValidationStatement(declarator, reference)).filter(
13639
13778
  (statement) => statement !== null
13640
13779
  );
13641
13780
  const declarationStatement = containingStatement(declarator);
13642
13781
  const declarationBlock = declarationStatement?.parent;
13643
13782
  return references.every((reference) => {
13644
- if (zodParseAncestor(reference) !== null || isSafePrevalidationInspection(reference)) {
13783
+ if (zodParseAncestor(reference) !== null || isSafePrevalidationInspection(reference) || useDominatedByNarrowing(reference, narrowings)) {
13645
13784
  return true;
13646
13785
  }
13647
13786
  if (declarationBlock === void 0) return false;
@@ -14174,7 +14313,13 @@ var NON_SCHEMA_TERMINALS = /* @__PURE__ */ new Set([
14174
14313
  "safeDecodeAsync",
14175
14314
  "toJSONSchema",
14176
14315
  "registry",
14177
- "implement"
14316
+ "implement",
14317
+ "flattenError",
14318
+ "formatError",
14319
+ "isNullable",
14320
+ "isOptional",
14321
+ "prettifyError",
14322
+ "treeifyError"
14178
14323
  ]);
14179
14324
  var terminalMethodName = (callee) => !callee.computed && callee.property.type === AST_NODE_TYPES55.Identifier ? callee.property.name : null;
14180
14325
  var calleeChainRoot = (node) => {
@@ -14423,7 +14568,7 @@ var rules = {
14423
14568
  };
14424
14569
  var meta = {
14425
14570
  name: "@sarj/eslint-plugin",
14426
- version: "15.4.0"
14571
+ version: "15.5.0"
14427
14572
  };
14428
14573
  var applicationOnlyRules = [
14429
14574
  "no-restricted-library-load",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "15.4.0",
3
+ "version": "15.5.0",
4
4
  "packageManager": "npm@11.19.0",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",