@sarj/eslint-plugin 11.0.1 → 11.1.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
@@ -2941,6 +2941,16 @@ var PAYLOAD_PROPS = /* @__PURE__ */ new Set([
2941
2941
  "metadata",
2942
2942
  "context"
2943
2943
  ]);
2944
+ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
2945
+ "AggregateError",
2946
+ "Error",
2947
+ "EvalError",
2948
+ "RangeError",
2949
+ "ReferenceError",
2950
+ "SyntaxError",
2951
+ "TypeError",
2952
+ "URIError"
2953
+ ]);
2944
2954
  function isCatchBinding(scope, name) {
2945
2955
  let current = scope;
2946
2956
  while (current) {
@@ -3055,6 +3065,48 @@ function nodeWithin(node, container) {
3055
3065
  function isJsonStringify(callee) {
3056
3066
  return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
3057
3067
  }
3068
+ function directLiteralValues(argument) {
3069
+ if (argument.type === "ObjectExpression") {
3070
+ return argument.properties.flatMap((property) => {
3071
+ if (property.type !== "Property" || property.computed) return [];
3072
+ const value = property.value;
3073
+ if (value.type === "AssignmentPattern" || value.type === "ArrayPattern" || value.type === "ObjectPattern" || value.type === "TSEmptyBodyFunctionExpression") {
3074
+ return [];
3075
+ }
3076
+ return [value];
3077
+ });
3078
+ }
3079
+ if (argument.type === "ArrayExpression") {
3080
+ return argument.elements.flatMap(
3081
+ (element) => element !== null && element.type !== "SpreadElement" ? [element] : []
3082
+ );
3083
+ }
3084
+ return argument.type === "SpreadElement" ? [] : [argument];
3085
+ }
3086
+ function expressionSuggestsError(expression, scope) {
3087
+ if (expression.type === "Identifier") {
3088
+ return ERROR_NAME_PATTERN.test(expression.name) || isCatchBinding(scope, expression.name);
3089
+ }
3090
+ return expression.type === "MemberExpression" && memberSuggestsError(expression, scope);
3091
+ }
3092
+ function nestedExpressionSuggestsError(expression, scope) {
3093
+ if (expression.type === "Identifier") {
3094
+ if (isCatchBinding(scope, expression.name)) return true;
3095
+ let current = scope;
3096
+ while (current !== null && !current.set.has(expression.name)) {
3097
+ current = current.upper;
3098
+ }
3099
+ const variable = current?.set.get(expression.name);
3100
+ if (variable === void 0 || variable.defs.length !== 1) return false;
3101
+ const definition = variable.defs[0];
3102
+ if (definition?.type !== "Variable") return false;
3103
+ const initializer = definition.node.init;
3104
+ return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && variable.references.every(
3105
+ (reference) => !reference.isWrite() || reference.init === true
3106
+ );
3107
+ }
3108
+ return expression.type === "MemberExpression" && memberSuggestsError(expression, scope);
3109
+ }
3058
3110
  var no_json_stringify_error_default = createRule({
3059
3111
  name: "no-json-stringify-error",
3060
3112
  meta: {
@@ -3079,18 +3131,11 @@ var no_json_stringify_error_default = createRule({
3079
3131
  return;
3080
3132
  }
3081
3133
  const scope = context.sourceCode.getScope(firstArg);
3082
- let suggestsError;
3083
- if (firstArg.type === "Identifier") {
3084
- suggestsError = ERROR_NAME_PATTERN.test(firstArg.name) || isCatchBinding(scope, firstArg.name);
3085
- } else if (firstArg.type === "MemberExpression") {
3086
- suggestsError = memberSuggestsError(firstArg, scope);
3087
- } else {
3088
- return;
3089
- }
3090
- if (!suggestsError) {
3091
- return;
3092
- }
3093
- if (isGuardedByInstanceofError(node, firstArg, context.sourceCode) || isNarrowedByEarlyReturn(node, firstArg, context.sourceCode)) {
3134
+ const isNestedLiteral = firstArg.type === "ObjectExpression" || firstArg.type === "ArrayExpression";
3135
+ const unsafeValue = directLiteralValues(firstArg).find(
3136
+ (value) => (isNestedLiteral ? nestedExpressionSuggestsError(value, scope) : expressionSuggestsError(value, scope)) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
3137
+ );
3138
+ if (unsafeValue === void 0) {
3094
3139
  return;
3095
3140
  }
3096
3141
  context.report({
@@ -5160,50 +5205,59 @@ var no_select_star_default = createRule({
5160
5205
 
5161
5206
  // src/rules/no-sentinel-return-on-catch.ts
5162
5207
  var import_utils33 = require("@typescript-eslint/utils");
5208
+ function unwrapSentinelExpression(arg) {
5209
+ let current = arg;
5210
+ while (current?.type === import_utils33.AST_NODE_TYPES.TSAsExpression || current?.type === import_utils33.AST_NODE_TYPES.TSTypeAssertion || current?.type === import_utils33.AST_NODE_TYPES.TSSatisfiesExpression) {
5211
+ current = current.expression;
5212
+ }
5213
+ return current;
5214
+ }
5163
5215
  function sentinelKind(arg) {
5164
- if (arg === null) {
5216
+ const value = unwrapSentinelExpression(arg);
5217
+ if (value === null) {
5165
5218
  return null;
5166
5219
  }
5167
- if (arg.type === import_utils33.AST_NODE_TYPES.Literal) {
5168
- if (arg.value === null) {
5220
+ if (value.type === import_utils33.AST_NODE_TYPES.Literal) {
5221
+ if (value.value === null) {
5169
5222
  return "nullish";
5170
5223
  }
5171
- if (typeof arg.value === "boolean") {
5224
+ if (typeof value.value === "boolean") {
5172
5225
  return "boolean";
5173
5226
  }
5174
- if (typeof arg.value === "string") {
5227
+ if (typeof value.value === "string") {
5175
5228
  return "string";
5176
5229
  }
5177
5230
  return null;
5178
5231
  }
5179
- if (arg.type === import_utils33.AST_NODE_TYPES.Identifier && arg.name === "undefined") {
5232
+ if (value.type === import_utils33.AST_NODE_TYPES.Identifier && value.name === "undefined") {
5180
5233
  return "nullish";
5181
5234
  }
5182
- if (arg.type === import_utils33.AST_NODE_TYPES.ArrayExpression) {
5235
+ if (value.type === import_utils33.AST_NODE_TYPES.ArrayExpression) {
5183
5236
  return "array";
5184
5237
  }
5185
- if (arg.type === import_utils33.AST_NODE_TYPES.ObjectExpression) {
5238
+ if (value.type === import_utils33.AST_NODE_TYPES.ObjectExpression) {
5186
5239
  return "object";
5187
5240
  }
5188
5241
  return null;
5189
5242
  }
5190
5243
  function isSentinelArgument(arg) {
5191
- if (arg === null) {
5244
+ const value = unwrapSentinelExpression(arg);
5245
+ if (value === null) {
5192
5246
  return false;
5193
5247
  }
5194
- if (arg.type === import_utils33.AST_NODE_TYPES.Literal && arg.value === null) {
5248
+ if (value.type === import_utils33.AST_NODE_TYPES.Literal && value.value === null) {
5195
5249
  return true;
5196
5250
  }
5197
- if (arg.type === import_utils33.AST_NODE_TYPES.Literal && arg.value === false) {
5251
+ if (value.type === import_utils33.AST_NODE_TYPES.Literal && value.value === false) {
5198
5252
  return true;
5199
5253
  }
5200
- if (arg.type === import_utils33.AST_NODE_TYPES.Identifier && arg.name === "undefined") {
5254
+ if (value.type === import_utils33.AST_NODE_TYPES.Identifier && value.name === "undefined") {
5201
5255
  return true;
5202
5256
  }
5203
- if (arg.type === import_utils33.AST_NODE_TYPES.ArrayExpression && arg.elements.length === 0) {
5257
+ if (value.type === import_utils33.AST_NODE_TYPES.ArrayExpression && value.elements.length === 0) {
5204
5258
  return true;
5205
5259
  }
5206
- if (arg.type === import_utils33.AST_NODE_TYPES.ObjectExpression && arg.properties.length === 0) {
5260
+ if (value.type === import_utils33.AST_NODE_TYPES.ObjectExpression && value.properties.length === 0) {
5207
5261
  return true;
5208
5262
  }
5209
5263
  return false;
@@ -5401,6 +5455,16 @@ function tryReturnsSafeParse(catchNode) {
5401
5455
  const only = tryBlock.body.length === 1 ? tryBlock.body[0] : void 0;
5402
5456
  return only !== void 0 && returnsMatching(only, isBodyDecodeNode);
5403
5457
  }
5458
+ function isIntentionalStackCapture(catchNode, caughtName) {
5459
+ if (caughtName === null) return false;
5460
+ const tryBody = tryBlockOf(catchNode).body;
5461
+ const only = tryBody.length === 1 ? tryBody[0] : void 0;
5462
+ if (only?.type !== import_utils33.AST_NODE_TYPES.ThrowStatement) return false;
5463
+ const thrown = unwrapSentinelExpression(only.argument);
5464
+ const constructsError = (thrown?.type === import_utils33.AST_NODE_TYPES.CallExpression || thrown?.type === import_utils33.AST_NODE_TYPES.NewExpression) && thrown.callee.type === import_utils33.AST_NODE_TYPES.Identifier && thrown.callee.name === "Error";
5465
+ if (!constructsError) return false;
5466
+ return catchNode.body.body.slice(0, -1).some((statement) => subtreeReadsName(statement, caughtName));
5467
+ }
5404
5468
  function tryBlockOf(catchNode) {
5405
5469
  return catchNode.parent.block;
5406
5470
  }
@@ -5521,6 +5585,9 @@ var no_sentinel_return_on_catch_default = createRule({
5521
5585
  if (tryReturnsSafeParse(node)) {
5522
5586
  return;
5523
5587
  }
5588
+ if (isIntentionalStackCapture(node, caughtName)) {
5589
+ return;
5590
+ }
5524
5591
  const kind = sentinelKind(last.argument);
5525
5592
  if (kind !== null && isNamedBooleanPredicate(node, kind)) {
5526
5593
  return;
@@ -5599,11 +5666,11 @@ var no_silent_promise_catch_default = createRule({
5599
5666
  meta: {
5600
5667
  type: "problem",
5601
5668
  docs: {
5602
- description: "Disallow `.catch()` handlers that silently swallow the rejection (e.g. `.catch(() => null)`); log, rethrow, or handle the error."
5669
+ description: "Disallow `.catch()` and second-argument `.then()` handlers that silently swallow a rejection; log, rethrow, or handle the error."
5603
5670
  },
5604
5671
  schema: [],
5605
5672
  messages: {
5606
- silentCatch: "This `.catch()` swallows the rejection without logging, rethrowing, or handling it \u2014 failures become invisible and callers get an indistinguishable sentinel. Log the error (and only then map to a fallback), or let it propagate."
5673
+ silentCatch: "This rejection handler swallows the error without logging, rethrowing, or handling it \u2014 failures become invisible and callers get an indistinguishable sentinel. Log the error (and only then map to a fallback), or let it propagate."
5607
5674
  }
5608
5675
  },
5609
5676
  defaultOptions: [],
@@ -5629,9 +5696,12 @@ var no_silent_promise_catch_default = createRule({
5629
5696
  };
5630
5697
  return {
5631
5698
  CallExpression(node) {
5632
- if (node.callee.type !== import_utils34.AST_NODE_TYPES.MemberExpression || node.callee.computed || node.callee.property.type !== import_utils34.AST_NODE_TYPES.Identifier || node.callee.property.name !== "catch") {
5699
+ if (node.callee.type !== import_utils34.AST_NODE_TYPES.MemberExpression || node.callee.computed || node.callee.property.type !== import_utils34.AST_NODE_TYPES.Identifier) {
5633
5700
  return;
5634
5701
  }
5702
+ const method = node.callee.property.name;
5703
+ const handlerIndex = method === "catch" ? 0 : method === "then" ? 1 : null;
5704
+ if (handlerIndex === null) return;
5635
5705
  if (isBodyParseCall(node.callee.object)) {
5636
5706
  return;
5637
5707
  }
@@ -5641,10 +5711,11 @@ var no_silent_promise_catch_default = createRule({
5641
5711
  if (node.parent.type === import_utils34.AST_NODE_TYPES.MemberExpression && node.parent.object === node) {
5642
5712
  return;
5643
5713
  }
5644
- if (node.arguments.length !== 1) {
5714
+ const expectedArguments = method === "catch" ? 1 : 2;
5715
+ if (node.arguments.length !== expectedArguments) {
5645
5716
  return;
5646
5717
  }
5647
- const handler = node.arguments[0];
5718
+ const handler = node.arguments[handlerIndex];
5648
5719
  if (handler === void 0 || handler.type !== import_utils34.AST_NODE_TYPES.ArrowFunctionExpression && handler.type !== import_utils34.AST_NODE_TYPES.FunctionExpression) {
5649
5720
  return;
5650
5721
  }
@@ -5907,6 +5978,11 @@ function isStringLiteralInit(node) {
5907
5978
  return false;
5908
5979
  }
5909
5980
  function isConcatOntoTarget(rhs, target) {
5981
+ if (rhs.type === "TemplateLiteral") {
5982
+ return rhs.expressions.some(
5983
+ (expression) => expression.type === "Identifier" && expression.name === target
5984
+ );
5985
+ }
5910
5986
  if (rhs.type !== "BinaryExpression" || rhs.operator !== "+") {
5911
5987
  return false;
5912
5988
  }
@@ -7459,6 +7535,15 @@ var SUCCESS_PAYLOAD_MEMBER_NAMES = /* @__PURE__ */ new Set([
7459
7535
  "value"
7460
7536
  ]);
7461
7537
  var REQUIRED_STATUS_MEMBER_COUNT = 1;
7538
+ var FUNCTION_RETURN_OWNER_TYPES = /* @__PURE__ */ new Set([
7539
+ import_utils51.AST_NODE_TYPES.ArrowFunctionExpression,
7540
+ import_utils51.AST_NODE_TYPES.FunctionDeclaration,
7541
+ import_utils51.AST_NODE_TYPES.FunctionExpression,
7542
+ import_utils51.AST_NODE_TYPES.TSDeclareFunction,
7543
+ import_utils51.AST_NODE_TYPES.TSEmptyBodyFunctionExpression,
7544
+ import_utils51.AST_NODE_TYPES.TSFunctionType,
7545
+ import_utils51.AST_NODE_TYPES.TSMethodSignature
7546
+ ]);
7462
7547
  function looksLikeMutuallyExclusiveState(typeLiteral) {
7463
7548
  let statusMemberCount = 0;
7464
7549
  let hasFailurePayload = false;
@@ -7499,6 +7584,17 @@ function getMemberName(member) {
7499
7584
  function isBooleanTyped(member) {
7500
7585
  return member.typeAnnotation?.typeAnnotation.type === import_utils51.AST_NODE_TYPES.TSBooleanKeyword;
7501
7586
  }
7587
+ function inlineReturnTypeLiteral(node) {
7588
+ let annotation = null;
7589
+ if (node.parent.type === import_utils51.AST_NODE_TYPES.TSTypeAnnotation) {
7590
+ annotation = node.parent;
7591
+ } else if (node.parent.type === import_utils51.AST_NODE_TYPES.TSTypeParameterInstantiation && node.parent.params.length === 1 && node.parent.params[0] === node && node.parent.parent.type === import_utils51.AST_NODE_TYPES.TSTypeReference && node.parent.parent.typeName.type === import_utils51.AST_NODE_TYPES.Identifier && node.parent.parent.typeName.name === "Promise" && node.parent.parent.parent.type === import_utils51.AST_NODE_TYPES.TSTypeAnnotation) {
7592
+ annotation = node.parent.parent.parent;
7593
+ }
7594
+ if (annotation === null) return null;
7595
+ const owner = annotation.parent;
7596
+ return FUNCTION_RETURN_OWNER_TYPES.has(owner.type) && "returnType" in owner && owner.returnType === annotation ? annotation : null;
7597
+ }
7502
7598
  var prefer_discriminated_union_default = createRule({
7503
7599
  name: "prefer-discriminated-union",
7504
7600
  meta: {
@@ -7538,6 +7634,10 @@ var prefer_discriminated_union_default = createRule({
7538
7634
  },
7539
7635
  "TSTypeAliasDeclaration > TSTypeLiteral"(node) {
7540
7636
  checkTypeLiteral(node, node.parent);
7637
+ },
7638
+ TSTypeLiteral(node) {
7639
+ const annotation = inlineReturnTypeLiteral(node);
7640
+ if (annotation !== null) checkTypeLiteral(node, annotation);
7541
7641
  }
7542
7642
  };
7543
7643
  }
@@ -8960,7 +9060,7 @@ var isSchemaParseReference = (node) => {
8960
9060
  const inner = unwrap4(node);
8961
9061
  return inner !== null && inner.type === import_utils59.AST_NODE_TYPES.MemberExpression && !inner.computed && inner.property.type === import_utils59.AST_NODE_TYPES.Identifier && (inner.property.name === "parse" || inner.property.name === "safeParse");
8962
9062
  };
8963
- var isRawPayloadSource = (node) => {
9063
+ var isRawPayloadSource = (node, isKnownLocalText) => {
8964
9064
  let current = unwrap4(node);
8965
9065
  if (current === null) return false;
8966
9066
  if (current.type === import_utils59.AST_NODE_TYPES.AwaitExpression) {
@@ -8984,20 +9084,26 @@ var isRawPayloadSource = (node) => {
8984
9084
  return !current.arguments.some(isSchemaParseReference) && isRawPayloadSource(callee.object);
8985
9085
  }
8986
9086
  const object = unwrap4(callee.object);
8987
- return property.name === "parse" && object !== null && object.type === import_utils59.AST_NODE_TYPES.Identifier && object.name === "JSON" && !isLocalFileRead(current.arguments[0]);
9087
+ return property.name === "parse" && object !== null && object.type === import_utils59.AST_NODE_TYPES.Identifier && object.name === "JSON" && !isLocalFileRead(current.arguments[0]) && isKnownLocalText?.(current.arguments[0]) !== true;
8988
9088
  };
8989
9089
  var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
9090
+ var isDirectLocalFileRead = (node) => {
9091
+ let current = unwrap4(node);
9092
+ if (current?.type === import_utils59.AST_NODE_TYPES.AwaitExpression) {
9093
+ current = unwrap4(current.argument);
9094
+ }
9095
+ if (current?.type !== import_utils59.AST_NODE_TYPES.CallExpression) return false;
9096
+ const callee = unwrap4(current.callee);
9097
+ const name = callee?.type === import_utils59.AST_NODE_TYPES.Identifier ? callee.name : callee?.type === import_utils59.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils59.AST_NODE_TYPES.Identifier ? callee.property.name : null;
9098
+ return name !== null && FILE_READ_RE.test(name);
9099
+ };
8990
9100
  var isLocalFileRead = (node) => {
8991
9101
  let found = false;
8992
9102
  const visit = (current) => {
8993
9103
  if (found || current === null || current === void 0) return;
8994
- if (current.type === import_utils59.AST_NODE_TYPES.CallExpression) {
8995
- const callee = unwrap4(current.callee);
8996
- const name = callee?.type === import_utils59.AST_NODE_TYPES.Identifier ? callee.name : callee?.type === import_utils59.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils59.AST_NODE_TYPES.Identifier ? callee.property.name : null;
8997
- if (name !== null && FILE_READ_RE.test(name)) {
8998
- found = true;
8999
- return;
9000
- }
9104
+ if (isDirectLocalFileRead(current)) {
9105
+ found = true;
9106
+ return;
9001
9107
  }
9002
9108
  for (const key of Object.keys(current)) {
9003
9109
  if (key === "parent") continue;
@@ -9059,6 +9165,87 @@ var isValidationRead = (node) => {
9059
9165
  }
9060
9166
  return callee.type === import_utils59.AST_NODE_TYPES.Identifier && GUARD_NAME_RE.test(callee.name);
9061
9167
  };
9168
+ var PRIMITIVE_TYPEOF_RESULTS = /* @__PURE__ */ new Set([
9169
+ "bigint",
9170
+ "boolean",
9171
+ "number",
9172
+ "string",
9173
+ "symbol",
9174
+ "undefined"
9175
+ ]);
9176
+ var bindingValidationPolarity = (test, bindingName) => {
9177
+ if (test.type === import_utils59.AST_NODE_TYPES.UnaryExpression && test.operator === "!") {
9178
+ const inner = bindingValidationPolarity(test.argument, bindingName);
9179
+ return inner === "valid-when-true" ? "valid-when-false" : inner === "valid-when-false" ? "valid-when-true" : null;
9180
+ }
9181
+ if (test.type === import_utils59.AST_NODE_TYPES.BinaryExpression) {
9182
+ const typeofName = (node) => node.type === import_utils59.AST_NODE_TYPES.UnaryExpression && node.operator === "typeof" && node.argument.type === import_utils59.AST_NODE_TYPES.Identifier ? node.argument.name : null;
9183
+ const literalType = (node) => node.type === import_utils59.AST_NODE_TYPES.Literal && typeof node.value === "string" && PRIMITIVE_TYPEOF_RESULTS.has(node.value) ? node.value : null;
9184
+ const matches = typeofName(test.left) === bindingName && literalType(test.right) !== null || typeofName(test.right) === bindingName && literalType(test.left) !== null;
9185
+ if (!matches) return null;
9186
+ if (test.operator === "===" || test.operator === "==") {
9187
+ return "valid-when-true";
9188
+ }
9189
+ return test.operator === "!==" || test.operator === "!=" ? "valid-when-false" : null;
9190
+ }
9191
+ return test.type === import_utils59.AST_NODE_TYPES.CallExpression && test.arguments.length === 1 && test.arguments[0]?.type === import_utils59.AST_NODE_TYPES.Identifier && test.arguments[0].name === bindingName && test.callee.type === import_utils59.AST_NODE_TYPES.MemberExpression && !test.callee.computed && test.callee.object.type === import_utils59.AST_NODE_TYPES.Identifier && test.callee.object.name === "Array" && test.callee.property.type === import_utils59.AST_NODE_TYPES.Identifier && test.callee.property.name === "isArray" ? "valid-when-true" : null;
9192
+ };
9193
+ var nodeWithin2 = (node, container) => node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
9194
+ var isFullyValidatedExtractedBinding = (member, source, context) => {
9195
+ const isValidationReference = (identifier) => {
9196
+ for (let current = identifier.parent; current !== void 0 && current !== null; current = current.parent) {
9197
+ if ((current.type === import_utils59.AST_NODE_TYPES.BinaryExpression || current.type === import_utils59.AST_NODE_TYPES.CallExpression || current.type === import_utils59.AST_NODE_TYPES.UnaryExpression) && bindingValidationPolarity(current, identifier.name) !== null) {
9198
+ return true;
9199
+ }
9200
+ if (current.type !== import_utils59.AST_NODE_TYPES.UnaryExpression && current.type !== import_utils59.AST_NODE_TYPES.MemberExpression && current.type !== import_utils59.AST_NODE_TYPES.CallExpression) {
9201
+ return false;
9202
+ }
9203
+ }
9204
+ return false;
9205
+ };
9206
+ const isGuardedUse = (identifier) => {
9207
+ for (let current = identifier.parent; current !== void 0 && current !== null; current = current.parent) {
9208
+ if (current.type === import_utils59.AST_NODE_TYPES.ConditionalExpression) {
9209
+ const polarity = bindingValidationPolarity(current.test, identifier.name);
9210
+ if (polarity === "valid-when-true" && nodeWithin2(identifier, current.consequent)) {
9211
+ return true;
9212
+ }
9213
+ if (polarity === "valid-when-false" && nodeWithin2(identifier, current.alternate)) {
9214
+ return true;
9215
+ }
9216
+ }
9217
+ if (current.type === import_utils59.AST_NODE_TYPES.IfStatement) {
9218
+ const polarity = bindingValidationPolarity(current.test, identifier.name);
9219
+ if (polarity === "valid-when-true" && nodeWithin2(identifier, current.consequent)) {
9220
+ return true;
9221
+ }
9222
+ if (polarity === "valid-when-false" && current.alternate !== null && nodeWithin2(identifier, current.alternate)) {
9223
+ return true;
9224
+ }
9225
+ }
9226
+ if (current.type === import_utils59.AST_NODE_TYPES.FunctionDeclaration || current.type === import_utils59.AST_NODE_TYPES.FunctionExpression || current.type === import_utils59.AST_NODE_TYPES.ArrowFunctionExpression) {
9227
+ return false;
9228
+ }
9229
+ }
9230
+ return false;
9231
+ };
9232
+ const declarator = member.parent;
9233
+ if (declarator.type !== import_utils59.AST_NODE_TYPES.VariableDeclarator || declarator.init !== member || declarator.id.type !== import_utils59.AST_NODE_TYPES.Identifier || declarator.parent.type !== import_utils59.AST_NODE_TYPES.VariableDeclaration || declarator.parent.kind !== "const") {
9234
+ return false;
9235
+ }
9236
+ const extracted = context.sourceCode.getDeclaredVariables(declarator)[0];
9237
+ if (extracted === void 0 || extracted.scope !== source.scope) return false;
9238
+ let hasValueUse = false;
9239
+ for (const reference of extracted.references) {
9240
+ const identifier = reference.identifier;
9241
+ if (identifier.type !== import_utils59.AST_NODE_TYPES.Identifier) return false;
9242
+ if (nodeWithin2(identifier, declarator)) continue;
9243
+ if (isValidationReference(identifier)) continue;
9244
+ hasValueUse = true;
9245
+ if (!isGuardedUse(identifier)) return false;
9246
+ }
9247
+ return hasValueUse;
9248
+ };
9062
9249
  var isGuardTestPosition = (node) => {
9063
9250
  let current = node;
9064
9251
  let parent = current.parent;
@@ -9082,13 +9269,13 @@ var isGuardTestPosition = (node) => {
9082
9269
  }
9083
9270
  return false;
9084
9271
  };
9085
- var isUnvalidatedVariableRef = (node, scope, tracked) => {
9272
+ var unvalidatedVariableRef = (node, scope, tracked) => {
9086
9273
  const unwrapped = unwrap4(node);
9087
9274
  if (unwrapped === null || unwrapped.type !== import_utils59.AST_NODE_TYPES.Identifier) {
9088
- return false;
9275
+ return null;
9089
9276
  }
9090
9277
  const variable = findVariable2(scope, unwrapped.name);
9091
- return variable !== null && tracked.has(variable);
9278
+ return variable !== null && tracked.has(variable) ? variable : null;
9092
9279
  };
9093
9280
  var prefer_schema_for_api_payload_default = createRule({
9094
9281
  name: "prefer-schema-for-api-payload",
@@ -9108,6 +9295,56 @@ var prefer_schema_for_api_payload_default = createRule({
9108
9295
  return {};
9109
9296
  }
9110
9297
  const unvalidatedVariables = /* @__PURE__ */ new Set();
9298
+ const aliasGroups = /* @__PURE__ */ new Map();
9299
+ const localFileTextVariables = /* @__PURE__ */ new Set();
9300
+ const localFileTextRef = (node, scope) => {
9301
+ const unwrapped = unwrap4(node);
9302
+ if (unwrapped?.type !== import_utils59.AST_NODE_TYPES.Identifier) return null;
9303
+ const variable = findVariable2(scope, unwrapped.name);
9304
+ return variable !== null && localFileTextVariables.has(variable) ? variable : null;
9305
+ };
9306
+ const updateLocalFileText = (target, value, scope) => {
9307
+ const source = localFileTextRef(value, scope);
9308
+ if (isDirectLocalFileRead(value) || source !== null && source.scope === target.scope) {
9309
+ localFileTextVariables.add(target);
9310
+ } else {
9311
+ localFileTextVariables.delete(target);
9312
+ }
9313
+ };
9314
+ const clearBinding = (variable) => {
9315
+ unvalidatedVariables.delete(variable);
9316
+ const group = aliasGroups.get(variable);
9317
+ aliasGroups.delete(variable);
9318
+ group?.delete(variable);
9319
+ };
9320
+ const clearAliasGroup = (variable) => {
9321
+ const group = aliasGroups.get(variable);
9322
+ if (group === void 0) {
9323
+ unvalidatedVariables.delete(variable);
9324
+ return;
9325
+ }
9326
+ for (const alias of group) {
9327
+ unvalidatedVariables.delete(alias);
9328
+ aliasGroups.delete(alias);
9329
+ }
9330
+ group.clear();
9331
+ };
9332
+ const trackRawBinding = (variable) => {
9333
+ clearBinding(variable);
9334
+ const group = /* @__PURE__ */ new Set([variable]);
9335
+ unvalidatedVariables.add(variable);
9336
+ aliasGroups.set(variable, group);
9337
+ };
9338
+ const trackAlias = (target, source) => {
9339
+ if (target === source) return;
9340
+ const group = aliasGroups.get(source) ?? /* @__PURE__ */ new Set([source]);
9341
+ if (aliasGroups.get(target) === group) return;
9342
+ clearBinding(target);
9343
+ unvalidatedVariables.add(target);
9344
+ group.add(target);
9345
+ aliasGroups.set(source, group);
9346
+ aliasGroups.set(target, group);
9347
+ };
9111
9348
  const isFullyNarrowedPattern = (declarator) => {
9112
9349
  const declared = context.sourceCode.getDeclaredVariables(declarator);
9113
9350
  return declared.length > 0 && declared.every(
@@ -9116,29 +9353,40 @@ var prefer_schema_for_api_payload_default = createRule({
9116
9353
  )
9117
9354
  );
9118
9355
  };
9119
- const trackInitializer = (declarator) => {
9120
- if (!isRawPayloadSource(declarator.init)) return;
9356
+ const trackInitializer = (declarator, scope) => {
9121
9357
  const declaredVars = context.sourceCode.getDeclaredVariables(declarator);
9122
9358
  const variable = declaredVars[0];
9123
- if (variable !== void 0) {
9124
- unvalidatedVariables.add(variable);
9359
+ if (variable === void 0) return;
9360
+ const localText = (candidate) => localFileTextRef(candidate, scope) !== null;
9361
+ if (isRawPayloadSource(declarator.init, localText)) {
9362
+ trackRawBinding(variable);
9363
+ return;
9125
9364
  }
9365
+ const source = unvalidatedVariableRef(declarator.init, scope, unvalidatedVariables);
9366
+ if (source !== null) trackAlias(variable, source);
9126
9367
  };
9127
9368
  return {
9128
9369
  VariableDeclarator(node) {
9129
9370
  const scope = context.sourceCode.getScope(node);
9130
9371
  if (node.id.type === import_utils59.AST_NODE_TYPES.Identifier) {
9131
- trackInitializer(node);
9372
+ const variable = context.sourceCode.getDeclaredVariables(node)[0];
9373
+ if (variable !== void 0) {
9374
+ updateLocalFileText(variable, node.init, scope);
9375
+ }
9376
+ trackInitializer(node, scope);
9132
9377
  return;
9133
9378
  }
9134
9379
  if (node.id.type === import_utils59.AST_NODE_TYPES.ObjectPattern || node.id.type === import_utils59.AST_NODE_TYPES.ArrayPattern) {
9135
- if (isRawPayloadSource(node.init)) {
9380
+ if (isRawPayloadSource(
9381
+ node.init,
9382
+ (candidate) => localFileTextRef(candidate, scope) !== null
9383
+ )) {
9136
9384
  if (!isFullyNarrowedPattern(node)) {
9137
9385
  context.report({ node: node.id, messageId: "unparsedJsonAccess" });
9138
9386
  }
9139
9387
  return;
9140
9388
  }
9141
- if (isUnvalidatedVariableRef(node.init, scope, unvalidatedVariables)) {
9389
+ if (unvalidatedVariableRef(node.init, scope, unvalidatedVariables) !== null) {
9142
9390
  context.report({ node: node.id, messageId: "unparsedJsonAccess" });
9143
9391
  }
9144
9392
  }
@@ -9148,22 +9396,29 @@ var prefer_schema_for_api_payload_default = createRule({
9148
9396
  if (node.left.type === import_utils59.AST_NODE_TYPES.Identifier) {
9149
9397
  const variable = findVariable2(scope, node.left.name);
9150
9398
  if (variable === null) return;
9151
- if (isRawPayloadSource(node.right)) {
9152
- unvalidatedVariables.add(variable);
9399
+ const isLocalText = (candidate) => localFileTextRef(candidate, scope) !== null;
9400
+ updateLocalFileText(variable, node.right, scope);
9401
+ if (isRawPayloadSource(node.right, isLocalText)) {
9402
+ trackRawBinding(variable);
9153
9403
  } else {
9154
- unvalidatedVariables.delete(variable);
9404
+ const source = unvalidatedVariableRef(node.right, scope, unvalidatedVariables);
9405
+ if (source === null) clearBinding(variable);
9406
+ else trackAlias(variable, source);
9155
9407
  }
9156
9408
  return;
9157
9409
  }
9158
9410
  if (node.left.type === import_utils59.AST_NODE_TYPES.ObjectPattern || node.left.type === import_utils59.AST_NODE_TYPES.ArrayPattern) {
9159
- if (isRawPayloadSource(node.right)) {
9411
+ if (isRawPayloadSource(
9412
+ node.right,
9413
+ (candidate) => localFileTextRef(candidate, scope) !== null
9414
+ )) {
9160
9415
  context.report({
9161
9416
  node: node.left,
9162
9417
  messageId: "unparsedJsonAccess"
9163
9418
  });
9164
9419
  return;
9165
9420
  }
9166
- if (isUnvalidatedVariableRef(node.right, scope, unvalidatedVariables)) {
9421
+ if (unvalidatedVariableRef(node.right, scope, unvalidatedVariables) !== null) {
9167
9422
  context.report({
9168
9423
  node: node.left,
9169
9424
  messageId: "unparsedJsonAccess"
@@ -9184,7 +9439,7 @@ var prefer_schema_for_api_payload_default = createRule({
9184
9439
  continue;
9185
9440
  }
9186
9441
  const variable = findVariable2(scope, unwrapped.name);
9187
- if (variable !== null) unvalidatedVariables.delete(variable);
9442
+ if (variable !== null) clearAliasGroup(variable);
9188
9443
  }
9189
9444
  },
9190
9445
  MemberExpression(node) {
@@ -9192,7 +9447,10 @@ var prefer_schema_for_api_payload_default = createRule({
9192
9447
  if (isValidationRead(node)) return;
9193
9448
  const scope = context.sourceCode.getScope(node);
9194
9449
  const obj = unwrap4(node.object);
9195
- if (isRawPayloadSource(obj)) {
9450
+ if (isRawPayloadSource(
9451
+ obj,
9452
+ (candidate) => localFileTextRef(candidate, scope) !== null
9453
+ )) {
9196
9454
  const parent = node.parent;
9197
9455
  if (parent.type === import_utils59.AST_NODE_TYPES.CallExpression && parent.callee === node && node.property.type === import_utils59.AST_NODE_TYPES.Identifier && (node.property.name === "parse" || node.property.name === "safeParse" || PROMISE_CHAIN_METHODS.has(node.property.name))) {
9198
9456
  return;
@@ -9200,12 +9458,13 @@ var prefer_schema_for_api_payload_default = createRule({
9200
9458
  context.report({ node, messageId: "unparsedJsonAccess" });
9201
9459
  return;
9202
9460
  }
9203
- if (obj !== null && obj.type === import_utils59.AST_NODE_TYPES.Identifier && isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)) {
9204
- context.report({ node, messageId: "unparsedJsonAccess" });
9205
- const variable = findVariable2(scope, obj.name);
9206
- if (variable !== null) {
9207
- unvalidatedVariables.delete(variable);
9461
+ const variable = obj?.type === import_utils59.AST_NODE_TYPES.Identifier ? unvalidatedVariableRef(obj, scope, unvalidatedVariables) : null;
9462
+ if (variable !== null) {
9463
+ if (isFullyValidatedExtractedBinding(node, variable, context)) {
9464
+ return;
9208
9465
  }
9466
+ context.report({ node, messageId: "unparsedJsonAccess" });
9467
+ clearAliasGroup(variable);
9209
9468
  }
9210
9469
  }
9211
9470
  };
@@ -10969,8 +11228,8 @@ function initProvablyLacksSignal(init) {
10969
11228
  }
10970
11229
  return true;
10971
11230
  }
10972
- function isStringish(node) {
10973
- return node.type === import_utils67.AST_NODE_TYPES.Literal && typeof node.value === "string" || node.type === import_utils67.AST_NODE_TYPES.TemplateLiteral;
11231
+ function isInlineUrl(node, resolvesToGlobal) {
11232
+ return node.type === import_utils67.AST_NODE_TYPES.Literal && typeof node.value === "string" || node.type === import_utils67.AST_NODE_TYPES.TemplateLiteral || node.type === import_utils67.AST_NODE_TYPES.NewExpression && node.callee.type === import_utils67.AST_NODE_TYPES.Identifier && node.callee.name === "URL" && resolvesToGlobal(node.callee);
10974
11233
  }
10975
11234
  var require_fetch_timeout_default = createRule({
10976
11235
  name: "require-fetch-timeout",
@@ -11022,7 +11281,7 @@ var require_fetch_timeout_default = createRule({
11022
11281
  return;
11023
11282
  }
11024
11283
  const [first, init] = node.arguments;
11025
- if (node.arguments.length === 1 && first !== void 0 && !isStringish(first)) {
11284
+ if (node.arguments.length === 1 && first !== void 0 && !isInlineUrl(first, resolvesToGlobal)) {
11026
11285
  return;
11027
11286
  }
11028
11287
  if (init === void 0 || initProvablyLacksSignal(init)) {
@@ -11039,7 +11298,7 @@ var CONFIGISH_TYPE_RE = /(?:Options|Opts|Config|Configuration|Settings|Params|Pr
11039
11298
  var CONFIGISH_NAME_RE = /^(?:options|opts|config|configuration|settings|params|props|args|env|environment|callbacks|flags|logger|log|clock)$/i;
11040
11299
  var HTTP_TRANSPORT_TYPE_RE = /^(?:KyInstance|AxiosInstance|Session)$/;
11041
11300
  var BUILTIN_CONTAINER_TYPE_RE = /^(?:Record|Map|WeakMap|Set|WeakSet|Array|ReadonlyArray|ReadonlyMap|ReadonlySet|Promise|Partial|Required|Readonly|Pick|Omit|Exclude|Extract|NonNullable|Awaited|Parameters|ReturnType|InstanceType)$/;
11042
- var VALUE_TYPE_RE = /^(?:ArrayBuffer|Blob|Buffer|Date|RegExp|URL|URLSearchParams)$/;
11301
+ var VALUE_TYPE_RE = /^(?:ArrayBuffer|Blob|Buffer|Date|NodePath|RegExp|URL|URLSearchParams)$/;
11043
11302
  var TRANSPORT_WRAPPER_NAME_RE = /(?:Client$|^Http[A-Z])/;
11044
11303
  var ERROR_VALUE_NAME_RE = /Error$/;
11045
11304
  var FLUENT_BUILDER_NAME_RE = /Builder$/;
@@ -11503,7 +11762,7 @@ var require_interface_for_injected_service_default = createRule({
11503
11762
  if (node.abstract === true) return;
11504
11763
  if (node.decorators.length > 0) return;
11505
11764
  const ctor = node.body.body.find(
11506
- (member) => member.type === import_utils68.AST_NODE_TYPES.MethodDefinition && member.kind === "constructor"
11765
+ (member) => member.type === import_utils68.AST_NODE_TYPES.MethodDefinition && member.kind === "constructor" && member.value.body !== null && member.value.body !== void 0
11507
11766
  );
11508
11767
  if (ctor === void 0) return;
11509
11768
  const constructorFacts = readConstructor(
@@ -12382,7 +12641,7 @@ var rules = {
12382
12641
  };
12383
12642
  var meta = {
12384
12643
  name: "@sarj/eslint-plugin",
12385
- version: "11.0.1"
12644
+ version: "11.1.0"
12386
12645
  };
12387
12646
  var applicationOnlyRules = [
12388
12647
  "no-restricted-library-load",
package/dist/index.d.cts CHANGED
@@ -453,7 +453,7 @@ type FlatPreset = {
453
453
  declare const plugin: {
454
454
  readonly meta: {
455
455
  readonly name: "@sarj/eslint-plugin";
456
- readonly version: "11.0.1";
456
+ readonly version: "11.1.0";
457
457
  };
458
458
  readonly rules: {
459
459
  readonly "duplicate-test-body": _typescript_eslint_utils_ts_eslint.RuleModule<"duplicateTestBody", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {