eslint-plugin-traceability 1.7.0 → 1.7.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/README.md +1 -0
- package/lib/src/index.d.ts +2 -2
- package/lib/src/index.js +2 -0
- 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-implements-utils.d.ts +75 -0
- package/lib/src/rules/helpers/valid-implements-utils.js +149 -0
- 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/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/valid-annotation-format.test.js +78 -0
- package/lib/tests/rules/valid-req-reference.test.js +34 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ module.exports = [
|
|
|
54
54
|
- `traceability/valid-annotation-format` Enforces correct format of traceability annotations. ([Documentation](docs/rules/valid-annotation-format.md))
|
|
55
55
|
- `traceability/valid-story-reference` Validates that `@story` references point to existing story files. ([Documentation](docs/rules/valid-story-reference.md))
|
|
56
56
|
- `traceability/valid-req-reference` Validates that `@req` references point to existing requirement IDs. ([Documentation](docs/rules/valid-req-reference.md))
|
|
57
|
+
- `traceability/prefer-implements-annotation` Recommends migration from legacy `@story`/`@req` annotations to `@implements` (disabled by default). ([Documentation](docs/rules/prefer-implements-annotation.md))
|
|
57
58
|
|
|
58
59
|
Configuration options: For detailed per-rule options (such as scopes, branch types, and story directory settings), see the individual rule docs in `docs/rules/` and the consolidated [API Reference](user-docs/api-reference.md).
|
|
59
60
|
|
package/lib/src/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { detectStaleAnnotations, updateAnnotationReferences, batchUpdateAnnotati
|
|
|
14
14
|
* @story docs/stories/002.0-DYNAMIC-RULE-LOADING.story.md
|
|
15
15
|
* @req REQ-RULE-LIST - Enumerate supported rule file names for plugin discovery
|
|
16
16
|
*/
|
|
17
|
-
declare const RULE_NAMES: readonly ["require-story-annotation", "require-req-annotation", "require-branch-annotation", "valid-annotation-format", "valid-story-reference", "valid-req-reference"];
|
|
17
|
+
declare const RULE_NAMES: readonly ["require-story-annotation", "require-req-annotation", "require-branch-annotation", "valid-annotation-format", "valid-story-reference", "valid-req-reference", "prefer-implements-annotation"];
|
|
18
18
|
type RuleName = (typeof RULE_NAMES)[number];
|
|
19
19
|
declare const rules: Record<RuleName, Rule.RuleModule>;
|
|
20
20
|
/**
|
|
@@ -54,7 +54,7 @@ declare const maintenance: {
|
|
|
54
54
|
};
|
|
55
55
|
export { rules, configs, maintenance };
|
|
56
56
|
declare const _default: {
|
|
57
|
-
rules: Record<"require-story-annotation" | "require-req-annotation" | "require-branch-annotation" | "valid-annotation-format" | "valid-story-reference" | "valid-req-reference", Rule.RuleModule>;
|
|
57
|
+
rules: Record<"require-story-annotation" | "require-req-annotation" | "require-branch-annotation" | "valid-annotation-format" | "valid-story-reference" | "valid-req-reference" | "prefer-implements-annotation", Rule.RuleModule>;
|
|
58
58
|
configs: {
|
|
59
59
|
recommended: {
|
|
60
60
|
plugins: {
|
package/lib/src/index.js
CHANGED
|
@@ -17,6 +17,7 @@ const RULE_NAMES = [
|
|
|
17
17
|
"valid-annotation-format",
|
|
18
18
|
"valid-story-reference",
|
|
19
19
|
"valid-req-reference",
|
|
20
|
+
"prefer-implements-annotation",
|
|
20
21
|
];
|
|
21
22
|
const rules = {};
|
|
22
23
|
exports.rules = rules;
|
|
@@ -85,6 +86,7 @@ const TRACEABILITY_RULE_SEVERITIES = {
|
|
|
85
86
|
"traceability/valid-annotation-format": "warn",
|
|
86
87
|
"traceability/valid-story-reference": "error",
|
|
87
88
|
"traceability/valid-req-reference": "error",
|
|
89
|
+
"traceability/prefer-implements-annotation": "warn",
|
|
88
90
|
};
|
|
89
91
|
/**
|
|
90
92
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helpers and types for the valid-annotation-format rule.
|
|
3
|
+
*
|
|
4
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
5
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
6
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
7
|
+
* @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
|
|
8
|
+
* @req REQ-FLEXIBLE-PARSING - Support reasonable variations in whitespace and formatting
|
|
9
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
10
|
+
* @req REQ-IMPLEMENTS-PARSE - Parse @implements annotations without affecting @story/@req
|
|
11
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Pending annotation state tracked while iterating through comment lines.
|
|
15
|
+
*/
|
|
16
|
+
export interface PendingAnnotation {
|
|
17
|
+
type: "story" | "req";
|
|
18
|
+
value: string;
|
|
19
|
+
hasValue: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalize a raw comment line to make annotation parsing more robust.
|
|
23
|
+
*
|
|
24
|
+
* This function trims whitespace, keeps any annotation tags that appear
|
|
25
|
+
* later in the line, and supports common JSDoc styles such as leading "*".
|
|
26
|
+
*
|
|
27
|
+
* It detects @story, @req, and @implements tags while preserving the rest
|
|
28
|
+
* of the line for downstream logic.
|
|
29
|
+
*/
|
|
30
|
+
export declare function normalizeCommentLine(rawLine: string): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Internal helpers and types for the valid-annotation-format rule.
|
|
4
|
+
*
|
|
5
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
6
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
7
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
8
|
+
* @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
|
|
9
|
+
* @req REQ-FLEXIBLE-PARSING - Support reasonable variations in whitespace and formatting
|
|
10
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
11
|
+
* @req REQ-IMPLEMENTS-PARSE - Parse @implements annotations without affecting @story/@req
|
|
12
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.normalizeCommentLine = normalizeCommentLine;
|
|
16
|
+
/**
|
|
17
|
+
* Normalize a raw comment line to make annotation parsing more robust.
|
|
18
|
+
*
|
|
19
|
+
* This function trims whitespace, keeps any annotation tags that appear
|
|
20
|
+
* later in the line, and supports common JSDoc styles such as leading "*".
|
|
21
|
+
*
|
|
22
|
+
* It detects @story, @req, and @implements tags while preserving the rest
|
|
23
|
+
* of the line for downstream logic.
|
|
24
|
+
*/
|
|
25
|
+
function normalizeCommentLine(rawLine) {
|
|
26
|
+
const trimmed = rawLine.trim();
|
|
27
|
+
if (!trimmed) {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
const annotationMatch = trimmed.match(/@story\b|@req\b|@implements\b/);
|
|
31
|
+
if (!annotationMatch || annotationMatch.index === undefined) {
|
|
32
|
+
const withoutLeadingStar = trimmed.replace(/^\*\s?/, "");
|
|
33
|
+
return withoutLeadingStar;
|
|
34
|
+
}
|
|
35
|
+
return trimmed.slice(annotationMatch.index);
|
|
36
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for @implements annotation validation used by valid-annotation-format.
|
|
3
|
+
*
|
|
4
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
5
|
+
* @req REQ-IMPLEMENTS-PARSE - Parse @implements annotations without affecting @story/@req
|
|
6
|
+
* @req REQ-FORMAT-VALIDATION - Validate @implements story path and requirement IDs
|
|
7
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
8
|
+
*/
|
|
9
|
+
import type { ResolvedAnnotationOptions } from "./valid-annotation-options";
|
|
10
|
+
/**
|
|
11
|
+
* Minimum number of tokens required for a valid @implements value:
|
|
12
|
+
* - one story path
|
|
13
|
+
* - at least one requirement ID
|
|
14
|
+
*
|
|
15
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
16
|
+
* @req REQ-IMPLEMENTS-PARSE
|
|
17
|
+
*/
|
|
18
|
+
export declare const MIN_IMPLEMENTS_TOKENS = 2;
|
|
19
|
+
/**
|
|
20
|
+
* Report a completely missing @implements value (no story path or req IDs).
|
|
21
|
+
*
|
|
22
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
23
|
+
* @req REQ-FORMAT-VALIDATION
|
|
24
|
+
*/
|
|
25
|
+
export declare function reportMissingImplementsValue(context: any, comment: any, options: ResolvedAnnotationOptions): void;
|
|
26
|
+
/**
|
|
27
|
+
* Report a value that has only a story path and no requirement IDs.
|
|
28
|
+
*
|
|
29
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
30
|
+
* @req REQ-FORMAT-VALIDATION
|
|
31
|
+
*/
|
|
32
|
+
export declare function reportMissingImplementsReqIds(context: any, comment: any, options: ResolvedAnnotationOptions): void;
|
|
33
|
+
/**
|
|
34
|
+
* Report an invalid story path inside @implements.
|
|
35
|
+
*
|
|
36
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
37
|
+
* @req REQ-FORMAT-VALIDATION
|
|
38
|
+
*/
|
|
39
|
+
export declare function reportInvalidImplementsStoryPath(context: any, comment: any, storyPath: string, options: ResolvedAnnotationOptions): void;
|
|
40
|
+
/**
|
|
41
|
+
* Report an invalid requirement ID token inside @implements.
|
|
42
|
+
*
|
|
43
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
44
|
+
* @req REQ-FORMAT-VALIDATION
|
|
45
|
+
* @req REQ-MIXED-SUPPORT
|
|
46
|
+
*/
|
|
47
|
+
export declare function reportInvalidImplementsReqId(context: any, comment: any, reqId: string, options: ResolvedAnnotationOptions): void;
|
|
48
|
+
type ImplementsDeps = {
|
|
49
|
+
MIN_IMPLEMENTS_TOKENS: number;
|
|
50
|
+
reportMissingImplementsValue: typeof reportMissingImplementsValue;
|
|
51
|
+
reportMissingImplementsReqIds: typeof reportMissingImplementsReqIds;
|
|
52
|
+
reportInvalidImplementsStoryPath: typeof reportInvalidImplementsStoryPath;
|
|
53
|
+
reportInvalidImplementsReqId: typeof reportInvalidImplementsReqId;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Validate an @implements annotation value.
|
|
57
|
+
*
|
|
58
|
+
* This helper encapsulates the logic previously in valid-annotation-format.ts:
|
|
59
|
+
* - trims the raw value
|
|
60
|
+
* - splits into tokens
|
|
61
|
+
* - enforces MIN_IMPLEMENTS_TOKENS
|
|
62
|
+
* - validates the story path using options.storyPattern
|
|
63
|
+
* - validates each requirement ID using options.reqPattern
|
|
64
|
+
* - delegates reporting to the provided helpers
|
|
65
|
+
*
|
|
66
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
67
|
+
* @req REQ-IMPLEMENTS-PARSE
|
|
68
|
+
* @req REQ-FORMAT-VALIDATION
|
|
69
|
+
* @req REQ-MIXED-SUPPORT
|
|
70
|
+
*/
|
|
71
|
+
export declare function validateImplementsAnnotationHelper(deps: ImplementsDeps, context: any, comment: any, args: {
|
|
72
|
+
rawValue: string | null | undefined;
|
|
73
|
+
options: ResolvedAnnotationOptions;
|
|
74
|
+
}): void;
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MIN_IMPLEMENTS_TOKENS = void 0;
|
|
4
|
+
exports.reportMissingImplementsValue = reportMissingImplementsValue;
|
|
5
|
+
exports.reportMissingImplementsReqIds = reportMissingImplementsReqIds;
|
|
6
|
+
exports.reportInvalidImplementsStoryPath = reportInvalidImplementsStoryPath;
|
|
7
|
+
exports.reportInvalidImplementsReqId = reportInvalidImplementsReqId;
|
|
8
|
+
exports.validateImplementsAnnotationHelper = validateImplementsAnnotationHelper;
|
|
9
|
+
const valid_annotation_utils_1 = require("./valid-annotation-utils");
|
|
10
|
+
/**
|
|
11
|
+
* Minimum number of tokens required for a valid @implements value:
|
|
12
|
+
* - one story path
|
|
13
|
+
* - at least one requirement ID
|
|
14
|
+
*
|
|
15
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
16
|
+
* @req REQ-IMPLEMENTS-PARSE
|
|
17
|
+
*/
|
|
18
|
+
exports.MIN_IMPLEMENTS_TOKENS = 2;
|
|
19
|
+
/**
|
|
20
|
+
* Report a completely missing @implements value (no story path or req IDs).
|
|
21
|
+
*
|
|
22
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
23
|
+
* @req REQ-FORMAT-VALIDATION
|
|
24
|
+
*/
|
|
25
|
+
function reportMissingImplementsValue(context, comment, options) {
|
|
26
|
+
const { storyExample, reqExample } = options;
|
|
27
|
+
context.report({
|
|
28
|
+
node: comment,
|
|
29
|
+
messageId: "invalidImplementsFormat",
|
|
30
|
+
data: {
|
|
31
|
+
details: `Missing story path and requirement IDs for @implements annotation. Expected a value like "${storyExample} ${reqExample}".`,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Report a value that has only a story path and no requirement IDs.
|
|
37
|
+
*
|
|
38
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
39
|
+
* @req REQ-FORMAT-VALIDATION
|
|
40
|
+
*/
|
|
41
|
+
function reportMissingImplementsReqIds(context, comment, options) {
|
|
42
|
+
const { storyExample, reqExample } = options;
|
|
43
|
+
context.report({
|
|
44
|
+
node: comment,
|
|
45
|
+
messageId: "invalidImplementsFormat",
|
|
46
|
+
data: {
|
|
47
|
+
details: `Missing requirement IDs for @implements annotation. Expected a value like "${storyExample} ${reqExample}".`,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Report an invalid story path inside @implements.
|
|
53
|
+
*
|
|
54
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
55
|
+
* @req REQ-FORMAT-VALIDATION
|
|
56
|
+
*/
|
|
57
|
+
function reportInvalidImplementsStoryPath(context, comment, storyPath, options) {
|
|
58
|
+
const { storyExample } = options;
|
|
59
|
+
context.report({
|
|
60
|
+
node: comment,
|
|
61
|
+
messageId: "invalidImplementsFormat",
|
|
62
|
+
data: {
|
|
63
|
+
details: `Invalid story path "${storyPath}" for @implements annotation. Expected a path like "${storyExample}".`,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Report an invalid requirement ID token inside @implements.
|
|
69
|
+
*
|
|
70
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
71
|
+
* @req REQ-FORMAT-VALIDATION
|
|
72
|
+
* @req REQ-MIXED-SUPPORT
|
|
73
|
+
*/
|
|
74
|
+
function reportInvalidImplementsReqId(context, comment, reqId, options) {
|
|
75
|
+
context.report({
|
|
76
|
+
node: comment,
|
|
77
|
+
messageId: "invalidReqFormat",
|
|
78
|
+
data: {
|
|
79
|
+
details: (0, valid_annotation_utils_1.buildReqErrorMessage)("invalid", reqId, options),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Prepare and validate the token array for an @implements value.
|
|
85
|
+
*
|
|
86
|
+
* Returns { storyPath, reqIds } when tokens are present and structurally valid,
|
|
87
|
+
* or null when a missing-value condition has been reported.
|
|
88
|
+
*/
|
|
89
|
+
function parseImplementsTokens(deps, context, comment, rest) {
|
|
90
|
+
const { MIN_IMPLEMENTS_TOKENS, reportMissingImplementsValue, reportMissingImplementsReqIds, } = deps;
|
|
91
|
+
const { rawValue, options } = rest;
|
|
92
|
+
const value = rawValue?.trim() ?? "";
|
|
93
|
+
if (!value) {
|
|
94
|
+
reportMissingImplementsValue(context, comment, options);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const tokens = value.split(/\s+/);
|
|
98
|
+
if (tokens.length < MIN_IMPLEMENTS_TOKENS) {
|
|
99
|
+
reportMissingImplementsReqIds(context, comment, options);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const [storyPath, ...reqIds] = tokens;
|
|
103
|
+
return { storyPath, reqIds };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Validate the parsed storyPath and reqIds against the provided patterns and
|
|
107
|
+
* delegate reporting of any invalid tokens.
|
|
108
|
+
*/
|
|
109
|
+
function validateImplementsTokens(deps, context, comment, rest) {
|
|
110
|
+
const { reportInvalidImplementsStoryPath, reportInvalidImplementsReqId } = deps;
|
|
111
|
+
const { parsed, options } = rest;
|
|
112
|
+
const { storyPath, reqIds } = parsed;
|
|
113
|
+
if (!options.storyPattern.test(storyPath)) {
|
|
114
|
+
reportInvalidImplementsStoryPath(context, comment, storyPath, options);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
for (const reqId of reqIds) {
|
|
118
|
+
if (!options.reqPattern.test(reqId)) {
|
|
119
|
+
reportInvalidImplementsReqId(context, comment, reqId, options);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Validate an @implements annotation value.
|
|
125
|
+
*
|
|
126
|
+
* This helper encapsulates the logic previously in valid-annotation-format.ts:
|
|
127
|
+
* - trims the raw value
|
|
128
|
+
* - splits into tokens
|
|
129
|
+
* - enforces MIN_IMPLEMENTS_TOKENS
|
|
130
|
+
* - validates the story path using options.storyPattern
|
|
131
|
+
* - validates each requirement ID using options.reqPattern
|
|
132
|
+
* - delegates reporting to the provided helpers
|
|
133
|
+
*
|
|
134
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
135
|
+
* @req REQ-IMPLEMENTS-PARSE
|
|
136
|
+
* @req REQ-FORMAT-VALIDATION
|
|
137
|
+
* @req REQ-MIXED-SUPPORT
|
|
138
|
+
*/
|
|
139
|
+
function validateImplementsAnnotationHelper(deps, context, comment, args) {
|
|
140
|
+
const { rawValue, options } = args;
|
|
141
|
+
const parsed = parseImplementsTokens(deps, context, comment, {
|
|
142
|
+
rawValue,
|
|
143
|
+
options,
|
|
144
|
+
});
|
|
145
|
+
if (!parsed) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
validateImplementsTokens(deps, context, comment, { parsed, options });
|
|
149
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule implementation for preferring the consolidated `@implements`
|
|
3
|
+
* annotation over legacy combinations of `@story` and `@req` within JSDoc
|
|
4
|
+
* block comments. This module provides:
|
|
5
|
+
*
|
|
6
|
+
* - Detection of legacy `@story` + `@req` patterns.
|
|
7
|
+
* - Identification of multi-story comment blocks that are not safely
|
|
8
|
+
* auto-fixable.
|
|
9
|
+
* - A conservative auto-fix that rewrites simple, single-story patterns into
|
|
10
|
+
* a single `@implements` annotation while preserving formatting.
|
|
11
|
+
*
|
|
12
|
+
* The rule is intended as an **optional migration aid** to help projects
|
|
13
|
+
* gradually move to the newer `@implements` format without breaking existing
|
|
14
|
+
* traceability links.
|
|
15
|
+
*
|
|
16
|
+
* @story docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
17
|
+
* @req REQ-OPTIONAL-WARNING - Emit configurable recommendation diagnostics for legacy @story/@req usage
|
|
18
|
+
* @req REQ-MULTI-STORY-DETECT - Detect multi-story patterns that cannot be auto-fixed
|
|
19
|
+
* @req REQ-SINGLE-STORY-FIX - Restrict auto-fix to single-story, single-path cases
|
|
20
|
+
* @req REQ-PRESERVE-FORMAT - Preserve original JSDoc indentation and prefix formatting
|
|
21
|
+
* @req REQ-VALID-OUTPUT - Avoid emitting auto-fixes for complex or ambiguous patterns
|
|
22
|
+
* @req REQ-BACKWARD-COMP-VALIDATION - Keep legacy @story/@req annotations valid when the rule is disabled
|
|
23
|
+
* @req REQ-AUTO-FIX - Provide safe, opt-in auto-fix for simple legacy patterns
|
|
24
|
+
*/
|
|
25
|
+
import type { Rule } from "eslint";
|
|
26
|
+
/**
|
|
27
|
+
* ESLint rule: prefer-implements-annotation
|
|
28
|
+
*
|
|
29
|
+
* Recommend migrating from legacy `@story` + `@req` annotations to the
|
|
30
|
+
* newer `@implements` format. This rule is **disabled by default** and
|
|
31
|
+
* is intended as an optional, opt-in migration aid.
|
|
32
|
+
*
|
|
33
|
+
* @story docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
34
|
+
* @req REQ-OPTIONAL-WARNING - Emit configurable recommendation diagnostics for legacy @story/@req usage
|
|
35
|
+
* @req REQ-MULTI-STORY-DETECT - Detect multi-story patterns that cannot be auto-fixed
|
|
36
|
+
* @req REQ-BACKWARD-COMP-VALIDATION - Keep legacy @story/@req annotations valid when the rule is disabled
|
|
37
|
+
*/
|
|
38
|
+
declare const preferImplementsAnnotationRule: Rule.RuleModule;
|
|
39
|
+
export default preferImplementsAnnotationRule;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const valid_annotation_format_internal_1 = require("./helpers/valid-annotation-format-internal");
|
|
4
|
+
// Maximum number of distinct @story paths allowed before treating as "multi-story".
|
|
5
|
+
// @req REQ-MULTI-STORY-DETECT - Centralized threshold constant for detecting multi-story patterns
|
|
6
|
+
const MULTI_STORY_THRESHOLD = 1;
|
|
7
|
+
// Minimum number of tokens required for a valid @story annotation line.
|
|
8
|
+
// @story docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
9
|
+
// @req REQ-MULTI-STORY-DETECT
|
|
10
|
+
const MIN_STORY_TOKENS = 2;
|
|
11
|
+
// Minimum number of tokens required for a valid @req annotation line, aligned with story tokens.
|
|
12
|
+
const MIN_REQ_TOKENS = MIN_STORY_TOKENS;
|
|
13
|
+
// Length of the opening "/*" portion of a block comment prefix.
|
|
14
|
+
const COMMENT_PREFIX_LENGTH = 2;
|
|
15
|
+
function collectStoryAndReqMetadata(comment) {
|
|
16
|
+
const rawValue = comment.value || "";
|
|
17
|
+
const rawLines = rawValue.split(/\r?\n/);
|
|
18
|
+
const storyLineIndices = [];
|
|
19
|
+
const reqLineIndices = [];
|
|
20
|
+
const reqIds = [];
|
|
21
|
+
let storyPath = null;
|
|
22
|
+
rawLines.forEach((rawLine, index) => {
|
|
23
|
+
const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(rawLine);
|
|
24
|
+
if (!normalized)
|
|
25
|
+
return;
|
|
26
|
+
if (/^@implements\b/.test(normalized)) {
|
|
27
|
+
// Mixed @implements usage should have been filtered out earlier
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (/^@story\b/.test(normalized)) {
|
|
31
|
+
const parts = normalized.split(/\s+/);
|
|
32
|
+
if (parts.length === MIN_STORY_TOKENS) {
|
|
33
|
+
storyLineIndices.push(index);
|
|
34
|
+
storyPath = parts[1];
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
storyPath = null;
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (/^@req\b/.test(normalized)) {
|
|
42
|
+
const parts = normalized.split(/\s+/);
|
|
43
|
+
if (parts.length === MIN_REQ_TOKENS) {
|
|
44
|
+
reqLineIndices.push(index);
|
|
45
|
+
reqIds.push(parts[1]);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Complex @req form; bail out entirely.
|
|
49
|
+
storyPath = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return { storyLineIndices, reqLineIndices, reqIds, storyPath };
|
|
54
|
+
}
|
|
55
|
+
function applyImplementsReplacement(context, comment, details) {
|
|
56
|
+
const { storyIdx, allIndicesToRemove, storyPath, reqIds } = details;
|
|
57
|
+
const rawValue = comment.value || "";
|
|
58
|
+
const rawLines = rawValue.split(/\r?\n/);
|
|
59
|
+
const implAnnotation = `@implements ${storyPath} ${reqIds.join(" ")}`;
|
|
60
|
+
// Determine the leading prefix (indentation and `*`) from the original @story line
|
|
61
|
+
const storyRawLine = rawLines[storyIdx];
|
|
62
|
+
const prefixMatch = storyRawLine.match(/^(\s*\*?\s*)/);
|
|
63
|
+
const linePrefix = prefixMatch ? prefixMatch[1] : "";
|
|
64
|
+
const implementsLine = `${linePrefix}${implAnnotation}`;
|
|
65
|
+
const fixedLines = [];
|
|
66
|
+
rawLines.forEach((line, index) => {
|
|
67
|
+
if (index === storyIdx) {
|
|
68
|
+
fixedLines.push(implementsLine);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (allIndicesToRemove.has(index)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
fixedLines.push(line);
|
|
75
|
+
});
|
|
76
|
+
const fixedValue = fixedLines.join("\n");
|
|
77
|
+
const sourceCode = context.getSourceCode();
|
|
78
|
+
return (fixer) => fixer.replaceTextRange([comment.range[0], comment.range[1]], sourceCode.text.slice(comment.range[0], comment.range[0] + COMMENT_PREFIX_LENGTH) +
|
|
79
|
+
fixedValue +
|
|
80
|
+
"*/");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build an ESLint auto-fix for simple single-story `@story` + `@req` JSDoc
|
|
84
|
+
* blocks, converting them to a single `@implements` annotation while
|
|
85
|
+
* preserving the original comment formatting.
|
|
86
|
+
*
|
|
87
|
+
* The fixer is intentionally conservative and only activates when:
|
|
88
|
+
* - There is exactly one distinct `@story` path.
|
|
89
|
+
* - Exactly one `@story` line is present.
|
|
90
|
+
* - At least one `@req` line is present.
|
|
91
|
+
* - Each `@req` line has the simple form `@req <REQ-ID>` (no extra tokens).
|
|
92
|
+
*
|
|
93
|
+
* When applicable, the fix:
|
|
94
|
+
* - Removes the original `@story` and `@req` lines.
|
|
95
|
+
* - Inserts a single `@implements` line in their place, preserving the
|
|
96
|
+
* original leading comment prefix (indentation and `*` markers).
|
|
97
|
+
*
|
|
98
|
+
* More complex patterns remain diagnostics-only with no fix to avoid
|
|
99
|
+
* producing invalid or ambiguous output.
|
|
100
|
+
*
|
|
101
|
+
* @implements docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
102
|
+
* @req REQ-AUTO-FIX - Provide safe, opt-in auto-fix for simple legacy patterns
|
|
103
|
+
* @req REQ-SINGLE-STORY-FIX - Restrict auto-fix to single-story, single-path cases
|
|
104
|
+
* @req REQ-PRESERVE-FORMAT - Preserve original JSDoc indentation and prefix formatting
|
|
105
|
+
* @req REQ-VALID-OUTPUT - Avoid emitting auto-fixes for complex or ambiguous patterns
|
|
106
|
+
*/
|
|
107
|
+
function buildImplementsAutoFix(context, comment, storyPaths) {
|
|
108
|
+
if (storyPaths.size !== 1)
|
|
109
|
+
return null;
|
|
110
|
+
const { storyLineIndices, reqLineIndices, reqIds, storyPath } = collectStoryAndReqMetadata(comment);
|
|
111
|
+
if (storyPaths.size !== 1 ||
|
|
112
|
+
storyLineIndices.length !== 1 ||
|
|
113
|
+
reqLineIndices.length < 1 ||
|
|
114
|
+
storyPath === null) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const storyIdx = storyLineIndices[0];
|
|
118
|
+
const allIndicesToRemove = new Set([
|
|
119
|
+
...storyLineIndices,
|
|
120
|
+
...reqLineIndices,
|
|
121
|
+
]);
|
|
122
|
+
return applyImplementsReplacement(context, comment, {
|
|
123
|
+
storyIdx,
|
|
124
|
+
allIndicesToRemove,
|
|
125
|
+
storyPath,
|
|
126
|
+
reqIds,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function analyzeComment(comment) {
|
|
130
|
+
const rawLines = (comment.value || "").split(/\r?\n/);
|
|
131
|
+
let hasStory = false;
|
|
132
|
+
let hasReq = false;
|
|
133
|
+
let hasImplements = false;
|
|
134
|
+
const storyPaths = new Set();
|
|
135
|
+
rawLines.forEach((rawLine) => {
|
|
136
|
+
const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(rawLine);
|
|
137
|
+
if (!normalized)
|
|
138
|
+
return;
|
|
139
|
+
if (/^@implements\b/.test(normalized)) {
|
|
140
|
+
hasImplements = true;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (/^@story\b/.test(normalized)) {
|
|
144
|
+
hasStory = true;
|
|
145
|
+
const parts = normalized.split(/\s+/);
|
|
146
|
+
if (parts.length >= MIN_STORY_TOKENS) {
|
|
147
|
+
storyPaths.add(parts[1]);
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (/^@req\b/.test(normalized)) {
|
|
152
|
+
hasReq = true;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
return { hasStory, hasReq, hasImplements, storyPaths };
|
|
156
|
+
}
|
|
157
|
+
function hasMultipleStories(storyPaths) {
|
|
158
|
+
// @req REQ-MULTI-STORY-DETECT - Use named threshold constant instead of a magic number
|
|
159
|
+
return storyPaths.size > MULTI_STORY_THRESHOLD;
|
|
160
|
+
}
|
|
161
|
+
function processComment(comment, context) {
|
|
162
|
+
const { hasStory, hasReq, hasImplements, storyPaths } = analyzeComment(comment);
|
|
163
|
+
if (!hasStory || !hasReq) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (hasImplements) {
|
|
167
|
+
context.report({
|
|
168
|
+
node: comment,
|
|
169
|
+
messageId: "cannotAutoFix",
|
|
170
|
+
data: {
|
|
171
|
+
reason: "comment mixes @story/@req with existing @implements annotations",
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (hasMultipleStories(storyPaths)) {
|
|
177
|
+
context.report({
|
|
178
|
+
node: comment,
|
|
179
|
+
messageId: "multiStoryDetected",
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const fix = buildImplementsAutoFix(context, comment, storyPaths);
|
|
184
|
+
context.report({
|
|
185
|
+
node: comment,
|
|
186
|
+
messageId: "preferImplements",
|
|
187
|
+
fix: fix ?? undefined,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* ESLint rule: prefer-implements-annotation
|
|
192
|
+
*
|
|
193
|
+
* Recommend migrating from legacy `@story` + `@req` annotations to the
|
|
194
|
+
* newer `@implements` format. This rule is **disabled by default** and
|
|
195
|
+
* is intended as an optional, opt-in migration aid.
|
|
196
|
+
*
|
|
197
|
+
* @story docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
198
|
+
* @req REQ-OPTIONAL-WARNING - Emit configurable recommendation diagnostics for legacy @story/@req usage
|
|
199
|
+
* @req REQ-MULTI-STORY-DETECT - Detect multi-story patterns that cannot be auto-fixed
|
|
200
|
+
* @req REQ-BACKWARD-COMP-VALIDATION - Keep legacy @story/@req annotations valid when the rule is disabled
|
|
201
|
+
*/
|
|
202
|
+
const preferImplementsAnnotationRule = {
|
|
203
|
+
meta: {
|
|
204
|
+
type: "suggestion",
|
|
205
|
+
docs: {
|
|
206
|
+
description: "Recommend using @implements instead of legacy @story + @req annotations (optional migration rule)",
|
|
207
|
+
recommended: false,
|
|
208
|
+
},
|
|
209
|
+
// Auto-fix support will be wired in a later iteration; the rule starts as
|
|
210
|
+
// a recommendation-only warning with no code modifications.
|
|
211
|
+
fixable: "code",
|
|
212
|
+
messages: {
|
|
213
|
+
/**
|
|
214
|
+
* Recommend migrating simple, single-story @story + @req blocks to a
|
|
215
|
+
* single @implements line. Auto-fix is provided where safe in a
|
|
216
|
+
* follow-up iteration.
|
|
217
|
+
*
|
|
218
|
+
* @story docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
219
|
+
* @req REQ-OPTIONAL-WARNING
|
|
220
|
+
*/
|
|
221
|
+
preferImplements: "Consider using @implements instead of @story + @req for clearer traceability. Run ESLint with --fix to auto-convert.",
|
|
222
|
+
/**
|
|
223
|
+
* Report situations where the rule detects a legacy annotation pattern
|
|
224
|
+
* but cannot safely provide an automatic fix. The `reason` field gives
|
|
225
|
+
* a short, human-readable explanation to guide manual migration.
|
|
226
|
+
*
|
|
227
|
+
* @story docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
228
|
+
* @req REQ-MULTI-STORY-DETECT
|
|
229
|
+
*/
|
|
230
|
+
cannotAutoFix: "Cannot auto-fix: {{reason}}. Manual migration to @implements required.",
|
|
231
|
+
/**
|
|
232
|
+
* Specialized message for the most common non-fixable case where more
|
|
233
|
+
* than one @story annotation appears in the same block, indicating a
|
|
234
|
+
* likely multi-story integration that must be converted manually.
|
|
235
|
+
*
|
|
236
|
+
* @story docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
237
|
+
* @req REQ-MULTI-STORY-DETECT
|
|
238
|
+
*/
|
|
239
|
+
multiStoryDetected: "Multiple @story annotations detected in the same comment block. Manually convert to separate @implements lines.",
|
|
240
|
+
},
|
|
241
|
+
schema: [],
|
|
242
|
+
},
|
|
243
|
+
/**
|
|
244
|
+
* Rule entrypoint.
|
|
245
|
+
*
|
|
246
|
+
* This initial implementation focuses on **detection and messaging only**:
|
|
247
|
+
* it surfaces recommendations when legacy `@story` + `@req` combinations are
|
|
248
|
+
* present but does not yet perform automatic code modifications.
|
|
249
|
+
*
|
|
250
|
+
* @story docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
251
|
+
* @req REQ-OPTIONAL-WARNING
|
|
252
|
+
* @req REQ-MULTI-STORY-DETECT
|
|
253
|
+
*/
|
|
254
|
+
create(context) {
|
|
255
|
+
const sourceCode = context.getSourceCode();
|
|
256
|
+
return {
|
|
257
|
+
/**
|
|
258
|
+
* Program-level visitor that scans all comments for legacy
|
|
259
|
+
* `@story` + `@req` usage and emits recommendation diagnostics.
|
|
260
|
+
*
|
|
261
|
+
* @story docs/stories/010.3-DEV-MIGRATE-TO-IMPLEMENTS.story.md
|
|
262
|
+
* @req REQ-OPTIONAL-WARNING - Emit recommendations when legacy annotations are detected
|
|
263
|
+
* @req REQ-MULTI-STORY-DETECT - Detect multi-story and mixed annotation patterns
|
|
264
|
+
*/
|
|
265
|
+
Program() {
|
|
266
|
+
const comments = sourceCode.getAllComments() || [];
|
|
267
|
+
comments
|
|
268
|
+
.filter((comment) => comment.type === "Block")
|
|
269
|
+
.forEach((comment) => {
|
|
270
|
+
processComment(comment, context);
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
exports.default = preferImplementsAnnotationRule;
|
|
@@ -2,34 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const valid_annotation_options_1 = require("./helpers/valid-annotation-options");
|
|
4
4
|
const valid_annotation_utils_1 = require("./helpers/valid-annotation-utils");
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* This function trims whitespace, keeps any annotation tags that appear
|
|
9
|
-
* later in the line, and supports common JSDoc styles such as leading "*".
|
|
10
|
-
*
|
|
11
|
-
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
12
|
-
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
13
|
-
* @req REQ-FLEXIBLE-PARSING - Support reasonable variations in whitespace and formatting
|
|
14
|
-
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
15
|
-
*/
|
|
16
|
-
function normalizeCommentLine(rawLine) {
|
|
17
|
-
const trimmed = rawLine.trim();
|
|
18
|
-
if (!trimmed) {
|
|
19
|
-
return "";
|
|
20
|
-
}
|
|
21
|
-
const annotationMatch = trimmed.match(/@story\b|@req\b/);
|
|
22
|
-
if (!annotationMatch || annotationMatch.index === undefined) {
|
|
23
|
-
const withoutLeadingStar = trimmed.replace(/^\*\s?/, "");
|
|
24
|
-
return withoutLeadingStar;
|
|
25
|
-
}
|
|
26
|
-
return trimmed.slice(annotationMatch.index);
|
|
27
|
-
}
|
|
5
|
+
const valid_implements_utils_1 = require("./helpers/valid-implements-utils");
|
|
6
|
+
const valid_annotation_format_internal_1 = require("./helpers/valid-annotation-format-internal");
|
|
28
7
|
/**
|
|
29
8
|
* Report an invalid @story annotation without applying a fix.
|
|
30
9
|
*
|
|
31
10
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
32
11
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
12
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
33
13
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
34
14
|
*/
|
|
35
15
|
function reportInvalidStoryFormat(context, comment, collapsed, options) {
|
|
@@ -91,6 +71,7 @@ function createStoryFix(context, comment, fixed) {
|
|
|
91
71
|
*
|
|
92
72
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
93
73
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
74
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
94
75
|
* @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
|
|
95
76
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
96
77
|
* @req REQ-AUTOFIX-SAFE - Auto-fix must be conservative and avoid changing semantics
|
|
@@ -117,11 +98,13 @@ function reportInvalidStoryFormatWithFix(context, comment, collapsed, fixed) {
|
|
|
117
98
|
*
|
|
118
99
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
119
100
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
101
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
120
102
|
* @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
|
|
121
103
|
* @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
|
|
122
104
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
123
105
|
* @req REQ-REGEX-VALIDATION - Validate configurable story regex patterns and fall back safely
|
|
124
106
|
* @req REQ-BACKWARD-COMP - Preserve behavior when invalid regex config is supplied
|
|
107
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
125
108
|
*/
|
|
126
109
|
function validateStoryAnnotation(context, comment, rawValue, options) {
|
|
127
110
|
const trimmed = rawValue.trim();
|
|
@@ -154,10 +137,12 @@ function validateStoryAnnotation(context, comment, rawValue, options) {
|
|
|
154
137
|
*
|
|
155
138
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
156
139
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
140
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
157
141
|
* @req REQ-REQ-FORMAT - Validate @req identifiers follow expected patterns
|
|
158
142
|
* @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
|
|
159
143
|
* @req REQ-REGEX-VALIDATION - Validate configurable requirement regex patterns and fall back safely
|
|
160
144
|
* @req REQ-BACKWARD-COMP - Preserve behavior when invalid regex config is supplied
|
|
145
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
161
146
|
*/
|
|
162
147
|
function validateReqAnnotation(context, comment, rawValue, options) {
|
|
163
148
|
const trimmed = rawValue.trim();
|
|
@@ -179,13 +164,47 @@ function validateReqAnnotation(context, comment, rawValue, options) {
|
|
|
179
164
|
});
|
|
180
165
|
}
|
|
181
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Validate an @implements annotation value and report detailed errors when needed.
|
|
169
|
+
*
|
|
170
|
+
* Expected format:
|
|
171
|
+
* @implements <storyPath> <REQ-ID> [<REQ-ID> ...]
|
|
172
|
+
*
|
|
173
|
+
* Validation rules:
|
|
174
|
+
* - Value must include at least a story path and one requirement ID.
|
|
175
|
+
* - Story path must match the same storyPattern used for @story (no auto-fix).
|
|
176
|
+
* - Each subsequent token must match reqPattern and is validated individually.
|
|
177
|
+
*
|
|
178
|
+
* Story path issues are reported with "invalidImplementsFormat" and
|
|
179
|
+
* requirement ID issues reuse the existing "invalidReqFormat" message.
|
|
180
|
+
*
|
|
181
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
182
|
+
* @req REQ-IMPLEMENTS-PARSE - Parse @implements annotations without affecting @story/@req
|
|
183
|
+
* @req REQ-FORMAT-VALIDATION - Validate @implements story path and requirement IDs
|
|
184
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
185
|
+
*/
|
|
186
|
+
function validateImplementsAnnotation(context, comment, rawValue, options) {
|
|
187
|
+
const deps = {
|
|
188
|
+
MIN_IMPLEMENTS_TOKENS: valid_implements_utils_1.MIN_IMPLEMENTS_TOKENS,
|
|
189
|
+
reportMissingImplementsReqIds: valid_implements_utils_1.reportMissingImplementsReqIds,
|
|
190
|
+
reportMissingImplementsValue: valid_implements_utils_1.reportMissingImplementsValue,
|
|
191
|
+
reportInvalidImplementsReqId: valid_implements_utils_1.reportInvalidImplementsReqId,
|
|
192
|
+
reportInvalidImplementsStoryPath: valid_implements_utils_1.reportInvalidImplementsStoryPath,
|
|
193
|
+
};
|
|
194
|
+
(0, valid_implements_utils_1.validateImplementsAnnotationHelper)(deps, context, comment, {
|
|
195
|
+
rawValue,
|
|
196
|
+
options,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
182
199
|
/**
|
|
183
200
|
* Finalize and validate the currently pending annotation, if any.
|
|
184
201
|
*
|
|
185
202
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
186
203
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
204
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
187
205
|
* @req REQ-SYNTAX-VALIDATION - Validate annotation syntax matches specification
|
|
188
206
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
207
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
189
208
|
*/
|
|
190
209
|
function finalizePendingAnnotation(context, comment, options, pending) {
|
|
191
210
|
if (!pending) {
|
|
@@ -193,8 +212,10 @@ function finalizePendingAnnotation(context, comment, options, pending) {
|
|
|
193
212
|
}
|
|
194
213
|
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
195
214
|
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
215
|
+
// @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
196
216
|
// @req REQ-SYNTAX-VALIDATION - Dispatch validation based on annotation type
|
|
197
217
|
// @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
218
|
+
// @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
198
219
|
if (pending.type === "story") {
|
|
199
220
|
validateStoryAnnotation(context, comment, pending.value, options);
|
|
200
221
|
}
|
|
@@ -208,9 +229,13 @@ function finalizePendingAnnotation(context, comment, options, pending) {
|
|
|
208
229
|
*
|
|
209
230
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
210
231
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
232
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
211
233
|
* @req REQ-SYNTAX-VALIDATION - Start new pending annotation when a tag is found
|
|
212
234
|
* @req REQ-MULTILINE-SUPPORT - Treat subsequent lines as continuation for pending annotation
|
|
213
235
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
236
|
+
* @req REQ-IMPLEMENTS-PARSE - Parse @implements annotations without affecting @story/@req
|
|
237
|
+
* @req REQ-FORMAT-VALIDATION - Validate @implements story path and requirement IDs
|
|
238
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
214
239
|
*/
|
|
215
240
|
function processCommentLine({ normalized, pending, context, comment, options, }) {
|
|
216
241
|
if (!normalized) {
|
|
@@ -218,10 +243,19 @@ function processCommentLine({ normalized, pending, context, comment, options, })
|
|
|
218
243
|
}
|
|
219
244
|
const isStory = /@story\b/.test(normalized);
|
|
220
245
|
const isReq = /@req\b/.test(normalized);
|
|
246
|
+
const isImplements = /@implements\b/.test(normalized);
|
|
247
|
+
// Handle @implements as an immediate, single-line annotation
|
|
248
|
+
if (isImplements) {
|
|
249
|
+
const implementsValue = normalized.replace(/^@implements\b/, "").trim();
|
|
250
|
+
validateImplementsAnnotation(context, comment, implementsValue, options);
|
|
251
|
+
return pending;
|
|
252
|
+
}
|
|
221
253
|
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
222
254
|
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
255
|
+
// @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
223
256
|
// @req REQ-SYNTAX-VALIDATION - Start new pending annotation when a tag is found
|
|
224
257
|
// @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
258
|
+
// @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
225
259
|
if (isStory || isReq) {
|
|
226
260
|
finalizePendingAnnotation(context, comment, options, pending);
|
|
227
261
|
const value = normalized.replace(/^@story\b|^@req\b/, "").trim();
|
|
@@ -233,8 +267,10 @@ function processCommentLine({ normalized, pending, context, comment, options, })
|
|
|
233
267
|
}
|
|
234
268
|
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
235
269
|
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
270
|
+
// @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
236
271
|
// @req REQ-MULTILINE-SUPPORT - Treat subsequent lines as continuation for pending annotation
|
|
237
272
|
// @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
273
|
+
// @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
238
274
|
if (pending) {
|
|
239
275
|
const continuation = normalized.trim();
|
|
240
276
|
if (!continuation) {
|
|
@@ -252,23 +288,30 @@ function processCommentLine({ normalized, pending, context, comment, options, })
|
|
|
252
288
|
return pending;
|
|
253
289
|
}
|
|
254
290
|
/**
|
|
255
|
-
* Process a single comment node and validate any @story/@req annotations it contains.
|
|
291
|
+
* Process a single comment node and validate any @story/@req/@implements annotations it contains.
|
|
256
292
|
*
|
|
257
|
-
* Supports annotations whose values span multiple lines within the same
|
|
293
|
+
* Supports @story and @req annotations whose values span multiple lines within the same
|
|
258
294
|
* comment block, collapsing whitespace so that the logical value can be
|
|
259
295
|
* validated against the configured patterns.
|
|
260
296
|
*
|
|
297
|
+
* @implements annotations are validated immediately per-line and are not
|
|
298
|
+
* accumulated into pending multi-line state.
|
|
299
|
+
*
|
|
261
300
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
262
301
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
302
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
263
303
|
* @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
|
|
264
304
|
* @req REQ-FLEXIBLE-PARSING - Support reasonable variations in whitespace and formatting
|
|
265
305
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
306
|
+
* @req REQ-IMPLEMENTS-PARSE - Parse @implements annotations without affecting @story/@req
|
|
307
|
+
* @req REQ-FORMAT-VALIDATION - Validate @implements story path and requirement IDs
|
|
308
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
266
309
|
*/
|
|
267
310
|
function processComment(context, comment, options) {
|
|
268
311
|
const rawLines = (comment.value || "").split(/\r?\n/);
|
|
269
312
|
let pending = null;
|
|
270
313
|
rawLines.forEach((rawLine) => {
|
|
271
|
-
const normalized = normalizeCommentLine(rawLine);
|
|
314
|
+
const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(rawLine);
|
|
272
315
|
pending = processCommentLine({
|
|
273
316
|
normalized,
|
|
274
317
|
pending,
|
|
@@ -283,7 +326,7 @@ exports.default = {
|
|
|
283
326
|
meta: {
|
|
284
327
|
type: "problem",
|
|
285
328
|
docs: {
|
|
286
|
-
description: "Validate format and syntax of @story and @
|
|
329
|
+
description: "Validate format and syntax of @story, @req, and @implements annotations",
|
|
287
330
|
recommended: "error",
|
|
288
331
|
},
|
|
289
332
|
messages: {
|
|
@@ -301,6 +344,14 @@ exports.default = {
|
|
|
301
344
|
* @req REQ-ERROR-CONSISTENCY - Use shared "Invalid annotation format: {{details}}." message pattern across rules
|
|
302
345
|
*/
|
|
303
346
|
invalidReqFormat: "Invalid annotation format: {{details}}.",
|
|
347
|
+
/**
|
|
348
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
349
|
+
* @req REQ-ERROR-SPECIFIC - Provide specific details about invalid @implements annotation format
|
|
350
|
+
* @req REQ-ERROR-CONTEXT - Include human-readable details about the expected @implements annotation format
|
|
351
|
+
* @req REQ-ERROR-CONSISTENCY - Use shared "Invalid annotation format: {{details}}." message pattern across rules
|
|
352
|
+
* @req REQ-FORMAT-VALIDATION - Validate @implements story path and requirement IDs
|
|
353
|
+
*/
|
|
354
|
+
invalidImplementsFormat: "Invalid annotation format: {{details}}.",
|
|
304
355
|
/**
|
|
305
356
|
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
306
357
|
* @req REQ-REGEX-VALIDATION - Surface configuration errors for invalid regex patterns
|
|
@@ -326,11 +377,15 @@ exports.default = {
|
|
|
326
377
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
327
378
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
328
379
|
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
380
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
329
381
|
* @req REQ-SYNTAX-VALIDATION - Ensure rule create function validates annotations syntax
|
|
330
382
|
* @req REQ-FORMAT-SPECIFICATION - Implement formatting checks per specification
|
|
331
383
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
332
384
|
* @req REQ-REGEX-VALIDATION - Derive validation regexes from shared options helper
|
|
333
385
|
* @req REQ-BACKWARD-COMP - Fall back to default patterns and continue validation on config errors
|
|
386
|
+
* @req REQ-IMPLEMENTS-PARSE - Parse @implements annotations without affecting @story/@req
|
|
387
|
+
* @req REQ-FORMAT-VALIDATION - Validate @implements story path and requirement IDs
|
|
388
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
334
389
|
*/
|
|
335
390
|
create(context) {
|
|
336
391
|
const sourceCode = context.getSourceCode();
|
|
@@ -338,16 +393,20 @@ exports.default = {
|
|
|
338
393
|
const optionErrors = (0, valid_annotation_options_1.getOptionErrors)();
|
|
339
394
|
return {
|
|
340
395
|
/**
|
|
341
|
-
* Program-level handler that inspects all comments for @story and @
|
|
396
|
+
* Program-level handler that inspects all comments for @story, @req, and @implements tags
|
|
342
397
|
*
|
|
343
398
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
344
399
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
345
400
|
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
401
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
346
402
|
* @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
|
|
347
403
|
* @req REQ-REQ-FORMAT - Validate @req identifiers follow expected patterns
|
|
348
404
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
349
405
|
* @req REQ-REGEX-VALIDATION - Surface regex configuration errors without blocking validation
|
|
350
406
|
* @req REQ-BACKWARD-COMP - Continue validating comments using default patterns on error
|
|
407
|
+
* @req REQ-IMPLEMENTS-PARSE - Parse @implements annotations without affecting @story/@req
|
|
408
|
+
* @req REQ-FORMAT-VALIDATION - Validate @implements story path and requirement IDs
|
|
409
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
351
410
|
*/
|
|
352
411
|
Program(node) {
|
|
353
412
|
if (optionErrors && optionErrors.length > 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
|
/**
|
|
@@ -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
|
+
});
|
|
@@ -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
|
});
|
|
@@ -32,6 +32,15 @@ describe("Valid Req Reference Rule (Story 010.0-DEV-DEEP-VALIDATION)", () => {
|
|
|
32
32
|
code: `// @story tests/fixtures/story_bullet.md
|
|
33
33
|
// @req REQ-BULLET-LIST`,
|
|
34
34
|
},
|
|
35
|
+
{
|
|
36
|
+
name: "[REQ-DEEP-IMPLEMENTS] single implements line with multiple requirements in multi-story fixture (see 010.2-DEV-MULTI-STORY-SUPPORT)",
|
|
37
|
+
code: `// @implements tests/fixtures/story_multi_a.md REQ-SHARED-ID REQ-ONLY-A`,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "[REQ-DEEP-IMPLEMENTS] multi-story implements with shared requirement IDs (see 010.2-DEV-MULTI-STORY-SUPPORT)",
|
|
41
|
+
code: `// @implements tests/fixtures/story_multi_a.md REQ-SHARED-ID REQ-ONLY-A
|
|
42
|
+
// @implements tests/fixtures/story_multi_b.md REQ-SHARED-ID REQ-ONLY-B`,
|
|
43
|
+
},
|
|
35
44
|
],
|
|
36
45
|
invalid: [
|
|
37
46
|
{
|
|
@@ -88,6 +97,31 @@ describe("Valid Req Reference Rule (Story 010.0-DEV-DEEP-VALIDATION)", () => {
|
|
|
88
97
|
},
|
|
89
98
|
],
|
|
90
99
|
},
|
|
100
|
+
{
|
|
101
|
+
name: "[REQ-DEEP-IMPLEMENTS] missing implements requirement in multi-story fixture (see 010.2-DEV-MULTI-STORY-SUPPORT)",
|
|
102
|
+
code: `// @implements tests/fixtures/story_multi_a.md REQ-NOT-IN-A`,
|
|
103
|
+
errors: [
|
|
104
|
+
{
|
|
105
|
+
messageId: "reqMissing",
|
|
106
|
+
data: {
|
|
107
|
+
reqId: "REQ-NOT-IN-A",
|
|
108
|
+
storyPath: "tests/fixtures/story_multi_a.md",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "[REQ-DEEP-IMPLEMENTS] disallow path traversal in implements story path (see 010.2-DEV-MULTI-STORY-SUPPORT)",
|
|
115
|
+
code: `// @implements ../tests/fixtures/story_multi_a.md REQ-SHARED-ID`,
|
|
116
|
+
errors: [
|
|
117
|
+
{
|
|
118
|
+
messageId: "invalidPath",
|
|
119
|
+
data: {
|
|
120
|
+
storyPath: "../tests/fixtures/story_multi_a.md",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
91
125
|
],
|
|
92
126
|
});
|
|
93
127
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.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",
|