eslint-plugin-traceability 1.5.1 → 1.6.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 +2 -0
- package/lib/src/rules/helpers/require-story-helpers.d.ts +4 -0
- package/lib/src/rules/helpers/require-story-helpers.js +6 -0
- package/lib/src/rules/require-branch-annotation.d.ts +11 -4
- package/lib/src/rules/require-branch-annotation.js +18 -4
- package/lib/src/rules/require-req-annotation.d.ts +9 -4
- package/lib/src/rules/require-req-annotation.js +69 -21
- package/lib/src/rules/require-story-annotation.d.ts +6 -0
- package/lib/src/rules/require-story-annotation.js +15 -1
- package/lib/src/rules/valid-annotation-format.js +154 -6
- package/lib/src/rules/valid-req-reference.js +30 -51
- package/lib/src/utils/annotation-checker.d.ts +7 -1
- package/lib/src/utils/annotation-checker.js +51 -8
- package/lib/tests/rules/auto-fix-behavior-008.test.d.ts +1 -0
- package/lib/tests/rules/auto-fix-behavior-008.test.js +160 -0
- package/lib/tests/rules/error-reporting.test.js +1 -0
- package/lib/tests/rules/require-req-annotation.test.js +139 -4
- package/lib/tests/rules/require-story-annotation.test.js +7 -0
- package/lib/tests/rules/valid-annotation-format.test.js +2 -0
- package/package.json +3 -4
|
@@ -7,21 +7,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
/**
|
|
8
8
|
* Rule to validate @req annotation references refer to existing requirements in story files
|
|
9
9
|
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
10
|
-
* @req REQ-DEEP-PARSE - Parse
|
|
11
|
-
* @req REQ-DEEP-MATCH -
|
|
12
|
-
* @req REQ-DEEP-CACHE - Cache
|
|
13
|
-
* @req REQ-DEEP-PATH -
|
|
10
|
+
* @req REQ-DEEP-PARSE - Parse comments and extract story/requirement metadata
|
|
11
|
+
* @req REQ-DEEP-MATCH - Match @req annotations to story file requirements
|
|
12
|
+
* @req REQ-DEEP-CACHE - Cache requirement IDs per story file for efficient validation
|
|
13
|
+
* @req REQ-DEEP-PATH - Validate and resolve story file paths safely
|
|
14
14
|
*/
|
|
15
15
|
const fs_1 = __importDefault(require("fs"));
|
|
16
16
|
const path_1 = __importDefault(require("path"));
|
|
17
17
|
/**
|
|
18
18
|
* Extract the story path from a JSDoc comment.
|
|
19
|
-
* Parses comment.value lines for @story annotation.
|
|
20
|
-
* @param comment any JSDoc comment node
|
|
21
|
-
* @returns story path or null if not found
|
|
22
|
-
*
|
|
23
19
|
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
24
|
-
* @req REQ-DEEP-PARSE -
|
|
20
|
+
* @req REQ-DEEP-PARSE - Parse JSDoc comment lines to locate @story annotations
|
|
25
21
|
*/
|
|
26
22
|
function extractStoryPath(comment) {
|
|
27
23
|
const rawLines = comment.value.split(/\r?\n/);
|
|
@@ -37,14 +33,11 @@ function extractStoryPath(comment) {
|
|
|
37
33
|
/**
|
|
38
34
|
* Validate a @req annotation line against the extracted story content.
|
|
39
35
|
* Performs path validation, file reading, caching, and requirement existence checks.
|
|
40
|
-
*
|
|
41
|
-
* @param opts options bag
|
|
42
|
-
*
|
|
43
36
|
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
44
|
-
* @req REQ-DEEP-PATH -
|
|
45
|
-
* @req REQ-DEEP-CACHE -
|
|
46
|
-
* @req REQ-DEEP-MATCH -
|
|
47
|
-
* @req REQ-DEEP-PARSE -
|
|
37
|
+
* @req REQ-DEEP-PATH - Validate and resolve referenced story file paths
|
|
38
|
+
* @req REQ-DEEP-CACHE - Cache requirement IDs discovered in story files
|
|
39
|
+
* @req REQ-DEEP-MATCH - Verify that a referenced requirement ID exists in the story
|
|
40
|
+
* @req REQ-DEEP-PARSE - Parse story file contents to extract requirement identifiers
|
|
48
41
|
*/
|
|
49
42
|
function validateReqLine(opts) {
|
|
50
43
|
const { comment, context, line, storyPath, cwd, reqCache } = opts;
|
|
@@ -96,15 +89,10 @@ function validateReqLine(opts) {
|
|
|
96
89
|
}
|
|
97
90
|
}
|
|
98
91
|
/**
|
|
99
|
-
* Handle a single annotation line.
|
|
100
|
-
* @story Updates the current story path when encountering an @story annotation
|
|
101
|
-
* @req Validates the requirement reference against the current story content
|
|
102
|
-
*
|
|
103
|
-
* @param opts handler options
|
|
104
|
-
*
|
|
92
|
+
* Handle a single annotation line for story or requirement metadata.
|
|
105
93
|
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
106
|
-
* @req REQ-DEEP-PARSE -
|
|
107
|
-
* @req REQ-DEEP-MATCH -
|
|
94
|
+
* @req REQ-DEEP-PARSE - Parse annotation lines for @story and @req tags
|
|
95
|
+
* @req REQ-DEEP-MATCH - Dispatch @req lines for validation against story requirements
|
|
108
96
|
*/
|
|
109
97
|
function handleAnnotationLine(opts) {
|
|
110
98
|
const { line, comment, context, cwd, reqCache, storyPath } = opts;
|
|
@@ -119,14 +107,11 @@ function handleAnnotationLine(opts) {
|
|
|
119
107
|
return storyPath;
|
|
120
108
|
}
|
|
121
109
|
/**
|
|
122
|
-
* Handle JSDoc story and req annotations.
|
|
123
|
-
*
|
|
124
|
-
* @param opts options for comment handling
|
|
125
|
-
*
|
|
110
|
+
* Handle JSDoc story and req annotations for a single comment block.
|
|
126
111
|
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
127
|
-
* @req REQ-DEEP-PARSE -
|
|
128
|
-
* @req REQ-DEEP-MATCH -
|
|
129
|
-
* @req REQ-DEEP-CACHE -
|
|
112
|
+
* @req REQ-DEEP-PARSE - Iterate comment lines to process @story/@req annotations
|
|
113
|
+
* @req REQ-DEEP-MATCH - Coordinate annotation handling across a comment block
|
|
114
|
+
* @req REQ-DEEP-CACHE - Maintain and reuse discovered story path across comments
|
|
130
115
|
*/
|
|
131
116
|
function handleComment(opts) {
|
|
132
117
|
const { comment, context, cwd, reqCache, rawStoryPath } = opts;
|
|
@@ -147,30 +132,25 @@ function handleComment(opts) {
|
|
|
147
132
|
}
|
|
148
133
|
/**
|
|
149
134
|
* Create a Program listener that iterates comments and validates annotations.
|
|
150
|
-
*
|
|
151
|
-
* @param context ESLint rule context
|
|
152
|
-
* @returns Program visitor function
|
|
153
|
-
*
|
|
154
135
|
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
155
|
-
* @req REQ-DEEP-CACHE -
|
|
156
|
-
* @req REQ-DEEP-PATH -
|
|
136
|
+
* @req REQ-DEEP-CACHE - Initialize and share a requirement cache for the program
|
|
137
|
+
* @req REQ-DEEP-PATH - Derive the working directory context for path resolution
|
|
157
138
|
*/
|
|
158
139
|
function programListener(context) {
|
|
159
140
|
const sourceCode = context.getSourceCode();
|
|
160
141
|
const cwd = process.cwd();
|
|
161
142
|
const reqCache = new Map();
|
|
162
143
|
let rawStoryPath = null;
|
|
144
|
+
/**
|
|
145
|
+
* Program visitor that walks all comments to validate story/requirement references.
|
|
146
|
+
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
147
|
+
* @req REQ-DEEP-PARSE - Collect all comments from the source code
|
|
148
|
+
* @req REQ-DEEP-MATCH - Drive comment-level handling for traceability checks
|
|
149
|
+
* @req REQ-DEEP-CACHE - Reuse story path and requirement cache across comments
|
|
150
|
+
* @req REQ-DEEP-PATH - Ensure validation respects project-relative paths
|
|
151
|
+
*/
|
|
163
152
|
return function Program() {
|
|
164
153
|
const comments = sourceCode.getAllComments() || [];
|
|
165
|
-
/**
|
|
166
|
-
* Process each comment to handle story and requirement annotations.
|
|
167
|
-
*
|
|
168
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
169
|
-
* @req REQ-DEEP-PARSE - Parse annotations from comment blocks
|
|
170
|
-
* @req REQ-DEEP-MATCH - Validate @req references found in comments
|
|
171
|
-
* @req REQ-DEEP-CACHE - Use cache for parsed story files to avoid repeated IO
|
|
172
|
-
* @req REQ-DEEP-PATH - Enforce path validation when resolving story files
|
|
173
|
-
*/
|
|
174
154
|
comments.forEach((comment) => {
|
|
175
155
|
rawStoryPath = handleComment({
|
|
176
156
|
comment,
|
|
@@ -197,12 +177,11 @@ exports.default = {
|
|
|
197
177
|
},
|
|
198
178
|
/**
|
|
199
179
|
* Rule create entrypoint that returns the Program visitor.
|
|
200
|
-
*
|
|
201
180
|
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
202
|
-
* @req REQ-DEEP-MATCH -
|
|
203
|
-
* @req REQ-DEEP-PARSE -
|
|
204
|
-
* @req REQ-DEEP-CACHE -
|
|
205
|
-
* @req REQ-DEEP-PATH -
|
|
181
|
+
* @req REQ-DEEP-MATCH - Register the Program visitor with ESLint
|
|
182
|
+
* @req REQ-DEEP-PARSE - Integrate comment parsing into the ESLint rule lifecycle
|
|
183
|
+
* @req REQ-DEEP-CACHE - Ensure cache and context are wired into the listener
|
|
184
|
+
* @req REQ-DEEP-PATH - Propagate path context into the program listener
|
|
206
185
|
*/
|
|
207
186
|
create(context) {
|
|
208
187
|
return { Program: programListener(context) };
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Helper to check @req annotation presence on TS declare functions and method signatures.
|
|
3
|
+
* This helper is intentionally scope/exportPriority agnostic and focuses solely
|
|
4
|
+
* on detection and reporting of @req annotations for the given node.
|
|
3
5
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
4
6
|
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
7
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
8
|
+
* @req REQ-ANNOTATION-REPORTING - Report missing @req annotation to context
|
|
5
9
|
*/
|
|
6
|
-
export declare function checkReqAnnotation(context: any, node: any
|
|
10
|
+
export declare function checkReqAnnotation(context: any, node: any, options?: {
|
|
11
|
+
enableFix?: boolean;
|
|
12
|
+
}): void;
|
|
@@ -48,11 +48,41 @@ function commentContainsReq(c) {
|
|
|
48
48
|
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
49
49
|
*/
|
|
50
50
|
function hasReqAnnotation(jsdoc, comments) {
|
|
51
|
+
// BRANCH @req detection on JSDoc or comments
|
|
52
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
53
|
+
// @req REQ-ANNOTATION-REQ-DETECTION
|
|
51
54
|
return ((jsdoc &&
|
|
52
55
|
typeof jsdoc.value === "string" &&
|
|
53
56
|
jsdoc.value.includes("@req")) ||
|
|
54
57
|
comments.some(commentContainsReq));
|
|
55
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Determine the most appropriate node to attach an inserted JSDoc to.
|
|
61
|
+
* Prefers outer function-like constructs such as methods, variable declarators,
|
|
62
|
+
* or wrapping expression statements for function expressions.
|
|
63
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
64
|
+
* @req REQ-ANNOTATION-AUTOFIX - Provide autofix for missing @req annotation
|
|
65
|
+
*/
|
|
66
|
+
function getFixTargetNode(node) {
|
|
67
|
+
const parent = node && node.parent;
|
|
68
|
+
if (!parent) {
|
|
69
|
+
return node;
|
|
70
|
+
}
|
|
71
|
+
// If the node is part of a class/obj method definition, attach to the MethodDefinition
|
|
72
|
+
if (parent.type === "MethodDefinition") {
|
|
73
|
+
return parent;
|
|
74
|
+
}
|
|
75
|
+
// If the node is the init of a variable declarator, attach to the VariableDeclarator
|
|
76
|
+
if (parent.type === "VariableDeclarator" && parent.init === node) {
|
|
77
|
+
return parent;
|
|
78
|
+
}
|
|
79
|
+
// If the parent is an expression statement (e.g. IIFE or assigned via expression),
|
|
80
|
+
// attach to the outer ExpressionStatement.
|
|
81
|
+
if (parent.type === "ExpressionStatement") {
|
|
82
|
+
return parent;
|
|
83
|
+
}
|
|
84
|
+
return node;
|
|
85
|
+
}
|
|
56
86
|
/**
|
|
57
87
|
* Creates a fix function that inserts a missing @req JSDoc before the node.
|
|
58
88
|
* Returned function is a proper named function so no inline arrow is used.
|
|
@@ -60,8 +90,9 @@ function hasReqAnnotation(jsdoc, comments) {
|
|
|
60
90
|
* @req REQ-ANNOTATION-AUTOFIX - Provide autofix for missing @req annotation
|
|
61
91
|
*/
|
|
62
92
|
function createMissingReqFix(node) {
|
|
93
|
+
const target = getFixTargetNode(node);
|
|
63
94
|
return function missingReqFix(fixer) {
|
|
64
|
-
return fixer.insertTextBefore(
|
|
95
|
+
return fixer.insertTextBefore(target, "/** @req <REQ-ID> */\n");
|
|
65
96
|
};
|
|
66
97
|
}
|
|
67
98
|
/**
|
|
@@ -73,29 +104,41 @@ function createMissingReqFix(node) {
|
|
|
73
104
|
* @req REQ-ERROR-SPECIFIC - Provide specific error details including node name
|
|
74
105
|
* @req REQ-ERROR-LOCATION - Include contextual location information in errors
|
|
75
106
|
*/
|
|
76
|
-
function reportMissing(context, node) {
|
|
77
|
-
const rawName = (0, require_story_utils_1.getNodeName)(node);
|
|
107
|
+
function reportMissing(context, node, enableFix = true) {
|
|
108
|
+
const rawName = (0, require_story_utils_1.getNodeName)(node) ?? (node && (0, require_story_utils_1.getNodeName)(node.parent));
|
|
78
109
|
const name = rawName ?? "(anonymous)";
|
|
79
|
-
|
|
110
|
+
const reportOptions = {
|
|
80
111
|
node,
|
|
81
112
|
messageId: "missingReq",
|
|
82
113
|
data: { name },
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
};
|
|
115
|
+
if (enableFix) {
|
|
116
|
+
reportOptions.fix = createMissingReqFix(node);
|
|
117
|
+
}
|
|
118
|
+
context.report(reportOptions);
|
|
85
119
|
}
|
|
86
120
|
/**
|
|
87
121
|
* Helper to check @req annotation presence on TS declare functions and method signatures.
|
|
122
|
+
* This helper is intentionally scope/exportPriority agnostic and focuses solely
|
|
123
|
+
* on detection and reporting of @req annotations for the given node.
|
|
88
124
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
89
125
|
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
126
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
127
|
+
* @req REQ-ANNOTATION-REPORTING - Report missing @req annotation to context
|
|
90
128
|
*/
|
|
91
|
-
function checkReqAnnotation(context, node) {
|
|
129
|
+
function checkReqAnnotation(context, node, options) {
|
|
130
|
+
const { enableFix = true } = options ?? {};
|
|
92
131
|
const sourceCode = context.getSourceCode();
|
|
93
132
|
const jsdoc = getJsdocComment(sourceCode, node);
|
|
94
133
|
const leading = getLeadingComments(node);
|
|
95
134
|
const comments = getCommentsBefore(sourceCode, node);
|
|
96
135
|
const all = combineComments(leading, comments);
|
|
97
136
|
const hasReq = hasReqAnnotation(jsdoc, all);
|
|
137
|
+
// BRANCH when a @req annotation is missing and must be reported
|
|
138
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
139
|
+
// @req REQ-ANNOTATION-REQ-DETECTION
|
|
140
|
+
// @req REQ-ANNOTATION-REPORTING
|
|
98
141
|
if (!hasReq) {
|
|
99
|
-
reportMissing(context, node);
|
|
142
|
+
reportMissing(context, node, enableFix);
|
|
100
143
|
}
|
|
101
144
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,160 @@
|
|
|
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/008.0-DEV-AUTO-FIX.story.md
|
|
8
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
9
|
+
* @req REQ-AUTOFIX-MISSING - Verify ESLint --fix automatically adds missing @story annotations to functions
|
|
10
|
+
* @req REQ-AUTOFIX-FORMAT - Verify ESLint --fix corrects simple annotation format issues for @story annotations
|
|
11
|
+
*/
|
|
12
|
+
const eslint_1 = require("eslint");
|
|
13
|
+
const require_story_annotation_1 = __importDefault(require("../../src/rules/require-story-annotation"));
|
|
14
|
+
const valid_annotation_format_1 = __importDefault(require("../../src/rules/valid-annotation-format"));
|
|
15
|
+
const functionRuleTester = new eslint_1.RuleTester({
|
|
16
|
+
languageOptions: {
|
|
17
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
const formatRuleTester = new eslint_1.RuleTester({
|
|
21
|
+
languageOptions: { parserOptions: { ecmaVersion: 2020 } },
|
|
22
|
+
});
|
|
23
|
+
describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
24
|
+
describe("[REQ-AUTOFIX-MISSING] require-story-annotation auto-fix", () => {
|
|
25
|
+
functionRuleTester.run("require-story-annotation --fix", require_story_annotation_1.default, {
|
|
26
|
+
valid: [
|
|
27
|
+
{
|
|
28
|
+
name: "[REQ-AUTOFIX-MISSING] already annotated function is unchanged",
|
|
29
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction alreadyAnnotated() {}`,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "[REQ-AUTOFIX-MISSING] already annotated class method is unchanged",
|
|
33
|
+
code: `class A {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
invalid: [
|
|
37
|
+
{
|
|
38
|
+
name: "[REQ-AUTOFIX-MISSING] adds @story before function declaration when missing",
|
|
39
|
+
code: `function autoFixMe() {}`,
|
|
40
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction autoFixMe() {}`,
|
|
41
|
+
errors: [
|
|
42
|
+
{
|
|
43
|
+
messageId: "missingStory",
|
|
44
|
+
suggestions: [
|
|
45
|
+
{
|
|
46
|
+
desc: "Add JSDoc @story annotation for function 'autoFixMe', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
47
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction autoFixMe() {}`,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "[REQ-AUTOFIX-MISSING] adds @story before function expression when missing",
|
|
55
|
+
code: `const fnExpr = function() {};`,
|
|
56
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
|
|
57
|
+
errors: [
|
|
58
|
+
{
|
|
59
|
+
messageId: "missingStory",
|
|
60
|
+
suggestions: [
|
|
61
|
+
{
|
|
62
|
+
desc: "Add JSDoc @story annotation for function 'fnExpr', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
63
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "[REQ-AUTOFIX-MISSING] adds @story before class method when missing",
|
|
71
|
+
code: `class C {\n method() {}\n}`,
|
|
72
|
+
output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
73
|
+
errors: [
|
|
74
|
+
{
|
|
75
|
+
messageId: "missingStory",
|
|
76
|
+
suggestions: [
|
|
77
|
+
{
|
|
78
|
+
desc: "Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
79
|
+
output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "[REQ-AUTOFIX-MISSING] adds @story before TS declare function when missing",
|
|
87
|
+
code: `declare function tsDecl(): void;`,
|
|
88
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
|
|
89
|
+
languageOptions: {
|
|
90
|
+
parser: require("@typescript-eslint/parser"),
|
|
91
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
92
|
+
},
|
|
93
|
+
errors: [
|
|
94
|
+
{
|
|
95
|
+
messageId: "missingStory",
|
|
96
|
+
suggestions: [
|
|
97
|
+
{
|
|
98
|
+
desc: "Add JSDoc @story annotation for function 'tsDecl', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
99
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "[REQ-AUTOFIX-MISSING] adds @story before TS method signature when missing",
|
|
107
|
+
code: `interface D {\n method(): void;\n}`,
|
|
108
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
|
|
109
|
+
languageOptions: {
|
|
110
|
+
parser: require("@typescript-eslint/parser"),
|
|
111
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
112
|
+
},
|
|
113
|
+
errors: [
|
|
114
|
+
{
|
|
115
|
+
messageId: "missingStory",
|
|
116
|
+
suggestions: [
|
|
117
|
+
{
|
|
118
|
+
desc: "Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
119
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe("[REQ-AUTOFIX-FORMAT] valid-annotation-format auto-fix", () => {
|
|
129
|
+
formatRuleTester.run("valid-annotation-format --fix simple @story extension issues", valid_annotation_format_1.default, {
|
|
130
|
+
valid: [
|
|
131
|
+
{
|
|
132
|
+
name: "[REQ-AUTOFIX-FORMAT] already-correct story path is unchanged",
|
|
133
|
+
code: `// @story docs/stories/005.0-DEV-EXAMPLE.story.md`,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
invalid: [
|
|
137
|
+
{
|
|
138
|
+
name: "[REQ-AUTOFIX-FORMAT] adds .md extension for .story path",
|
|
139
|
+
code: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story`,
|
|
140
|
+
output: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md`,
|
|
141
|
+
errors: [
|
|
142
|
+
{
|
|
143
|
+
messageId: "invalidStoryFormat",
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "[REQ-AUTOFIX-FORMAT] adds .story.md extension when missing entirely",
|
|
149
|
+
code: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION`,
|
|
150
|
+
output: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md`,
|
|
151
|
+
errors: [
|
|
152
|
+
{
|
|
153
|
+
messageId: "invalidStoryFormat",
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -29,6 +29,7 @@ describe("Error Reporting Enhancements for require-story-annotation (Story 007.0
|
|
|
29
29
|
{
|
|
30
30
|
name: "[REQ-ERROR-SPECIFIC] missing @story annotation should report specific details and suggestion",
|
|
31
31
|
code: `function bar() {}`,
|
|
32
|
+
output: "/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}",
|
|
32
33
|
errors: [
|
|
33
34
|
{
|
|
34
35
|
messageId: "missingStory",
|
|
@@ -44,24 +44,80 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
44
44
|
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
|
+
{
|
|
48
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] valid FunctionExpression with @req annotation",
|
|
49
|
+
code: `const fn = /**\n * @req REQ-EXAMPLE\n */\nfunction() {};`,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] valid MethodDefinition with @req annotation",
|
|
53
|
+
code: `class C {\n /**\n * @req REQ-EXAMPLE\n */\n m() {}\n}`,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] valid TS FunctionExpression in variable declarator with @req",
|
|
57
|
+
code: `const fn = /**\n * @req REQ-EXAMPLE\n */\nfunction () {};`,
|
|
58
|
+
languageOptions: {
|
|
59
|
+
parser: require("@typescript-eslint/parser"),
|
|
60
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] valid exported TS FunctionExpression in variable declarator with @req",
|
|
65
|
+
code: `export const fn = /**\n * @req REQ-EXAMPLE\n */\nfunction () {};`,
|
|
66
|
+
languageOptions: {
|
|
67
|
+
parser: require("@typescript-eslint/parser"),
|
|
68
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "[REQ-CONFIGURABLE-SCOPE][Story 003.0] FunctionExpression ignored when scope only includes FunctionDeclaration",
|
|
73
|
+
code: `const fn = function () {};`,
|
|
74
|
+
options: [{ scope: ["FunctionDeclaration"] }],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported function ignored when exportPriority is 'exported'",
|
|
78
|
+
code: `function nonExported() {}`,
|
|
79
|
+
options: [{ exportPriority: "exported" }],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported function required when exportPriority is 'exported'",
|
|
83
|
+
code: `/** @req REQ-EXAMPLE */\nexport function exportedFn() {}`,
|
|
84
|
+
options: [{ exportPriority: "exported" }],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported function ignored when exportPriority is 'non-exported'",
|
|
88
|
+
code: `export function exportedFn() {}`,
|
|
89
|
+
options: [{ exportPriority: "non-exported" }],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported function required when exportPriority is 'non-exported'",
|
|
93
|
+
code: `/** @req REQ-EXAMPLE */\nfunction nonExported() {}`,
|
|
94
|
+
options: [{ exportPriority: "non-exported" }],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported method ignored when exportPriority is 'non-exported'",
|
|
98
|
+
code: `export class C {\n m() {}\n}`,
|
|
99
|
+
options: [{ exportPriority: "non-exported" }],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported method required when exportPriority is 'non-exported'",
|
|
103
|
+
code: `class C {\n /** @req REQ-EXAMPLE */\n m() {}\n}`,
|
|
104
|
+
options: [{ exportPriority: "non-exported" }],
|
|
105
|
+
},
|
|
47
106
|
],
|
|
48
107
|
invalid: [
|
|
49
108
|
{
|
|
50
109
|
name: "[REQ-ANNOTATION-REQUIRED] missing @req on function without JSDoc",
|
|
51
110
|
code: `function baz() {}`,
|
|
52
|
-
output: `/** @req <REQ-ID> */\nfunction baz() {}`,
|
|
53
111
|
errors: [{ messageId: "missingReq", data: { name: "baz" } }],
|
|
54
112
|
},
|
|
55
113
|
{
|
|
56
114
|
name: "[REQ-ANNOTATION-REQUIRED] missing @req on function with only @story annotation",
|
|
57
115
|
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction qux() {}`,
|
|
58
|
-
output: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\n/** @req <REQ-ID> */\nfunction qux() {}`,
|
|
59
116
|
errors: [{ messageId: "missingReq", data: { name: "qux" } }],
|
|
60
117
|
},
|
|
61
118
|
{
|
|
62
119
|
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSDeclareFunction",
|
|
63
120
|
code: `declare function baz(): void;`,
|
|
64
|
-
output: `/** @req <REQ-ID> */\ndeclare function baz(): void;`,
|
|
65
121
|
errors: [{ messageId: "missingReq", data: { name: "baz" } }],
|
|
66
122
|
languageOptions: {
|
|
67
123
|
parser: require("@typescript-eslint/parser"),
|
|
@@ -71,13 +127,92 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
71
127
|
{
|
|
72
128
|
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSMethodSignature",
|
|
73
129
|
code: `interface I { method(): void; }`,
|
|
74
|
-
output: `interface I { /** @req <REQ-ID> */\nmethod(): void; }`,
|
|
75
130
|
errors: [{ messageId: "missingReq", data: { name: "method" } }],
|
|
76
131
|
languageOptions: {
|
|
77
132
|
parser: require("@typescript-eslint/parser"),
|
|
78
133
|
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
79
134
|
},
|
|
80
135
|
},
|
|
136
|
+
{
|
|
137
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] missing @req on FunctionExpression assigned to variable",
|
|
138
|
+
code: `const fn = function () {};`,
|
|
139
|
+
errors: [{ messageId: "missingReq", data: { name: "fn" } }],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] missing @req on anonymous FunctionExpression (no variable name)",
|
|
143
|
+
code: `(function () {})();`,
|
|
144
|
+
errors: [{ messageId: "missingReq", data: { name: "(anonymous)" } }],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] missing @req on MethodDefinition in class",
|
|
148
|
+
code: `class C {\n m() {}\n}`,
|
|
149
|
+
errors: [{ messageId: "missingReq" }],
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] missing @req on MethodDefinition in object literal",
|
|
153
|
+
code: `const o = { m() {} };`,
|
|
154
|
+
errors: [{ messageId: "missingReq" }],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] missing @req on TS FunctionExpression in variable declarator",
|
|
158
|
+
code: `const fn = function () {};`,
|
|
159
|
+
errors: [{ messageId: "missingReq", data: { name: "fn" } }],
|
|
160
|
+
languageOptions: {
|
|
161
|
+
parser: require("@typescript-eslint/parser"),
|
|
162
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] missing @req on exported TS FunctionExpression in variable declarator",
|
|
167
|
+
code: `export const fn = function () {};`,
|
|
168
|
+
errors: [{ messageId: "missingReq", data: { name: "fn" } }],
|
|
169
|
+
languageOptions: {
|
|
170
|
+
parser: require("@typescript-eslint/parser"),
|
|
171
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "[REQ-CONFIGURABLE-SCOPE][Story 003.0] FunctionDeclaration still reported when scope only includes FunctionDeclaration",
|
|
176
|
+
code: `function scoped() {}`,
|
|
177
|
+
options: [{ scope: ["FunctionDeclaration"] }],
|
|
178
|
+
errors: [{ messageId: "missingReq", data: { name: "scoped" } }],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported function reported when exportPriority is 'exported'",
|
|
182
|
+
code: `export function exportedFn() {}`,
|
|
183
|
+
options: [{ exportPriority: "exported" }],
|
|
184
|
+
errors: [{ messageId: "missingReq", data: { name: "exportedFn" } }],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported function reported when exportPriority is 'non-exported'",
|
|
188
|
+
code: `function nonExported() {}`,
|
|
189
|
+
options: [{ exportPriority: "non-exported" }],
|
|
190
|
+
errors: [{ messageId: "missingReq", data: { name: "nonExported" } }],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported method reported when exportPriority is 'exported'",
|
|
194
|
+
code: `export class C {\n m() {}\n}`,
|
|
195
|
+
errors: [{ messageId: "missingReq" }],
|
|
196
|
+
options: [{ exportPriority: "exported" }],
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported method reported when exportPriority is 'non-exported'",
|
|
200
|
+
code: `class C {\n m() {}\n}`,
|
|
201
|
+
errors: [{ messageId: "missingReq" }],
|
|
202
|
+
options: [{ exportPriority: "non-exported" }],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported FunctionExpression reported when exportPriority is 'exported'",
|
|
206
|
+
code: `export const fn = function () {};`,
|
|
207
|
+
options: [{ exportPriority: "exported" }],
|
|
208
|
+
errors: [{ messageId: "missingReq", data: { name: "fn" } }],
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported FunctionExpression reported when exportPriority is 'non-exported'",
|
|
212
|
+
code: `const fn = function () {};`,
|
|
213
|
+
options: [{ exportPriority: "non-exported" }],
|
|
214
|
+
errors: [{ messageId: "missingReq", data: { name: "fn" } }],
|
|
215
|
+
},
|
|
81
216
|
],
|
|
82
217
|
});
|
|
83
218
|
});
|