eslint-plugin-traceability 1.7.0 → 1.8.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/CHANGELOG.md +82 -0
- package/README.md +73 -32
- package/docs/ci-cd-pipeline.md +224 -0
- package/docs/cli-integration.md +22 -0
- package/docs/code-quality-refactor-opportunities-2025-12-03.md +78 -0
- package/docs/config-presets.md +38 -0
- package/docs/conventional-commits-guide.md +185 -0
- package/docs/custom-rules-development-guide.md +659 -0
- package/docs/decisions/0001-allow-dynamic-require-for-built-plugins.md +26 -0
- package/docs/decisions/001-typescript-for-eslint-plugin.accepted.md +111 -0
- package/docs/decisions/002-jest-for-eslint-testing.accepted.md +137 -0
- package/docs/decisions/003-code-quality-ratcheting-plan.md +48 -0
- package/docs/decisions/004-automated-version-bumping-for-ci-cd.md +196 -0
- package/docs/decisions/005-github-actions-validation-tooling.accepted.md +144 -0
- package/docs/decisions/006-semantic-release-for-automated-publishing.accepted.md +227 -0
- package/docs/decisions/007-github-releases-over-changelog.accepted.md +216 -0
- package/docs/decisions/008-ci-audit-flags.accepted.md +60 -0
- package/docs/decisions/009-security-focused-lint-rules.accepted.md +64 -0
- package/docs/decisions/010-implements-annotation-for-multi-story-requirements.proposed.md +184 -0
- package/docs/decisions/adr-0001-console-usage-for-cli-guards.md +190 -0
- package/docs/decisions/adr-accept-dev-dep-risk-glob.md +40 -0
- package/docs/decisions/adr-commit-branch-tests.md +54 -0
- package/docs/decisions/adr-maintenance-cli-interface.md +140 -0
- package/docs/decisions/adr-pre-push-parity.md +112 -0
- package/docs/decisions/code-quality-ratcheting-plan.md +53 -0
- package/docs/dependency-health.md +238 -0
- package/docs/eslint-9-setup-guide.md +517 -0
- package/docs/eslint-plugin-development-guide.md +487 -0
- package/docs/functionality-coverage-2025-12-03.md +250 -0
- package/docs/jest-testing-guide.md +100 -0
- package/docs/rules/prefer-implements-annotation.md +219 -0
- package/docs/rules/require-branch-annotation.md +71 -0
- package/docs/rules/require-req-annotation.md +203 -0
- package/docs/rules/require-story-annotation.md +159 -0
- package/docs/rules/valid-annotation-format.md +418 -0
- package/docs/rules/valid-req-reference.md +153 -0
- package/docs/rules/valid-story-reference.md +120 -0
- package/docs/security-incidents/2025-11-17-glob-cli-incident.md +45 -0
- package/docs/security-incidents/2025-11-18-brace-expansion-redos.md +45 -0
- package/docs/security-incidents/2025-11-18-bundled-dev-deps-accepted-risk.md +93 -0
- package/docs/security-incidents/2025-11-18-tar-race-condition.md +43 -0
- package/docs/security-incidents/2025-12-03-dependency-health-review.md +58 -0
- package/docs/security-incidents/SECURITY-INCIDENT-2025-11-18-semantic-release-bundled-npm.known-error.md +104 -0
- package/docs/security-incidents/SECURITY-INCIDENT-TEMPLATE.md +37 -0
- package/docs/security-incidents/dependency-override-rationale.md +57 -0
- package/docs/security-incidents/dev-deps-high.json +116 -0
- package/docs/security-incidents/handling-procedure.md +54 -0
- package/docs/stories/001.0-DEV-PLUGIN-SETUP.story.md +92 -0
- package/docs/stories/002.0-DEV-ESLINT-CONFIG.story.md +82 -0
- package/docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md +112 -0
- package/docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md +153 -0
- package/docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md +138 -0
- package/docs/stories/006.0-DEV-FILE-VALIDATION.story.md +144 -0
- package/docs/stories/007.0-DEV-ERROR-REPORTING.story.md +163 -0
- package/docs/stories/008.0-DEV-AUTO-FIX.story.md +150 -0
- package/docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md +117 -0
- package/docs/stories/010.0-DEV-DEEP-VALIDATION.story.md +124 -0
- package/docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md +149 -0
- package/docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md +216 -0
- package/docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md +236 -0
- package/docs/stories/developer-story.map.md +120 -0
- package/docs/ts-jest-presets-guide.md +548 -0
- package/lib/src/index.d.ts +2 -2
- package/lib/src/index.js +2 -0
- package/lib/src/maintenance/batch.d.ts +5 -0
- package/lib/src/maintenance/batch.js +5 -0
- package/lib/src/maintenance/cli.js +34 -212
- package/lib/src/maintenance/commands.d.ts +32 -0
- package/lib/src/maintenance/commands.js +139 -0
- package/lib/src/maintenance/detect.d.ts +2 -0
- package/lib/src/maintenance/detect.js +4 -0
- package/lib/src/maintenance/flags.d.ts +99 -0
- package/lib/src/maintenance/flags.js +121 -0
- package/lib/src/maintenance/report.d.ts +2 -0
- package/lib/src/maintenance/report.js +2 -0
- package/lib/src/maintenance/update.d.ts +4 -0
- package/lib/src/maintenance/update.js +4 -0
- package/lib/src/rules/helpers/require-story-io.d.ts +3 -0
- package/lib/src/rules/helpers/require-story-io.js +20 -6
- package/lib/src/rules/helpers/valid-annotation-format-internal.d.ts +30 -0
- package/lib/src/rules/helpers/valid-annotation-format-internal.js +36 -0
- package/lib/src/rules/helpers/valid-annotation-options.js +15 -4
- package/lib/src/rules/helpers/valid-annotation-utils.js +5 -0
- package/lib/src/rules/helpers/valid-implements-utils.d.ts +75 -0
- package/lib/src/rules/helpers/valid-implements-utils.js +149 -0
- package/lib/src/rules/helpers/valid-story-reference-helpers.d.ts +3 -4
- package/lib/src/rules/prefer-implements-annotation.d.ts +39 -0
- package/lib/src/rules/prefer-implements-annotation.js +276 -0
- package/lib/src/rules/valid-annotation-format.js +87 -28
- package/lib/src/rules/valid-req-reference.js +71 -0
- package/lib/src/utils/reqAnnotationDetection.d.ts +4 -1
- package/lib/src/utils/reqAnnotationDetection.js +43 -15
- package/lib/tests/maintenance/cli.test.js +89 -0
- package/lib/tests/plugin-default-export-and-configs.test.js +3 -0
- package/lib/tests/rules/prefer-implements-annotation.test.d.ts +1 -0
- package/lib/tests/rules/prefer-implements-annotation.test.js +84 -0
- package/lib/tests/rules/require-req-annotation.test.js +8 -1
- package/lib/tests/rules/require-story-annotation.test.js +9 -4
- package/lib/tests/rules/valid-annotation-format.test.js +78 -0
- package/lib/tests/rules/valid-req-reference.test.js +34 -0
- package/lib/tests/utils/ts-language-options.d.ts +1 -7
- package/lib/tests/utils/ts-language-options.js +8 -5
- package/package.json +7 -3
- package/user-docs/api-reference.md +507 -0
- package/user-docs/eslint-9-setup-guide.md +639 -0
- package/user-docs/examples.md +74 -0
- package/user-docs/migration-guide.md +158 -0
|
@@ -14,6 +14,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
*/
|
|
15
15
|
const fs_1 = __importDefault(require("fs"));
|
|
16
16
|
const path_1 = __importDefault(require("path"));
|
|
17
|
+
/**
|
|
18
|
+
* Token index configuration for @implements annotations.
|
|
19
|
+
* This clarifies the expected positions of the story path and first requirement ID
|
|
20
|
+
* and avoids hard-coded "magic number" indices in parsing logic.
|
|
21
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
22
|
+
*/
|
|
23
|
+
const IMPLEMENTS_TOKENS = {
|
|
24
|
+
STORY_INDEX: 1,
|
|
25
|
+
FIRST_REQ_INDEX: 2,
|
|
26
|
+
};
|
|
17
27
|
/**
|
|
18
28
|
* Extract the story path from a JSDoc comment.
|
|
19
29
|
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
@@ -164,11 +174,68 @@ function validateReqLine(opts) {
|
|
|
164
174
|
reqSet,
|
|
165
175
|
});
|
|
166
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Parse an @implements annotation line into its story path and requirement IDs.
|
|
179
|
+
* Expects the format: "@implements <storyPath> <REQ-ID-1> <REQ-ID-2> ..."
|
|
180
|
+
* Invalid formats (missing storyPath or reqIds) are ignored by this deep rule.
|
|
181
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
182
|
+
* @req REQ-IMPLEMENTS-VALIDATE - Support validation of @implements annotations
|
|
183
|
+
* @req REQ-MIXED-SUPPORT - Allow mixed @story/@req/@implements usage in the same comment
|
|
184
|
+
* @req REQ-SCOPED-IDS - Treat requirement IDs as scoped to the referenced story file
|
|
185
|
+
*/
|
|
186
|
+
function parseImplementsLine(line) {
|
|
187
|
+
const parts = line.split(/\s+/);
|
|
188
|
+
const storyPath = parts[IMPLEMENTS_TOKENS.STORY_INDEX];
|
|
189
|
+
const reqIds = parts.slice(IMPLEMENTS_TOKENS.FIRST_REQ_INDEX);
|
|
190
|
+
if (!storyPath || reqIds.length === 0) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return { storyPath, reqIds };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Validate an @implements annotation line against the referenced story content.
|
|
197
|
+
* Performs path validation, file reading, caching, and requirement existence checks
|
|
198
|
+
* for each requirement ID listed on the line.
|
|
199
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
200
|
+
* @req REQ-IMPLEMENTS-VALIDATE - Validate that all @implements requirement IDs exist
|
|
201
|
+
* @req REQ-MIXED-SUPPORT - Ensure @implements can coexist with @story/@req annotations
|
|
202
|
+
* @req REQ-SCOPED-IDS - Validate requirement IDs in the scope of their explicit story
|
|
203
|
+
*/
|
|
204
|
+
function validateImplementsLine(opts) {
|
|
205
|
+
const { comment, context, line, cwd, reqCache } = opts;
|
|
206
|
+
const parsed = parseImplementsLine(line);
|
|
207
|
+
if (!parsed) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const { storyPath, reqIds } = parsed;
|
|
211
|
+
const { reqSet } = resolveStoryAndRequirements({
|
|
212
|
+
comment,
|
|
213
|
+
context,
|
|
214
|
+
storyPath,
|
|
215
|
+
cwd,
|
|
216
|
+
reqCache,
|
|
217
|
+
});
|
|
218
|
+
if (!reqSet) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
for (const reqId of reqIds) {
|
|
222
|
+
checkRequirementExists({
|
|
223
|
+
comment,
|
|
224
|
+
context,
|
|
225
|
+
reqId,
|
|
226
|
+
storyPath,
|
|
227
|
+
reqSet,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
167
231
|
/**
|
|
168
232
|
* Handle a single annotation line for story or requirement metadata.
|
|
169
233
|
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
170
234
|
* @req REQ-DEEP-PARSE - Parse annotation lines for @story and @req tags
|
|
171
235
|
* @req REQ-DEEP-MATCH - Dispatch @req lines for validation against story requirements
|
|
236
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
237
|
+
* @req REQ-IMPLEMENTS-VALIDATE - Dispatch @implements lines for validation
|
|
238
|
+
* @req REQ-MIXED-SUPPORT - Support mixed annotation types without interfering with each other
|
|
172
239
|
*/
|
|
173
240
|
function handleAnnotationLine(opts) {
|
|
174
241
|
const { line, comment, context, cwd, reqCache, storyPath } = opts;
|
|
@@ -180,6 +247,10 @@ function handleAnnotationLine(opts) {
|
|
|
180
247
|
validateReqLine({ comment, context, line, storyPath, cwd, reqCache });
|
|
181
248
|
return storyPath;
|
|
182
249
|
}
|
|
250
|
+
else if (line.startsWith("@implements")) {
|
|
251
|
+
validateImplementsLine({ comment, context, line, cwd, reqCache });
|
|
252
|
+
return storyPath;
|
|
253
|
+
}
|
|
183
254
|
return storyPath;
|
|
184
255
|
}
|
|
185
256
|
/**
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Helper to determine whether a JSDoc or any nearby comments contain a
|
|
2
|
+
* Helper to determine whether a JSDoc or any nearby comments contain a requirement annotation.
|
|
3
|
+
* Treats both @req and @implements annotations as evidence of requirement coverage.
|
|
3
4
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
5
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
4
6
|
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
7
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements as requirement coverage
|
|
5
8
|
*/
|
|
6
9
|
export declare function hasReqAnnotation(jsdoc: any, comments: any[], context?: any, node?: any): boolean;
|
|
@@ -8,17 +8,24 @@ exports.hasReqAnnotation = hasReqAnnotation;
|
|
|
8
8
|
*/
|
|
9
9
|
const require_story_io_1 = require("../rules/helpers/require-story-io");
|
|
10
10
|
/**
|
|
11
|
-
* Predicate helper to check whether a comment contains a
|
|
11
|
+
* Predicate helper to check whether a comment contains a requirement annotation.
|
|
12
|
+
* Treats both @req and @implements annotations as satisfying requirement presence checks.
|
|
12
13
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
14
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
13
15
|
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req tag inside a comment
|
|
16
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements as requirement annotation
|
|
14
17
|
*/
|
|
15
18
|
function commentContainsReq(c) {
|
|
16
|
-
return c &&
|
|
19
|
+
return (c &&
|
|
20
|
+
typeof c.value === "string" &&
|
|
21
|
+
(c.value.includes("@req") || c.value.includes("@implements")));
|
|
17
22
|
}
|
|
18
23
|
/**
|
|
19
|
-
* Line-based helper adapted from linesBeforeHasStory to detect
|
|
24
|
+
* Line-based helper adapted from linesBeforeHasStory to detect requirement annotations.
|
|
25
|
+
* Lines containing either @req or @implements are treated as annotated.
|
|
20
26
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
21
27
|
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req in preceding source lines
|
|
28
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements in preceding source lines
|
|
22
29
|
*/
|
|
23
30
|
function linesBeforeHasReq(sourceCode, node) {
|
|
24
31
|
const lines = sourceCode && sourceCode.lines;
|
|
@@ -33,44 +40,53 @@ function linesBeforeHasReq(sourceCode, node) {
|
|
|
33
40
|
}
|
|
34
41
|
const from = Math.max(0, startLine - 1 - require_story_io_1.LOOKBACK_LINES);
|
|
35
42
|
const to = Math.max(0, startLine - 1);
|
|
36
|
-
// Scan each physical line in the configured lookback window for
|
|
43
|
+
// Scan each physical line in the configured lookback window for @req or @implements markers.
|
|
37
44
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
38
45
|
// @req REQ-ANNOTATION-REQ-DETECTION - Search preceding lines for @req text
|
|
46
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Search preceding lines for @implements text
|
|
39
47
|
for (let i = from; i < to; i++) {
|
|
40
48
|
const text = lines[i];
|
|
41
|
-
// When a line contains @req we treat the function as already annotated.
|
|
49
|
+
// When a line contains @req or @implements we treat the function as already annotated.
|
|
42
50
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
43
51
|
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req marker in raw source lines
|
|
44
|
-
|
|
52
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Detect @implements marker in raw source lines
|
|
53
|
+
if (typeof text === "string" &&
|
|
54
|
+
(text.includes("@req") || text.includes("@implements"))) {
|
|
45
55
|
return true;
|
|
46
56
|
}
|
|
47
57
|
}
|
|
48
58
|
return false;
|
|
49
59
|
}
|
|
50
60
|
/**
|
|
51
|
-
* Parent-chain helper adapted from parentChainHasStory to detect
|
|
61
|
+
* Parent-chain helper adapted from parentChainHasStory to detect requirement annotations.
|
|
62
|
+
* Accepts both @req and @implements in parent-chain comments as satisfying requirement presence.
|
|
52
63
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
53
64
|
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req in parent-chain comments
|
|
65
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements in parent-chain comments
|
|
54
66
|
*/
|
|
55
67
|
function parentChainHasReq(sourceCode, node) {
|
|
56
68
|
let p = node && node.parent;
|
|
57
69
|
// Walk up the parent chain and inspect comments attached to each ancestor.
|
|
70
|
+
// Accept both @req and @implements markers when local comments are absent.
|
|
58
71
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
59
72
|
// @req REQ-ANNOTATION-REQ-DETECTION - Traverse parent nodes when local comments are absent
|
|
73
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Allow @implements to satisfy requirement on parents
|
|
60
74
|
while (p) {
|
|
61
75
|
const pComments = typeof sourceCode?.getCommentsBefore === "function"
|
|
62
76
|
? sourceCode.getCommentsBefore(p) || []
|
|
63
77
|
: [];
|
|
64
|
-
// Look for @req in comments immediately preceding each parent node.
|
|
78
|
+
// Look for @req or @implements in comments immediately preceding each parent node.
|
|
65
79
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
66
80
|
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req markers in parent comments
|
|
81
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Detect @implements markers in parent comments
|
|
67
82
|
if (Array.isArray(pComments) && pComments.some(commentContainsReq)) {
|
|
68
83
|
return true;
|
|
69
84
|
}
|
|
70
85
|
const pLeading = p.leadingComments || [];
|
|
71
|
-
// Also inspect leadingComments attached directly to the parent node.
|
|
86
|
+
// Also inspect leadingComments attached directly to the parent node, accepting @req or @implements.
|
|
72
87
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
73
88
|
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req markers in parent leadingComments
|
|
89
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Detect @implements markers in parent leadingComments
|
|
74
90
|
if (Array.isArray(pLeading) && pLeading.some(commentContainsReq)) {
|
|
75
91
|
return true;
|
|
76
92
|
}
|
|
@@ -79,9 +95,12 @@ function parentChainHasReq(sourceCode, node) {
|
|
|
79
95
|
return false;
|
|
80
96
|
}
|
|
81
97
|
/**
|
|
82
|
-
* Fallback text window helper adapted from fallbackTextBeforeHasStory to detect
|
|
98
|
+
* Fallback text window helper adapted from fallbackTextBeforeHasStory to detect requirement annotations.
|
|
99
|
+
* Treats both @req and @implements in the fallback text window as requirement presence.
|
|
83
100
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
101
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
84
102
|
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req in fallback text window before node
|
|
103
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements in fallback text window before node
|
|
85
104
|
*/
|
|
86
105
|
function fallbackTextBeforeHasReq(sourceCode, node) {
|
|
87
106
|
// Guard against unsupported sourceCode or nodes without a usable range.
|
|
@@ -101,10 +120,13 @@ function fallbackTextBeforeHasReq(sourceCode, node) {
|
|
|
101
120
|
try {
|
|
102
121
|
const start = Math.max(0, range[0] - require_story_io_1.FALLBACK_WINDOW);
|
|
103
122
|
const textBefore = sourceCode.getText().slice(start, range[0]);
|
|
104
|
-
// Detect @req in the bounded text window immediately preceding the node.
|
|
123
|
+
// Detect @req or @implements in the bounded text window immediately preceding the node.
|
|
105
124
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
125
|
+
// @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
106
126
|
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req marker in fallback text window
|
|
107
|
-
|
|
127
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Detect @implements marker in fallback text window
|
|
128
|
+
if (typeof textBefore === "string" &&
|
|
129
|
+
(textBefore.includes("@req") || textBefore.includes("@implements"))) {
|
|
108
130
|
return true;
|
|
109
131
|
}
|
|
110
132
|
}
|
|
@@ -117,9 +139,12 @@ function fallbackTextBeforeHasReq(sourceCode, node) {
|
|
|
117
139
|
return false;
|
|
118
140
|
}
|
|
119
141
|
/**
|
|
120
|
-
* Helper to determine whether a JSDoc or any nearby comments contain a
|
|
142
|
+
* Helper to determine whether a JSDoc or any nearby comments contain a requirement annotation.
|
|
143
|
+
* Treats both @req and @implements annotations as evidence of requirement coverage.
|
|
121
144
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
145
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
122
146
|
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
147
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements as requirement coverage
|
|
123
148
|
*/
|
|
124
149
|
function hasReqAnnotation(jsdoc, comments, context, node) {
|
|
125
150
|
try {
|
|
@@ -129,6 +154,7 @@ function hasReqAnnotation(jsdoc, comments, context, node) {
|
|
|
129
154
|
// Prefer robust, location-based heuristics when sourceCode and node are available.
|
|
130
155
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
131
156
|
// @req REQ-ANNOTATION-REQ-DETECTION - Use multiple heuristics to detect @req markers around the node
|
|
157
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Use multiple heuristics to detect @implements markers around the node
|
|
132
158
|
if (sourceCode && node) {
|
|
133
159
|
if (linesBeforeHasReq(sourceCode, node) ||
|
|
134
160
|
parentChainHasReq(sourceCode, node) ||
|
|
@@ -142,11 +168,13 @@ function hasReqAnnotation(jsdoc, comments, context, node) {
|
|
|
142
168
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
143
169
|
// @req REQ-ANNOTATION-REQ-DETECTION - Fail gracefully when advanced detection heuristics throw
|
|
144
170
|
}
|
|
145
|
-
// BRANCH
|
|
171
|
+
// BRANCH requirement detection on JSDoc or comments, accepting both @req and @implements.
|
|
146
172
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
173
|
+
// @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
147
174
|
// @req REQ-ANNOTATION-REQ-DETECTION
|
|
175
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS
|
|
148
176
|
return ((jsdoc &&
|
|
149
177
|
typeof jsdoc.value === "string" &&
|
|
150
|
-
jsdoc.value.includes("@req")) ||
|
|
178
|
+
(jsdoc.value.includes("@req") || jsdoc.value.includes("@implements"))) ||
|
|
151
179
|
comments.some(commentContainsReq));
|
|
152
180
|
}
|
|
@@ -145,6 +145,31 @@ describe("Maintenance CLI (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
|
145
145
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
|
+
it("[REQ-MAINT-SAFE] report exits 2 and prints error on invalid --format value", () => {
|
|
149
|
+
const dir = withTempDir();
|
|
150
|
+
process.chdir(dir);
|
|
151
|
+
const errorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
|
|
152
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
153
|
+
const code = (0, cli_1.runMaintenanceCli)([
|
|
154
|
+
"node",
|
|
155
|
+
"traceability-maint",
|
|
156
|
+
"report",
|
|
157
|
+
"--format",
|
|
158
|
+
"yaml",
|
|
159
|
+
]);
|
|
160
|
+
try {
|
|
161
|
+
expect(code).toBe(2);
|
|
162
|
+
expect(errorSpy).toHaveBeenCalledTimes(1);
|
|
163
|
+
const message = String(errorSpy.mock.calls[0][0]);
|
|
164
|
+
expect(message).toContain("Invalid format: yaml");
|
|
165
|
+
expect(message).toContain("Expected 'text' or 'json'");
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
errorSpy.mockRestore();
|
|
169
|
+
logSpy.mockRestore();
|
|
170
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
148
173
|
it("[REQ-MAINT-DETECT] detect supports --json output", () => {
|
|
149
174
|
const dir = withTempDir();
|
|
150
175
|
process.chdir(dir);
|
|
@@ -169,4 +194,68 @@ describe("Maintenance CLI (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
|
169
194
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
170
195
|
}
|
|
171
196
|
});
|
|
197
|
+
it("[REQ-MAINT-DETECT] detect with non-existent --root exits 0 and reports no stale annotations", () => {
|
|
198
|
+
const dir = withTempDir();
|
|
199
|
+
process.chdir(dir);
|
|
200
|
+
const missingRoot = path_1.default.join(dir, "missing-root");
|
|
201
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
202
|
+
const code = (0, cli_1.runMaintenanceCli)([
|
|
203
|
+
"node",
|
|
204
|
+
"traceability-maint",
|
|
205
|
+
"detect",
|
|
206
|
+
"--root",
|
|
207
|
+
missingRoot,
|
|
208
|
+
]);
|
|
209
|
+
try {
|
|
210
|
+
expect(code).toBe(0);
|
|
211
|
+
expect(logSpy).toHaveBeenCalledWith("No stale @story annotations found.");
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
logSpy.mockRestore();
|
|
215
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
it("[REQ-MAINT-SAFE] prints help and exits 0 when no subcommand is provided", () => {
|
|
219
|
+
const dir = withTempDir();
|
|
220
|
+
process.chdir(dir);
|
|
221
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
222
|
+
const errorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
|
|
223
|
+
const code = (0, cli_1.runMaintenanceCli)(["node", "traceability-maint"]);
|
|
224
|
+
try {
|
|
225
|
+
expect(code).toBe(0);
|
|
226
|
+
expect(logSpy).toHaveBeenCalled();
|
|
227
|
+
const allMessages = logSpy.mock.calls.flat().join("\n");
|
|
228
|
+
expect(allMessages).toContain("traceability-maint - Traceability annotation maintenance tools");
|
|
229
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
logSpy.mockRestore();
|
|
233
|
+
errorSpy.mockRestore();
|
|
234
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
it("[REQ-MAINT-SAFE] detect catches filesystem permission errors and exits 2 with prefixed error message", () => {
|
|
238
|
+
const dir = withTempDir();
|
|
239
|
+
process.chdir(dir);
|
|
240
|
+
const errorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
|
|
241
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
242
|
+
const statSpy = jest.spyOn(fs_1.default, "statSync").mockImplementation(() => {
|
|
243
|
+
const err = new Error("EACCES simulated");
|
|
244
|
+
err.code = "EACCES";
|
|
245
|
+
throw err;
|
|
246
|
+
});
|
|
247
|
+
const code = (0, cli_1.runMaintenanceCli)(["node", "traceability-maint", "detect"]);
|
|
248
|
+
try {
|
|
249
|
+
expect(code).toBe(2);
|
|
250
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
251
|
+
const message = String(errorSpy.mock.calls[0][0]);
|
|
252
|
+
expect(message).toContain("traceability-maint failed:");
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
statSpy.mockRestore();
|
|
256
|
+
errorSpy.mockRestore();
|
|
257
|
+
logSpy.mockRestore();
|
|
258
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
259
|
+
}
|
|
260
|
+
});
|
|
172
261
|
});
|
|
@@ -55,6 +55,7 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
|
|
|
55
55
|
"valid-annotation-format",
|
|
56
56
|
"valid-story-reference",
|
|
57
57
|
"valid-req-reference",
|
|
58
|
+
"prefer-implements-annotation",
|
|
58
59
|
];
|
|
59
60
|
// Act: get actual rule names from plugin
|
|
60
61
|
const actual = Object.keys(index_1.rules);
|
|
@@ -79,10 +80,12 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
|
|
|
79
80
|
expect(recommendedRules).toHaveProperty("traceability/require-branch-annotation", "error");
|
|
80
81
|
expect(recommendedRules).toHaveProperty("traceability/valid-story-reference", "error");
|
|
81
82
|
expect(recommendedRules).toHaveProperty("traceability/valid-req-reference", "error");
|
|
83
|
+
expect(recommendedRules).toHaveProperty("traceability/prefer-implements-annotation", "warn");
|
|
82
84
|
});
|
|
83
85
|
it("[REQ-ERROR-SEVERITY] configs.strict uses same severity mapping as recommended", () => {
|
|
84
86
|
const strictRules = index_1.configs.strict[0].rules;
|
|
85
87
|
const recommendedRules = index_1.configs.recommended[0].rules;
|
|
86
88
|
expect(strictRules).toEqual(recommendedRules);
|
|
89
|
+
expect(strictRules).toHaveProperty("traceability/prefer-implements-annotation", "warn");
|
|
87
90
|
});
|
|
88
91
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
/**
|
|
7
|
+
* Tests for: docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
8
|
+
* @story docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
9
|
+
* @req REQ-OPTIONAL-WARNING - Verify rule emits recommendations for legacy @story/@req usage
|
|
10
|
+
* @req REQ-MULTI-STORY-DETECT - Verify rule detects multi-story and mixed-annotation patterns
|
|
11
|
+
* @req REQ-CONFIG-SEVERITY - Verify rule is disabled by default and can be enabled as warn/error
|
|
12
|
+
*/
|
|
13
|
+
const eslint_1 = require("eslint");
|
|
14
|
+
const prefer_implements_annotation_1 = __importDefault(require("../../src/rules/prefer-implements-annotation"));
|
|
15
|
+
const ruleTester = new eslint_1.RuleTester({
|
|
16
|
+
languageOptions: {
|
|
17
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
describe("prefer-implements-annotation rule (Story 010.3-DEV-MIGRATE-TO-IMPLEMENTS)", () => {
|
|
21
|
+
ruleTester.run("prefer-implements-annotation", prefer_implements_annotation_1.default, {
|
|
22
|
+
valid: [
|
|
23
|
+
{
|
|
24
|
+
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with only @story is ignored",
|
|
25
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction onlyStory() {}`,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with only @req is ignored",
|
|
29
|
+
code: `/**\n * @req REQ-ONLY\n */\nfunction onlyReq() {}`,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with @implements only is ignored",
|
|
33
|
+
code: `/**\n * @implements docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction alreadyImplements() {}`,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
invalid: [
|
|
37
|
+
{
|
|
38
|
+
name: "[REQ-OPTIONAL-WARNING] single-story @story + @req block triggers preferImplements message",
|
|
39
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED\n */\nfunction legacy() {}`,
|
|
40
|
+
output: `/**\n * @implements docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction legacy() {}`,
|
|
41
|
+
errors: [{ messageId: "preferImplements" }],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "[REQ-MULTI-STORY-DETECT] mixed @story/@req and @implements triggers cannotAutoFix",
|
|
45
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED\n * @implements docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction mixed() {}`,
|
|
46
|
+
errors: [
|
|
47
|
+
{
|
|
48
|
+
messageId: "cannotAutoFix",
|
|
49
|
+
data: {
|
|
50
|
+
reason: "comment mixes @story/@req with existing @implements annotations",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "[REQ-MULTI-STORY-DETECT] multiple @story paths in same block trigger multiStoryDetected",
|
|
57
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED\n * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md\n * @req REQ-BRANCH-DETECTION\n */\nfunction multiStory() {}`,
|
|
58
|
+
errors: [{ messageId: "multiStoryDetected" }],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "[REQ-AUTO-FIX] single @story + single @req auto-fixes to single @implements line",
|
|
62
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED\n */\nfunction autoFixSingleReq() {}`,
|
|
63
|
+
output: `/**\n * @implements docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction autoFixSingleReq() {}`,
|
|
64
|
+
errors: [{ messageId: "preferImplements" }],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "[REQ-SINGLE-STORY-FIX] single @story with multiple @req lines auto-fixes to single @implements line containing all REQ IDs",
|
|
68
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ONE\n * @req REQ-TWO\n * @req REQ-THREE\n */\nfunction autoFixMultiReq() {}`,
|
|
69
|
+
output: `/**\n * @implements docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ONE REQ-TWO REQ-THREE\n */\nfunction autoFixMultiReq() {}`,
|
|
70
|
+
errors: [{ messageId: "preferImplements" }],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "[REQ-AUTO-FIX] complex @req content (extra description) does not auto-fix but still warns",
|
|
74
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED must handle extra description\n */\nfunction complexReqNoAutoFix() {}`,
|
|
75
|
+
errors: [{ messageId: "preferImplements" }],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "[REQ-AUTO-FIX] complex @story content (extra description) does not auto-fix but still warns",
|
|
79
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md additional descriptive text\n * @req REQ-ANNOTATION-REQUIRED\n */\nfunction complexStoryNoAutoFix() {}`,
|
|
80
|
+
errors: [{ messageId: "preferImplements" }],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -12,6 +12,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
*
|
|
13
13
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
14
14
|
* @req REQ-TYPESCRIPT-SUPPORT - Verify TypeScript declarations are checked via shared annotation checker helper
|
|
15
|
+
*
|
|
16
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
17
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Verify @implements is accepted as satisfying requirement annotations
|
|
15
18
|
*/
|
|
16
19
|
const eslint_1 = require("eslint");
|
|
17
20
|
const require_req_annotation_1 = __importDefault(require("../../src/rules/require-req-annotation"));
|
|
@@ -73,6 +76,10 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
73
76
|
name: "[REQ-ANNOTATION-REQUIRED] valid with only @req annotation",
|
|
74
77
|
code: `/**\n * @req REQ-EXAMPLE\n */\nfunction foo() {}`,
|
|
75
78
|
},
|
|
79
|
+
{
|
|
80
|
+
name: "[REQ-REQUIRE-ACCEPTS-IMPLEMENTS] valid with only @implements annotation",
|
|
81
|
+
code: `/**\n * @implements docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction implOnly() {}`,
|
|
82
|
+
},
|
|
76
83
|
{
|
|
77
84
|
name: "[REQ-ANNOTATION-REQUIRED] valid with @story and @req annotations",
|
|
78
85
|
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-EXAMPLE\n */\nfunction bar() {}`,
|
|
@@ -131,7 +138,7 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
131
138
|
],
|
|
132
139
|
invalid: [
|
|
133
140
|
{
|
|
134
|
-
name: "[REQ-ANNOTATION-REQUIRED] missing @req on function without JSDoc",
|
|
141
|
+
name: "[REQ-ANNOTATION-REQUIRED][REQ-REQUIRE-ACCEPTS-IMPLEMENTS] missing @req on function without JSDoc remains invalid under multi-story support",
|
|
135
142
|
code: `function baz() {}`,
|
|
136
143
|
errors: [
|
|
137
144
|
{
|
|
@@ -6,15 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
/**
|
|
7
7
|
* Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
8
8
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
9
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
9
10
|
* @req REQ-ANNOTATION-REQUIRED - Verify require-story-annotation rule enforces @story annotation on functions
|
|
11
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Verify @implements annotation is accepted as satisfying story requirements
|
|
10
12
|
*/
|
|
11
13
|
const eslint_1 = require("eslint");
|
|
12
14
|
const require_story_annotation_1 = __importDefault(require("../../src/rules/require-story-annotation"));
|
|
13
15
|
const ts_language_options_1 = require("../utils/ts-language-options");
|
|
14
16
|
const ruleTester = new eslint_1.RuleTester({
|
|
15
|
-
languageOptions:
|
|
16
|
-
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
17
|
-
},
|
|
17
|
+
languageOptions: ts_language_options_1.tsRuleTesterLanguageOptions,
|
|
18
18
|
});
|
|
19
19
|
describe("Require Story Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", () => {
|
|
20
20
|
ruleTester.run("require-story-annotation", require_story_annotation_1.default, {
|
|
@@ -23,6 +23,10 @@ describe("Require Story Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)",
|
|
|
23
23
|
name: "[REQ-ANNOTATION-REQUIRED] valid with JSDoc @story annotation",
|
|
24
24
|
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction foo() {}`,
|
|
25
25
|
},
|
|
26
|
+
{
|
|
27
|
+
name: "[REQ-REQUIRE-ACCEPTS-IMPLEMENTS] valid with only @implements annotation",
|
|
28
|
+
code: `/**\n * @implements docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction implOnly() {}`,
|
|
29
|
+
},
|
|
26
30
|
{
|
|
27
31
|
name: "[REQ-ANNOTATION-REQUIRED] valid with line comment @story annotation",
|
|
28
32
|
code: `// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
@@ -60,7 +64,8 @@ declare function tsDecl(): void;`,
|
|
|
60
64
|
],
|
|
61
65
|
invalid: [
|
|
62
66
|
{
|
|
63
|
-
|
|
67
|
+
// Backward compatibility: plain unannotated functions remain invalid under multi-story support
|
|
68
|
+
name: "[REQ-ANNOTATION-REQUIRED][BACKCOMPAT] missing @story annotation on function with no @implements",
|
|
64
69
|
code: `function bar() {}`,
|
|
65
70
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}`,
|
|
66
71
|
errors: [
|
|
@@ -18,6 +18,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
18
18
|
* @req REQ-CONFIGURABLE-PATTERNS-REQ - Rule supports configurable requirement ID regex patterns
|
|
19
19
|
* @req REQ-CONFIGURABLE-PATTERNS-EXAMPLES - Rule supports configurable example strings in error messages
|
|
20
20
|
* @req REQ-CONFIGURABLE-PATTERNS-FALLBACK - Invalid regex patterns fall back to default behavior without crashing
|
|
21
|
+
* Tests for: docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
22
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
23
|
+
* @req REQ-IMPLEMENTS-PARSE - Rule parses @implements annotations with story and requirement references
|
|
24
|
+
* @req REQ-FORMAT-VALIDATION - Rule validates story and requirement formats inside @implements annotations
|
|
25
|
+
* @req REQ-MIXED-SUPPORT - Rule supports mixed @story/@req/@implements usage in the same comment
|
|
21
26
|
*/
|
|
22
27
|
const eslint_1 = require("eslint");
|
|
23
28
|
const valid_annotation_format_1 = __importDefault(require("../../src/rules/valid-annotation-format"));
|
|
@@ -168,6 +173,27 @@ describe("Valid Annotation Format Rule (Story 005.0-DEV-ANNOTATION-VALIDATION)",
|
|
|
168
173
|
},
|
|
169
174
|
],
|
|
170
175
|
},
|
|
176
|
+
{
|
|
177
|
+
name: "[REQ-IMPLEMENTS-PARSE] valid single @implements with one story and one requirement (default patterns)",
|
|
178
|
+
code: `/**
|
|
179
|
+
* @implements docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md REQ-IMPLEMENTS-PARSE
|
|
180
|
+
*/`,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "[REQ-IMPLEMENTS-PARSE] valid multiple @implements lines with different stories and requirements",
|
|
184
|
+
code: `/**
|
|
185
|
+
* @implements docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md REQ-IMPLEMENTS-PARSE REQ-FORMAT-VALIDATION
|
|
186
|
+
* @implements docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-FORMAT-SPECIFICATION
|
|
187
|
+
*/`,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "[REQ-MIXED-SUPPORT] valid mixed @story/@req/@implements usage in same block comment",
|
|
191
|
+
code: `/**
|
|
192
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
193
|
+
* @req REQ-MIXED-SUPPORT
|
|
194
|
+
* @implements docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md REQ-IMPLEMENTS-PARSE REQ-FORMAT-VALIDATION REQ-MIXED-SUPPORT
|
|
195
|
+
*/`,
|
|
196
|
+
},
|
|
171
197
|
],
|
|
172
198
|
invalid: [
|
|
173
199
|
makeInvalidStory({
|
|
@@ -480,6 +506,58 @@ describe("Valid Annotation Format Rule (Story 005.0-DEV-ANNOTATION-VALIDATION)",
|
|
|
480
506
|
},
|
|
481
507
|
],
|
|
482
508
|
},
|
|
509
|
+
makeInvalid({
|
|
510
|
+
name: "[REQ-IMPLEMENTS-PARSE] @implements with no value is invalid",
|
|
511
|
+
code: `/**
|
|
512
|
+
* @implements
|
|
513
|
+
*/`,
|
|
514
|
+
messageId: "invalidImplementsFormat",
|
|
515
|
+
details: 'Missing story path and requirement IDs for @implements annotation. Expected a value like "docs/stories/005.0-DEV-EXAMPLE.story.md REQ-EXAMPLE".',
|
|
516
|
+
}),
|
|
517
|
+
makeInvalid({
|
|
518
|
+
name: "[REQ-IMPLEMENTS-PARSE] @implements with only story path and no requirement IDs is invalid",
|
|
519
|
+
code: `/**
|
|
520
|
+
* @implements docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
521
|
+
*/`,
|
|
522
|
+
messageId: "invalidImplementsFormat",
|
|
523
|
+
details: 'Missing requirement IDs for @implements annotation. Expected a value like "docs/stories/005.0-DEV-EXAMPLE.story.md REQ-EXAMPLE".',
|
|
524
|
+
}),
|
|
525
|
+
makeInvalid({
|
|
526
|
+
name: "[REQ-FORMAT-VALIDATION] @implements with invalid story path format",
|
|
527
|
+
code: `/**
|
|
528
|
+
* @implements invalid/path.txt REQ-IMPLEMENTS-PARSE
|
|
529
|
+
*/`,
|
|
530
|
+
messageId: "invalidImplementsFormat",
|
|
531
|
+
details: 'Invalid story path "invalid/path.txt" for @implements annotation. Expected a path like "docs/stories/005.0-DEV-EXAMPLE.story.md".',
|
|
532
|
+
}),
|
|
533
|
+
{
|
|
534
|
+
name: "[REQ-FORMAT-VALIDATION] @implements with invalid requirement ID format",
|
|
535
|
+
code: `/**
|
|
536
|
+
* @implements docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md REQ-VALID invalid-format
|
|
537
|
+
*/`,
|
|
538
|
+
errors: [
|
|
539
|
+
{
|
|
540
|
+
messageId: "invalidReqFormat",
|
|
541
|
+
data: {
|
|
542
|
+
details: 'Invalid requirement ID "invalid-format" for @req annotation. Expected an identifier like "REQ-EXAMPLE" (uppercase letters, numbers, and dashes only).',
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: "[REQ-FORMAT-VALIDATION] @implements with multiple requirement IDs where one is invalid",
|
|
549
|
+
code: `/**
|
|
550
|
+
* @implements docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md REQ-VALID-1 REQ-VALID-2 bad-id
|
|
551
|
+
*/`,
|
|
552
|
+
errors: [
|
|
553
|
+
{
|
|
554
|
+
messageId: "invalidReqFormat",
|
|
555
|
+
data: {
|
|
556
|
+
details: 'Invalid requirement ID "bad-id" for @req annotation. Expected an identifier like "REQ-EXAMPLE" (uppercase letters, numbers, and dashes only).',
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
],
|
|
560
|
+
},
|
|
483
561
|
],
|
|
484
562
|
});
|
|
485
563
|
});
|