@sarj/eslint-plugin 15.17.5 → 15.17.7

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,
@@ -6846,22 +6869,23 @@ var no_restated_comment_default = createRule({
6846
6869
  // src/rules/no-restated-jsdoc.ts
6847
6870
  var import_utils39 = require("@typescript-eslint/utils");
6848
6871
  var NO_RESTATED_JSDOC_DOCUMENTATION = {
6849
- summary: "Flag a JSDoc block whose description and tags only re-spell the signature they document.",
6872
+ summary: "Flag JSDoc prose that appears to repeat declaration names without adding behavioral information.",
6850
6873
  rationale: "Signature-only JSDoc duplicates type information and drifts without helping callers.",
6851
6874
  remediation: "Delete the block or document behavior, constraints, failures, or context the signature cannot express.",
6852
6875
  category: "maintainability",
6853
6876
  aliases: ["jsdoc-restates-signature"],
6854
6877
  autofix: "suggestion",
6855
- limitations: ["Generated files, detached blocks, unknown tags, empty blocks, and JSDoc with information absent from the signature are excluded."],
6878
+ limitations: ["Generated files, detached blocks, intervening comments, unknown tags, explicit JSDoc type payloads, empty blocks, and JSDoc with information absent from the signature are excluded.", "Negation, conditions, constraints, sentinel values, numeric details, and quoted text conservatively preserve the block, even when a declaration name contains the same words."],
6856
6879
  examples: [
6857
6880
  { id: "behavioral-jsdoc", title: "Document behavior absent from the signature", outcome: "no-match", files: [{ path: "src/users.ts", source: "/** Get the user while bypassing the read replica. */\nexport function getUser(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 0, public: true },
6858
- { id: "signature-jsdoc", title: "Remove JSDoc that only repeats the signature", outcome: "match", files: [{ path: "src/users.ts", source: "/** Get the user by id. */\nexport function getUserById(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true }
6881
+ { id: "signature-jsdoc", title: "Remove JSDoc that only repeats the signature", outcome: "match", files: [{ path: "src/users.ts", source: "/** Get the user by id. */\nexport function getUserById(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true },
6882
+ { id: "negated-behavior", scenarioId: "negation", title: "Keep behavior even when its words resemble the declaration", outcome: "no-match", files: [{ path: "src/users.ts", source: "/** Does not cache the user. */\nexport function cacheUser(user: unknown) { return user; }" }], focusPath: "src/users.ts", expectedCount: 0, public: true },
6883
+ { id: "repeated-behavior-name", scenarioId: "negation", title: "Review prose that merely repeats the declaration name", outcome: "match", files: [{ path: "src/users.ts", source: "/** Cache the user. */\nexport function cacheUser(user: unknown) { return user; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true }
6859
6884
  ]
6860
6885
  };
6861
6886
  var MODELLED_TAGS = /* @__PURE__ */ new Set([
6862
6887
  "arg",
6863
6888
  "argument",
6864
- "async",
6865
6889
  "description",
6866
6890
  "param",
6867
6891
  "return",
@@ -6880,7 +6904,8 @@ var STOPWORDS2 = new Set(
6880
6904
  returns returning result optional required default true false null undefined
6881
6905
  string number boolean array list promise`.split(/\s+/)
6882
6906
  );
6883
- var WORD_RE2 = /[A-Za-z]+/g;
6907
+ var WORD_RE2 = new RegExp("\\p{L}+", "gu");
6908
+ var BEHAVIORAL_PROSE_RE = /\b(?:not|no|never|if|when|unless|must|should|may|can|could|would|optional|required|default|true|false|null|undefined|before|after|until|once|again|only|always)\b|\d|["'`<>=+*/%&|!~^-]/i;
6884
6909
  function parseJsDoc(value) {
6885
6910
  const lines = value.replace(/^\*/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, ""));
6886
6911
  const description = [];
@@ -6900,12 +6925,13 @@ function parseJsDoc(value) {
6900
6925
  return { description: description.join("\n").trim(), tags };
6901
6926
  }
6902
6927
  function covered(text, known) {
6928
+ if (BEHAVIORAL_PROSE_RE.test(text)) return false;
6903
6929
  const stems = /* @__PURE__ */ new Set();
6904
6930
  for (const token of known) stems.add(stem(token));
6905
6931
  return proseTokens(text).every((word) => known.has(word) || stems.has(stem(word)));
6906
6932
  }
6907
6933
  function proseTokens(text) {
6908
- return (text.match(WORD_RE2) ?? []).map((word) => word.toLowerCase()).filter((word) => word.length > 1 && !STOPWORDS2.has(word));
6934
+ return (text.match(WORD_RE2) ?? []).map((word) => word.toLowerCase()).filter((word) => !STOPWORDS2.has(word));
6909
6935
  }
6910
6936
  function declarationNames(node) {
6911
6937
  switch (node.type) {
@@ -6962,11 +6988,11 @@ var no_restated_jsdoc_default = createRule({
6962
6988
  type: "suggestion",
6963
6989
  hasSuggestions: true,
6964
6990
  docs: {
6965
- description: "Flag a JSDoc block whose description and tags only re-spell the signature they document."
6991
+ description: "Flag JSDoc prose that appears to repeat declaration names without adding behavioral information."
6966
6992
  },
6967
6993
  schema: [],
6968
6994
  messages: {
6969
- restatesSignature: "JSDoc only re-spells the signature \u2014 delete it; if the signature still needs explanation, improve its names or types. Keep constraints, failures, and rationale.",
6995
+ restatesSignature: "JSDoc appears to repeat declaration names \u2014 consider removing repetition. Keep type contracts, constraints, failures, and rationale.",
6970
6996
  deleteBlock: "Delete the JSDoc block."
6971
6997
  }
6972
6998
  },
@@ -6989,8 +7015,9 @@ var no_restated_jsdoc_default = createRule({
6989
7015
  const tagNames = new Set(tags.map((tag) => tag.name));
6990
7016
  if ([...tagNames].some((name) => !MODELLED_TAGS.has(name))) continue;
6991
7017
  if (isProtected(describedText)) continue;
6992
- const token = sourceCode.getTokenAfter(comment, { includeComments: false });
7018
+ const token = sourceCode.getTokenAfter(comment, { includeComments: true });
6993
7019
  if (token === null || token.loc.start.line !== comment.loc.end.line + 1) continue;
7020
+ if (token.type === "Line" || token.type === "Block") continue;
6994
7021
  let node = sourceCode.getNodeByRangeIndex(token.range[0]);
6995
7022
  let declaration = null;
6996
7023
  while (node != null && node.type !== import_utils39.AST_NODE_TYPES.Program) {
@@ -7001,6 +7028,8 @@ var no_restated_jsdoc_default = createRule({
7001
7028
  if (declaration === null) continue;
7002
7029
  const paramTags = tags.filter((tag) => PARAM_TAGS.has(tag.name));
7003
7030
  const returnTags = tags.filter((tag) => RETURN_TAGS.has(tag.name));
7031
+ if ([...paramTags, ...returnTags].some((tag) => /^\s*\{/.test(tag.text))) continue;
7032
+ if (paramTags.some((tag) => /^\s*(?:\[|[A-Za-z_$][\w$]*\.)/.test(tag.text))) continue;
7004
7033
  if (describedText.length === 0 && paramTags.length === 0 && returnTags.length === 0) {
7005
7034
  continue;
7006
7035
  }
@@ -19729,7 +19758,7 @@ var RULES = {
19729
19758
  };
19730
19759
  var meta = {
19731
19760
  name: "@sarj/eslint-plugin",
19732
- version: "15.17.5"
19761
+ version: "15.17.7"
19733
19762
  };
19734
19763
  var APPLICATION_ONLY_RULES = [];
19735
19764
  var LIBRARY_IMPORT_POLICY = ["error", {
@@ -19739,6 +19768,8 @@ var LIBRARY_IMPORT_POLICY = ["error", {
19739
19768
  var ADVISORY_RULES = [
19740
19769
  "@sarj/excessive-commentary",
19741
19770
  "@sarj/no-bespoke-api-case-conversion",
19771
+ "@sarj/no-restated-comment",
19772
+ "@sarj/no-restated-jsdoc",
19742
19773
  "@sarj/prefer-millisecond-control-duration-schema",
19743
19774
  "@sarj/prefer-module-level-refined-schema",
19744
19775
  "@sarj/prefer-multi-value-zod-literal",
@@ -19786,8 +19817,8 @@ var RECOMMENDED_RULES = {
19786
19817
  "@sarj/no-production-browser-source-maps": "error",
19787
19818
  "@sarj/no-repeated-string-literal": "error",
19788
19819
  "@sarj/no-router-refresh-polling": "error",
19789
- "@sarj/no-restated-comment": "error",
19790
- "@sarj/no-restated-jsdoc": "error",
19820
+ "@sarj/no-restated-comment": "warn",
19821
+ "@sarj/no-restated-jsdoc": "warn",
19791
19822
  "@sarj/no-secret-in-log": "error",
19792
19823
  "@sarj/no-server-env-in-client-component": "error",
19793
19824
  "@sarj/no-select-star": "error",
@@ -19882,8 +19913,8 @@ var STRICT_RULES = {
19882
19913
  "@sarj/no-raw-fetch-outside-clients": "error",
19883
19914
  "@sarj/no-repeated-string-literal": "error",
19884
19915
  "@sarj/no-router-refresh-polling": "error",
19885
- "@sarj/no-restated-comment": "error",
19886
- "@sarj/no-restated-jsdoc": "error",
19916
+ "@sarj/no-restated-comment": "warn",
19917
+ "@sarj/no-restated-jsdoc": "warn",
19887
19918
  "@sarj/no-secret-in-log": "error",
19888
19919
  "@sarj/no-server-env-in-client-component": "error",
19889
19920
  "@sarj/no-select-star": "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/no-restated-jsdoc", "@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,8 +537,8 @@ 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";
541
- readonly "@sarj/no-restated-jsdoc": "error";
540
+ readonly "@sarj/no-restated-comment": "warn";
541
+ readonly "@sarj/no-restated-jsdoc": "warn";
542
542
  readonly "@sarj/no-secret-in-log": "error";
543
543
  readonly "@sarj/no-server-env-in-client-component": "error";
544
544
  readonly "@sarj/no-select-star": "error";
@@ -820,8 +820,8 @@ 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";
824
- readonly "@sarj/no-restated-jsdoc": "error";
823
+ readonly "@sarj/no-restated-comment": "warn";
824
+ readonly "@sarj/no-restated-jsdoc": "warn";
825
825
  readonly "@sarj/no-secret-in-log": "error";
826
826
  readonly "@sarj/no-server-env-in-client-component": "error";
827
827
  readonly "@sarj/no-select-star": "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.7";
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/no-restated-jsdoc", "@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,8 +537,8 @@ 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";
541
- readonly "@sarj/no-restated-jsdoc": "error";
540
+ readonly "@sarj/no-restated-comment": "warn";
541
+ readonly "@sarj/no-restated-jsdoc": "warn";
542
542
  readonly "@sarj/no-secret-in-log": "error";
543
543
  readonly "@sarj/no-server-env-in-client-component": "error";
544
544
  readonly "@sarj/no-select-star": "error";
@@ -820,8 +820,8 @@ 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";
824
- readonly "@sarj/no-restated-jsdoc": "error";
823
+ readonly "@sarj/no-restated-comment": "warn";
824
+ readonly "@sarj/no-restated-jsdoc": "warn";
825
825
  readonly "@sarj/no-secret-in-log": "error";
826
826
  readonly "@sarj/no-server-env-in-client-component": "error";
827
827
  readonly "@sarj/no-select-star": "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.7";
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,
@@ -6805,22 +6828,23 @@ var no_restated_comment_default = createRule({
6805
6828
  // src/rules/no-restated-jsdoc.ts
6806
6829
  import { AST_NODE_TYPES as AST_NODE_TYPES29 } from "@typescript-eslint/utils";
6807
6830
  var NO_RESTATED_JSDOC_DOCUMENTATION = {
6808
- summary: "Flag a JSDoc block whose description and tags only re-spell the signature they document.",
6831
+ summary: "Flag JSDoc prose that appears to repeat declaration names without adding behavioral information.",
6809
6832
  rationale: "Signature-only JSDoc duplicates type information and drifts without helping callers.",
6810
6833
  remediation: "Delete the block or document behavior, constraints, failures, or context the signature cannot express.",
6811
6834
  category: "maintainability",
6812
6835
  aliases: ["jsdoc-restates-signature"],
6813
6836
  autofix: "suggestion",
6814
- limitations: ["Generated files, detached blocks, unknown tags, empty blocks, and JSDoc with information absent from the signature are excluded."],
6837
+ limitations: ["Generated files, detached blocks, intervening comments, unknown tags, explicit JSDoc type payloads, empty blocks, and JSDoc with information absent from the signature are excluded.", "Negation, conditions, constraints, sentinel values, numeric details, and quoted text conservatively preserve the block, even when a declaration name contains the same words."],
6815
6838
  examples: [
6816
6839
  { id: "behavioral-jsdoc", title: "Document behavior absent from the signature", outcome: "no-match", files: [{ path: "src/users.ts", source: "/** Get the user while bypassing the read replica. */\nexport function getUser(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 0, public: true },
6817
- { id: "signature-jsdoc", title: "Remove JSDoc that only repeats the signature", outcome: "match", files: [{ path: "src/users.ts", source: "/** Get the user by id. */\nexport function getUserById(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true }
6840
+ { id: "signature-jsdoc", title: "Remove JSDoc that only repeats the signature", outcome: "match", files: [{ path: "src/users.ts", source: "/** Get the user by id. */\nexport function getUserById(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true },
6841
+ { id: "negated-behavior", scenarioId: "negation", title: "Keep behavior even when its words resemble the declaration", outcome: "no-match", files: [{ path: "src/users.ts", source: "/** Does not cache the user. */\nexport function cacheUser(user: unknown) { return user; }" }], focusPath: "src/users.ts", expectedCount: 0, public: true },
6842
+ { id: "repeated-behavior-name", scenarioId: "negation", title: "Review prose that merely repeats the declaration name", outcome: "match", files: [{ path: "src/users.ts", source: "/** Cache the user. */\nexport function cacheUser(user: unknown) { return user; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true }
6818
6843
  ]
6819
6844
  };
6820
6845
  var MODELLED_TAGS = /* @__PURE__ */ new Set([
6821
6846
  "arg",
6822
6847
  "argument",
6823
- "async",
6824
6848
  "description",
6825
6849
  "param",
6826
6850
  "return",
@@ -6839,7 +6863,8 @@ var STOPWORDS2 = new Set(
6839
6863
  returns returning result optional required default true false null undefined
6840
6864
  string number boolean array list promise`.split(/\s+/)
6841
6865
  );
6842
- var WORD_RE2 = /[A-Za-z]+/g;
6866
+ var WORD_RE2 = new RegExp("\\p{L}+", "gu");
6867
+ var BEHAVIORAL_PROSE_RE = /\b(?:not|no|never|if|when|unless|must|should|may|can|could|would|optional|required|default|true|false|null|undefined|before|after|until|once|again|only|always)\b|\d|["'`<>=+*/%&|!~^-]/i;
6843
6868
  function parseJsDoc(value) {
6844
6869
  const lines = value.replace(/^\*/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, ""));
6845
6870
  const description = [];
@@ -6859,12 +6884,13 @@ function parseJsDoc(value) {
6859
6884
  return { description: description.join("\n").trim(), tags };
6860
6885
  }
6861
6886
  function covered(text, known) {
6887
+ if (BEHAVIORAL_PROSE_RE.test(text)) return false;
6862
6888
  const stems = /* @__PURE__ */ new Set();
6863
6889
  for (const token of known) stems.add(stem(token));
6864
6890
  return proseTokens(text).every((word) => known.has(word) || stems.has(stem(word)));
6865
6891
  }
6866
6892
  function proseTokens(text) {
6867
- return (text.match(WORD_RE2) ?? []).map((word) => word.toLowerCase()).filter((word) => word.length > 1 && !STOPWORDS2.has(word));
6893
+ return (text.match(WORD_RE2) ?? []).map((word) => word.toLowerCase()).filter((word) => !STOPWORDS2.has(word));
6868
6894
  }
6869
6895
  function declarationNames(node) {
6870
6896
  switch (node.type) {
@@ -6921,11 +6947,11 @@ var no_restated_jsdoc_default = createRule({
6921
6947
  type: "suggestion",
6922
6948
  hasSuggestions: true,
6923
6949
  docs: {
6924
- description: "Flag a JSDoc block whose description and tags only re-spell the signature they document."
6950
+ description: "Flag JSDoc prose that appears to repeat declaration names without adding behavioral information."
6925
6951
  },
6926
6952
  schema: [],
6927
6953
  messages: {
6928
- restatesSignature: "JSDoc only re-spells the signature \u2014 delete it; if the signature still needs explanation, improve its names or types. Keep constraints, failures, and rationale.",
6954
+ restatesSignature: "JSDoc appears to repeat declaration names \u2014 consider removing repetition. Keep type contracts, constraints, failures, and rationale.",
6929
6955
  deleteBlock: "Delete the JSDoc block."
6930
6956
  }
6931
6957
  },
@@ -6948,8 +6974,9 @@ var no_restated_jsdoc_default = createRule({
6948
6974
  const tagNames = new Set(tags.map((tag) => tag.name));
6949
6975
  if ([...tagNames].some((name) => !MODELLED_TAGS.has(name))) continue;
6950
6976
  if (isProtected(describedText)) continue;
6951
- const token = sourceCode.getTokenAfter(comment, { includeComments: false });
6977
+ const token = sourceCode.getTokenAfter(comment, { includeComments: true });
6952
6978
  if (token === null || token.loc.start.line !== comment.loc.end.line + 1) continue;
6979
+ if (token.type === "Line" || token.type === "Block") continue;
6953
6980
  let node = sourceCode.getNodeByRangeIndex(token.range[0]);
6954
6981
  let declaration = null;
6955
6982
  while (node != null && node.type !== AST_NODE_TYPES29.Program) {
@@ -6960,6 +6987,8 @@ var no_restated_jsdoc_default = createRule({
6960
6987
  if (declaration === null) continue;
6961
6988
  const paramTags = tags.filter((tag) => PARAM_TAGS.has(tag.name));
6962
6989
  const returnTags = tags.filter((tag) => RETURN_TAGS.has(tag.name));
6990
+ if ([...paramTags, ...returnTags].some((tag) => /^\s*\{/.test(tag.text))) continue;
6991
+ if (paramTags.some((tag) => /^\s*(?:\[|[A-Za-z_$][\w$]*\.)/.test(tag.text))) continue;
6963
6992
  if (describedText.length === 0 && paramTags.length === 0 && returnTags.length === 0) {
6964
6993
  continue;
6965
6994
  }
@@ -9078,10 +9107,10 @@ var no_trailing_value_narration_default = createRule({
9078
9107
  });
9079
9108
 
9080
9109
  // 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";
9110
+ import { AST_NODE_TYPES as AST_NODE_TYPES36, AST_TOKEN_TYPES as AST_TOKEN_TYPES3 } from "@typescript-eslint/utils";
9082
9111
 
9083
9112
  // src/rules/_comment-wall.ts
9084
- import { AST_NODE_TYPES as AST_NODE_TYPES35, AST_TOKEN_TYPES } from "@typescript-eslint/utils";
9113
+ import { AST_NODE_TYPES as AST_NODE_TYPES35, AST_TOKEN_TYPES as AST_TOKEN_TYPES2 } from "@typescript-eslint/utils";
9085
9114
  var WALL_DEFAULTS = {
9086
9115
  // Below three rows "a wall" is not a fair description of what the reader sees.
9087
9116
  minCommentedMembers: 3,
@@ -9147,7 +9176,7 @@ function commentBody(comment) {
9147
9176
  return comment.value.replace(/^\*+/, "").replace(/^[ \t]*\*[ \t]?/gm, "").trim();
9148
9177
  }
9149
9178
  function hasJsDocTag(comment) {
9150
- return comment.type === AST_TOKEN_TYPES.Block && comment.value.startsWith("*") && /(?:^|\s)@[A-Za-z][\w-]*\b/u.test(commentBody(comment));
9179
+ return comment.type === AST_TOKEN_TYPES2.Block && comment.value.startsWith("*") && /(?:^|\s)@[A-Za-z][\w-]*\b/u.test(commentBody(comment));
9151
9180
  }
9152
9181
  function carriesValue(body2) {
9153
9182
  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 +9312,7 @@ var no_declaration_comment_wall_default = createRule({
9283
9312
  const before = sourceCode.getTokenBefore(lead, { includeComments: false });
9284
9313
  if (before === null || before.loc.end.line < lead.loc.start.line) {
9285
9314
  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) {
9315
+ if (lead.type === AST_TOKEN_TYPES3.Line && previousLine?.type === AST_TOKEN_TYPES3.Line && previousLine.loc.start.column === lead.loc.start.column) {
9287
9316
  return void 0;
9288
9317
  }
9289
9318
  return lead;
@@ -9526,7 +9555,7 @@ var no_union_in_comment_default = createRule({
9526
9555
  });
9527
9556
 
9528
9557
  // 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";
9558
+ import { AST_NODE_TYPES as AST_NODE_TYPES38, AST_TOKEN_TYPES as AST_TOKEN_TYPES4 } from "@typescript-eslint/utils";
9530
9559
  var NO_TYPE_MEMBER_COMMENT_WALL_DOCUMENTATION = {
9531
9560
  summary: "Flag an object type whose member comments mostly re-spell the members' own names and types.",
9532
9561
  rationale: "Repetitive member comments add scanning cost while hiding the comments that describe facts absent from the type.",
@@ -9591,7 +9620,7 @@ var no_type_member_comment_wall_default = createRule({
9591
9620
  const before = sourceCode.getTokenBefore(lead, { includeComments: false });
9592
9621
  if (before === null || before.loc.end.line < lead.loc.start.line) {
9593
9622
  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) {
9623
+ if (lead.type === AST_TOKEN_TYPES4.Line && previousLine?.type === AST_TOKEN_TYPES4.Line && previousLine.loc.start.column === lead.loc.start.column) {
9595
9624
  return void 0;
9596
9625
  }
9597
9626
  return lead;
@@ -19724,7 +19753,7 @@ var RULES = {
19724
19753
  };
19725
19754
  var meta = {
19726
19755
  name: "@sarj/eslint-plugin",
19727
- version: "15.17.5"
19756
+ version: "15.17.7"
19728
19757
  };
19729
19758
  var APPLICATION_ONLY_RULES = [];
19730
19759
  var LIBRARY_IMPORT_POLICY = ["error", {
@@ -19734,6 +19763,8 @@ var LIBRARY_IMPORT_POLICY = ["error", {
19734
19763
  var ADVISORY_RULES = [
19735
19764
  "@sarj/excessive-commentary",
19736
19765
  "@sarj/no-bespoke-api-case-conversion",
19766
+ "@sarj/no-restated-comment",
19767
+ "@sarj/no-restated-jsdoc",
19737
19768
  "@sarj/prefer-millisecond-control-duration-schema",
19738
19769
  "@sarj/prefer-module-level-refined-schema",
19739
19770
  "@sarj/prefer-multi-value-zod-literal",
@@ -19781,8 +19812,8 @@ var RECOMMENDED_RULES = {
19781
19812
  "@sarj/no-production-browser-source-maps": "error",
19782
19813
  "@sarj/no-repeated-string-literal": "error",
19783
19814
  "@sarj/no-router-refresh-polling": "error",
19784
- "@sarj/no-restated-comment": "error",
19785
- "@sarj/no-restated-jsdoc": "error",
19815
+ "@sarj/no-restated-comment": "warn",
19816
+ "@sarj/no-restated-jsdoc": "warn",
19786
19817
  "@sarj/no-secret-in-log": "error",
19787
19818
  "@sarj/no-server-env-in-client-component": "error",
19788
19819
  "@sarj/no-select-star": "error",
@@ -19877,8 +19908,8 @@ var STRICT_RULES = {
19877
19908
  "@sarj/no-raw-fetch-outside-clients": "error",
19878
19909
  "@sarj/no-repeated-string-literal": "error",
19879
19910
  "@sarj/no-router-refresh-polling": "error",
19880
- "@sarj/no-restated-comment": "error",
19881
- "@sarj/no-restated-jsdoc": "error",
19911
+ "@sarj/no-restated-comment": "warn",
19912
+ "@sarj/no-restated-jsdoc": "warn",
19882
19913
  "@sarj/no-secret-in-log": "error",
19883
19914
  "@sarj/no-server-env-in-client-component": "error",
19884
19915
  "@sarj/no-select-star": "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.7",
4
4
  "packageManager": "npm@12.0.2",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",