@sarj/eslint-plugin 15.17.5 → 15.17.6

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
@@ -1448,6 +1448,10 @@ function normalizeToken(word) {
1448
1448
  return lower.length > TOKEN_PLURAL_MIN && lower.endsWith("s") && !lower.endsWith("ss") ? lower.slice(0, -1) : lower;
1449
1449
  }
1450
1450
  function restatableStatementBelow(comment, sourceCode) {
1451
+ const node = restatableStatementNodeBelow(comment, sourceCode);
1452
+ return node === null ? null : sourceCode.getText(node);
1453
+ }
1454
+ function restatableStatementNodeBelow(comment, sourceCode) {
1451
1455
  const token = sourceCode.getTokenAfter(comment, { includeComments: false });
1452
1456
  if (token === null || token.loc.start.line !== comment.loc.end.line + 1) return null;
1453
1457
  for (let node = sourceCode.getNodeByRangeIndex(token.range[0]); node != null && node.type !== import_utils7.AST_NODE_TYPES.Program; node = node.parent) {
@@ -1458,7 +1462,7 @@ function restatableStatementBelow(comment, sourceCode) {
1458
1462
  if (node.type === import_utils7.AST_NODE_TYPES.VariableDeclaration && isTrivialInitializer(node)) {
1459
1463
  return null;
1460
1464
  }
1461
- return sourceCode.getText(node);
1465
+ return node;
1462
1466
  }
1463
1467
  return null;
1464
1468
  }
@@ -6716,25 +6720,31 @@ var MODALITY_RE = /\b(?:can|could|should|shall|may|might|must|will|would|cannot)
6716
6720
  var LEAD_IN_RE = /:$/;
6717
6721
  var EMPHASIS_RE = /\*\w[^*]*\*|`[^`]+`/;
6718
6722
  var NEGATION_WORD_RE = /\b(?:no|not|never|neither|nor|without|none|non)\b/i;
6723
+ var SEMANTIC_RELATION_RE = /\b(?:only|if|unless|when|while|except|each|every|any|all|before|after|until|since|again|once|twice|already|still|from|to|into|onto|back|versus|rather|instead)\b/i;
6719
6724
  var ACTION_STMT_RE = /[\w.$\])]\s*\(|^\s*(?:return|throw|await|yield)\b/;
6720
6725
  var NON_ASCII_LETTER_RE = /[^\p{ASCII}\p{N}\p{P}\p{Z}]/u;
6721
6726
  var WALL_NARRATION_RE2 = /^(?:(?:\d+[.)]|(?:phase|step)\s+\d+\s*:?)\s*)?(?:add|build|call|check|compute|copy|count|create|fetch|filter|find|get|handle|load|map|merge|parse|process|read|remove|return|save|send|set|sort|store|update|validate|write)(?:s|es|d|ed|ing)?\b/i;
6722
6727
  var WALL_CLUSTER_MAX_LINE_GAP = 8;
6723
6728
  var WALL_CLUSTER_MIN_COMMENTS = 3;
6724
6729
  var NO_RESTATED_COMMENT_DOCUMENTATION = {
6725
- summary: "Flag a single-line comment whose every word already appears on the statement below it.",
6730
+ summary: "Flag short standalone comments that repeat the adjacent statement's identifiers.",
6726
6731
  rationale: "A comment that only repeats code adds no context and can become stale independently.",
6727
- remediation: "Delete the comment or replace it with the reason, constraint, or consequence absent from the code.",
6732
+ remediation: "Remove a genuine restatement; retain conditions, constraints, rationale, and information absent from the statement.",
6728
6733
  category: "maintainability",
6729
6734
  autofix: "suggestion",
6730
- limitations: ["Directives, protected references, questions, multi-line prose, comments with novel content, and generated files are excluded."],
6735
+ limitations: [
6736
+ "Only standalone line comments of two to eight words with at least two content tokens directly above a supported single-line statement are inspected; one-word headings and sentence-ending punctuation are excluded.",
6737
+ "Evidence comes from that statement's identifier tokens, not strings, template text, comments, or neighboring statements. Stopwords and inflection folding are heuristic, not proof of semantic equivalence.",
6738
+ "Directives, protected references, conditions, restrictions, ordering, repetition, direction, questions, prose paragraphs, sibling-group labels, novel content, and generated files are excluded. Deletion is an optional suggestion, never an automatic fix.",
6739
+ "The conservative sibling-group exemption includes enclosing sibling groups and declaration/type-query pairs; it can miss genuine restatements inside those groups."
6740
+ ],
6731
6741
  examples: [
6732
- { id: "reason-comment", title: "Keep the reason the code cannot express", outcome: "no-match", files: [{ path: "src/cache.ts", source: "// Serialize because the cache key is stable across deploys.\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 0, public: true },
6742
+ { id: "reason-comment", title: "Keep a condition the statement does not establish", outcome: "no-match", files: [{ path: "src/cache.ts", source: "// Serialize key only after validation.\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 0, public: true },
6733
6743
  { id: "restated-comment", title: "Remove a comment that repeats the statement", outcome: "match", files: [{ path: "src/cache.ts", source: "// Serialize key\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 1, public: true }
6734
6744
  ]
6735
6745
  };
6736
- function areAdjacentLineComments2(a, b) {
6737
- return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
6746
+ function areAdjacentLineComments2(a, b, isStandalone) {
6747
+ return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && isStandalone(a) && isStandalone(b) && b.loc.start.line === a.loc.end.line + 1;
6738
6748
  }
6739
6749
  function headsSiblingRun(node) {
6740
6750
  const parent = node.parent;
@@ -6752,12 +6762,12 @@ var no_restated_comment_default = createRule({
6752
6762
  type: "suggestion",
6753
6763
  hasSuggestions: true,
6754
6764
  docs: {
6755
- description: "Flag a single-line comment whose every word already appears on the statement below it."
6765
+ description: NO_RESTATED_COMMENT_DOCUMENTATION.summary
6756
6766
  },
6757
6767
  schema: [],
6758
6768
  messages: {
6759
6769
  deleteComment: "Delete the redundant comment.",
6760
- restatesLineBelow: "Comment restates the statement below it \u2014 delete it, or replace it with the *why*; the code already carries the *what*."
6770
+ restatesLineBelow: "Comment repeats the adjacent statement's identifiers \u2014 consider removing it; retain any condition, constraint, or rationale absent from the code."
6761
6771
  }
6762
6772
  },
6763
6773
  defaultOptions: [],
@@ -6766,19 +6776,30 @@ var no_restated_comment_default = createRule({
6766
6776
  return {};
6767
6777
  }
6768
6778
  const sourceCode = context.sourceCode;
6769
- const lines = sourceCode.lines;
6770
6779
  function isStandalone(comment) {
6771
6780
  const before = sourceCode.getTokenBefore(comment, { includeComments: false });
6772
6781
  return !before || before.loc.end.line < comment.loc.start.line;
6773
6782
  }
6774
- function labelsASiblingRun(comment) {
6775
- const token = sourceCode.getTokenAfter(comment, { includeComments: false });
6776
- if (token === null) return false;
6777
- for (let node = sourceCode.getNodeByRangeIndex(token.range[0]); node != null && node.type !== import_utils38.AST_NODE_TYPES.Program; node = node.parent) {
6783
+ function labelsASiblingRun(statement) {
6784
+ for (let node = statement; node !== void 0 && node.type !== import_utils38.AST_NODE_TYPES.Program; node = node.parent) {
6778
6785
  if (headsSiblingRun(node)) return true;
6779
6786
  }
6780
6787
  return false;
6781
6788
  }
6789
+ function headsValueTypeGroup(statement) {
6790
+ if (statement.type !== import_utils38.AST_NODE_TYPES.VariableDeclaration) return false;
6791
+ const parent = statement.parent;
6792
+ if (!("body" in parent) || !Array.isArray(parent.body)) return false;
6793
+ const body2 = parent.body;
6794
+ const next = body2[body2.indexOf(statement) + 1];
6795
+ if (next?.type !== import_utils38.AST_NODE_TYPES.TSTypeAliasDeclaration) return false;
6796
+ const names = new Set(statement.declarations.flatMap(({ id }) => id.type === import_utils38.AST_NODE_TYPES.Identifier ? [id.name] : []));
6797
+ const tokens = sourceCode.getTokens(next);
6798
+ return tokens.some((token, index) => {
6799
+ const nextToken = tokens[index + 1];
6800
+ return token.value === "typeof" && nextToken?.type === import_utils38.AST_TOKEN_TYPES.Identifier && names.has(nextToken.value);
6801
+ });
6802
+ }
6782
6803
  return {
6783
6804
  Program() {
6784
6805
  const comments = sourceCode.getAllComments();
@@ -6806,7 +6827,7 @@ var no_restated_comment_default = createRule({
6806
6827
  if (comment === void 0 || comment.type !== "Line") continue;
6807
6828
  if (wallMembers.has(comment)) continue;
6808
6829
  if (!isStandalone(comment)) continue;
6809
- if (areAdjacentLineComments2(comments[i - 1], comment) || areAdjacentLineComments2(comment, comments[i + 1])) {
6830
+ if (areAdjacentLineComments2(comments[i - 1], comment, isStandalone) || areAdjacentLineComments2(comment, comments[i + 1], isStandalone)) {
6810
6831
  continue;
6811
6832
  }
6812
6833
  const body2 = comment.value.replace(/^\/*/, "").trim();
@@ -6814,17 +6835,19 @@ var no_restated_comment_default = createRule({
6814
6835
  if (DIRECTIVE_RE3.test(body2) || CODEY_RE.test(body2) || BANNERISH_RE.test(body2)) continue;
6815
6836
  if (NON_ASCII_LETTER_RE.test(body2) || isProtected(body2)) continue;
6816
6837
  if (MODALITY_RE.test(body2) || LEAD_IN_RE.test(body2) || EMPHASIS_RE.test(body2)) continue;
6817
- if (NEGATION_WORD_RE.test(body2)) continue;
6818
- if (body2.split(/\s+/).length > MAX_WORDS) continue;
6838
+ if (NEGATION_WORD_RE.test(body2) || SEMANTIC_RELATION_RE.test(body2)) continue;
6839
+ const wordCount2 = body2.split(/\s+/).length;
6840
+ if (wordCount2 < 2 || wordCount2 > MAX_WORDS || /[.!?]$/.test(body2)) continue;
6819
6841
  const tokens = contentTokens(body2);
6820
6842
  if (tokens.length < MIN_CONTENT_TOKENS) continue;
6821
- const statement = restatableStatementBelow(comment, sourceCode);
6822
- if (statement === null) continue;
6843
+ const statementNode = restatableStatementNodeBelow(comment, sourceCode);
6844
+ if (statementNode === null) continue;
6845
+ const statement = sourceCode.getText(statementNode);
6823
6846
  if (restatesStatementHead(body2, statement)) continue;
6824
- const line = lines[comment.loc.start.line] ?? "";
6825
- if (!ACTION_STMT_RE.test(line)) continue;
6826
- if (labelsASiblingRun(comment)) continue;
6827
- if (restates(tokens, codeTokens(line))) {
6847
+ if (!ACTION_STMT_RE.test(statement)) continue;
6848
+ if (labelsASiblingRun(statementNode) || headsValueTypeGroup(statementNode)) continue;
6849
+ const identifiers = sourceCode.getTokens(statementNode).filter((token) => token.type === import_utils38.AST_TOKEN_TYPES.Identifier).map((token) => token.value).join(" ");
6850
+ if (restates(tokens, codeTokens(identifiers))) {
6828
6851
  const removal = wholeLineRemovalRange(sourceCode.text, comment);
6829
6852
  context.report({
6830
6853
  node: comment,
@@ -19729,7 +19752,7 @@ var RULES = {
19729
19752
  };
19730
19753
  var meta = {
19731
19754
  name: "@sarj/eslint-plugin",
19732
- version: "15.17.5"
19755
+ version: "15.17.6"
19733
19756
  };
19734
19757
  var APPLICATION_ONLY_RULES = [];
19735
19758
  var LIBRARY_IMPORT_POLICY = ["error", {
@@ -19739,6 +19762,7 @@ var LIBRARY_IMPORT_POLICY = ["error", {
19739
19762
  var ADVISORY_RULES = [
19740
19763
  "@sarj/excessive-commentary",
19741
19764
  "@sarj/no-bespoke-api-case-conversion",
19765
+ "@sarj/no-restated-comment",
19742
19766
  "@sarj/prefer-millisecond-control-duration-schema",
19743
19767
  "@sarj/prefer-module-level-refined-schema",
19744
19768
  "@sarj/prefer-multi-value-zod-literal",
@@ -19786,7 +19810,7 @@ var RECOMMENDED_RULES = {
19786
19810
  "@sarj/no-production-browser-source-maps": "error",
19787
19811
  "@sarj/no-repeated-string-literal": "error",
19788
19812
  "@sarj/no-router-refresh-polling": "error",
19789
- "@sarj/no-restated-comment": "error",
19813
+ "@sarj/no-restated-comment": "warn",
19790
19814
  "@sarj/no-restated-jsdoc": "error",
19791
19815
  "@sarj/no-secret-in-log": "error",
19792
19816
  "@sarj/no-server-env-in-client-component": "error",
@@ -19882,7 +19906,7 @@ var STRICT_RULES = {
19882
19906
  "@sarj/no-raw-fetch-outside-clients": "error",
19883
19907
  "@sarj/no-repeated-string-literal": "error",
19884
19908
  "@sarj/no-router-refresh-polling": "error",
19885
- "@sarj/no-restated-comment": "error",
19909
+ "@sarj/no-restated-comment": "warn",
19886
19910
  "@sarj/no-restated-jsdoc": "error",
19887
19911
  "@sarj/no-secret-in-log": "error",
19888
19912
  "@sarj/no-server-env-in-client-component": "error",
package/dist/index.d.cts CHANGED
@@ -320,7 +320,7 @@ declare const RULES: {
320
320
  /** @deprecated All repositories use one policy; retained for import compatibility. */
321
321
  declare const APPLICATION_ONLY_RULES: readonly [];
322
322
  /** Rules staged as non-blocking warnings while corpus adoption evidence accumulates. */
323
- declare const ADVISORY_RULES: readonly ["@sarj/excessive-commentary", "@sarj/no-bespoke-api-case-conversion", "@sarj/prefer-millisecond-control-duration-schema", "@sarj/prefer-module-level-refined-schema", "@sarj/prefer-multi-value-zod-literal", "@sarj/prefer-named-callback-domain", "@sarj/prefer-named-complex-return-type", "@sarj/prefer-node-crypto-hash", "@sarj/prefer-node-fs-promises", "@sarj/prefer-nullish-filter-predicate", "@sarj/prefer-shared-zod-enum", "@sarj/prefer-switch-for-repeated-equality", "@sarj/require-interface-for-exported-class", "@sarj/require-sql-access-class", "@sarj/sole-export-matches-filename"];
323
+ declare const ADVISORY_RULES: readonly ["@sarj/excessive-commentary", "@sarj/no-bespoke-api-case-conversion", "@sarj/no-restated-comment", "@sarj/prefer-millisecond-control-duration-schema", "@sarj/prefer-module-level-refined-schema", "@sarj/prefer-multi-value-zod-literal", "@sarj/prefer-named-callback-domain", "@sarj/prefer-named-complex-return-type", "@sarj/prefer-node-crypto-hash", "@sarj/prefer-node-fs-promises", "@sarj/prefer-nullish-filter-predicate", "@sarj/prefer-shared-zod-enum", "@sarj/prefer-switch-for-repeated-equality", "@sarj/require-interface-for-exported-class", "@sarj/require-sql-access-class", "@sarj/sole-export-matches-filename"];
324
324
  declare const RECOMMENDED_RULES: {
325
325
  readonly "no-restricted-imports": readonly ["error", {
326
326
  readonly paths: {
@@ -537,7 +537,7 @@ declare const RECOMMENDED_RULES: {
537
537
  readonly "@sarj/no-production-browser-source-maps": "error";
538
538
  readonly "@sarj/no-repeated-string-literal": "error";
539
539
  readonly "@sarj/no-router-refresh-polling": "error";
540
- readonly "@sarj/no-restated-comment": "error";
540
+ readonly "@sarj/no-restated-comment": "warn";
541
541
  readonly "@sarj/no-restated-jsdoc": "error";
542
542
  readonly "@sarj/no-secret-in-log": "error";
543
543
  readonly "@sarj/no-server-env-in-client-component": "error";
@@ -820,7 +820,7 @@ declare const STRICT_RULES: {
820
820
  readonly "@sarj/no-raw-fetch-outside-clients": "error";
821
821
  readonly "@sarj/no-repeated-string-literal": "error";
822
822
  readonly "@sarj/no-router-refresh-polling": "error";
823
- readonly "@sarj/no-restated-comment": "error";
823
+ readonly "@sarj/no-restated-comment": "warn";
824
824
  readonly "@sarj/no-restated-jsdoc": "error";
825
825
  readonly "@sarj/no-secret-in-log": "error";
826
826
  readonly "@sarj/no-server-env-in-client-component": "error";
@@ -893,7 +893,7 @@ type FlatPreset = {
893
893
  declare const PLUGIN: {
894
894
  readonly meta: {
895
895
  readonly name: "@sarj/eslint-plugin";
896
- readonly version: "15.17.5";
896
+ readonly version: "15.17.6";
897
897
  };
898
898
  readonly rules: {
899
899
  readonly "excessive-commentary": DocumentedRule<readonly [], "excessive">;
package/dist/index.d.ts CHANGED
@@ -320,7 +320,7 @@ declare const RULES: {
320
320
  /** @deprecated All repositories use one policy; retained for import compatibility. */
321
321
  declare const APPLICATION_ONLY_RULES: readonly [];
322
322
  /** Rules staged as non-blocking warnings while corpus adoption evidence accumulates. */
323
- declare const ADVISORY_RULES: readonly ["@sarj/excessive-commentary", "@sarj/no-bespoke-api-case-conversion", "@sarj/prefer-millisecond-control-duration-schema", "@sarj/prefer-module-level-refined-schema", "@sarj/prefer-multi-value-zod-literal", "@sarj/prefer-named-callback-domain", "@sarj/prefer-named-complex-return-type", "@sarj/prefer-node-crypto-hash", "@sarj/prefer-node-fs-promises", "@sarj/prefer-nullish-filter-predicate", "@sarj/prefer-shared-zod-enum", "@sarj/prefer-switch-for-repeated-equality", "@sarj/require-interface-for-exported-class", "@sarj/require-sql-access-class", "@sarj/sole-export-matches-filename"];
323
+ declare const ADVISORY_RULES: readonly ["@sarj/excessive-commentary", "@sarj/no-bespoke-api-case-conversion", "@sarj/no-restated-comment", "@sarj/prefer-millisecond-control-duration-schema", "@sarj/prefer-module-level-refined-schema", "@sarj/prefer-multi-value-zod-literal", "@sarj/prefer-named-callback-domain", "@sarj/prefer-named-complex-return-type", "@sarj/prefer-node-crypto-hash", "@sarj/prefer-node-fs-promises", "@sarj/prefer-nullish-filter-predicate", "@sarj/prefer-shared-zod-enum", "@sarj/prefer-switch-for-repeated-equality", "@sarj/require-interface-for-exported-class", "@sarj/require-sql-access-class", "@sarj/sole-export-matches-filename"];
324
324
  declare const RECOMMENDED_RULES: {
325
325
  readonly "no-restricted-imports": readonly ["error", {
326
326
  readonly paths: {
@@ -537,7 +537,7 @@ declare const RECOMMENDED_RULES: {
537
537
  readonly "@sarj/no-production-browser-source-maps": "error";
538
538
  readonly "@sarj/no-repeated-string-literal": "error";
539
539
  readonly "@sarj/no-router-refresh-polling": "error";
540
- readonly "@sarj/no-restated-comment": "error";
540
+ readonly "@sarj/no-restated-comment": "warn";
541
541
  readonly "@sarj/no-restated-jsdoc": "error";
542
542
  readonly "@sarj/no-secret-in-log": "error";
543
543
  readonly "@sarj/no-server-env-in-client-component": "error";
@@ -820,7 +820,7 @@ declare const STRICT_RULES: {
820
820
  readonly "@sarj/no-raw-fetch-outside-clients": "error";
821
821
  readonly "@sarj/no-repeated-string-literal": "error";
822
822
  readonly "@sarj/no-router-refresh-polling": "error";
823
- readonly "@sarj/no-restated-comment": "error";
823
+ readonly "@sarj/no-restated-comment": "warn";
824
824
  readonly "@sarj/no-restated-jsdoc": "error";
825
825
  readonly "@sarj/no-secret-in-log": "error";
826
826
  readonly "@sarj/no-server-env-in-client-component": "error";
@@ -893,7 +893,7 @@ type FlatPreset = {
893
893
  declare const PLUGIN: {
894
894
  readonly meta: {
895
895
  readonly name: "@sarj/eslint-plugin";
896
- readonly version: "15.17.5";
896
+ readonly version: "15.17.6";
897
897
  };
898
898
  readonly rules: {
899
899
  readonly "excessive-commentary": DocumentedRule<readonly [], "excessive">;
package/dist/index.js CHANGED
@@ -1397,6 +1397,10 @@ function normalizeToken(word) {
1397
1397
  return lower.length > TOKEN_PLURAL_MIN && lower.endsWith("s") && !lower.endsWith("ss") ? lower.slice(0, -1) : lower;
1398
1398
  }
1399
1399
  function restatableStatementBelow(comment, sourceCode) {
1400
+ const node = restatableStatementNodeBelow(comment, sourceCode);
1401
+ return node === null ? null : sourceCode.getText(node);
1402
+ }
1403
+ function restatableStatementNodeBelow(comment, sourceCode) {
1400
1404
  const token = sourceCode.getTokenAfter(comment, { includeComments: false });
1401
1405
  if (token === null || token.loc.start.line !== comment.loc.end.line + 1) return null;
1402
1406
  for (let node = sourceCode.getNodeByRangeIndex(token.range[0]); node != null && node.type !== AST_NODE_TYPES6.Program; node = node.parent) {
@@ -1407,7 +1411,7 @@ function restatableStatementBelow(comment, sourceCode) {
1407
1411
  if (node.type === AST_NODE_TYPES6.VariableDeclaration && isTrivialInitializer(node)) {
1408
1412
  return null;
1409
1413
  }
1410
- return sourceCode.getText(node);
1414
+ return node;
1411
1415
  }
1412
1416
  return null;
1413
1417
  }
@@ -6633,7 +6637,7 @@ var no_repeated_string_literal_default = createRule({
6633
6637
  });
6634
6638
 
6635
6639
  // src/rules/no-restated-comment.ts
6636
- import { AST_NODE_TYPES as AST_NODE_TYPES28 } from "@typescript-eslint/utils";
6640
+ import { AST_NODE_TYPES as AST_NODE_TYPES28, AST_TOKEN_TYPES } from "@typescript-eslint/utils";
6637
6641
 
6638
6642
  // src/rules/_comment-edits.ts
6639
6643
  import "@typescript-eslint/utils";
@@ -6675,25 +6679,31 @@ var MODALITY_RE = /\b(?:can|could|should|shall|may|might|must|will|would|cannot)
6675
6679
  var LEAD_IN_RE = /:$/;
6676
6680
  var EMPHASIS_RE = /\*\w[^*]*\*|`[^`]+`/;
6677
6681
  var NEGATION_WORD_RE = /\b(?:no|not|never|neither|nor|without|none|non)\b/i;
6682
+ var SEMANTIC_RELATION_RE = /\b(?:only|if|unless|when|while|except|each|every|any|all|before|after|until|since|again|once|twice|already|still|from|to|into|onto|back|versus|rather|instead)\b/i;
6678
6683
  var ACTION_STMT_RE = /[\w.$\])]\s*\(|^\s*(?:return|throw|await|yield)\b/;
6679
6684
  var NON_ASCII_LETTER_RE = /[^\p{ASCII}\p{N}\p{P}\p{Z}]/u;
6680
6685
  var WALL_NARRATION_RE2 = /^(?:(?:\d+[.)]|(?:phase|step)\s+\d+\s*:?)\s*)?(?:add|build|call|check|compute|copy|count|create|fetch|filter|find|get|handle|load|map|merge|parse|process|read|remove|return|save|send|set|sort|store|update|validate|write)(?:s|es|d|ed|ing)?\b/i;
6681
6686
  var WALL_CLUSTER_MAX_LINE_GAP = 8;
6682
6687
  var WALL_CLUSTER_MIN_COMMENTS = 3;
6683
6688
  var NO_RESTATED_COMMENT_DOCUMENTATION = {
6684
- summary: "Flag a single-line comment whose every word already appears on the statement below it.",
6689
+ summary: "Flag short standalone comments that repeat the adjacent statement's identifiers.",
6685
6690
  rationale: "A comment that only repeats code adds no context and can become stale independently.",
6686
- remediation: "Delete the comment or replace it with the reason, constraint, or consequence absent from the code.",
6691
+ remediation: "Remove a genuine restatement; retain conditions, constraints, rationale, and information absent from the statement.",
6687
6692
  category: "maintainability",
6688
6693
  autofix: "suggestion",
6689
- limitations: ["Directives, protected references, questions, multi-line prose, comments with novel content, and generated files are excluded."],
6694
+ limitations: [
6695
+ "Only standalone line comments of two to eight words with at least two content tokens directly above a supported single-line statement are inspected; one-word headings and sentence-ending punctuation are excluded.",
6696
+ "Evidence comes from that statement's identifier tokens, not strings, template text, comments, or neighboring statements. Stopwords and inflection folding are heuristic, not proof of semantic equivalence.",
6697
+ "Directives, protected references, conditions, restrictions, ordering, repetition, direction, questions, prose paragraphs, sibling-group labels, novel content, and generated files are excluded. Deletion is an optional suggestion, never an automatic fix.",
6698
+ "The conservative sibling-group exemption includes enclosing sibling groups and declaration/type-query pairs; it can miss genuine restatements inside those groups."
6699
+ ],
6690
6700
  examples: [
6691
- { id: "reason-comment", title: "Keep the reason the code cannot express", outcome: "no-match", files: [{ path: "src/cache.ts", source: "// Serialize because the cache key is stable across deploys.\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 0, public: true },
6701
+ { id: "reason-comment", title: "Keep a condition the statement does not establish", outcome: "no-match", files: [{ path: "src/cache.ts", source: "// Serialize key only after validation.\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 0, public: true },
6692
6702
  { id: "restated-comment", title: "Remove a comment that repeats the statement", outcome: "match", files: [{ path: "src/cache.ts", source: "// Serialize key\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 1, public: true }
6693
6703
  ]
6694
6704
  };
6695
- function areAdjacentLineComments2(a, b) {
6696
- return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
6705
+ function areAdjacentLineComments2(a, b, isStandalone) {
6706
+ return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && isStandalone(a) && isStandalone(b) && b.loc.start.line === a.loc.end.line + 1;
6697
6707
  }
6698
6708
  function headsSiblingRun(node) {
6699
6709
  const parent = node.parent;
@@ -6711,12 +6721,12 @@ var no_restated_comment_default = createRule({
6711
6721
  type: "suggestion",
6712
6722
  hasSuggestions: true,
6713
6723
  docs: {
6714
- description: "Flag a single-line comment whose every word already appears on the statement below it."
6724
+ description: NO_RESTATED_COMMENT_DOCUMENTATION.summary
6715
6725
  },
6716
6726
  schema: [],
6717
6727
  messages: {
6718
6728
  deleteComment: "Delete the redundant comment.",
6719
- restatesLineBelow: "Comment restates the statement below it \u2014 delete it, or replace it with the *why*; the code already carries the *what*."
6729
+ restatesLineBelow: "Comment repeats the adjacent statement's identifiers \u2014 consider removing it; retain any condition, constraint, or rationale absent from the code."
6720
6730
  }
6721
6731
  },
6722
6732
  defaultOptions: [],
@@ -6725,19 +6735,30 @@ var no_restated_comment_default = createRule({
6725
6735
  return {};
6726
6736
  }
6727
6737
  const sourceCode = context.sourceCode;
6728
- const lines = sourceCode.lines;
6729
6738
  function isStandalone(comment) {
6730
6739
  const before = sourceCode.getTokenBefore(comment, { includeComments: false });
6731
6740
  return !before || before.loc.end.line < comment.loc.start.line;
6732
6741
  }
6733
- function labelsASiblingRun(comment) {
6734
- const token = sourceCode.getTokenAfter(comment, { includeComments: false });
6735
- if (token === null) return false;
6736
- for (let node = sourceCode.getNodeByRangeIndex(token.range[0]); node != null && node.type !== AST_NODE_TYPES28.Program; node = node.parent) {
6742
+ function labelsASiblingRun(statement) {
6743
+ for (let node = statement; node !== void 0 && node.type !== AST_NODE_TYPES28.Program; node = node.parent) {
6737
6744
  if (headsSiblingRun(node)) return true;
6738
6745
  }
6739
6746
  return false;
6740
6747
  }
6748
+ function headsValueTypeGroup(statement) {
6749
+ if (statement.type !== AST_NODE_TYPES28.VariableDeclaration) return false;
6750
+ const parent = statement.parent;
6751
+ if (!("body" in parent) || !Array.isArray(parent.body)) return false;
6752
+ const body2 = parent.body;
6753
+ const next = body2[body2.indexOf(statement) + 1];
6754
+ if (next?.type !== AST_NODE_TYPES28.TSTypeAliasDeclaration) return false;
6755
+ const names = new Set(statement.declarations.flatMap(({ id }) => id.type === AST_NODE_TYPES28.Identifier ? [id.name] : []));
6756
+ const tokens = sourceCode.getTokens(next);
6757
+ return tokens.some((token, index) => {
6758
+ const nextToken = tokens[index + 1];
6759
+ return token.value === "typeof" && nextToken?.type === AST_TOKEN_TYPES.Identifier && names.has(nextToken.value);
6760
+ });
6761
+ }
6741
6762
  return {
6742
6763
  Program() {
6743
6764
  const comments = sourceCode.getAllComments();
@@ -6765,7 +6786,7 @@ var no_restated_comment_default = createRule({
6765
6786
  if (comment === void 0 || comment.type !== "Line") continue;
6766
6787
  if (wallMembers.has(comment)) continue;
6767
6788
  if (!isStandalone(comment)) continue;
6768
- if (areAdjacentLineComments2(comments[i - 1], comment) || areAdjacentLineComments2(comment, comments[i + 1])) {
6789
+ if (areAdjacentLineComments2(comments[i - 1], comment, isStandalone) || areAdjacentLineComments2(comment, comments[i + 1], isStandalone)) {
6769
6790
  continue;
6770
6791
  }
6771
6792
  const body2 = comment.value.replace(/^\/*/, "").trim();
@@ -6773,17 +6794,19 @@ var no_restated_comment_default = createRule({
6773
6794
  if (DIRECTIVE_RE3.test(body2) || CODEY_RE.test(body2) || BANNERISH_RE.test(body2)) continue;
6774
6795
  if (NON_ASCII_LETTER_RE.test(body2) || isProtected(body2)) continue;
6775
6796
  if (MODALITY_RE.test(body2) || LEAD_IN_RE.test(body2) || EMPHASIS_RE.test(body2)) continue;
6776
- if (NEGATION_WORD_RE.test(body2)) continue;
6777
- if (body2.split(/\s+/).length > MAX_WORDS) continue;
6797
+ if (NEGATION_WORD_RE.test(body2) || SEMANTIC_RELATION_RE.test(body2)) continue;
6798
+ const wordCount2 = body2.split(/\s+/).length;
6799
+ if (wordCount2 < 2 || wordCount2 > MAX_WORDS || /[.!?]$/.test(body2)) continue;
6778
6800
  const tokens = contentTokens(body2);
6779
6801
  if (tokens.length < MIN_CONTENT_TOKENS) continue;
6780
- const statement = restatableStatementBelow(comment, sourceCode);
6781
- if (statement === null) continue;
6802
+ const statementNode = restatableStatementNodeBelow(comment, sourceCode);
6803
+ if (statementNode === null) continue;
6804
+ const statement = sourceCode.getText(statementNode);
6782
6805
  if (restatesStatementHead(body2, statement)) continue;
6783
- const line = lines[comment.loc.start.line] ?? "";
6784
- if (!ACTION_STMT_RE.test(line)) continue;
6785
- if (labelsASiblingRun(comment)) continue;
6786
- if (restates(tokens, codeTokens(line))) {
6806
+ if (!ACTION_STMT_RE.test(statement)) continue;
6807
+ if (labelsASiblingRun(statementNode) || headsValueTypeGroup(statementNode)) continue;
6808
+ const identifiers = sourceCode.getTokens(statementNode).filter((token) => token.type === AST_TOKEN_TYPES.Identifier).map((token) => token.value).join(" ");
6809
+ if (restates(tokens, codeTokens(identifiers))) {
6787
6810
  const removal = wholeLineRemovalRange(sourceCode.text, comment);
6788
6811
  context.report({
6789
6812
  node: comment,
@@ -9078,10 +9101,10 @@ var no_trailing_value_narration_default = createRule({
9078
9101
  });
9079
9102
 
9080
9103
  // src/rules/no-declaration-comment-wall.ts
9081
- import { AST_NODE_TYPES as AST_NODE_TYPES36, AST_TOKEN_TYPES as AST_TOKEN_TYPES2 } from "@typescript-eslint/utils";
9104
+ import { AST_NODE_TYPES as AST_NODE_TYPES36, AST_TOKEN_TYPES as AST_TOKEN_TYPES3 } from "@typescript-eslint/utils";
9082
9105
 
9083
9106
  // src/rules/_comment-wall.ts
9084
- import { AST_NODE_TYPES as AST_NODE_TYPES35, AST_TOKEN_TYPES } from "@typescript-eslint/utils";
9107
+ import { AST_NODE_TYPES as AST_NODE_TYPES35, AST_TOKEN_TYPES as AST_TOKEN_TYPES2 } from "@typescript-eslint/utils";
9085
9108
  var WALL_DEFAULTS = {
9086
9109
  // Below three rows "a wall" is not a fair description of what the reader sees.
9087
9110
  minCommentedMembers: 3,
@@ -9147,7 +9170,7 @@ function commentBody(comment) {
9147
9170
  return comment.value.replace(/^\*+/, "").replace(/^[ \t]*\*[ \t]?/gm, "").trim();
9148
9171
  }
9149
9172
  function hasJsDocTag(comment) {
9150
- return comment.type === AST_TOKEN_TYPES.Block && comment.value.startsWith("*") && /(?:^|\s)@[A-Za-z][\w-]*\b/u.test(commentBody(comment));
9173
+ return comment.type === AST_TOKEN_TYPES2.Block && comment.value.startsWith("*") && /(?:^|\s)@[A-Za-z][\w-]*\b/u.test(commentBody(comment));
9151
9174
  }
9152
9175
  function carriesValue(body2) {
9153
9176
  return isProtected(body2) || VALUE_TAG_RE2.test(body2) || DEFAULT_RE.test(body2) || DIGIT_RE.test(body2) || UNIT_WORD_RE.test(body2) || EXAMPLE_RE.test(body2) || BANNER_RE.test(body2) || NON_ASCII_LETTER_RE2.test(body2);
@@ -9283,7 +9306,7 @@ var no_declaration_comment_wall_default = createRule({
9283
9306
  const before = sourceCode.getTokenBefore(lead, { includeComments: false });
9284
9307
  if (before === null || before.loc.end.line < lead.loc.start.line) {
9285
9308
  const previousLine = endingOn.get(lead.loc.start.line - 1);
9286
- if (lead.type === AST_TOKEN_TYPES2.Line && previousLine?.type === AST_TOKEN_TYPES2.Line && previousLine.loc.start.column === lead.loc.start.column) {
9309
+ if (lead.type === AST_TOKEN_TYPES3.Line && previousLine?.type === AST_TOKEN_TYPES3.Line && previousLine.loc.start.column === lead.loc.start.column) {
9287
9310
  return void 0;
9288
9311
  }
9289
9312
  return lead;
@@ -9526,7 +9549,7 @@ var no_union_in_comment_default = createRule({
9526
9549
  });
9527
9550
 
9528
9551
  // src/rules/no-type-member-comment-wall.ts
9529
- import { AST_NODE_TYPES as AST_NODE_TYPES38, AST_TOKEN_TYPES as AST_TOKEN_TYPES3 } from "@typescript-eslint/utils";
9552
+ import { AST_NODE_TYPES as AST_NODE_TYPES38, AST_TOKEN_TYPES as AST_TOKEN_TYPES4 } from "@typescript-eslint/utils";
9530
9553
  var NO_TYPE_MEMBER_COMMENT_WALL_DOCUMENTATION = {
9531
9554
  summary: "Flag an object type whose member comments mostly re-spell the members' own names and types.",
9532
9555
  rationale: "Repetitive member comments add scanning cost while hiding the comments that describe facts absent from the type.",
@@ -9591,7 +9614,7 @@ var no_type_member_comment_wall_default = createRule({
9591
9614
  const before = sourceCode.getTokenBefore(lead, { includeComments: false });
9592
9615
  if (before === null || before.loc.end.line < lead.loc.start.line) {
9593
9616
  const previousLine = endingOn.get(lead.loc.start.line - 1);
9594
- if (lead.type === AST_TOKEN_TYPES3.Line && previousLine?.type === AST_TOKEN_TYPES3.Line && previousLine.loc.start.column === lead.loc.start.column) {
9617
+ if (lead.type === AST_TOKEN_TYPES4.Line && previousLine?.type === AST_TOKEN_TYPES4.Line && previousLine.loc.start.column === lead.loc.start.column) {
9595
9618
  return void 0;
9596
9619
  }
9597
9620
  return lead;
@@ -19724,7 +19747,7 @@ var RULES = {
19724
19747
  };
19725
19748
  var meta = {
19726
19749
  name: "@sarj/eslint-plugin",
19727
- version: "15.17.5"
19750
+ version: "15.17.6"
19728
19751
  };
19729
19752
  var APPLICATION_ONLY_RULES = [];
19730
19753
  var LIBRARY_IMPORT_POLICY = ["error", {
@@ -19734,6 +19757,7 @@ var LIBRARY_IMPORT_POLICY = ["error", {
19734
19757
  var ADVISORY_RULES = [
19735
19758
  "@sarj/excessive-commentary",
19736
19759
  "@sarj/no-bespoke-api-case-conversion",
19760
+ "@sarj/no-restated-comment",
19737
19761
  "@sarj/prefer-millisecond-control-duration-schema",
19738
19762
  "@sarj/prefer-module-level-refined-schema",
19739
19763
  "@sarj/prefer-multi-value-zod-literal",
@@ -19781,7 +19805,7 @@ var RECOMMENDED_RULES = {
19781
19805
  "@sarj/no-production-browser-source-maps": "error",
19782
19806
  "@sarj/no-repeated-string-literal": "error",
19783
19807
  "@sarj/no-router-refresh-polling": "error",
19784
- "@sarj/no-restated-comment": "error",
19808
+ "@sarj/no-restated-comment": "warn",
19785
19809
  "@sarj/no-restated-jsdoc": "error",
19786
19810
  "@sarj/no-secret-in-log": "error",
19787
19811
  "@sarj/no-server-env-in-client-component": "error",
@@ -19877,7 +19901,7 @@ var STRICT_RULES = {
19877
19901
  "@sarj/no-raw-fetch-outside-clients": "error",
19878
19902
  "@sarj/no-repeated-string-literal": "error",
19879
19903
  "@sarj/no-router-refresh-polling": "error",
19880
- "@sarj/no-restated-comment": "error",
19904
+ "@sarj/no-restated-comment": "warn",
19881
19905
  "@sarj/no-restated-jsdoc": "error",
19882
19906
  "@sarj/no-secret-in-log": "error",
19883
19907
  "@sarj/no-server-env-in-client-component": "error",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "15.17.5",
3
+ "version": "15.17.6",
4
4
  "packageManager": "npm@12.0.2",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",