@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 +68 -37
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +75 -44
- package/package.json +1 -1
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
|
|
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
|
|
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: "
|
|
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: [
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
6775
|
-
|
|
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
|
-
|
|
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
|
|
6822
|
-
if (
|
|
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
|
-
|
|
6825
|
-
if (
|
|
6826
|
-
|
|
6827
|
-
if (restates(tokens, codeTokens(
|
|
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
|
|
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 =
|
|
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) =>
|
|
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
|
|
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
|
|
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:
|
|
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.
|
|
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": "
|
|
19790
|
-
"@sarj/no-restated-jsdoc": "
|
|
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": "
|
|
19886
|
-
"@sarj/no-restated-jsdoc": "
|
|
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": "
|
|
541
|
-
readonly "@sarj/no-restated-jsdoc": "
|
|
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": "
|
|
824
|
-
readonly "@sarj/no-restated-jsdoc": "
|
|
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.
|
|
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": "
|
|
541
|
-
readonly "@sarj/no-restated-jsdoc": "
|
|
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": "
|
|
824
|
-
readonly "@sarj/no-restated-jsdoc": "
|
|
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.
|
|
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
|
|
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
|
|
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: "
|
|
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: [
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
6734
|
-
|
|
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
|
-
|
|
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
|
|
6781
|
-
if (
|
|
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
|
-
|
|
6784
|
-
if (
|
|
6785
|
-
|
|
6786
|
-
if (restates(tokens, codeTokens(
|
|
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
|
|
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 =
|
|
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) =>
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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 ===
|
|
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 ===
|
|
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
|
|
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 ===
|
|
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.
|
|
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": "
|
|
19785
|
-
"@sarj/no-restated-jsdoc": "
|
|
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": "
|
|
19881
|
-
"@sarj/no-restated-jsdoc": "
|
|
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",
|