@sarj/eslint-plugin 16.22.0 → 16.22.1

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.
Files changed (3) hide show
  1. package/dist/index.cjs +50 -128
  2. package/dist/index.js +354 -432
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -866,7 +866,7 @@ function isSearchParamReceiver(call, services) {
866
866
  // package.json
867
867
  var package_default = {
868
868
  name: "@sarj/eslint-plugin",
869
- version: "16.22.0",
869
+ version: "16.22.1",
870
870
  packageManager: "npm@12.2.0",
871
871
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
872
872
  type: "module",
@@ -7294,10 +7294,10 @@ var import_utils44 = require("@typescript-eslint/utils");
7294
7294
  function stripSqlNoise(text) {
7295
7295
  return scanSqlNoise(text);
7296
7296
  }
7297
- function sqlSingleQuotedRanges(text) {
7298
- const ranges = [];
7299
- scanSqlNoise(text, (start, end) => ranges.push([start, end]));
7300
- return ranges;
7297
+ function sqlNoiseInfo(text) {
7298
+ const singleQuotedRanges = [];
7299
+ const masked = scanSqlNoise(text, (start, end) => singleQuotedRanges.push([start, end]));
7300
+ return { text: masked, singleQuotedRanges };
7301
7301
  }
7302
7302
  function scanSqlNoise(text, onSingleQuoted) {
7303
7303
  const out = text.split("");
@@ -7383,6 +7383,7 @@ function maskSqlRange(text, out, start, end) {
7383
7383
  }
7384
7384
  var SUBSTITUTION_MARKER = "?";
7385
7385
  function sqlTextOf(node) {
7386
+ node = unwrapExpression(node);
7386
7387
  switch (node.type) {
7387
7388
  case import_utils44.AST_NODE_TYPES.Literal:
7388
7389
  return typeof node.value === "string" ? node.value : null;
@@ -7410,7 +7411,7 @@ function fragmentArrayText(node) {
7410
7411
  if (separator === null) return null;
7411
7412
  const parts = [];
7412
7413
  for (const element of node.elements) {
7413
- if (element === null || join4 !== null && element.type === import_utils44.AST_NODE_TYPES.ArrayExpression) {
7414
+ if (element === null || join4 !== null && unwrapExpression(element).type === import_utils44.AST_NODE_TYPES.ArrayExpression) {
7414
7415
  return null;
7415
7416
  }
7416
7417
  const part = sqlTextOf(element);
@@ -7422,8 +7423,10 @@ function fragmentArrayText(node) {
7422
7423
  return parts.length > 0 ? parts.join(separator) : null;
7423
7424
  }
7424
7425
  function joinedFragmentCall(node) {
7425
- const parent = node.parent;
7426
- return parent?.type === import_utils44.AST_NODE_TYPES.MemberExpression && parent.object === node && !parent.computed && parent.property.type === import_utils44.AST_NODE_TYPES.Identifier && parent.property.name === "join" && parent.parent?.type === import_utils44.AST_NODE_TYPES.CallExpression && parent.parent.callee === parent ? parent.parent : null;
7426
+ const value = outerExpression(node);
7427
+ const parent = value.parent;
7428
+ const callee = parent?.type === import_utils44.AST_NODE_TYPES.MemberExpression ? outerExpression(parent) : null;
7429
+ return parent?.type === import_utils44.AST_NODE_TYPES.MemberExpression && parent.object === value && import_utils44.ASTUtils.getPropertyName(parent) === "join" && callee?.parent?.type === import_utils44.AST_NODE_TYPES.CallExpression && callee.parent.callee === callee ? callee.parent : null;
7427
7430
  }
7428
7431
  function markConsumed(node, consumed) {
7429
7432
  consumed.add(node);
@@ -7465,6 +7468,8 @@ function createSqlListener(handler) {
7465
7468
  markConsumed(node, consumed);
7466
7469
  const separator = join4.arguments[0];
7467
7470
  if (separator !== void 0 && staticString(separator) !== null) markConsumed(separator, consumed);
7471
+ const member = unwrapExpression(join4.callee);
7472
+ if (member.type === import_utils44.AST_NODE_TYPES.MemberExpression && member.computed) markConsumed(member.property, consumed);
7468
7473
  },
7469
7474
  TemplateLiteral: (node) => {
7470
7475
  visit(node);
@@ -7475,11 +7480,11 @@ function createSqlListener(handler) {
7475
7480
  };
7476
7481
  }
7477
7482
  function isDiscardedSqlText(node) {
7478
- let current = node;
7483
+ let current = outerExpression(node);
7479
7484
  let parent = current.parent;
7480
7485
  while (parent !== void 0 && parent !== null) {
7481
- if (unwrapExpression(parent) === current || parent.type === import_utils44.AST_NODE_TYPES.BinaryExpression && parent.operator === "+") {
7482
- current = parent;
7486
+ if (parent.type === import_utils44.AST_NODE_TYPES.BinaryExpression && parent.operator === "+") {
7487
+ current = outerExpression(parent);
7483
7488
  parent = current.parent;
7484
7489
  continue;
7485
7490
  }
@@ -7561,8 +7566,7 @@ function isStaticFragment(expression) {
7561
7566
  function runtimeInterpolations(template) {
7562
7567
  const parts = template.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw);
7563
7568
  const statement = parts.join(RUNTIME_MARKER);
7564
- const ranges = sqlSingleQuotedRanges(statement);
7565
- const visible = stripSqlNoise(statement);
7569
+ const { singleQuotedRanges: ranges, text: visible } = sqlNoiseInfo(statement);
7566
7570
  let offset = 0;
7567
7571
  return template.expressions.flatMap(
7568
7572
  (expression, index) => {
@@ -7583,16 +7587,13 @@ function runtimeConcatOperands(node) {
7583
7587
  return [];
7584
7588
  }
7585
7589
  const operands = concatOperands(node);
7586
- const hasStringLiteral = operands.some(
7587
- (operand) => operand.type === import_utils45.AST_NODE_TYPES.Literal && typeof operand.value === "string" || operand.type === import_utils45.AST_NODE_TYPES.TemplateLiteral && operand.expressions.length === 0
7588
- );
7590
+ const hasStringLiteral = operands.some((operand) => staticString(operand) !== null);
7589
7591
  if (!hasStringLiteral) {
7590
7592
  return [];
7591
7593
  }
7592
7594
  const parts = operands.map((operand) => staticString(operand) ?? RUNTIME_MARKER);
7593
7595
  const statement = parts.join("");
7594
- const ranges = sqlSingleQuotedRanges(statement);
7595
- const visible = stripSqlNoise(statement);
7596
+ const { singleQuotedRanges: ranges, text: visible } = sqlNoiseInfo(statement);
7596
7597
  let offset = 0;
7597
7598
  return operands.flatMap((operand, index) => {
7598
7599
  const inValue = ranges.some(([start, end]) => start < offset && offset < end);
@@ -7606,10 +7607,17 @@ function runtimeConcatOperands(node) {
7606
7607
  });
7607
7608
  }
7608
7609
  function concatOperands(node) {
7609
- if (node.type === import_utils45.AST_NODE_TYPES.BinaryExpression && node.operator === "+") {
7610
- return [...concatOperands(node.left), ...concatOperands(node.right)];
7610
+ const operands = [];
7611
+ const pending = [node];
7612
+ while (pending.length > 0) {
7613
+ const next = pending.pop();
7614
+ if (next === void 0) continue;
7615
+ const value = unwrapExpression(next);
7616
+ if (value.type === import_utils45.AST_NODE_TYPES.BinaryExpression && value.operator === "+") {
7617
+ pending.push(value.right, value.left);
7618
+ } else operands.push(value);
7611
7619
  }
7612
- return [node];
7620
+ return operands;
7613
7621
  }
7614
7622
  var SQL_STATEMENT_RE = /\b(?:select\s|insert\s+into\b|insert\s+or\b|update\s+\w|delete\s+from\b|replace\s+into\b|merge\s+into\b|upsert\s+into\b|create\s+(?:temp(?:orary)?\s+)?(?:table|index|view|trigger|schema|database)\b|alter\s+table\b|drop\s+(?:table|index|view|trigger)\b|truncate\s+table\b|pragma\s+\w|with\s+\w+\s+as\s*\(|from\s+\w+\s+where\b)/i;
7615
7623
  var RUNTIME_MARKER = " ? ";
@@ -7617,6 +7625,7 @@ function looksLikeSql(node) {
7617
7625
  return SQL_STATEMENT_RE.test(stripSqlNoise(staticStatementText(node)));
7618
7626
  }
7619
7627
  function staticStatementText(node) {
7628
+ node = unwrapExpression(node);
7620
7629
  if (node.type === import_utils45.AST_NODE_TYPES.TemplateLiteral) {
7621
7630
  return node.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join(RUNTIME_MARKER);
7622
7631
  }
@@ -8686,46 +8695,15 @@ var STRONG_SECURITY_WORDS = /* @__PURE__ */ new Set([
8686
8695
  "uuid",
8687
8696
  "verificationcode"
8688
8697
  ]);
8689
- var NON_SECURITY_ID_WORDS = /* @__PURE__ */ new Set([
8690
- "aria",
8691
- "cache",
8692
- "component",
8693
- "correlation",
8694
- "dev",
8695
- "dialog",
8696
- "dom",
8697
- "element",
8698
- "execution",
8699
- "field",
8700
- "form",
8701
- "hmr",
8702
- "input",
8703
- "marker",
8704
- "menu",
8705
- "mock",
8706
- "perf",
8707
- "req",
8708
- "request",
8709
- "select",
8710
- "tab",
8711
- "temp",
8712
- "test",
8713
- "tmp",
8714
- "trace"
8715
- ]);
8716
- function nameWords(name) {
8717
- return name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").split(/[^A-Za-z0-9]+/u).filter(Boolean).map((word) => word.toLowerCase());
8718
- }
8719
8698
  function isStrongSecurityName(name) {
8720
8699
  const words2 = nameWords(name);
8721
8700
  return words2.some((word) => STRONG_SECURITY_WORDS.has(word)) || words2.some(
8722
8701
  (word, index) => word === "api" && words2[index + 1] === "key" || word === "auth" && words2[index + 1] === "id" || word === "verification" && words2[index + 1] === "code"
8723
8702
  );
8724
8703
  }
8725
- function isNonSecurityName(name) {
8726
- return nameWords(name).some((word) => NON_SECURITY_ID_WORDS.has(word));
8704
+ function nameWords(name) {
8705
+ return name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").split(/[^A-Za-z0-9]+/u).filter(Boolean).map((word) => word.toLowerCase());
8727
8706
  }
8728
- var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
8729
8707
  function isMathRandomCall(node) {
8730
8708
  if (node.type !== "CallExpression") {
8731
8709
  return false;
@@ -8769,67 +8747,6 @@ function findEnclosingNames(node) {
8769
8747
  }
8770
8748
  return names;
8771
8749
  }
8772
- function isConcatenatedIntoPathOrDomId(node) {
8773
- const valueNode = climbValueChain(node);
8774
- let current = valueNode;
8775
- let parent = current.parent;
8776
- let top;
8777
- while (parent) {
8778
- if (parent.type === "BinaryExpression" && parent.operator === "+" && (parent.left === current || parent.right === current)) {
8779
- top = parent;
8780
- current = parent;
8781
- parent = current.parent;
8782
- continue;
8783
- }
8784
- if (parent.type === "TemplateLiteral") {
8785
- top = parent;
8786
- current = parent;
8787
- parent = current.parent;
8788
- continue;
8789
- }
8790
- break;
8791
- }
8792
- if (!top) {
8793
- return false;
8794
- }
8795
- const parts = [];
8796
- collectStaticStringParts(top, parts);
8797
- return parts.some((part) => PATH_OR_DOM_MARKER.test(part));
8798
- }
8799
- function climbValueChain(node) {
8800
- let current = node;
8801
- let parent = current.parent;
8802
- while (parent) {
8803
- if (parent.type === "MemberExpression" && parent.object === current && import_utils51.ASTUtils.getPropertyName(parent) !== null) {
8804
- current = parent;
8805
- parent = current.parent;
8806
- continue;
8807
- }
8808
- if (parent.type === "CallExpression" && parent.callee === current) {
8809
- current = parent;
8810
- parent = current.parent;
8811
- continue;
8812
- }
8813
- break;
8814
- }
8815
- return current;
8816
- }
8817
- function collectStaticStringParts(node, out) {
8818
- if (node.type === "Literal" && typeof node.value === "string") {
8819
- out.push(node.value);
8820
- return;
8821
- }
8822
- if (node.type === "TemplateLiteral") {
8823
- for (const quasi of node.quasis) {
8824
- out.push(quasi.value.cooked ?? quasi.value.raw);
8825
- }
8826
- return;
8827
- }
8828
- if (node.type === "BinaryExpression" && node.operator === "+") {
8829
- collectStaticStringParts(node.left, out);
8830
- collectStaticStringParts(node.right, out);
8831
- }
8832
- }
8833
8750
  var no_insecure_random_id_default = createRule({
8834
8751
  name: "no-insecure-random-id",
8835
8752
  documentation: NO_INSECURE_RANDOM_ID_DOCUMENTATION,
@@ -8857,13 +8774,6 @@ var no_insecure_random_id_default = createRule({
8857
8774
  const names = findEnclosingNames(node);
8858
8775
  if (names.some(isStrongSecurityName)) {
8859
8776
  context.report({ node, messageId: "insecureRandomId" });
8860
- return;
8861
- }
8862
- if (names.some(isNonSecurityName)) {
8863
- return;
8864
- }
8865
- if (isConcatenatedIntoPathOrDomId(node)) {
8866
- return;
8867
8777
  }
8868
8778
  }
8869
8779
  };
@@ -9795,15 +9705,24 @@ function typeMayContainObject(type, checker) {
9795
9705
  function syntaxMayContainObject(node) {
9796
9706
  const expression = unwrapComparisonExpression(node);
9797
9707
  if (expression.type === import_utils55.AST_NODE_TYPES.ObjectExpression) return true;
9708
+ if (expression.type === import_utils55.AST_NODE_TYPES.Literal) return "regex" in expression;
9709
+ if (expression.type === import_utils55.AST_NODE_TYPES.TemplateLiteral || expression.type === import_utils55.AST_NODE_TYPES.UnaryExpression) return false;
9798
9710
  if (expression.type !== import_utils55.AST_NODE_TYPES.ArrayExpression) return null;
9711
+ let unknown = false;
9799
9712
  for (const element of expression.elements) {
9800
9713
  if (element === null) continue;
9801
- if (element.type === import_utils55.AST_NODE_TYPES.SpreadElement) return null;
9802
- if (element.type !== import_utils55.AST_NODE_TYPES.Literal && !(element.type === import_utils55.AST_NODE_TYPES.TemplateLiteral && element.expressions.length === 0)) {
9803
- return true;
9804
- }
9714
+ const result = element.type === import_utils55.AST_NODE_TYPES.SpreadElement ? null : syntaxMayContainObject(element);
9715
+ if (result === true) return true;
9716
+ if (result === null) unknown = true;
9805
9717
  }
9806
- return false;
9718
+ return unknown ? null : false;
9719
+ }
9720
+ function untypedArrayMayContainObject(node) {
9721
+ return node.elements.some((element) => {
9722
+ if (element === null || element.type === import_utils55.AST_NODE_TYPES.SpreadElement) return false;
9723
+ const value = unwrapComparisonExpression(element);
9724
+ return value.type === import_utils55.AST_NODE_TYPES.ArrayExpression ? untypedArrayMayContainObject(value) : syntaxMayContainObject(value) !== false;
9725
+ });
9807
9726
  }
9808
9727
  var no_json_stringify_object_equality_default = createRule({
9809
9728
  name: "no-json-stringify-object-equality",
@@ -9832,7 +9751,10 @@ var no_json_stringify_object_equality_default = createRule({
9832
9751
  function mayContainObject(node) {
9833
9752
  const syntax = syntaxMayContainObject(node);
9834
9753
  if (syntax !== null) return syntax;
9835
- if (services === null || checker === void 0) return false;
9754
+ if (services === null || checker === void 0) {
9755
+ const expression = unwrapComparisonExpression(node);
9756
+ return expression.type === import_utils55.AST_NODE_TYPES.ArrayExpression && untypedArrayMayContainObject(expression);
9757
+ }
9836
9758
  const type = checker.getTypeAtLocation(
9837
9759
  services.esTreeNodeToTSNodeMap.get(node)
9838
9760
  );