eslint-plugin-traceability 1.15.0 → 1.16.1
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/CHANGELOG.md +3 -3
- package/README.md +61 -13
- package/lib/src/index.js +59 -0
- package/lib/src/rules/helpers/require-story-core.js +1 -1
- package/lib/src/rules/helpers/require-story-helpers.d.ts +10 -3
- package/lib/src/rules/helpers/require-story-helpers.js +84 -7
- package/lib/src/rules/no-redundant-annotation.js +73 -55
- package/lib/src/rules/require-branch-annotation.js +2 -2
- package/lib/src/rules/require-req-annotation.js +2 -2
- package/lib/src/rules/require-story-annotation.js +8 -3
- package/lib/src/rules/require-traceability.js +8 -9
- package/lib/src/utils/annotation-checker.js +23 -4
- package/lib/tests/cli-error-handling.test.js +10 -1
- package/lib/tests/integration/cli-integration.test.js +5 -0
- package/lib/tests/integration/require-traceability-aliases.integration.test.js +126 -0
- package/lib/tests/plugin-default-export-and-configs.test.js +23 -0
- package/lib/tests/rules/auto-fix-behavior-008.test.js +7 -7
- package/lib/tests/rules/error-reporting.test.js +1 -1
- package/lib/tests/rules/no-redundant-annotation.test.js +20 -0
- package/lib/tests/rules/require-story-annotation.test.js +75 -10
- package/lib/tests/rules/require-story-helpers.test.js +224 -0
- package/lib/tests/rules/require-story-utils.test.d.ts +7 -0
- package/lib/tests/rules/require-story-utils.test.js +158 -0
- package/lib/tests/utils/annotation-checker-branches.test.d.ts +5 -0
- package/lib/tests/utils/annotation-checker-branches.test.js +103 -0
- package/lib/tests/utils/annotation-scope-analyzer.test.js +134 -0
- package/lib/tests/utils/branch-annotation-helpers.test.js +66 -0
- package/package.json +2 -2
- package/user-docs/api-reference.md +71 -15
- package/user-docs/examples.md +24 -13
- package/user-docs/migration-guide.md +127 -4
- package/user-docs/traceability-overview.md +116 -0
- package/lib/tests/integration/dogfooding-validation.test.js +0 -129
- /package/lib/tests/integration/{dogfooding-validation.test.d.ts → require-traceability-aliases.integration.test.d.ts} +0 -0
|
@@ -6,6 +6,12 @@ describe("annotation-scope-analyzer helpers (Story 027.0-DEV-REDUNDANT-ANNOTATIO
|
|
|
6
6
|
const key = (0, annotation_scope_analyzer_1.toStoryReqKey)("docs/stories/001.story.md", "REQ-ONE");
|
|
7
7
|
expect(key).toBe("docs/stories/001.story.md|REQ-ONE");
|
|
8
8
|
});
|
|
9
|
+
it("[REQ-DUPLICATION-DETECTION] normalizes missing story or requirement to empty segments", () => {
|
|
10
|
+
const noStory = (0, annotation_scope_analyzer_1.toStoryReqKey)(null, "REQ-ONE");
|
|
11
|
+
const noReq = (0, annotation_scope_analyzer_1.toStoryReqKey)("docs/stories/001.story.md", undefined);
|
|
12
|
+
expect(noStory).toBe("|REQ-ONE");
|
|
13
|
+
expect(noReq).toBe("docs/stories/001.story.md|");
|
|
14
|
+
});
|
|
9
15
|
it("[REQ-DUPLICATION-DETECTION] extracts pairs from @story/@req sequences", () => {
|
|
10
16
|
const text = `// @story docs/stories/001.story.md\n// @req REQ-ONE`;
|
|
11
17
|
const pairs = (0, annotation_scope_analyzer_1.extractStoryReqPairsFromText)(text);
|
|
@@ -13,6 +19,10 @@ describe("annotation-scope-analyzer helpers (Story 027.0-DEV-REDUNDANT-ANNOTATIO
|
|
|
13
19
|
"docs/stories/001.story.md|REQ-ONE",
|
|
14
20
|
]);
|
|
15
21
|
});
|
|
22
|
+
it("[REQ-DUPLICATION-DETECTION] returns empty set when text has no annotations", () => {
|
|
23
|
+
const pairs = (0, annotation_scope_analyzer_1.extractStoryReqPairsFromText)("");
|
|
24
|
+
expect(pairs.size).toBe(0);
|
|
25
|
+
});
|
|
16
26
|
it("[REQ-SCOPE-ANALYSIS] extracts pairs from @supports lines", () => {
|
|
17
27
|
const text = `// @supports docs/stories/002.story.md REQ-A REQ-B OTHER`;
|
|
18
28
|
const pairs = (0, annotation_scope_analyzer_1.extractStoryReqPairsFromText)(text);
|
|
@@ -27,6 +37,10 @@ describe("annotation-scope-analyzer helpers (Story 027.0-DEV-REDUNDANT-ANNOTATIO
|
|
|
27
37
|
const pairs = (0, annotation_scope_analyzer_1.extractStoryReqPairsFromComments)(comments);
|
|
28
38
|
expect(pairs.size).toBe(2);
|
|
29
39
|
});
|
|
40
|
+
it("[REQ-DUPLICATION-DETECTION] returns empty set for empty comments list", () => {
|
|
41
|
+
const pairs = (0, annotation_scope_analyzer_1.extractStoryReqPairsFromComments)([]);
|
|
42
|
+
expect(pairs.size).toBe(0);
|
|
43
|
+
});
|
|
30
44
|
it("[REQ-DUPLICATION-DETECTION] determines full coverage correctly", () => {
|
|
31
45
|
const parent = new Set([
|
|
32
46
|
"story|REQ-ONE",
|
|
@@ -37,6 +51,11 @@ describe("annotation-scope-analyzer helpers (Story 027.0-DEV-REDUNDANT-ANNOTATIO
|
|
|
37
51
|
expect((0, annotation_scope_analyzer_1.arePairsFullyCovered)(childCovered, parent)).toBe(true);
|
|
38
52
|
expect((0, annotation_scope_analyzer_1.arePairsFullyCovered)(childNotCovered, parent)).toBe(false);
|
|
39
53
|
});
|
|
54
|
+
it("[REQ-DUPLICATION-DETECTION] treats empty child or parent as not covered", () => {
|
|
55
|
+
const nonEmpty = new Set(["story|REQ-ONE"]);
|
|
56
|
+
expect((0, annotation_scope_analyzer_1.arePairsFullyCovered)(new Set(), nonEmpty)).toBe(false);
|
|
57
|
+
expect((0, annotation_scope_analyzer_1.arePairsFullyCovered)(nonEmpty, new Set())).toBe(false);
|
|
58
|
+
});
|
|
40
59
|
it("[REQ-STATEMENT-SIGNIFICANCE] respects alwaysCovered and strictness levels", () => {
|
|
41
60
|
const base = {
|
|
42
61
|
strictness: "moderate",
|
|
@@ -49,6 +68,39 @@ describe("annotation-scope-analyzer helpers (Story 027.0-DEV-REDUNDANT-ANNOTATIO
|
|
|
49
68
|
expect((0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)({ type: "ExpressionStatement" }, base, branchTypes)).toBe(true);
|
|
50
69
|
expect((0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)({ type: "IfStatement" }, base, branchTypes)).toBe(false);
|
|
51
70
|
});
|
|
71
|
+
it("[REQ-CONFIGURABLE-STRICTNESS] treats permissive mode as only honoring alwaysCovered list", () => {
|
|
72
|
+
const options = {
|
|
73
|
+
strictness: "permissive",
|
|
74
|
+
allowEmphasisDuplication: false,
|
|
75
|
+
maxScopeDepth: 3,
|
|
76
|
+
alwaysCovered: ["ReturnStatement"],
|
|
77
|
+
};
|
|
78
|
+
const branchTypes = ["IfStatement"];
|
|
79
|
+
expect((0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)({ type: "ReturnStatement" }, options, branchTypes)).toBe(true);
|
|
80
|
+
expect((0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)({ type: "ExpressionStatement" }, options, branchTypes)).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
it("[REQ-CONFIGURABLE-STRICTNESS] treats strict mode as allowing any non-branch statement", () => {
|
|
83
|
+
const options = {
|
|
84
|
+
strictness: "strict",
|
|
85
|
+
allowEmphasisDuplication: false,
|
|
86
|
+
maxScopeDepth: 3,
|
|
87
|
+
alwaysCovered: [],
|
|
88
|
+
};
|
|
89
|
+
const branchTypes = ["IfStatement"];
|
|
90
|
+
expect((0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)({ type: "ExpressionStatement" }, options, branchTypes)).toBe(true);
|
|
91
|
+
expect((0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)({ type: "IfStatement" }, options, branchTypes)).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
it("[REQ-STATEMENT-SIGNIFICANCE] returns false for null or non-node values", () => {
|
|
94
|
+
const options = {
|
|
95
|
+
strictness: "moderate",
|
|
96
|
+
allowEmphasisDuplication: false,
|
|
97
|
+
maxScopeDepth: 3,
|
|
98
|
+
alwaysCovered: [],
|
|
99
|
+
};
|
|
100
|
+
const branchTypes = [];
|
|
101
|
+
expect((0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)(null, options, branchTypes)).toBe(false);
|
|
102
|
+
expect((0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)({}, options, branchTypes)).toBe(false);
|
|
103
|
+
});
|
|
52
104
|
it("[REQ-SAFE-REMOVAL] computes removal range for full-line comment", () => {
|
|
53
105
|
const source = `const x = 1;\n// @story docs/stories/001.story.md\nconst y = 2;\n`;
|
|
54
106
|
const sourceCode = {
|
|
@@ -63,6 +115,77 @@ describe("annotation-scope-analyzer helpers (Story 027.0-DEV-REDUNDANT-ANNOTATIO
|
|
|
63
115
|
const removed = source.slice(0, removalStart) + source.slice(removalEnd);
|
|
64
116
|
expect(removed).toBe("const x = 1;\nconst y = 2;\n");
|
|
65
117
|
});
|
|
118
|
+
it("[REQ-SAFE-REMOVAL] computes removal range for full-line comment with Windows newlines", () => {
|
|
119
|
+
const source = "const x = 1;\r\n// @story docs/stories/001.story.md\r\nconst y = 2;\r\n";
|
|
120
|
+
const sourceCode = {
|
|
121
|
+
getText() {
|
|
122
|
+
return source;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
const start = source.indexOf("// @story");
|
|
126
|
+
const end = start + "// @story docs/stories/001.story.md".length;
|
|
127
|
+
const comment = { range: [start, end] };
|
|
128
|
+
const [removalStart, removalEnd] = (0, annotation_scope_analyzer_1.getCommentRemovalRange)(comment, sourceCode);
|
|
129
|
+
const removed = source.slice(0, removalStart) + source.slice(removalEnd);
|
|
130
|
+
expect(removed).toBe("const x = 1;\r\nconst y = 2;\r\n");
|
|
131
|
+
});
|
|
132
|
+
it("[REQ-SAFE-REMOVAL] computes removal range for full-line comment with standalone CR newline", () => {
|
|
133
|
+
const source = "const x = 1;\r// @story docs/stories/001.story.md\rconst y = 2;\r";
|
|
134
|
+
const sourceCode = {
|
|
135
|
+
getText() {
|
|
136
|
+
return source;
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
const start = source.indexOf("// @story");
|
|
140
|
+
const end = start + "// @story docs/stories/001.story.md".length;
|
|
141
|
+
const comment = { range: [start, end] };
|
|
142
|
+
const [removalStart, removalEnd] = (0, annotation_scope_analyzer_1.getCommentRemovalRange)(comment, sourceCode);
|
|
143
|
+
const removed = source.slice(0, removalStart) + source.slice(removalEnd);
|
|
144
|
+
expect(removed).toBe("const x = 1;\rconst y = 2;\r");
|
|
145
|
+
});
|
|
146
|
+
it("[REQ-SAFE-REMOVAL] computes removal range for inline comment", () => {
|
|
147
|
+
const source = "const x = 1; // @story docs/stories/001.story.md\nconst y = 2;\n";
|
|
148
|
+
const sourceCode = {
|
|
149
|
+
getText() {
|
|
150
|
+
return source;
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
const start = source.indexOf("// @story");
|
|
154
|
+
const end = start + "// @story docs/stories/001.story.md".length;
|
|
155
|
+
const comment = { range: [start, end] };
|
|
156
|
+
const [removalStart, removalEnd] = (0, annotation_scope_analyzer_1.getCommentRemovalRange)(comment, sourceCode);
|
|
157
|
+
const removed = source.slice(0, removalStart) + source.slice(removalEnd);
|
|
158
|
+
expect(removed).toBe("const x = 1; \nconst y = 2;\n");
|
|
159
|
+
});
|
|
160
|
+
it("[REQ-SAFE-REMOVAL] consumes trailing spaces and tabs following a full-line comment", () => {
|
|
161
|
+
const source = "const x = 1;\n// @story docs/stories/001.story.md \nconst y = 2;\n";
|
|
162
|
+
const sourceCode = {
|
|
163
|
+
getText() {
|
|
164
|
+
return source;
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
const start = source.indexOf("// @story");
|
|
168
|
+
const end = start + "// @story docs/stories/001.story.md".length;
|
|
169
|
+
const comment = { range: [start, end] };
|
|
170
|
+
const [removalStart, removalEnd] = (0, annotation_scope_analyzer_1.getCommentRemovalRange)(comment, sourceCode);
|
|
171
|
+
const removed = source.slice(0, removalStart) + source.slice(removalEnd);
|
|
172
|
+
expect(removed).toBe("const x = 1;\nconst y = 2;\n");
|
|
173
|
+
});
|
|
174
|
+
it("[REQ-SAFE-REMOVAL] handles full-line comment at end of file without trailing newline", () => {
|
|
175
|
+
const source = "const x = 1;\n// @story docs/stories/001.story.md";
|
|
176
|
+
const sourceCode = {
|
|
177
|
+
getText() {
|
|
178
|
+
return source;
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
const start = source.indexOf("// @story");
|
|
182
|
+
const end = start + "// @story docs/stories/001.story.md".length;
|
|
183
|
+
const comment = { range: [start, end] };
|
|
184
|
+
const [removalStart, removalEnd] = (0, annotation_scope_analyzer_1.getCommentRemovalRange)(comment, sourceCode);
|
|
185
|
+
const removed = source.slice(0, removalStart) + source.slice(removalEnd);
|
|
186
|
+
expect(removed).toBe("const x = 1;\n");
|
|
187
|
+
expect(removalEnd).toBe(source.length);
|
|
188
|
+
});
|
|
66
189
|
it("[REQ-SAFE-REMOVAL] returns [0, 0] for comments with invalid range length (EXPECTS EXPECTED_RANGE_LENGTH usage)", () => {
|
|
67
190
|
const source = "const x = 1;";
|
|
68
191
|
const sourceCode = {
|
|
@@ -74,4 +197,15 @@ describe("annotation-scope-analyzer helpers (Story 027.0-DEV-REDUNDANT-ANNOTATIO
|
|
|
74
197
|
const range = (0, annotation_scope_analyzer_1.getCommentRemovalRange)(comment, sourceCode);
|
|
75
198
|
expect(range).toEqual([0, 0]);
|
|
76
199
|
});
|
|
200
|
+
it("[REQ-SAFE-REMOVAL] returns [0, 0] when comment range is not an array", () => {
|
|
201
|
+
const source = "const x = 1;";
|
|
202
|
+
const sourceCode = {
|
|
203
|
+
getText() {
|
|
204
|
+
return source;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
const comment = { range: null };
|
|
208
|
+
const range = (0, annotation_scope_analyzer_1.getCommentRemovalRange)(comment, sourceCode);
|
|
209
|
+
expect(range).toEqual([0, 0]);
|
|
210
|
+
});
|
|
77
211
|
});
|
|
@@ -44,4 +44,70 @@ describe("validateBranchTypes helper (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () =
|
|
|
44
44
|
}));
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
|
+
it("should gather SwitchCase comment text via gatherBranchCommentText (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () => {
|
|
48
|
+
// Fake SourceCode-like object with lines aligned to PRE_COMMENT_OFFSET logic
|
|
49
|
+
const sourceCode = {
|
|
50
|
+
lines: [
|
|
51
|
+
"// @story first part",
|
|
52
|
+
"// continuation second part",
|
|
53
|
+
"case 1:",
|
|
54
|
+
],
|
|
55
|
+
getCommentsBefore: () => [],
|
|
56
|
+
getText: jest.fn(),
|
|
57
|
+
};
|
|
58
|
+
// SwitchCase-like node with loc.start.line corresponding to "case 1:" line (line 3)
|
|
59
|
+
const switchCaseNode = {
|
|
60
|
+
type: "SwitchCase",
|
|
61
|
+
loc: {
|
|
62
|
+
start: { line: 3, column: 0 },
|
|
63
|
+
end: { line: 3, column: 7 },
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, switchCaseNode);
|
|
67
|
+
// Expect combined text using space separator and preserving leading //
|
|
68
|
+
expect(text).toBe("// @story first part // continuation second part");
|
|
69
|
+
});
|
|
70
|
+
it("should gather comment text for CatchClause and loop nodes via gatherBranchCommentText (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () => {
|
|
71
|
+
// CatchClause: comments come from getCommentsBefore when beforeText already contains @story
|
|
72
|
+
const catchComments = [
|
|
73
|
+
{ type: "Line", value: "@story catch branch story" },
|
|
74
|
+
{ type: "Line", value: "additional info" },
|
|
75
|
+
];
|
|
76
|
+
const sourceCodeCatch = {
|
|
77
|
+
getCommentsBefore: jest.fn().mockReturnValue(catchComments),
|
|
78
|
+
getText: jest.fn().mockReturnValue("@story existing beforeText"),
|
|
79
|
+
lines: [],
|
|
80
|
+
};
|
|
81
|
+
const catchNode = {
|
|
82
|
+
type: "CatchClause",
|
|
83
|
+
loc: {
|
|
84
|
+
start: { line: 10, column: 0 },
|
|
85
|
+
end: { line: 12, column: 1 },
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const catchText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCodeCatch, catchNode);
|
|
89
|
+
expect(sourceCodeCatch.getCommentsBefore).toHaveBeenCalledWith(catchNode);
|
|
90
|
+
expect(catchText).toContain("@story catch branch story");
|
|
91
|
+
expect(catchText).toContain("additional info");
|
|
92
|
+
// Loop node: ForStatement currently uses beforeComments.map(extractCommentValue).join(" ")
|
|
93
|
+
const loopComments = [
|
|
94
|
+
{ type: "Line", value: "@story loop branch story" },
|
|
95
|
+
{ type: "Block", value: "loop details" },
|
|
96
|
+
];
|
|
97
|
+
const sourceCodeLoop = {
|
|
98
|
+
getCommentsBefore: jest.fn().mockReturnValue(loopComments),
|
|
99
|
+
getText: jest.fn().mockReturnValue("@story loop beforeText"),
|
|
100
|
+
lines: [],
|
|
101
|
+
};
|
|
102
|
+
const forNode = {
|
|
103
|
+
type: "ForStatement",
|
|
104
|
+
loc: {
|
|
105
|
+
start: { line: 20, column: 0 },
|
|
106
|
+
end: { line: 25, column: 1 },
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
const loopText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCodeLoop, forNode);
|
|
110
|
+
expect(sourceCodeLoop.getCommentsBefore).toHaveBeenCalledWith(forNode);
|
|
111
|
+
expect(loopText).toBe("@story loop branch story loop details");
|
|
112
|
+
});
|
|
47
113
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.1",
|
|
4
4
|
"description": "A customizable ESLint plugin that enforces traceability annotations in your code, ensuring each implementation is linked to its requirement or test case.",
|
|
5
5
|
"main": "lib/src/index.js",
|
|
6
6
|
"types": "lib/src/index.d.ts",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"lint-staged": "^16.2.7",
|
|
91
91
|
"prettier": "^3.6.2",
|
|
92
92
|
"semantic-release": "25.0.2",
|
|
93
|
-
"ts-jest": "^29.4.
|
|
93
|
+
"ts-jest": "^29.4.6",
|
|
94
94
|
"typescript": "^5.9.3",
|
|
95
95
|
"secretlint": "11.2.5",
|
|
96
96
|
"@secretlint/secretlint-rule-preset-recommend": "11.2.5"
|
|
@@ -11,19 +11,30 @@ Security and dependency hygiene for the published package are enforced by the sa
|
|
|
11
11
|
|
|
12
12
|
Each rule enforces traceability conventions in your code. Below is a summary of each rule exposed by this plugin.
|
|
13
13
|
|
|
14
|
+
For function-level traceability, new configurations should treat `traceability/require-traceability` as the **canonical** rule: it composes both story and requirement checks and understands both the newer `@supports` style and the legacy `@story` / `@req` pairing. The older keys `traceability/require-story-annotation` and `traceability/require-req-annotation` remain available as **backward-compatible aliases** so existing configs keep working, but they are no longer the primary entry points and are mainly useful when you need to tune severities independently. For new and multi-story scenarios, prefer `@supports` annotations; `@story` and `@req` remain valid and are still appropriate for simple single-story code paths where a consolidated `@supports` tag is not yet required.
|
|
15
|
+
|
|
14
16
|
In addition to the core `@story` and `@req` annotations, the plugin also understands `@supports` for code that fulfills requirements from multiple stories—for example, a consuming project might use a path like
|
|
15
17
|
`@supports docs/stories/010.0-PAYMENTS.story.md#REQ-PAYMENTS-REFUND`
|
|
16
18
|
to indicate that a given function supports a particular requirement from a payments story document within that project’s own `docs/stories` tree. For a detailed explanation of `@supports` behavior and validation, see [Migration Guide](migration-guide.md) (section **3.1 Multi-story @supports annotations**). Additional background on multi-story semantics is available in the project’s internal rule documentation, which is intended for maintainers rather than end users.
|
|
17
19
|
|
|
18
20
|
The `prefer-supports-annotation` rule is an **opt-in migration helper** that is disabled by default and **not** part of any built-in preset. It can be enabled and given a severity like `"warn"` or `"error"` using normal ESLint rule configuration when you want to gradually encourage multi-story `@supports` usage. The legacy rule key `traceability/prefer-implements-annotation` remains available as a **deprecated alias** for backward compatibility, but new configurations should prefer `traceability/prefer-supports-annotation`. Detailed behavior and migration guidance are documented in the project’s internal rule documentation, which is targeted for maintainers; typical end users can rely on the high-level guidance in this API reference and the [Migration Guide](migration-guide.md).
|
|
19
21
|
|
|
22
|
+
### Function-level rules overview
|
|
23
|
+
|
|
24
|
+
For function-level traceability, the plugin exposes a unified rule and two legacy keys:
|
|
25
|
+
|
|
26
|
+
- `traceability/require-traceability` is the **canonical function-level rule** for new configurations. It ensures functions and methods have both story coverage and requirement coverage, and it accepts either `@supports` (preferred) or legacy `@story` / `@req` annotations.
|
|
27
|
+
- `traceability/require-story-annotation` and `traceability/require-req-annotation` are **backward-compatible aliases** that focus on the story and requirement aspects separately. They are retained for existing configurations and share the same underlying implementation model as the unified rule, but new ESLint configs should normally rely on `traceability/require-traceability` rather than enabling these legacy keys directly.
|
|
28
|
+
|
|
29
|
+
All three rule keys can still be configured individually if you need fine-grained control (for example, to tune severities separately), but the recommended and strict presets enable `traceability/require-traceability` by default and keep the legacy keys primarily for projects that adopted them before the unified rule existed.
|
|
30
|
+
|
|
20
31
|
### traceability/require-traceability
|
|
21
32
|
|
|
22
33
|
Description: Unified function-level traceability rule that composes the behavior of `traceability/require-story-annotation` and `traceability/require-req-annotation`. When enabled, it enforces that in‑scope functions and methods carry both a story reference (`@story` or an equivalent `@supports` tag) and at least one requirement reference (`@req` or, when configured, `@supports`). The recommended flat‑config presets in this plugin enable `traceability/require-traceability` by default alongside the legacy rule keys for backward compatibility, so that existing configurations referring to `traceability/require-story-annotation` or `traceability/require-req-annotation` continue to work without change.
|
|
23
34
|
|
|
24
35
|
### traceability/require-story-annotation
|
|
25
36
|
|
|
26
|
-
Description: Ensures every function declaration has a
|
|
37
|
+
Description: **Legacy function-level key:** This rule key is retained for backward compatibility and conceptually composes the same checks as `traceability/require-traceability`. New configurations should normally enable `traceability/require-traceability` instead and rely on this key only when you need to tune it independently. Ensures every function declaration has a traceability annotation, preferring `@supports` for story coverage while still accepting legacy `@story` annotations referencing the related user story. When you adopt multi-story `@supports` annotations, this rule also accepts `@supports` as an alternative way to prove story coverage, so either `@story` or at least one `@supports` tag will satisfy the presence check. When run with `--fix`, the rule inserts a single-line placeholder JSDoc `@story` annotation above missing functions, methods, TypeScript declare functions, and interface method signatures using a built-in template aligned with Story 008.0. This template is now configurable on a per-rule basis, and the rule exposes an explicit auto-fix toggle so you can choose between diagnostic-only behavior and automatic placeholder insertion. The default template remains aligned with Story 008.0, but you can now customize it per rule configuration and optionally disable auto-fix entirely when you only want diagnostics without edits.
|
|
27
38
|
|
|
28
39
|
Options:
|
|
29
40
|
|
|
@@ -32,6 +43,7 @@ Options:
|
|
|
32
43
|
- `annotationTemplate` (string, optional) – Overrides the default placeholder JSDoc used when inserting missing `@story` annotations for functions and non-method constructs. When omitted or blank, the built-in template from Story 008.0 is used.
|
|
33
44
|
- `methodAnnotationTemplate` (string, optional) – Overrides the default placeholder JSDoc used when inserting missing `@story` annotations for class methods and TypeScript method signatures. When omitted or blank, falls back to `annotationTemplate` if provided, otherwise the built-in template.
|
|
34
45
|
- `autoFix` (boolean, optional) – When set to `false`, disables all automatic fix behavior for this rule while retaining its suggestions and diagnostics. When omitted or `true`, the rule behaves as before, inserting placeholder annotations in `--fix` mode.
|
|
46
|
+
- `excludeTestCallbacks` (boolean, optional) – When `true` (default), excludes anonymous arrow functions that are direct callbacks to common test framework functions (for example, Jest/Mocha/Vitest `describe`/`it`/`test`/`beforeEach`/`afterEach`/`beforeAll`/`afterAll`, plus focused/skipped/concurrent variants such as `fdescribe`, `xdescribe`, `fit`, `xit`, `test.concurrent`, `describe.concurrent`) from function-level annotation requirements. This assumes those test files are already covered by file-level `@supports` annotations and `traceability/require-test-traceability`. When set to `false`, these callbacks are treated like any other arrow function and must be annotated when in-scope.
|
|
35
47
|
|
|
36
48
|
Default Severity: `error`
|
|
37
49
|
Example:
|
|
@@ -46,9 +58,11 @@ function initAuth() {
|
|
|
46
58
|
}
|
|
47
59
|
```
|
|
48
60
|
|
|
61
|
+
Among the supported scopes, anonymous callbacks passed directly to common test framework functions are excluded from annotation requirements by default via `excludeTestCallbacks`; projects that prefer stricter enforcement for these callbacks can disable this exclusion by setting `excludeTestCallbacks: false` in their rule configuration.
|
|
62
|
+
|
|
49
63
|
### traceability/require-req-annotation
|
|
50
64
|
|
|
51
|
-
Description: Ensures that function-like constructs consistently declare their linked
|
|
65
|
+
Description: **Legacy function-level key:** This rule key is retained for backward compatibility and conceptually composes the same checks as `traceability/require-traceability`. New configurations should normally enable `traceability/require-traceability` instead and rely on this key only when you need to tune it independently. Ensures that function-like constructs consistently declare their linked requirements via traceability annotations, preferring `@supports` when possible while still accepting `@req`. The rule targets the same function-like node types as `traceability/require-story-annotation` (standard function declarations, non-arrow function expressions used as callbacks or assignments, class/object methods, TypeScript declare functions, and interface method signatures), and enforces that each of them has at least one `@req` tag in the nearest associated JSDoc comment. When you adopt multi-story `@supports` annotations, this rule also treats `@supports story-path REQ-ID...` tags as satisfying the requirement coverage check, although deep verification of requirement IDs continues to be handled by `traceability/valid-req-reference`. Arrow functions (`ArrowFunctionExpression`) are not currently checked by this rule.
|
|
52
66
|
|
|
53
67
|
This rule is typically used alongside `traceability/require-story-annotation` so that each function-level traceability block includes both an `@story` and an `@req` annotation, but it can also be enabled independently if you only want to enforce requirement linkage. Unlike `traceability/require-story-annotation`, this rule does not currently provide an auto-fix mode for inserting placeholder `@req` annotations; it only reports missing or malformed requirement annotations on the configured scopes.
|
|
54
68
|
|
|
@@ -72,7 +86,7 @@ function initAuth() {
|
|
|
72
86
|
|
|
73
87
|
### traceability/require-branch-annotation
|
|
74
88
|
|
|
75
|
-
Description: Ensures significant code branches (if/else chains, loops, switch cases, try/catch) have
|
|
89
|
+
Description: Ensures significant code branches (if/else chains, loops, switch cases, try/catch) have traceability coverage, typically via a single `@supports` line, while still accepting legacy `@story` and `@req` annotations in nearby comments. When you adopt multi-story `@supports` annotations, a single `@supports <storyPath> <REQ-ID>...` line placed in any of the valid branch comment locations is treated as satisfying both the story and requirement presence checks for that branch, while detailed format validation of the `@supports` value (including story paths and requirement IDs) continues to be handled by `traceability/valid-annotation-format`, `traceability/valid-story-reference`, and `traceability/valid-req-reference`.
|
|
76
90
|
|
|
77
91
|
For most branches, the rule looks for annotations in comments immediately preceding the branch keyword (for example, the line above an `if` or `for` statement). For `catch` clauses and `else if` branches, the rule is formatter-aware and accepts annotations in additional positions that common formatters like Prettier use when they reflow code.
|
|
78
92
|
|
|
@@ -112,7 +126,7 @@ if (error) {
|
|
|
112
126
|
|
|
113
127
|
### traceability/valid-annotation-format
|
|
114
128
|
|
|
115
|
-
Description: Validates that all traceability annotations (`@story
|
|
129
|
+
Description: Validates that all traceability annotations (`@supports` as the preferred form for new code, plus legacy `@story` and `@req`) follow the correct JSDoc or inline comment format. When run with `--fix`, the rule limits changes to safe `@story` path suffix normalization only—for example, adding `.md` when the path ends with `.story`, or adding `.story.md` when the base path has no extension—using targeted replacements implemented in the `getFixedStoryPath` and `reportInvalidStoryFormatWithFix` helpers. It does not change directories, infer new story names, or modify any surrounding comment text or whitespace, in line with Story 008.0. Selective disabling of this suffix-normalization auto-fix behavior is available via the `autoFix` option, which defaults to `true` for backward compatibility.
|
|
116
130
|
|
|
117
131
|
Options:
|
|
118
132
|
|
|
@@ -150,21 +164,34 @@ The `valid-annotation-format` rule is intentionally **backward compatible** with
|
|
|
150
164
|
Deep requirement checking for both `@req` and `@supports` is handled by the `valid-req-reference` rule in the plugin's internal docs. Advanced edge cases and internal semantics are mainly of interest to maintainers; typical end users can rely on the options and examples in this API reference when configuring the rule for their projects.
|
|
151
165
|
|
|
152
166
|
Default Severity: `error`
|
|
153
|
-
|
|
167
|
+
|
|
168
|
+
Primary example (recommended `@supports` style):
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
/**
|
|
172
|
+
* @supports docs/stories/005.0-DEV-VALIDATION.story.md#REQ-FORMAT-VALIDATION
|
|
173
|
+
*/
|
|
174
|
+
function example() {
|
|
175
|
+
// ...
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Legacy example (`@story` + `@req`, still valid but not preferred for new code):
|
|
154
180
|
|
|
155
181
|
```javascript
|
|
156
182
|
/**
|
|
157
183
|
* @story docs/stories/005.0-DEV-VALIDATION.story.md
|
|
158
184
|
* @req REQ-FORMAT-VALIDATION
|
|
159
185
|
*/
|
|
160
|
-
function
|
|
186
|
+
function legacyExample() {
|
|
161
187
|
// ...
|
|
162
188
|
}
|
|
163
189
|
```
|
|
164
190
|
|
|
165
191
|
### traceability/valid-story-reference
|
|
166
192
|
|
|
167
|
-
Description: Checks that the file paths
|
|
193
|
+
Description: Checks that the story file paths used by traceability annotations point to existing story markdown files in an `@supports`‑first world. Modern code typically encodes story paths in `@supports` tags (for example, `@supports docs/stories/010.0-PAYMENTS.story.md#REQ-ID`), but this rule continues to operate on the underlying `@story` values for file‑existence checks, keeping legacy annotations and mixed blocks fully supported.
|
|
194
|
+
|
|
168
195
|
Options:
|
|
169
196
|
Configure rule behavior using an options object with these properties:
|
|
170
197
|
|
|
@@ -190,31 +217,57 @@ Example configuration:
|
|
|
190
217
|
```
|
|
191
218
|
|
|
192
219
|
Default Severity: `error`
|
|
193
|
-
|
|
220
|
+
|
|
221
|
+
Primary example (recommended `@supports` style, with a companion `@story` used for existence checks):
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
/**
|
|
225
|
+
* @supports docs/stories/006.0-DEV-STORY-EXISTS.story.md#REQ-STORY-EXISTS
|
|
226
|
+
* @story docs/stories/006.0-DEV-STORY-EXISTS.story.md // used for file-existence validation; kept for backward compatibility
|
|
227
|
+
*/
|
|
228
|
+
function example() {
|
|
229
|
+
// ...
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Legacy-only example (`@story` + `@req`, still supported as an input to this rule):
|
|
194
234
|
|
|
195
235
|
```javascript
|
|
196
236
|
/**
|
|
197
237
|
* @story docs/stories/006.0-DEV-STORY-EXISTS.story.md
|
|
198
238
|
* @req REQ-STORY-EXISTS
|
|
199
239
|
*/
|
|
200
|
-
function
|
|
240
|
+
function legacyExample() {
|
|
201
241
|
// ...
|
|
202
242
|
}
|
|
203
243
|
```
|
|
204
244
|
|
|
205
245
|
### traceability/valid-req-reference
|
|
206
246
|
|
|
207
|
-
Description: Verifies that the IDs used in
|
|
247
|
+
Description: Verifies that the requirement IDs used in traceability annotations match known requirement identifiers, whether they appear in modern `@supports` tags or in legacy `@req` annotations.
|
|
248
|
+
|
|
208
249
|
Options: None
|
|
209
250
|
Default Severity: `error`
|
|
210
|
-
|
|
251
|
+
|
|
252
|
+
Primary example (recommended `@supports` style with requirement IDs):
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
/**
|
|
256
|
+
* @supports docs/stories/007.0-DEV-REQ-REFERENCE.story.md#REQ-VALID-REFERENCE REQ-VALID-REFERENCE-EDGE
|
|
257
|
+
*/
|
|
258
|
+
function example() {
|
|
259
|
+
// ...
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Legacy example (`@story` + `@req`, still valid for backward compatibility):
|
|
211
264
|
|
|
212
265
|
```javascript
|
|
213
266
|
/**
|
|
214
267
|
* @story docs/stories/007.0-DEV-REQ-REFERENCE.story.md
|
|
215
268
|
* @req REQ-VALID-REFERENCE
|
|
216
269
|
*/
|
|
217
|
-
function
|
|
270
|
+
function legacyExample() {
|
|
218
271
|
// ...
|
|
219
272
|
}
|
|
220
273
|
```
|
|
@@ -296,12 +349,14 @@ Behavior notes:
|
|
|
296
349
|
|
|
297
350
|
Default Severity: `warn`
|
|
298
351
|
|
|
299
|
-
This rule is
|
|
352
|
+
This rule is enabled at severity `warn` in both the `recommended` and `strict` presets. You can override its behavior in your own configuration — for example, by raising it to `error` for stricter enforcement, or by explicitly disabling it if you prefer to keep statement-level duplication.
|
|
353
|
+
|
|
354
|
+
Configuration example (override preset severity from `warn` to `error`):
|
|
300
355
|
|
|
301
356
|
```jsonc
|
|
302
357
|
{
|
|
303
358
|
"rules": {
|
|
304
|
-
"traceability/no-redundant-annotation": "
|
|
359
|
+
"traceability/no-redundant-annotation": "error",
|
|
305
360
|
},
|
|
306
361
|
}
|
|
307
362
|
```
|
|
@@ -327,7 +382,7 @@ Main behaviors:
|
|
|
327
382
|
|
|
328
383
|
```js
|
|
329
384
|
/**
|
|
330
|
-
* @supports docs/stories/010.0-PAYMENTS.story.md#REQ-PAYMENTS-REFUND REQ-PAYMENTS-EDGE
|
|
385
|
+
* @supports docs/stories/010.0-PAYMENTS.story.md#REQ-PAYMENTS-REFUND REQ-PAYMENTS-REFUND-EDGE
|
|
331
386
|
*/
|
|
332
387
|
```
|
|
333
388
|
|
|
@@ -432,6 +487,7 @@ Core rules enabled by the `recommended` preset:
|
|
|
432
487
|
- `traceability/valid-story-reference`: `error`
|
|
433
488
|
- `traceability/valid-req-reference`: `error`
|
|
434
489
|
- `traceability/require-test-traceability`: `error`
|
|
490
|
+
- `traceability/no-redundant-annotation`: `warn`
|
|
435
491
|
|
|
436
492
|
Usage:
|
|
437
493
|
|
package/user-docs/examples.md
CHANGED
|
@@ -43,7 +43,21 @@ npx eslint "src/**/*.js"
|
|
|
43
43
|
|
|
44
44
|
## 3. CLI Invocation Example
|
|
45
45
|
|
|
46
|
-
You can use the plugin without a config file by specifying rules inline:
|
|
46
|
+
You can use the plugin without a config file by specifying rules inline. The recommended approach for new setups is to use the unified `traceability/require-traceability` rule:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx eslint --no-eslintrc \
|
|
50
|
+
--rule "traceability/require-traceability:error" \
|
|
51
|
+
sample.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This unified function-level rule enforces both story and requirement coverage via `@supports` (preferred) or, for backward compatibility, via legacy `@story`/`@req` annotations.
|
|
55
|
+
|
|
56
|
+
Replace `sample.js` with your JavaScript or TypeScript file.
|
|
57
|
+
|
|
58
|
+
### 3.1 Legacy aliases (for existing configurations)
|
|
59
|
+
|
|
60
|
+
If you have older configurations that already refer to the legacy keys `traceability/require-story-annotation` and `traceability/require-req-annotation`, you can still enable them explicitly to avoid breaking those setups:
|
|
47
61
|
|
|
48
62
|
```bash
|
|
49
63
|
npx eslint --no-eslintrc \
|
|
@@ -53,9 +67,7 @@ npx eslint --no-eslintrc \
|
|
|
53
67
|
```
|
|
54
68
|
|
|
55
69
|
- `--no-eslintrc` tells ESLint to ignore user configs.
|
|
56
|
-
- `--rule` options enable the
|
|
57
|
-
|
|
58
|
-
Replace `sample.js` with your JavaScript or TypeScript file.
|
|
70
|
+
- `--rule` options enable either the unified rule (recommended for new configurations) or the legacy aliases when you must preserve older setups.
|
|
59
71
|
|
|
60
72
|
## 4. Linting a Specific Directory
|
|
61
73
|
|
|
@@ -103,6 +115,9 @@ describe("Story 021.0-DEV-TEST-TRACEABILITY", () => {
|
|
|
103
115
|
// Act
|
|
104
116
|
const result = performOperation(input);
|
|
105
117
|
|
|
118
|
+
// Assert
|
|
119
|
+
const result = performOperation(input);
|
|
120
|
+
|
|
106
121
|
// Assert
|
|
107
122
|
expect(result).toBe("edge-ok");
|
|
108
123
|
});
|
|
@@ -125,13 +140,11 @@ In this version, annotations are placed immediately before each significant bran
|
|
|
125
140
|
|
|
126
141
|
```ts
|
|
127
142
|
function pickCategory(score: number): string {
|
|
128
|
-
// @
|
|
129
|
-
// @req REQ-BRANCH-DETECTION
|
|
143
|
+
// @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-BRANCH-DETECTION
|
|
130
144
|
if (score >= 80) {
|
|
131
145
|
return "high";
|
|
132
146
|
}
|
|
133
|
-
// @
|
|
134
|
-
// @req REQ-DUAL-POSITION-DETECTION-ELSE-IF
|
|
147
|
+
// @supports docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION-ELSE-IF
|
|
135
148
|
else if (score >= 50) {
|
|
136
149
|
return "medium";
|
|
137
150
|
}
|
|
@@ -156,13 +169,11 @@ Prettier may reflow your `else if` line, wrap the condition, or move comments in
|
|
|
156
169
|
|
|
157
170
|
```ts
|
|
158
171
|
function pickCategory(score: number): string {
|
|
159
|
-
// @
|
|
160
|
-
// @req REQ-BRANCH-DETECTION
|
|
172
|
+
// @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-BRANCH-DETECTION
|
|
161
173
|
if (score >= 80) {
|
|
162
174
|
return "high";
|
|
163
175
|
} else if (score >= 50) {
|
|
164
|
-
// @
|
|
165
|
-
// @req REQ-DUAL-POSITION-DETECTION-ELSE-IF
|
|
176
|
+
// @supports docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION-ELSE-IF
|
|
166
177
|
return "medium";
|
|
167
178
|
} else {
|
|
168
179
|
return "low";
|
|
@@ -173,6 +184,6 @@ function pickCategory(score: number): string {
|
|
|
173
184
|
Depending on your Prettier version and configuration, the exact layout of the `else if` line and braces may differ, but as long as your annotations are in one of the supported locations, the rule will accept them.
|
|
174
185
|
|
|
175
186
|
- Notes:
|
|
176
|
-
- For most branch types, `traceability/require-branch-annotation` associates comments immediately before the branch keyword (such as `if`, `else`, `switch`, `case`) with that branch.
|
|
187
|
+
- For most branch types, `traceability/require-branch-annotation` associates comments immediately before the branch keyword (such as `if`, `else`, `switch`, `case`) with that branch. Branches can be annotated either with a single `@supports` line (preferred), or with the older `@story`/`@req` pair for backward compatibility. The rule treats a valid `@supports` annotation as satisfying both the story and requirement presence checks.
|
|
177
188
|
- For `catch` clauses and `else if` branches, the rule is formatter-aware and also looks at comments between the condition and the block, as well as the first comment-only lines inside the block body, so you do not need to fight Prettier if it moves your annotations.
|
|
178
189
|
- When annotations exist in more than one place around an `else if` branch, the rule prefers comments immediately before the `else if` line, then comments between the condition and the block, and finally comments inside the block body, matching the behavior described in the API reference and stories `025.0` and `026.0`.
|