@sarj/eslint-plugin 9.5.0 → 9.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +145 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +145 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -533,6 +533,14 @@ function restatesStatementHead(body, statement) {
|
|
|
533
533
|
|
|
534
534
|
// src/rules/no-comment-cruft.ts
|
|
535
535
|
var LEADING_PREAMBLE_MIN = 4;
|
|
536
|
+
var WALL_MIN_STATEMENTS = 4;
|
|
537
|
+
var WALL_MIN_COMMENTS = 3;
|
|
538
|
+
var WALL_MIN_COMMENTED_RATIO = 0.6;
|
|
539
|
+
var WALL_MIN_WEAK_RATIO = 0.75;
|
|
540
|
+
var WALL_MAX_WORDS = 18;
|
|
541
|
+
var WALL_MAX_NOVEL_WORDS = 2;
|
|
542
|
+
var WALL_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|now|finally|lastly|add|append|assign|await|build|calculate|call|check|clear|close|compute|convert|copy|count|create|declare|define|delete|extract|fetch|filter|find|format|generate|get|handle|initialize|insert|iterate|join|load|log|loop|map|merge|open|parse|print|process|push|read|remove|render|reset|return|save|send|set|setup|sort|split|start|stop|store|update|validate|wrap|write)(?:s|es|d|ed|ing)?\b/i;
|
|
543
|
+
var WALL_STEP_PREFIX_RE = /^(?:\d+[.)]|(?:phase|step)\s+\d+\s*:)\s*/i;
|
|
536
544
|
var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|after(?:wards| that)?|finally|lastly|now)\s*[,:]\s*\S/i;
|
|
537
545
|
var META_COMMENTARY_RE = /\b(?:keeping (?:it|this) simple|could be (?:refactored|improved|cleaned up|simplified)|refactor(?:ed|ing)? (?:later|this)|not sure (?:if|whether|why|how)|quick[- ](?:and[- ]dirty|fix)|(?:a |bit of a )?hacky|is a hack|temporary (?:solution|workaround|fix|hack)|revisit (?:this|later|below)|clean (?:this|it) up|not ideal|placeholder for now)\b/i;
|
|
538
546
|
var FOR_NOW_RE = /\bfor now\b/i;
|
|
@@ -745,6 +753,63 @@ var STATEMENT_CONTAINERS = /* @__PURE__ */ new Set([
|
|
|
745
753
|
import_utils6.AST_NODE_TYPES.TSModuleBlock,
|
|
746
754
|
import_utils6.AST_NODE_TYPES.TSInterfaceBody
|
|
747
755
|
]);
|
|
756
|
+
function directStatements(container) {
|
|
757
|
+
switch (container.type) {
|
|
758
|
+
case import_utils6.AST_NODE_TYPES.Program:
|
|
759
|
+
case import_utils6.AST_NODE_TYPES.BlockStatement:
|
|
760
|
+
case import_utils6.AST_NODE_TYPES.ClassBody:
|
|
761
|
+
case import_utils6.AST_NODE_TYPES.StaticBlock:
|
|
762
|
+
case import_utils6.AST_NODE_TYPES.TSModuleBlock:
|
|
763
|
+
return container.body;
|
|
764
|
+
case import_utils6.AST_NODE_TYPES.SwitchCase:
|
|
765
|
+
return container.consequent;
|
|
766
|
+
case import_utils6.AST_NODE_TYPES.TSInterfaceBody:
|
|
767
|
+
return container.body;
|
|
768
|
+
default:
|
|
769
|
+
return [];
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
var WALL_STATEMENTS = /* @__PURE__ */ new Set([
|
|
773
|
+
import_utils6.AST_NODE_TYPES.ExpressionStatement,
|
|
774
|
+
import_utils6.AST_NODE_TYPES.ReturnStatement,
|
|
775
|
+
import_utils6.AST_NODE_TYPES.ThrowStatement,
|
|
776
|
+
import_utils6.AST_NODE_TYPES.VariableDeclaration,
|
|
777
|
+
import_utils6.AST_NODE_TYPES.IfStatement,
|
|
778
|
+
import_utils6.AST_NODE_TYPES.ForStatement,
|
|
779
|
+
import_utils6.AST_NODE_TYPES.ForOfStatement,
|
|
780
|
+
import_utils6.AST_NODE_TYPES.ForInStatement,
|
|
781
|
+
import_utils6.AST_NODE_TYPES.WhileStatement,
|
|
782
|
+
import_utils6.AST_NODE_TYPES.DoWhileStatement,
|
|
783
|
+
import_utils6.AST_NODE_TYPES.SwitchStatement,
|
|
784
|
+
import_utils6.AST_NODE_TYPES.TryStatement
|
|
785
|
+
]);
|
|
786
|
+
function statementAttachmentBelow(comment, sourceCode) {
|
|
787
|
+
const token = sourceCode.getTokenAfter(comment, { includeComments: false });
|
|
788
|
+
if (token === null || token.loc.start.line !== comment.loc.end.line + 1 || token.loc.start.column !== comment.loc.start.column) {
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
for (let node = sourceCode.getNodeByRangeIndex(token.range[0]); node?.parent != null; node = node.parent) {
|
|
792
|
+
if (!STATEMENT_CONTAINERS.has(node.parent.type) || !WALL_STATEMENTS.has(node.type)) {
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
const siblings = directStatements(node.parent);
|
|
796
|
+
const index = siblings.indexOf(node);
|
|
797
|
+
return index < 0 ? null : { container: node.parent, index, statement: sourceCode.getText(node) };
|
|
798
|
+
}
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
function isWeakWalkthroughComment(body, statement) {
|
|
802
|
+
const normalized = body.replace(WALL_STEP_PREFIX_RE, "");
|
|
803
|
+
if (normalized.length === 0 || normalized.endsWith("?") || normalized.split(/\s+/).length > WALL_MAX_WORDS || isDirective(normalized) || isProtected(normalized) || !WALL_NARRATION_RE.test(normalized)) {
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
const words = contentTokens(normalized);
|
|
807
|
+
const described = words.slice(1);
|
|
808
|
+
if (described.length === 0) return false;
|
|
809
|
+
const code = codeTokens(statement);
|
|
810
|
+
const matched = described.filter((word) => restates([word], code)).length;
|
|
811
|
+
return matched / described.length >= 0.5 && described.length - matched <= WALL_MAX_NOVEL_WORDS;
|
|
812
|
+
}
|
|
748
813
|
var TYPE_MEMBER_CONTAINERS = /* @__PURE__ */ new Set([
|
|
749
814
|
import_utils6.AST_NODE_TYPES.TSInterfaceBody,
|
|
750
815
|
import_utils6.AST_NODE_TYPES.TSTypeLiteral
|
|
@@ -800,6 +865,7 @@ var no_comment_cruft_default = createRule({
|
|
|
800
865
|
commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
|
|
801
866
|
sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
|
|
802
867
|
fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines.",
|
|
868
|
+
commentWall: "Statement comment wall ({{count}} narrated steps) \u2014 delete the walkthrough and name the operations in code; keep only constraints or rationale.",
|
|
803
869
|
redundantNarration: "Comment narrates the code \u2014 delete it or say *why*, not *what*. Code is self-documenting.",
|
|
804
870
|
untrackedTodo: "Untracked TODO/FIXME marker \u2014 add an issue ticket or context link."
|
|
805
871
|
}
|
|
@@ -819,6 +885,48 @@ var no_comment_cruft_default = createRule({
|
|
|
819
885
|
function isJsDoc(comment) {
|
|
820
886
|
return comment.type === "Block" && /^\*/.test(comment.value);
|
|
821
887
|
}
|
|
888
|
+
function findCommentWalls(comments) {
|
|
889
|
+
const attached = /* @__PURE__ */ new Map();
|
|
890
|
+
for (const comment of comments) {
|
|
891
|
+
if (comment.type !== "Line" || !isStandalone(comment)) continue;
|
|
892
|
+
const attachment = statementAttachmentBelow(comment, sourceCode);
|
|
893
|
+
if (attachment === null) continue;
|
|
894
|
+
const body = stripCommentMarker(comment.value);
|
|
895
|
+
const entries = attached.get(attachment.container) ?? [];
|
|
896
|
+
entries.push({
|
|
897
|
+
comment,
|
|
898
|
+
index: attachment.index,
|
|
899
|
+
weak: isWeakWalkthroughComment(body, attachment.statement)
|
|
900
|
+
});
|
|
901
|
+
attached.set(attachment.container, entries);
|
|
902
|
+
}
|
|
903
|
+
const walls = [];
|
|
904
|
+
for (const entries of attached.values()) {
|
|
905
|
+
const sorted = entries.toSorted((left, right) => left.index - right.index);
|
|
906
|
+
const clusters = [];
|
|
907
|
+
for (const entry of sorted) {
|
|
908
|
+
const cluster = clusters.at(-1);
|
|
909
|
+
if (cluster !== void 0 && entry.index <= (cluster.at(-1)?.index ?? 0) + 2) {
|
|
910
|
+
cluster.push(entry);
|
|
911
|
+
} else {
|
|
912
|
+
clusters.push([entry]);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
for (const cluster of clusters) {
|
|
916
|
+
const firstIndex = cluster[0]?.index;
|
|
917
|
+
const lastIndex = cluster.at(-1)?.index;
|
|
918
|
+
if (firstIndex === void 0 || lastIndex === void 0) continue;
|
|
919
|
+
const span = lastIndex - firstIndex + 1;
|
|
920
|
+
const weak = cluster.filter((entry) => entry.weak).map((entry) => entry.comment);
|
|
921
|
+
if (span < WALL_MIN_STATEMENTS || weak.length < WALL_MIN_COMMENTS || cluster.length / span < WALL_MIN_COMMENTED_RATIO || weak.length / cluster.length < WALL_MIN_WEAK_RATIO) {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
const leader = weak[0];
|
|
925
|
+
if (leader !== void 0) walls.push({ leader, members: new Set(weak) });
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return walls;
|
|
929
|
+
}
|
|
822
930
|
function isSectionJsDoc(comment) {
|
|
823
931
|
const texts = comment.value.split("\n").map(stripCommentMarker).filter((line) => line.length > 0 && !isDirective(line));
|
|
824
932
|
return texts.length > 0 && texts.every(
|
|
@@ -851,6 +959,9 @@ var no_comment_cruft_default = createRule({
|
|
|
851
959
|
return {
|
|
852
960
|
Program() {
|
|
853
961
|
const comments = sourceCode.getAllComments();
|
|
962
|
+
const walls = findCommentWalls(comments);
|
|
963
|
+
const wallByLeader = new Map(walls.map((wall) => [wall.leader, wall]));
|
|
964
|
+
const wallMembers = new Set(walls.flatMap((wall) => [...wall.members]));
|
|
854
965
|
const firstCodeLine = sourceCode.ast.tokens[0]?.loc.start.line ?? Number.MAX_SAFE_INTEGER;
|
|
855
966
|
const enumerated = comments.filter(
|
|
856
967
|
(c) => c.type === "Line" && ENUMERATION_RE.test(stripCommentMarker(c.value))
|
|
@@ -858,6 +969,16 @@ var no_comment_cruft_default = createRule({
|
|
|
858
969
|
for (let i = 0; i < comments.length; i++) {
|
|
859
970
|
const comment = comments[i];
|
|
860
971
|
if (comment === void 0) continue;
|
|
972
|
+
const wall = wallByLeader.get(comment);
|
|
973
|
+
if (wall !== void 0) {
|
|
974
|
+
context.report({
|
|
975
|
+
node: comment,
|
|
976
|
+
messageId: "commentWall",
|
|
977
|
+
data: { count: String(wall.members.size) }
|
|
978
|
+
});
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
if (wallMembers.has(comment)) continue;
|
|
861
982
|
if (isJsDoc(comment)) {
|
|
862
983
|
if (isStandalone(comment) && isSectionJsDoc(comment)) {
|
|
863
984
|
context.report({ node: comment, messageId: "sectionBanner" });
|
|
@@ -3368,6 +3489,9 @@ var EMPHASIS_RE = /\*\w[^*]*\*|`[^`]+`/;
|
|
|
3368
3489
|
var NEGATION_WORD_RE = /\b(?:no|not|never|neither|nor|without|none|non)\b/i;
|
|
3369
3490
|
var ACTION_STMT_RE = /[\w.$\])]\s*\(|^\s*(?:return|throw|await|yield)\b/;
|
|
3370
3491
|
var NON_ASCII_LETTER_RE = /[^\p{ASCII}\p{N}\p{P}\p{Z}]/u;
|
|
3492
|
+
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;
|
|
3493
|
+
var WALL_CLUSTER_MAX_LINE_GAP = 8;
|
|
3494
|
+
var WALL_CLUSTER_MIN_COMMENTS = 3;
|
|
3371
3495
|
function areAdjacentLineComments2(a, b) {
|
|
3372
3496
|
return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
|
|
3373
3497
|
}
|
|
@@ -3414,9 +3538,29 @@ var no_restated_comment_default = createRule({
|
|
|
3414
3538
|
return {
|
|
3415
3539
|
Program() {
|
|
3416
3540
|
const comments = sourceCode.getAllComments();
|
|
3541
|
+
const wallMembers = /* @__PURE__ */ new Set();
|
|
3542
|
+
let cluster = [];
|
|
3543
|
+
for (const candidate of comments) {
|
|
3544
|
+
const body = candidate.value.replace(/^\/*/, "").trim();
|
|
3545
|
+
if (candidate.type !== "Line" || !isStandalone(candidate) || isProtected(body) || !WALL_NARRATION_RE2.test(body)) {
|
|
3546
|
+
continue;
|
|
3547
|
+
}
|
|
3548
|
+
const previous = cluster.at(-1);
|
|
3549
|
+
if (previous !== void 0 && candidate.loc.start.line - previous.loc.start.line > WALL_CLUSTER_MAX_LINE_GAP) {
|
|
3550
|
+
if (cluster.length >= WALL_CLUSTER_MIN_COMMENTS) {
|
|
3551
|
+
for (const member of cluster) wallMembers.add(member);
|
|
3552
|
+
}
|
|
3553
|
+
cluster = [];
|
|
3554
|
+
}
|
|
3555
|
+
cluster.push(candidate);
|
|
3556
|
+
}
|
|
3557
|
+
if (cluster.length >= WALL_CLUSTER_MIN_COMMENTS) {
|
|
3558
|
+
for (const member of cluster) wallMembers.add(member);
|
|
3559
|
+
}
|
|
3417
3560
|
for (let i = 0; i < comments.length; i++) {
|
|
3418
3561
|
const comment = comments[i];
|
|
3419
3562
|
if (comment === void 0 || comment.type !== "Line") continue;
|
|
3563
|
+
if (wallMembers.has(comment)) continue;
|
|
3420
3564
|
if (!isStandalone(comment)) continue;
|
|
3421
3565
|
if (areAdjacentLineComments2(comments[i - 1], comment) || areAdjacentLineComments2(comment, comments[i + 1])) {
|
|
3422
3566
|
continue;
|
|
@@ -9547,7 +9691,7 @@ var rules = {
|
|
|
9547
9691
|
};
|
|
9548
9692
|
var meta = {
|
|
9549
9693
|
name: "@sarj/eslint-plugin",
|
|
9550
|
-
version: "9.
|
|
9694
|
+
version: "9.6.0"
|
|
9551
9695
|
};
|
|
9552
9696
|
var recommendedRules = {
|
|
9553
9697
|
"@sarj/enforce-file-structure": "warn",
|