@sarj/eslint-plugin 16.10.0 → 16.11.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
@@ -52,7 +52,7 @@ module.exports = __toCommonJS(src_exports);
52
52
  // package.json
53
53
  var package_default = {
54
54
  name: "@sarj/eslint-plugin",
55
- version: "16.10.0",
55
+ version: "16.11.0",
56
56
  packageManager: "npm@12.2.0",
57
57
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
58
58
  type: "module",
@@ -7706,7 +7706,8 @@ var NO_IMPOSSIBLE_ZOD_LITERAL_BOUNDS_DOCUMENTATION = {
7706
7706
  remediation: "Choose compatible lower and upper bounds, or remove the constraint that does not express the intended domain.",
7707
7707
  category: "correctness",
7708
7708
  limitations: [
7709
- "Only finite numeric literals in a single number, string, or array schema chain are compared; explicit int() chains also require an admitted integer.",
7709
+ "Only finite numeric literals in a single schema chain are compared; number().int(), int(), int32(), uint32(), strings and arrays require an admitted integer value or length.",
7710
+ "int32() and uint32() include their intrinsic ranges. The managed preset delegates explicit real-bound conflicts to upstream zod/no-conflicting-checks to avoid duplicate findings.",
7710
7711
  "Integer-domain comparisons abstain near the JavaScript safe-integer boundary.",
7711
7712
  "Chains with dynamic bounds, non-bound validators, transforms, pipes, or preprocessors are skipped.",
7712
7713
  "Test and generated files are excluded."
@@ -7742,7 +7743,14 @@ var NO_IMPOSSIBLE_ZOD_LITERAL_BOUNDS_DOCUMENTATION = {
7742
7743
  }
7743
7744
  ]
7744
7745
  };
7745
- var KINDS = /* @__PURE__ */ new Set(["array", "number", "string"]);
7746
+ var KINDS = /* @__PURE__ */ new Map([
7747
+ ["array", "array"],
7748
+ ["number", "number"],
7749
+ ["string", "string"],
7750
+ ["int", "integer"],
7751
+ ["int32", "int32"],
7752
+ ["uint32", "uint32"]
7753
+ ]);
7746
7754
  var NUMBER_METHODS = /* @__PURE__ */ new Set([
7747
7755
  "gt",
7748
7756
  "gte",
@@ -7788,11 +7796,14 @@ function strongerUpper(current, candidate2) {
7788
7796
  }
7789
7797
  return current;
7790
7798
  }
7799
+ function isRealEmpty(lower, upper) {
7800
+ if (lower === null || upper === null) return false;
7801
+ return lower.value > upper.value || lower.value === upper.value && (lower.exclusive || upper.exclusive);
7802
+ }
7791
7803
  function isEmpty(lower, upper, integer) {
7792
7804
  if (lower === null || upper === null) return false;
7793
- const realEmpty = lower.value > upper.value || lower.value === upper.value && (lower.exclusive || upper.exclusive);
7805
+ if (isRealEmpty(lower, upper)) return true;
7794
7806
  if (integer) {
7795
- if (realEmpty) return false;
7796
7807
  const safeMargin = Number.MAX_SAFE_INTEGER - 1;
7797
7808
  if (Math.abs(lower.value) >= safeMargin || Math.abs(upper.value) >= safeMargin) {
7798
7809
  return false;
@@ -7801,11 +7812,12 @@ function isEmpty(lower, upper, integer) {
7801
7812
  const maximum = upper.exclusive ? Math.ceil(upper.value) - 1 : Math.floor(upper.value);
7802
7813
  return minimum > maximum;
7803
7814
  }
7804
- return realEmpty;
7815
+ return false;
7805
7816
  }
7806
7817
  function isOutermostCall(node) {
7807
- const parent = node.parent;
7808
- return !(parent?.type === import_utils43.AST_NODE_TYPES.MemberExpression && parent.object === node && parent.parent?.type === import_utils43.AST_NODE_TYPES.CallExpression && parent.parent.callee === parent);
7818
+ const expression = outerExpression(node);
7819
+ const parent = expression.parent;
7820
+ return !(parent?.type === import_utils43.AST_NODE_TYPES.MemberExpression && parent.object === expression && parent.parent?.type === import_utils43.AST_NODE_TYPES.CallExpression && parent.parent.callee === parent);
7809
7821
  }
7810
7822
  var no_impossible_zod_literal_bounds_default = createRule({
7811
7823
  name: "no-impossible-zod-literal-bounds",
@@ -7815,13 +7827,24 @@ var no_impossible_zod_literal_bounds_default = createRule({
7815
7827
  docs: {
7816
7828
  description: "Disallow same-chain literal Zod bounds whose accepted set is mathematically empty."
7817
7829
  },
7818
- schema: [],
7830
+ schema: [
7831
+ {
7832
+ type: "object",
7833
+ additionalProperties: false,
7834
+ properties: {
7835
+ delegateRealContradictions: {
7836
+ type: "boolean",
7837
+ description: "Let enabled upstream zod/no-conflicting-checks own explicit real-bound contradictions."
7838
+ }
7839
+ }
7840
+ }
7841
+ ],
7819
7842
  messages: {
7820
7843
  impossibleBounds: "This Zod {{kind}} schema accepts no values: {{lower}} conflicts with {{upper}}."
7821
7844
  }
7822
7845
  },
7823
- defaultOptions: [],
7824
- create(context) {
7846
+ defaultOptions: [{}],
7847
+ create(context, [options]) {
7825
7848
  const sourceCode = context.sourceCode;
7826
7849
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
7827
7850
  return {};
@@ -7835,9 +7858,13 @@ var no_impossible_zod_literal_bounds_default = createRule({
7835
7858
  if (binding === void 0) return false;
7836
7859
  let scope = sourceCode.getScope(node);
7837
7860
  while (scope !== null) {
7838
- const variable = scope.variables.find((candidate2) => candidate2.name === node.name);
7861
+ const variable = scope.variables.find(
7862
+ (candidate2) => candidate2.name === node.name
7863
+ );
7839
7864
  if (variable !== void 0) {
7840
- return variable.defs.some((definition) => definition.name === binding);
7865
+ return variable.defs.some(
7866
+ (definition) => definition.name === binding
7867
+ );
7841
7868
  }
7842
7869
  scope = scope.upper;
7843
7870
  }
@@ -7870,7 +7897,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
7870
7897
  return null;
7871
7898
  }
7872
7899
  const name = import_utils43.ASTUtils.getPropertyName(callee);
7873
- return name !== null && KINDS.has(name) ? name : null;
7900
+ return name === null ? null : KINDS.get(name) ?? null;
7874
7901
  }
7875
7902
  function isInsideReshapingCall(node) {
7876
7903
  let child = node;
@@ -7892,10 +7919,33 @@ var no_impossible_zod_literal_bounds_default = createRule({
7892
7919
  return false;
7893
7920
  }
7894
7921
  function contradiction(chain) {
7895
- const allowed = chain.kind === "number" ? NUMBER_METHODS : LENGTH_METHODS;
7922
+ const bounds = readConstraints(chain);
7923
+ if (bounds === null) return null;
7924
+ let { lower, upper } = bounds;
7925
+ const { integer, numeric } = bounds;
7926
+ if (isRealEmpty(lower, upper) && (numeric && integer || options?.delegateRealContradictions === true)) {
7927
+ return null;
7928
+ }
7929
+ if (chain.kind === "int32" || chain.kind === "uint32") {
7930
+ lower = strongerLower(lower, {
7931
+ exclusive: false,
7932
+ label: `${chain.kind}() minimum`,
7933
+ value: chain.kind === "int32" ? -(2 ** 31) : 0
7934
+ });
7935
+ upper = strongerUpper(upper, {
7936
+ exclusive: false,
7937
+ label: `${chain.kind}() maximum`,
7938
+ value: chain.kind === "int32" ? 2 ** 31 - 1 : 2 ** 32 - 1
7939
+ });
7940
+ }
7941
+ return isEmpty(lower, upper, integer) && lower !== null && upper !== null ? { lower, upper } : null;
7942
+ }
7943
+ function readConstraints(chain) {
7944
+ const numeric = chain.kind !== "string" && chain.kind !== "array";
7945
+ const allowed = numeric ? NUMBER_METHODS : LENGTH_METHODS;
7896
7946
  let lower = null;
7897
7947
  let upper = null;
7898
- let integer = false;
7948
+ let integer = chain.kind !== "number";
7899
7949
  function addBound(method, semantic, value) {
7900
7950
  const label = `${method}(${String(value)})`;
7901
7951
  if (semantic !== null) {
@@ -7939,13 +7989,13 @@ var no_impossible_zod_literal_bounds_default = createRule({
7939
7989
  const semantic = zeroBoundSemantic(method);
7940
7990
  const value = semantic?.value ?? finiteNumber(node.arguments[0]);
7941
7991
  if (value === null) return null;
7942
- if (chain.kind !== "number" && (!Number.isInteger(value) || value < 0)) {
7992
+ if (!numeric && value < 0) {
7943
7993
  return null;
7944
7994
  }
7945
7995
  if (semantic !== null && node.arguments.length !== 0) return null;
7946
7996
  addBound(method, semantic, value);
7947
7997
  }
7948
- return isEmpty(lower, upper, integer) && lower !== null && upper !== null ? { lower, upper } : null;
7998
+ return { integer, lower, numeric, upper };
7949
7999
  }
7950
8000
  return {
7951
8001
  ImportDeclaration(node) {
@@ -7961,7 +8011,9 @@ var no_impossible_zod_literal_bounds_default = createRule({
7961
8011
  namespaces.add(specifier.local.name);
7962
8012
  importBindings.set(specifier.local.name, specifier.local);
7963
8013
  } else if (imported !== null && KINDS.has(imported)) {
7964
- constructors.set(specifier.local.name, imported);
8014
+ const kind = KINDS.get(imported);
8015
+ if (kind !== void 0)
8016
+ constructors.set(specifier.local.name, kind);
7965
8017
  importBindings.set(specifier.local.name, specifier.local);
7966
8018
  } else if (imported === "preprocess") {
7967
8019
  preprocessors.add(specifier.local.name);
@@ -22145,10 +22197,39 @@ var REQUIRE_FETCH_TIMEOUT_DOCUMENTATION = {
22145
22197
  rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
22146
22198
  remediation: "Pass an abort signal, such as `AbortSignal.timeout(ms)`, in the fetch init.",
22147
22199
  category: "correctness",
22148
- limitations: ["Signal presence establishes an explicit cancellation path, not a guaranteed timeout. Literal null, global undefined, and void 0 do not provide a signal. Unknown spreads and dynamic values are not inferred. Forwarded Request objects can carry an existing signal."],
22200
+ limitations: [
22201
+ "Signal presence establishes an explicit cancellation path, not a guaranteed timeout. Literal null, global undefined, and void 0 do not provide a signal. Erased TypeScript wrappers preserve the same analysis. Unknown spreads and dynamic values are not inferred. Forwarded Request objects retain their signal unless an init explicitly resets it to null.",
22202
+ "Accessors and unproved prototypes are excluded because native option conversion can attach or replace a signal before it is read."
22203
+ ],
22149
22204
  examples: [
22150
- { id: "bounded-fetch", title: "Bound the request", outcome: "no-match", files: [{ path: "src/client.ts", source: "await fetch(url, { signal: AbortSignal.timeout(5000) });" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
22151
- { id: "unbounded-fetch", title: "Do not leave fetch unbounded", outcome: "match", files: [{ path: "src/client.ts", source: "await fetch('https://api.example.com/items');" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
22205
+ {
22206
+ id: "bounded-fetch",
22207
+ title: "Bound the request",
22208
+ outcome: "no-match",
22209
+ files: [
22210
+ {
22211
+ path: "src/client.ts",
22212
+ source: "await fetch(url, { signal: AbortSignal.timeout(5000) });"
22213
+ }
22214
+ ],
22215
+ focusPath: "src/client.ts",
22216
+ expectedCount: 0,
22217
+ public: true
22218
+ },
22219
+ {
22220
+ id: "unbounded-fetch",
22221
+ title: "Do not leave fetch unbounded",
22222
+ outcome: "match",
22223
+ files: [
22224
+ {
22225
+ path: "src/client.ts",
22226
+ source: "await fetch('https://api.example.com/items');"
22227
+ }
22228
+ ],
22229
+ focusPath: "src/client.ts",
22230
+ expectedCount: 1,
22231
+ public: true
22232
+ }
22152
22233
  ]
22153
22234
  };
22154
22235
  var GLOBAL_OBJECTS2 = /* @__PURE__ */ new Set([
@@ -22165,23 +22246,50 @@ function matchesAnyPattern3(filename, patterns) {
22165
22246
  }
22166
22247
  return false;
22167
22248
  }
22168
- function initProvablyLacksSignal(init, resolvesToGlobal) {
22169
- if (init.type !== import_utils119.AST_NODE_TYPES.ObjectExpression) return false;
22170
- let lacksSignal = true;
22249
+ function emptySignal(expression, resolvesToGlobal) {
22250
+ const value = unwrapExpression(expression);
22251
+ if (value.type === import_utils119.AST_NODE_TYPES.Literal && value.value === null)
22252
+ return "null";
22253
+ if (value.type === import_utils119.AST_NODE_TYPES.Identifier && value.name === "undefined" && resolvesToGlobal(value))
22254
+ return "undefined";
22255
+ if (value.type === import_utils119.AST_NODE_TYPES.UnaryExpression && value.operator === "void") {
22256
+ const argument = unwrapExpression(value.argument);
22257
+ if (argument.type === import_utils119.AST_NODE_TYPES.Literal && argument.value === 0)
22258
+ return "undefined";
22259
+ }
22260
+ return null;
22261
+ }
22262
+ function initSignalState(init, resolvesToGlobal) {
22263
+ init = unwrapExpression(init);
22264
+ if (init.type !== import_utils119.AST_NODE_TYPES.ObjectExpression) return "unknown";
22265
+ let state = "absent";
22171
22266
  for (const prop of init.properties) {
22172
22267
  if (prop.type === import_utils119.AST_NODE_TYPES.SpreadElement) {
22173
- lacksSignal = false;
22268
+ state = "unknown";
22174
22269
  continue;
22175
22270
  }
22271
+ if (prop.kind !== "init" || hasUnprovedPrototype(prop)) return "unknown";
22176
22272
  const isSignal = !prop.computed && prop.key.type === import_utils119.AST_NODE_TYPES.Identifier && prop.key.name === "signal" || prop.key.type === import_utils119.AST_NODE_TYPES.Literal && prop.key.value === "signal";
22177
22273
  if (isSignal) {
22178
- const value = prop.value;
22179
- lacksSignal = prop.kind === "init" && !prop.method && (value.type === import_utils119.AST_NODE_TYPES.Literal && value.value === null || value.type === import_utils119.AST_NODE_TYPES.Identifier && value.name === "undefined" && resolvesToGlobal(value) || value.type === import_utils119.AST_NODE_TYPES.UnaryExpression && value.operator === "void" && value.argument.type === import_utils119.AST_NODE_TYPES.Literal && value.argument.value === 0);
22274
+ state = !prop.method ? emptySignal(prop.value, resolvesToGlobal) ?? "unknown" : "unknown";
22275
+ } else if (!prop.computed && import_utils119.ASTUtils.getPropertyName(prop) === "__proto__" && state === "absent") {
22276
+ state = "unknown";
22180
22277
  } else if (prop.computed && prop.key.type !== import_utils119.AST_NODE_TYPES.Literal) {
22181
- lacksSignal = false;
22278
+ state = "unknown";
22182
22279
  }
22183
22280
  }
22184
- return lacksSignal;
22281
+ return state;
22282
+ }
22283
+ function hasUnprovedPrototype(property) {
22284
+ if (property.computed || property.method || property.shorthand || import_utils119.ASTUtils.getPropertyName(property) !== "__proto__")
22285
+ return false;
22286
+ const prototype = unwrapExpression(property.value);
22287
+ if (prototype.type === import_utils119.AST_NODE_TYPES.Literal && prototype.value === null)
22288
+ return false;
22289
+ if (prototype.type !== import_utils119.AST_NODE_TYPES.ObjectExpression) return true;
22290
+ return prototype.properties.some(
22291
+ (member) => member.type === import_utils119.AST_NODE_TYPES.SpreadElement || member.kind !== "init" || !member.computed && import_utils119.ASTUtils.getPropertyName(member) === "__proto__" || member.computed && member.key.type !== import_utils119.AST_NODE_TYPES.Literal
22292
+ );
22185
22293
  }
22186
22294
  function isInlineUrl(node, resolvesToGlobal) {
22187
22295
  node = unwrapExpression(node);
@@ -22227,40 +22335,50 @@ var require_fetch_timeout_default = createRule({
22227
22335
  return variable === null || variable.defs.length === 0;
22228
22336
  }
22229
22337
  function isGlobalFetchCall2(callee) {
22338
+ callee = unwrapExpression(callee);
22230
22339
  if (callee.type === import_utils119.AST_NODE_TYPES.Identifier) {
22231
22340
  return callee.name === "fetch" && resolvesToGlobal(callee);
22232
22341
  }
22233
- return callee.type === import_utils119.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils119.AST_NODE_TYPES.Identifier && callee.property.name === "fetch" && callee.object.type === import_utils119.AST_NODE_TYPES.Identifier && GLOBAL_OBJECTS2.has(callee.object.name) && resolvesToGlobal(callee.object);
22342
+ const receiver = callee.type === import_utils119.AST_NODE_TYPES.MemberExpression ? unwrapExpression(callee.object) : null;
22343
+ return callee.type === import_utils119.AST_NODE_TYPES.MemberExpression && import_utils119.ASTUtils.getPropertyName(callee) === "fetch" && receiver?.type === import_utils119.AST_NODE_TYPES.Identifier && GLOBAL_OBJECTS2.has(receiver.name) && resolvesToGlobal(receiver);
22234
22344
  }
22235
- function localConstInitProvablyLacksSignal(identifier) {
22345
+ function localConstInitSignalState(identifier) {
22236
22346
  const variable = import_utils119.ASTUtils.findVariable(
22237
22347
  context.sourceCode.getScope(identifier),
22238
22348
  identifier.name
22239
22349
  );
22240
- if (variable?.defs.length !== 1) return false;
22350
+ if (variable?.defs.length !== 1) return "unknown";
22241
22351
  const definition = variable.defs[0];
22242
- if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init?.type !== import_utils119.AST_NODE_TYPES.ObjectExpression || !initProvablyLacksSignal(definition.node.init, resolvesToGlobal)) {
22243
- return false;
22352
+ if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || unwrapExpression(definition.node.init).type !== import_utils119.AST_NODE_TYPES.ObjectExpression) {
22353
+ return "unknown";
22244
22354
  }
22245
22355
  for (const reference of variable.references) {
22246
22356
  const ref = reference.identifier;
22247
22357
  if (ref === identifier || ref === definition.name) continue;
22248
22358
  const member = ref.parent;
22249
- if (member.type !== import_utils119.AST_NODE_TYPES.MemberExpression || member.object !== ref || member.computed || member.property.type !== import_utils119.AST_NODE_TYPES.Identifier || member.property.name === "signal" || member.parent.type !== import_utils119.AST_NODE_TYPES.AssignmentExpression || member.parent.left !== member) {
22250
- return false;
22359
+ if (member.type !== import_utils119.AST_NODE_TYPES.MemberExpression || member.object !== ref || member.computed || member.property.type !== import_utils119.AST_NODE_TYPES.Identifier || member.property.name === "signal" || member.property.name === "__proto__" || member.parent.type !== import_utils119.AST_NODE_TYPES.AssignmentExpression || member.parent.left !== member) {
22360
+ return "unknown";
22251
22361
  }
22252
22362
  }
22253
- return true;
22363
+ return initSignalState(definition.node.init, resolvesToGlobal);
22254
22364
  }
22255
22365
  function isForwardedRequest(argument) {
22256
- let value = argument;
22366
+ let value = unwrapExpression(argument);
22257
22367
  if (value.type === import_utils119.AST_NODE_TYPES.Identifier) {
22258
- const binding = import_utils119.ASTUtils.findVariable(context.sourceCode.getScope(value), value.name);
22368
+ const binding = import_utils119.ASTUtils.findVariable(
22369
+ context.sourceCode.getScope(value),
22370
+ value.name
22371
+ );
22259
22372
  const definition = binding?.defs.length === 1 ? binding.defs[0] : void 0;
22260
- if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
22261
- value = definition.node.init;
22373
+ if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some(
22374
+ (reference) => reference.isWrite() && reference.init !== true
22375
+ ))
22376
+ return false;
22377
+ value = unwrapExpression(definition.node.init);
22262
22378
  }
22263
- return value.type === import_utils119.AST_NODE_TYPES.NewExpression && value.callee.type === import_utils119.AST_NODE_TYPES.Identifier && value.callee.name === "Request" && resolvesToGlobal(value.callee);
22379
+ if (value.type !== import_utils119.AST_NODE_TYPES.NewExpression) return false;
22380
+ const callee = unwrapExpression(value.callee);
22381
+ return callee.type === import_utils119.AST_NODE_TYPES.Identifier && callee.name === "Request" && resolvesToGlobal(callee);
22264
22382
  }
22265
22383
  return {
22266
22384
  CallExpression(node) {
@@ -22268,11 +22386,13 @@ var require_fetch_timeout_default = createRule({
22268
22386
  if (!isGlobalFetchCall2(unwrappedNodeCallee)) {
22269
22387
  return;
22270
22388
  }
22271
- const [first, init] = node.arguments;
22272
- if (first !== void 0 && (node.arguments.length === 1 && !isInlineUrl(first, resolvesToGlobal) || isForwardedRequest(first))) {
22389
+ const [first, rawInit] = node.arguments;
22390
+ const init = rawInit === void 0 ? void 0 : unwrapExpression(rawInit);
22391
+ const state = init === void 0 ? "unknown" : init.type === import_utils119.AST_NODE_TYPES.Identifier ? localConstInitSignalState(init) : initSignalState(init, resolvesToGlobal);
22392
+ if (first !== void 0 && (node.arguments.length === 1 && !isInlineUrl(first, resolvesToGlobal) || isForwardedRequest(first) && state !== "null")) {
22273
22393
  return;
22274
22394
  }
22275
- if (init === void 0 || initProvablyLacksSignal(init, resolvesToGlobal) || init.type === import_utils119.AST_NODE_TYPES.Identifier && localConstInitProvablyLacksSignal(init)) {
22395
+ if (init === void 0 || state !== "unknown" || first !== void 0 && isInlineUrl(first, resolvesToGlobal) && emptySignal(init, resolvesToGlobal) !== null) {
22276
22396
  context.report({ node, messageId: "missingSignal" });
22277
22397
  }
22278
22398
  }
package/dist/index.d.cts CHANGED
@@ -176,7 +176,9 @@ declare const RULES: {
176
176
  readonly "no-detached-global-fetch": DocumentedRule<readonly [], "detachedGlobalFetch">;
177
177
  readonly "no-json-stringify-error": DocumentedRule<readonly [], "noJsonStringifyError">;
178
178
  readonly "no-json-stringify-object-equality": DocumentedRule<readonly [], "serializedObjectEquality">;
179
- readonly "no-impossible-zod-literal-bounds": DocumentedRule<readonly [], "impossibleBounds">;
179
+ readonly "no-impossible-zod-literal-bounds": DocumentedRule<readonly [{
180
+ readonly delegateRealContradictions?: boolean;
181
+ }?], "impossibleBounds">;
180
182
  readonly "no-in-operator-on-built-in-collections": DocumentedRule<readonly [], "ambiguousCollectionIn">;
181
183
  readonly "no-redundant-optional-array-default": DocumentedRule<readonly [], "redundantOptionalArrayDefault">;
182
184
  readonly "interface-contract-members-private": DocumentedRule<readonly [], "nonContractMemberMustBePrivate">;
@@ -983,7 +985,9 @@ declare const PLUGIN: {
983
985
  readonly "no-detached-global-fetch": DocumentedRule<readonly [], "detachedGlobalFetch">;
984
986
  readonly "no-json-stringify-error": DocumentedRule<readonly [], "noJsonStringifyError">;
985
987
  readonly "no-json-stringify-object-equality": DocumentedRule<readonly [], "serializedObjectEquality">;
986
- readonly "no-impossible-zod-literal-bounds": DocumentedRule<readonly [], "impossibleBounds">;
988
+ readonly "no-impossible-zod-literal-bounds": DocumentedRule<readonly [{
989
+ readonly delegateRealContradictions?: boolean;
990
+ }?], "impossibleBounds">;
987
991
  readonly "no-in-operator-on-built-in-collections": DocumentedRule<readonly [], "ambiguousCollectionIn">;
988
992
  readonly "no-redundant-optional-array-default": DocumentedRule<readonly [], "redundantOptionalArrayDefault">;
989
993
  readonly "interface-contract-members-private": DocumentedRule<readonly [], "nonContractMemberMustBePrivate">;
package/dist/index.d.ts CHANGED
@@ -176,7 +176,9 @@ declare const RULES: {
176
176
  readonly "no-detached-global-fetch": DocumentedRule<readonly [], "detachedGlobalFetch">;
177
177
  readonly "no-json-stringify-error": DocumentedRule<readonly [], "noJsonStringifyError">;
178
178
  readonly "no-json-stringify-object-equality": DocumentedRule<readonly [], "serializedObjectEquality">;
179
- readonly "no-impossible-zod-literal-bounds": DocumentedRule<readonly [], "impossibleBounds">;
179
+ readonly "no-impossible-zod-literal-bounds": DocumentedRule<readonly [{
180
+ readonly delegateRealContradictions?: boolean;
181
+ }?], "impossibleBounds">;
180
182
  readonly "no-in-operator-on-built-in-collections": DocumentedRule<readonly [], "ambiguousCollectionIn">;
181
183
  readonly "no-redundant-optional-array-default": DocumentedRule<readonly [], "redundantOptionalArrayDefault">;
182
184
  readonly "interface-contract-members-private": DocumentedRule<readonly [], "nonContractMemberMustBePrivate">;
@@ -983,7 +985,9 @@ declare const PLUGIN: {
983
985
  readonly "no-detached-global-fetch": DocumentedRule<readonly [], "detachedGlobalFetch">;
984
986
  readonly "no-json-stringify-error": DocumentedRule<readonly [], "noJsonStringifyError">;
985
987
  readonly "no-json-stringify-object-equality": DocumentedRule<readonly [], "serializedObjectEquality">;
986
- readonly "no-impossible-zod-literal-bounds": DocumentedRule<readonly [], "impossibleBounds">;
988
+ readonly "no-impossible-zod-literal-bounds": DocumentedRule<readonly [{
989
+ readonly delegateRealContradictions?: boolean;
990
+ }?], "impossibleBounds">;
987
991
  readonly "no-in-operator-on-built-in-collections": DocumentedRule<readonly [], "ambiguousCollectionIn">;
988
992
  readonly "no-redundant-optional-array-default": DocumentedRule<readonly [], "redundantOptionalArrayDefault">;
989
993
  readonly "interface-contract-members-private": DocumentedRule<readonly [], "nonContractMemberMustBePrivate">;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var package_default = {
3
3
  name: "@sarj/eslint-plugin",
4
- version: "16.10.0",
4
+ version: "16.11.0",
5
5
  packageManager: "npm@12.2.0",
6
6
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
7
7
  type: "module",
@@ -7680,7 +7680,8 @@ var NO_IMPOSSIBLE_ZOD_LITERAL_BOUNDS_DOCUMENTATION = {
7680
7680
  remediation: "Choose compatible lower and upper bounds, or remove the constraint that does not express the intended domain.",
7681
7681
  category: "correctness",
7682
7682
  limitations: [
7683
- "Only finite numeric literals in a single number, string, or array schema chain are compared; explicit int() chains also require an admitted integer.",
7683
+ "Only finite numeric literals in a single schema chain are compared; number().int(), int(), int32(), uint32(), strings and arrays require an admitted integer value or length.",
7684
+ "int32() and uint32() include their intrinsic ranges. The managed preset delegates explicit real-bound conflicts to upstream zod/no-conflicting-checks to avoid duplicate findings.",
7684
7685
  "Integer-domain comparisons abstain near the JavaScript safe-integer boundary.",
7685
7686
  "Chains with dynamic bounds, non-bound validators, transforms, pipes, or preprocessors are skipped.",
7686
7687
  "Test and generated files are excluded."
@@ -7716,7 +7717,14 @@ var NO_IMPOSSIBLE_ZOD_LITERAL_BOUNDS_DOCUMENTATION = {
7716
7717
  }
7717
7718
  ]
7718
7719
  };
7719
- var KINDS = /* @__PURE__ */ new Set(["array", "number", "string"]);
7720
+ var KINDS = /* @__PURE__ */ new Map([
7721
+ ["array", "array"],
7722
+ ["number", "number"],
7723
+ ["string", "string"],
7724
+ ["int", "integer"],
7725
+ ["int32", "int32"],
7726
+ ["uint32", "uint32"]
7727
+ ]);
7720
7728
  var NUMBER_METHODS = /* @__PURE__ */ new Set([
7721
7729
  "gt",
7722
7730
  "gte",
@@ -7762,11 +7770,14 @@ function strongerUpper(current, candidate2) {
7762
7770
  }
7763
7771
  return current;
7764
7772
  }
7773
+ function isRealEmpty(lower, upper) {
7774
+ if (lower === null || upper === null) return false;
7775
+ return lower.value > upper.value || lower.value === upper.value && (lower.exclusive || upper.exclusive);
7776
+ }
7765
7777
  function isEmpty(lower, upper, integer) {
7766
7778
  if (lower === null || upper === null) return false;
7767
- const realEmpty = lower.value > upper.value || lower.value === upper.value && (lower.exclusive || upper.exclusive);
7779
+ if (isRealEmpty(lower, upper)) return true;
7768
7780
  if (integer) {
7769
- if (realEmpty) return false;
7770
7781
  const safeMargin = Number.MAX_SAFE_INTEGER - 1;
7771
7782
  if (Math.abs(lower.value) >= safeMargin || Math.abs(upper.value) >= safeMargin) {
7772
7783
  return false;
@@ -7775,11 +7786,12 @@ function isEmpty(lower, upper, integer) {
7775
7786
  const maximum = upper.exclusive ? Math.ceil(upper.value) - 1 : Math.floor(upper.value);
7776
7787
  return minimum > maximum;
7777
7788
  }
7778
- return realEmpty;
7789
+ return false;
7779
7790
  }
7780
7791
  function isOutermostCall(node) {
7781
- const parent = node.parent;
7782
- return !(parent?.type === AST_NODE_TYPES37.MemberExpression && parent.object === node && parent.parent?.type === AST_NODE_TYPES37.CallExpression && parent.parent.callee === parent);
7792
+ const expression = outerExpression(node);
7793
+ const parent = expression.parent;
7794
+ return !(parent?.type === AST_NODE_TYPES37.MemberExpression && parent.object === expression && parent.parent?.type === AST_NODE_TYPES37.CallExpression && parent.parent.callee === parent);
7783
7795
  }
7784
7796
  var no_impossible_zod_literal_bounds_default = createRule({
7785
7797
  name: "no-impossible-zod-literal-bounds",
@@ -7789,13 +7801,24 @@ var no_impossible_zod_literal_bounds_default = createRule({
7789
7801
  docs: {
7790
7802
  description: "Disallow same-chain literal Zod bounds whose accepted set is mathematically empty."
7791
7803
  },
7792
- schema: [],
7804
+ schema: [
7805
+ {
7806
+ type: "object",
7807
+ additionalProperties: false,
7808
+ properties: {
7809
+ delegateRealContradictions: {
7810
+ type: "boolean",
7811
+ description: "Let enabled upstream zod/no-conflicting-checks own explicit real-bound contradictions."
7812
+ }
7813
+ }
7814
+ }
7815
+ ],
7793
7816
  messages: {
7794
7817
  impossibleBounds: "This Zod {{kind}} schema accepts no values: {{lower}} conflicts with {{upper}}."
7795
7818
  }
7796
7819
  },
7797
- defaultOptions: [],
7798
- create(context) {
7820
+ defaultOptions: [{}],
7821
+ create(context, [options]) {
7799
7822
  const sourceCode = context.sourceCode;
7800
7823
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
7801
7824
  return {};
@@ -7809,9 +7832,13 @@ var no_impossible_zod_literal_bounds_default = createRule({
7809
7832
  if (binding === void 0) return false;
7810
7833
  let scope = sourceCode.getScope(node);
7811
7834
  while (scope !== null) {
7812
- const variable = scope.variables.find((candidate2) => candidate2.name === node.name);
7835
+ const variable = scope.variables.find(
7836
+ (candidate2) => candidate2.name === node.name
7837
+ );
7813
7838
  if (variable !== void 0) {
7814
- return variable.defs.some((definition) => definition.name === binding);
7839
+ return variable.defs.some(
7840
+ (definition) => definition.name === binding
7841
+ );
7815
7842
  }
7816
7843
  scope = scope.upper;
7817
7844
  }
@@ -7844,7 +7871,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
7844
7871
  return null;
7845
7872
  }
7846
7873
  const name = ASTUtils17.getPropertyName(callee);
7847
- return name !== null && KINDS.has(name) ? name : null;
7874
+ return name === null ? null : KINDS.get(name) ?? null;
7848
7875
  }
7849
7876
  function isInsideReshapingCall(node) {
7850
7877
  let child = node;
@@ -7866,10 +7893,33 @@ var no_impossible_zod_literal_bounds_default = createRule({
7866
7893
  return false;
7867
7894
  }
7868
7895
  function contradiction(chain) {
7869
- const allowed = chain.kind === "number" ? NUMBER_METHODS : LENGTH_METHODS;
7896
+ const bounds = readConstraints(chain);
7897
+ if (bounds === null) return null;
7898
+ let { lower, upper } = bounds;
7899
+ const { integer, numeric } = bounds;
7900
+ if (isRealEmpty(lower, upper) && (numeric && integer || options?.delegateRealContradictions === true)) {
7901
+ return null;
7902
+ }
7903
+ if (chain.kind === "int32" || chain.kind === "uint32") {
7904
+ lower = strongerLower(lower, {
7905
+ exclusive: false,
7906
+ label: `${chain.kind}() minimum`,
7907
+ value: chain.kind === "int32" ? -(2 ** 31) : 0
7908
+ });
7909
+ upper = strongerUpper(upper, {
7910
+ exclusive: false,
7911
+ label: `${chain.kind}() maximum`,
7912
+ value: chain.kind === "int32" ? 2 ** 31 - 1 : 2 ** 32 - 1
7913
+ });
7914
+ }
7915
+ return isEmpty(lower, upper, integer) && lower !== null && upper !== null ? { lower, upper } : null;
7916
+ }
7917
+ function readConstraints(chain) {
7918
+ const numeric = chain.kind !== "string" && chain.kind !== "array";
7919
+ const allowed = numeric ? NUMBER_METHODS : LENGTH_METHODS;
7870
7920
  let lower = null;
7871
7921
  let upper = null;
7872
- let integer = false;
7922
+ let integer = chain.kind !== "number";
7873
7923
  function addBound(method, semantic, value) {
7874
7924
  const label = `${method}(${String(value)})`;
7875
7925
  if (semantic !== null) {
@@ -7913,13 +7963,13 @@ var no_impossible_zod_literal_bounds_default = createRule({
7913
7963
  const semantic = zeroBoundSemantic(method);
7914
7964
  const value = semantic?.value ?? finiteNumber(node.arguments[0]);
7915
7965
  if (value === null) return null;
7916
- if (chain.kind !== "number" && (!Number.isInteger(value) || value < 0)) {
7966
+ if (!numeric && value < 0) {
7917
7967
  return null;
7918
7968
  }
7919
7969
  if (semantic !== null && node.arguments.length !== 0) return null;
7920
7970
  addBound(method, semantic, value);
7921
7971
  }
7922
- return isEmpty(lower, upper, integer) && lower !== null && upper !== null ? { lower, upper } : null;
7972
+ return { integer, lower, numeric, upper };
7923
7973
  }
7924
7974
  return {
7925
7975
  ImportDeclaration(node) {
@@ -7935,7 +7985,9 @@ var no_impossible_zod_literal_bounds_default = createRule({
7935
7985
  namespaces.add(specifier.local.name);
7936
7986
  importBindings.set(specifier.local.name, specifier.local);
7937
7987
  } else if (imported !== null && KINDS.has(imported)) {
7938
- constructors.set(specifier.local.name, imported);
7988
+ const kind = KINDS.get(imported);
7989
+ if (kind !== void 0)
7990
+ constructors.set(specifier.local.name, kind);
7939
7991
  importBindings.set(specifier.local.name, specifier.local);
7940
7992
  } else if (imported === "preprocess") {
7941
7993
  preprocessors.add(specifier.local.name);
@@ -22157,16 +22209,48 @@ var require_assert_never_default = createRule({
22157
22209
  });
22158
22210
 
22159
22211
  // src/rules/require-fetch-timeout.ts
22160
- import { AST_NODE_TYPES as AST_NODE_TYPES103, ASTUtils as ASTUtils64 } from "@typescript-eslint/utils";
22212
+ import {
22213
+ AST_NODE_TYPES as AST_NODE_TYPES103,
22214
+ ASTUtils as ASTUtils64
22215
+ } from "@typescript-eslint/utils";
22161
22216
  var REQUIRE_FETCH_TIMEOUT_DOCUMENTATION = {
22162
22217
  summary: "Require an explicit abort signal on locally analyzable global fetch calls.",
22163
22218
  rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
22164
22219
  remediation: "Pass an abort signal, such as `AbortSignal.timeout(ms)`, in the fetch init.",
22165
22220
  category: "correctness",
22166
- limitations: ["Signal presence establishes an explicit cancellation path, not a guaranteed timeout. Literal null, global undefined, and void 0 do not provide a signal. Unknown spreads and dynamic values are not inferred. Forwarded Request objects can carry an existing signal."],
22221
+ limitations: [
22222
+ "Signal presence establishes an explicit cancellation path, not a guaranteed timeout. Literal null, global undefined, and void 0 do not provide a signal. Erased TypeScript wrappers preserve the same analysis. Unknown spreads and dynamic values are not inferred. Forwarded Request objects retain their signal unless an init explicitly resets it to null.",
22223
+ "Accessors and unproved prototypes are excluded because native option conversion can attach or replace a signal before it is read."
22224
+ ],
22167
22225
  examples: [
22168
- { id: "bounded-fetch", title: "Bound the request", outcome: "no-match", files: [{ path: "src/client.ts", source: "await fetch(url, { signal: AbortSignal.timeout(5000) });" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
22169
- { id: "unbounded-fetch", title: "Do not leave fetch unbounded", outcome: "match", files: [{ path: "src/client.ts", source: "await fetch('https://api.example.com/items');" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
22226
+ {
22227
+ id: "bounded-fetch",
22228
+ title: "Bound the request",
22229
+ outcome: "no-match",
22230
+ files: [
22231
+ {
22232
+ path: "src/client.ts",
22233
+ source: "await fetch(url, { signal: AbortSignal.timeout(5000) });"
22234
+ }
22235
+ ],
22236
+ focusPath: "src/client.ts",
22237
+ expectedCount: 0,
22238
+ public: true
22239
+ },
22240
+ {
22241
+ id: "unbounded-fetch",
22242
+ title: "Do not leave fetch unbounded",
22243
+ outcome: "match",
22244
+ files: [
22245
+ {
22246
+ path: "src/client.ts",
22247
+ source: "await fetch('https://api.example.com/items');"
22248
+ }
22249
+ ],
22250
+ focusPath: "src/client.ts",
22251
+ expectedCount: 1,
22252
+ public: true
22253
+ }
22170
22254
  ]
22171
22255
  };
22172
22256
  var GLOBAL_OBJECTS2 = /* @__PURE__ */ new Set([
@@ -22183,23 +22267,50 @@ function matchesAnyPattern3(filename, patterns) {
22183
22267
  }
22184
22268
  return false;
22185
22269
  }
22186
- function initProvablyLacksSignal(init, resolvesToGlobal) {
22187
- if (init.type !== AST_NODE_TYPES103.ObjectExpression) return false;
22188
- let lacksSignal = true;
22270
+ function emptySignal(expression, resolvesToGlobal) {
22271
+ const value = unwrapExpression(expression);
22272
+ if (value.type === AST_NODE_TYPES103.Literal && value.value === null)
22273
+ return "null";
22274
+ if (value.type === AST_NODE_TYPES103.Identifier && value.name === "undefined" && resolvesToGlobal(value))
22275
+ return "undefined";
22276
+ if (value.type === AST_NODE_TYPES103.UnaryExpression && value.operator === "void") {
22277
+ const argument = unwrapExpression(value.argument);
22278
+ if (argument.type === AST_NODE_TYPES103.Literal && argument.value === 0)
22279
+ return "undefined";
22280
+ }
22281
+ return null;
22282
+ }
22283
+ function initSignalState(init, resolvesToGlobal) {
22284
+ init = unwrapExpression(init);
22285
+ if (init.type !== AST_NODE_TYPES103.ObjectExpression) return "unknown";
22286
+ let state = "absent";
22189
22287
  for (const prop of init.properties) {
22190
22288
  if (prop.type === AST_NODE_TYPES103.SpreadElement) {
22191
- lacksSignal = false;
22289
+ state = "unknown";
22192
22290
  continue;
22193
22291
  }
22292
+ if (prop.kind !== "init" || hasUnprovedPrototype(prop)) return "unknown";
22194
22293
  const isSignal = !prop.computed && prop.key.type === AST_NODE_TYPES103.Identifier && prop.key.name === "signal" || prop.key.type === AST_NODE_TYPES103.Literal && prop.key.value === "signal";
22195
22294
  if (isSignal) {
22196
- const value = prop.value;
22197
- lacksSignal = prop.kind === "init" && !prop.method && (value.type === AST_NODE_TYPES103.Literal && value.value === null || value.type === AST_NODE_TYPES103.Identifier && value.name === "undefined" && resolvesToGlobal(value) || value.type === AST_NODE_TYPES103.UnaryExpression && value.operator === "void" && value.argument.type === AST_NODE_TYPES103.Literal && value.argument.value === 0);
22295
+ state = !prop.method ? emptySignal(prop.value, resolvesToGlobal) ?? "unknown" : "unknown";
22296
+ } else if (!prop.computed && ASTUtils64.getPropertyName(prop) === "__proto__" && state === "absent") {
22297
+ state = "unknown";
22198
22298
  } else if (prop.computed && prop.key.type !== AST_NODE_TYPES103.Literal) {
22199
- lacksSignal = false;
22299
+ state = "unknown";
22200
22300
  }
22201
22301
  }
22202
- return lacksSignal;
22302
+ return state;
22303
+ }
22304
+ function hasUnprovedPrototype(property) {
22305
+ if (property.computed || property.method || property.shorthand || ASTUtils64.getPropertyName(property) !== "__proto__")
22306
+ return false;
22307
+ const prototype = unwrapExpression(property.value);
22308
+ if (prototype.type === AST_NODE_TYPES103.Literal && prototype.value === null)
22309
+ return false;
22310
+ if (prototype.type !== AST_NODE_TYPES103.ObjectExpression) return true;
22311
+ return prototype.properties.some(
22312
+ (member) => member.type === AST_NODE_TYPES103.SpreadElement || member.kind !== "init" || !member.computed && ASTUtils64.getPropertyName(member) === "__proto__" || member.computed && member.key.type !== AST_NODE_TYPES103.Literal
22313
+ );
22203
22314
  }
22204
22315
  function isInlineUrl(node, resolvesToGlobal) {
22205
22316
  node = unwrapExpression(node);
@@ -22245,40 +22356,50 @@ var require_fetch_timeout_default = createRule({
22245
22356
  return variable === null || variable.defs.length === 0;
22246
22357
  }
22247
22358
  function isGlobalFetchCall2(callee) {
22359
+ callee = unwrapExpression(callee);
22248
22360
  if (callee.type === AST_NODE_TYPES103.Identifier) {
22249
22361
  return callee.name === "fetch" && resolvesToGlobal(callee);
22250
22362
  }
22251
- return callee.type === AST_NODE_TYPES103.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES103.Identifier && callee.property.name === "fetch" && callee.object.type === AST_NODE_TYPES103.Identifier && GLOBAL_OBJECTS2.has(callee.object.name) && resolvesToGlobal(callee.object);
22363
+ const receiver = callee.type === AST_NODE_TYPES103.MemberExpression ? unwrapExpression(callee.object) : null;
22364
+ return callee.type === AST_NODE_TYPES103.MemberExpression && ASTUtils64.getPropertyName(callee) === "fetch" && receiver?.type === AST_NODE_TYPES103.Identifier && GLOBAL_OBJECTS2.has(receiver.name) && resolvesToGlobal(receiver);
22252
22365
  }
22253
- function localConstInitProvablyLacksSignal(identifier) {
22366
+ function localConstInitSignalState(identifier) {
22254
22367
  const variable = ASTUtils64.findVariable(
22255
22368
  context.sourceCode.getScope(identifier),
22256
22369
  identifier.name
22257
22370
  );
22258
- if (variable?.defs.length !== 1) return false;
22371
+ if (variable?.defs.length !== 1) return "unknown";
22259
22372
  const definition = variable.defs[0];
22260
- if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init?.type !== AST_NODE_TYPES103.ObjectExpression || !initProvablyLacksSignal(definition.node.init, resolvesToGlobal)) {
22261
- return false;
22373
+ if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || unwrapExpression(definition.node.init).type !== AST_NODE_TYPES103.ObjectExpression) {
22374
+ return "unknown";
22262
22375
  }
22263
22376
  for (const reference of variable.references) {
22264
22377
  const ref = reference.identifier;
22265
22378
  if (ref === identifier || ref === definition.name) continue;
22266
22379
  const member = ref.parent;
22267
- if (member.type !== AST_NODE_TYPES103.MemberExpression || member.object !== ref || member.computed || member.property.type !== AST_NODE_TYPES103.Identifier || member.property.name === "signal" || member.parent.type !== AST_NODE_TYPES103.AssignmentExpression || member.parent.left !== member) {
22268
- return false;
22380
+ if (member.type !== AST_NODE_TYPES103.MemberExpression || member.object !== ref || member.computed || member.property.type !== AST_NODE_TYPES103.Identifier || member.property.name === "signal" || member.property.name === "__proto__" || member.parent.type !== AST_NODE_TYPES103.AssignmentExpression || member.parent.left !== member) {
22381
+ return "unknown";
22269
22382
  }
22270
22383
  }
22271
- return true;
22384
+ return initSignalState(definition.node.init, resolvesToGlobal);
22272
22385
  }
22273
22386
  function isForwardedRequest(argument) {
22274
- let value = argument;
22387
+ let value = unwrapExpression(argument);
22275
22388
  if (value.type === AST_NODE_TYPES103.Identifier) {
22276
- const binding = ASTUtils64.findVariable(context.sourceCode.getScope(value), value.name);
22389
+ const binding = ASTUtils64.findVariable(
22390
+ context.sourceCode.getScope(value),
22391
+ value.name
22392
+ );
22277
22393
  const definition = binding?.defs.length === 1 ? binding.defs[0] : void 0;
22278
- if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
22279
- value = definition.node.init;
22394
+ if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some(
22395
+ (reference) => reference.isWrite() && reference.init !== true
22396
+ ))
22397
+ return false;
22398
+ value = unwrapExpression(definition.node.init);
22280
22399
  }
22281
- return value.type === AST_NODE_TYPES103.NewExpression && value.callee.type === AST_NODE_TYPES103.Identifier && value.callee.name === "Request" && resolvesToGlobal(value.callee);
22400
+ if (value.type !== AST_NODE_TYPES103.NewExpression) return false;
22401
+ const callee = unwrapExpression(value.callee);
22402
+ return callee.type === AST_NODE_TYPES103.Identifier && callee.name === "Request" && resolvesToGlobal(callee);
22282
22403
  }
22283
22404
  return {
22284
22405
  CallExpression(node) {
@@ -22286,11 +22407,13 @@ var require_fetch_timeout_default = createRule({
22286
22407
  if (!isGlobalFetchCall2(unwrappedNodeCallee)) {
22287
22408
  return;
22288
22409
  }
22289
- const [first, init] = node.arguments;
22290
- if (first !== void 0 && (node.arguments.length === 1 && !isInlineUrl(first, resolvesToGlobal) || isForwardedRequest(first))) {
22410
+ const [first, rawInit] = node.arguments;
22411
+ const init = rawInit === void 0 ? void 0 : unwrapExpression(rawInit);
22412
+ const state = init === void 0 ? "unknown" : init.type === AST_NODE_TYPES103.Identifier ? localConstInitSignalState(init) : initSignalState(init, resolvesToGlobal);
22413
+ if (first !== void 0 && (node.arguments.length === 1 && !isInlineUrl(first, resolvesToGlobal) || isForwardedRequest(first) && state !== "null")) {
22291
22414
  return;
22292
22415
  }
22293
- if (init === void 0 || initProvablyLacksSignal(init, resolvesToGlobal) || init.type === AST_NODE_TYPES103.Identifier && localConstInitProvablyLacksSignal(init)) {
22416
+ if (init === void 0 || state !== "unknown" || first !== void 0 && isInlineUrl(first, resolvesToGlobal) && emptySignal(init, resolvesToGlobal) !== null) {
22294
22417
  context.report({ node, messageId: "missingSignal" });
22295
22418
  }
22296
22419
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "16.10.0",
3
+ "version": "16.11.0",
4
4
  "packageManager": "npm@12.2.0",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",