@sarj/eslint-plugin 9.4.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 +2 -1
package/dist/index.d.cts
CHANGED
|
@@ -99,7 +99,7 @@ declare const rules: {
|
|
|
99
99
|
"no-client-side-data-fetching": _typescript_eslint_utils_ts_eslint.RuleModule<"noClientFetch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
100
100
|
name: string;
|
|
101
101
|
};
|
|
102
|
-
"no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
102
|
+
"no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "commentWall" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
103
103
|
name: string;
|
|
104
104
|
};
|
|
105
105
|
"no-conditional-in-test": _typescript_eslint_utils_ts_eslint.RuleModule<"noConditionalInTest", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
@@ -397,7 +397,7 @@ type FlatPreset = {
|
|
|
397
397
|
declare const plugin: {
|
|
398
398
|
meta: {
|
|
399
399
|
readonly name: "@sarj/eslint-plugin";
|
|
400
|
-
readonly version: "9.
|
|
400
|
+
readonly version: "9.6.0";
|
|
401
401
|
};
|
|
402
402
|
rules: {
|
|
403
403
|
"enforce-file-structure": _typescript_eslint_utils_ts_eslint.RuleModule<"importsFirst" | "useServerDirective", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
@@ -409,7 +409,7 @@ declare const plugin: {
|
|
|
409
409
|
"no-client-side-data-fetching": _typescript_eslint_utils_ts_eslint.RuleModule<"noClientFetch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
410
410
|
name: string;
|
|
411
411
|
};
|
|
412
|
-
"no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
412
|
+
"no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "commentWall" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
413
413
|
name: string;
|
|
414
414
|
};
|
|
415
415
|
"no-conditional-in-test": _typescript_eslint_utils_ts_eslint.RuleModule<"noConditionalInTest", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
package/dist/index.d.ts
CHANGED
|
@@ -99,7 +99,7 @@ declare const rules: {
|
|
|
99
99
|
"no-client-side-data-fetching": _typescript_eslint_utils_ts_eslint.RuleModule<"noClientFetch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
100
100
|
name: string;
|
|
101
101
|
};
|
|
102
|
-
"no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
102
|
+
"no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "commentWall" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
103
103
|
name: string;
|
|
104
104
|
};
|
|
105
105
|
"no-conditional-in-test": _typescript_eslint_utils_ts_eslint.RuleModule<"noConditionalInTest", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
@@ -397,7 +397,7 @@ type FlatPreset = {
|
|
|
397
397
|
declare const plugin: {
|
|
398
398
|
meta: {
|
|
399
399
|
readonly name: "@sarj/eslint-plugin";
|
|
400
|
-
readonly version: "9.
|
|
400
|
+
readonly version: "9.6.0";
|
|
401
401
|
};
|
|
402
402
|
rules: {
|
|
403
403
|
"enforce-file-structure": _typescript_eslint_utils_ts_eslint.RuleModule<"importsFirst" | "useServerDirective", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
@@ -409,7 +409,7 @@ declare const plugin: {
|
|
|
409
409
|
"no-client-side-data-fetching": _typescript_eslint_utils_ts_eslint.RuleModule<"noClientFetch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
410
410
|
name: string;
|
|
411
411
|
};
|
|
412
|
-
"no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
412
|
+
"no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "commentWall" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
413
413
|
name: string;
|
|
414
414
|
};
|
|
415
415
|
"no-conditional-in-test": _typescript_eslint_utils_ts_eslint.RuleModule<"noConditionalInTest", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
package/dist/index.js
CHANGED
|
@@ -492,6 +492,14 @@ function restatesStatementHead(body, statement) {
|
|
|
492
492
|
|
|
493
493
|
// src/rules/no-comment-cruft.ts
|
|
494
494
|
var LEADING_PREAMBLE_MIN = 4;
|
|
495
|
+
var WALL_MIN_STATEMENTS = 4;
|
|
496
|
+
var WALL_MIN_COMMENTS = 3;
|
|
497
|
+
var WALL_MIN_COMMENTED_RATIO = 0.6;
|
|
498
|
+
var WALL_MIN_WEAK_RATIO = 0.75;
|
|
499
|
+
var WALL_MAX_WORDS = 18;
|
|
500
|
+
var WALL_MAX_NOVEL_WORDS = 2;
|
|
501
|
+
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;
|
|
502
|
+
var WALL_STEP_PREFIX_RE = /^(?:\d+[.)]|(?:phase|step)\s+\d+\s*:)\s*/i;
|
|
495
503
|
var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|after(?:wards| that)?|finally|lastly|now)\s*[,:]\s*\S/i;
|
|
496
504
|
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;
|
|
497
505
|
var FOR_NOW_RE = /\bfor now\b/i;
|
|
@@ -704,6 +712,63 @@ var STATEMENT_CONTAINERS = /* @__PURE__ */ new Set([
|
|
|
704
712
|
AST_NODE_TYPES5.TSModuleBlock,
|
|
705
713
|
AST_NODE_TYPES5.TSInterfaceBody
|
|
706
714
|
]);
|
|
715
|
+
function directStatements(container) {
|
|
716
|
+
switch (container.type) {
|
|
717
|
+
case AST_NODE_TYPES5.Program:
|
|
718
|
+
case AST_NODE_TYPES5.BlockStatement:
|
|
719
|
+
case AST_NODE_TYPES5.ClassBody:
|
|
720
|
+
case AST_NODE_TYPES5.StaticBlock:
|
|
721
|
+
case AST_NODE_TYPES5.TSModuleBlock:
|
|
722
|
+
return container.body;
|
|
723
|
+
case AST_NODE_TYPES5.SwitchCase:
|
|
724
|
+
return container.consequent;
|
|
725
|
+
case AST_NODE_TYPES5.TSInterfaceBody:
|
|
726
|
+
return container.body;
|
|
727
|
+
default:
|
|
728
|
+
return [];
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
var WALL_STATEMENTS = /* @__PURE__ */ new Set([
|
|
732
|
+
AST_NODE_TYPES5.ExpressionStatement,
|
|
733
|
+
AST_NODE_TYPES5.ReturnStatement,
|
|
734
|
+
AST_NODE_TYPES5.ThrowStatement,
|
|
735
|
+
AST_NODE_TYPES5.VariableDeclaration,
|
|
736
|
+
AST_NODE_TYPES5.IfStatement,
|
|
737
|
+
AST_NODE_TYPES5.ForStatement,
|
|
738
|
+
AST_NODE_TYPES5.ForOfStatement,
|
|
739
|
+
AST_NODE_TYPES5.ForInStatement,
|
|
740
|
+
AST_NODE_TYPES5.WhileStatement,
|
|
741
|
+
AST_NODE_TYPES5.DoWhileStatement,
|
|
742
|
+
AST_NODE_TYPES5.SwitchStatement,
|
|
743
|
+
AST_NODE_TYPES5.TryStatement
|
|
744
|
+
]);
|
|
745
|
+
function statementAttachmentBelow(comment, sourceCode) {
|
|
746
|
+
const token = sourceCode.getTokenAfter(comment, { includeComments: false });
|
|
747
|
+
if (token === null || token.loc.start.line !== comment.loc.end.line + 1 || token.loc.start.column !== comment.loc.start.column) {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
for (let node = sourceCode.getNodeByRangeIndex(token.range[0]); node?.parent != null; node = node.parent) {
|
|
751
|
+
if (!STATEMENT_CONTAINERS.has(node.parent.type) || !WALL_STATEMENTS.has(node.type)) {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const siblings = directStatements(node.parent);
|
|
755
|
+
const index = siblings.indexOf(node);
|
|
756
|
+
return index < 0 ? null : { container: node.parent, index, statement: sourceCode.getText(node) };
|
|
757
|
+
}
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
function isWeakWalkthroughComment(body, statement) {
|
|
761
|
+
const normalized = body.replace(WALL_STEP_PREFIX_RE, "");
|
|
762
|
+
if (normalized.length === 0 || normalized.endsWith("?") || normalized.split(/\s+/).length > WALL_MAX_WORDS || isDirective(normalized) || isProtected(normalized) || !WALL_NARRATION_RE.test(normalized)) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
const words = contentTokens(normalized);
|
|
766
|
+
const described = words.slice(1);
|
|
767
|
+
if (described.length === 0) return false;
|
|
768
|
+
const code = codeTokens(statement);
|
|
769
|
+
const matched = described.filter((word) => restates([word], code)).length;
|
|
770
|
+
return matched / described.length >= 0.5 && described.length - matched <= WALL_MAX_NOVEL_WORDS;
|
|
771
|
+
}
|
|
707
772
|
var TYPE_MEMBER_CONTAINERS = /* @__PURE__ */ new Set([
|
|
708
773
|
AST_NODE_TYPES5.TSInterfaceBody,
|
|
709
774
|
AST_NODE_TYPES5.TSTypeLiteral
|
|
@@ -759,6 +824,7 @@ var no_comment_cruft_default = createRule({
|
|
|
759
824
|
commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
|
|
760
825
|
sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
|
|
761
826
|
fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines.",
|
|
827
|
+
commentWall: "Statement comment wall ({{count}} narrated steps) \u2014 delete the walkthrough and name the operations in code; keep only constraints or rationale.",
|
|
762
828
|
redundantNarration: "Comment narrates the code \u2014 delete it or say *why*, not *what*. Code is self-documenting.",
|
|
763
829
|
untrackedTodo: "Untracked TODO/FIXME marker \u2014 add an issue ticket or context link."
|
|
764
830
|
}
|
|
@@ -778,6 +844,48 @@ var no_comment_cruft_default = createRule({
|
|
|
778
844
|
function isJsDoc(comment) {
|
|
779
845
|
return comment.type === "Block" && /^\*/.test(comment.value);
|
|
780
846
|
}
|
|
847
|
+
function findCommentWalls(comments) {
|
|
848
|
+
const attached = /* @__PURE__ */ new Map();
|
|
849
|
+
for (const comment of comments) {
|
|
850
|
+
if (comment.type !== "Line" || !isStandalone(comment)) continue;
|
|
851
|
+
const attachment = statementAttachmentBelow(comment, sourceCode);
|
|
852
|
+
if (attachment === null) continue;
|
|
853
|
+
const body = stripCommentMarker(comment.value);
|
|
854
|
+
const entries = attached.get(attachment.container) ?? [];
|
|
855
|
+
entries.push({
|
|
856
|
+
comment,
|
|
857
|
+
index: attachment.index,
|
|
858
|
+
weak: isWeakWalkthroughComment(body, attachment.statement)
|
|
859
|
+
});
|
|
860
|
+
attached.set(attachment.container, entries);
|
|
861
|
+
}
|
|
862
|
+
const walls = [];
|
|
863
|
+
for (const entries of attached.values()) {
|
|
864
|
+
const sorted = entries.toSorted((left, right) => left.index - right.index);
|
|
865
|
+
const clusters = [];
|
|
866
|
+
for (const entry of sorted) {
|
|
867
|
+
const cluster = clusters.at(-1);
|
|
868
|
+
if (cluster !== void 0 && entry.index <= (cluster.at(-1)?.index ?? 0) + 2) {
|
|
869
|
+
cluster.push(entry);
|
|
870
|
+
} else {
|
|
871
|
+
clusters.push([entry]);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
for (const cluster of clusters) {
|
|
875
|
+
const firstIndex = cluster[0]?.index;
|
|
876
|
+
const lastIndex = cluster.at(-1)?.index;
|
|
877
|
+
if (firstIndex === void 0 || lastIndex === void 0) continue;
|
|
878
|
+
const span = lastIndex - firstIndex + 1;
|
|
879
|
+
const weak = cluster.filter((entry) => entry.weak).map((entry) => entry.comment);
|
|
880
|
+
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) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
const leader = weak[0];
|
|
884
|
+
if (leader !== void 0) walls.push({ leader, members: new Set(weak) });
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return walls;
|
|
888
|
+
}
|
|
781
889
|
function isSectionJsDoc(comment) {
|
|
782
890
|
const texts = comment.value.split("\n").map(stripCommentMarker).filter((line) => line.length > 0 && !isDirective(line));
|
|
783
891
|
return texts.length > 0 && texts.every(
|
|
@@ -810,6 +918,9 @@ var no_comment_cruft_default = createRule({
|
|
|
810
918
|
return {
|
|
811
919
|
Program() {
|
|
812
920
|
const comments = sourceCode.getAllComments();
|
|
921
|
+
const walls = findCommentWalls(comments);
|
|
922
|
+
const wallByLeader = new Map(walls.map((wall) => [wall.leader, wall]));
|
|
923
|
+
const wallMembers = new Set(walls.flatMap((wall) => [...wall.members]));
|
|
813
924
|
const firstCodeLine = sourceCode.ast.tokens[0]?.loc.start.line ?? Number.MAX_SAFE_INTEGER;
|
|
814
925
|
const enumerated = comments.filter(
|
|
815
926
|
(c) => c.type === "Line" && ENUMERATION_RE.test(stripCommentMarker(c.value))
|
|
@@ -817,6 +928,16 @@ var no_comment_cruft_default = createRule({
|
|
|
817
928
|
for (let i = 0; i < comments.length; i++) {
|
|
818
929
|
const comment = comments[i];
|
|
819
930
|
if (comment === void 0) continue;
|
|
931
|
+
const wall = wallByLeader.get(comment);
|
|
932
|
+
if (wall !== void 0) {
|
|
933
|
+
context.report({
|
|
934
|
+
node: comment,
|
|
935
|
+
messageId: "commentWall",
|
|
936
|
+
data: { count: String(wall.members.size) }
|
|
937
|
+
});
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (wallMembers.has(comment)) continue;
|
|
820
941
|
if (isJsDoc(comment)) {
|
|
821
942
|
if (isStandalone(comment) && isSectionJsDoc(comment)) {
|
|
822
943
|
context.report({ node: comment, messageId: "sectionBanner" });
|
|
@@ -3327,6 +3448,9 @@ var EMPHASIS_RE = /\*\w[^*]*\*|`[^`]+`/;
|
|
|
3327
3448
|
var NEGATION_WORD_RE = /\b(?:no|not|never|neither|nor|without|none|non)\b/i;
|
|
3328
3449
|
var ACTION_STMT_RE = /[\w.$\])]\s*\(|^\s*(?:return|throw|await|yield)\b/;
|
|
3329
3450
|
var NON_ASCII_LETTER_RE = /[^\p{ASCII}\p{N}\p{P}\p{Z}]/u;
|
|
3451
|
+
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;
|
|
3452
|
+
var WALL_CLUSTER_MAX_LINE_GAP = 8;
|
|
3453
|
+
var WALL_CLUSTER_MIN_COMMENTS = 3;
|
|
3330
3454
|
function areAdjacentLineComments2(a, b) {
|
|
3331
3455
|
return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
|
|
3332
3456
|
}
|
|
@@ -3373,9 +3497,29 @@ var no_restated_comment_default = createRule({
|
|
|
3373
3497
|
return {
|
|
3374
3498
|
Program() {
|
|
3375
3499
|
const comments = sourceCode.getAllComments();
|
|
3500
|
+
const wallMembers = /* @__PURE__ */ new Set();
|
|
3501
|
+
let cluster = [];
|
|
3502
|
+
for (const candidate of comments) {
|
|
3503
|
+
const body = candidate.value.replace(/^\/*/, "").trim();
|
|
3504
|
+
if (candidate.type !== "Line" || !isStandalone(candidate) || isProtected(body) || !WALL_NARRATION_RE2.test(body)) {
|
|
3505
|
+
continue;
|
|
3506
|
+
}
|
|
3507
|
+
const previous = cluster.at(-1);
|
|
3508
|
+
if (previous !== void 0 && candidate.loc.start.line - previous.loc.start.line > WALL_CLUSTER_MAX_LINE_GAP) {
|
|
3509
|
+
if (cluster.length >= WALL_CLUSTER_MIN_COMMENTS) {
|
|
3510
|
+
for (const member of cluster) wallMembers.add(member);
|
|
3511
|
+
}
|
|
3512
|
+
cluster = [];
|
|
3513
|
+
}
|
|
3514
|
+
cluster.push(candidate);
|
|
3515
|
+
}
|
|
3516
|
+
if (cluster.length >= WALL_CLUSTER_MIN_COMMENTS) {
|
|
3517
|
+
for (const member of cluster) wallMembers.add(member);
|
|
3518
|
+
}
|
|
3376
3519
|
for (let i = 0; i < comments.length; i++) {
|
|
3377
3520
|
const comment = comments[i];
|
|
3378
3521
|
if (comment === void 0 || comment.type !== "Line") continue;
|
|
3522
|
+
if (wallMembers.has(comment)) continue;
|
|
3379
3523
|
if (!isStandalone(comment)) continue;
|
|
3380
3524
|
if (areAdjacentLineComments2(comments[i - 1], comment) || areAdjacentLineComments2(comment, comments[i + 1])) {
|
|
3381
3525
|
continue;
|
|
@@ -9512,7 +9656,7 @@ var rules = {
|
|
|
9512
9656
|
};
|
|
9513
9657
|
var meta = {
|
|
9514
9658
|
name: "@sarj/eslint-plugin",
|
|
9515
|
-
version: "9.
|
|
9659
|
+
version: "9.6.0"
|
|
9516
9660
|
};
|
|
9517
9661
|
var recommendedRules = {
|
|
9518
9662
|
"@sarj/enforce-file-structure": "warn",
|