eslint-plugin-traceability 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/src/index.d.ts +2 -2
- package/lib/src/rules/require-branch-annotation.js +21 -147
- package/lib/src/rules/require-story-annotation.d.ts +3 -12
- package/lib/src/rules/require-story-annotation.js +153 -170
- package/lib/src/utils/branch-annotation-helpers.d.ts +46 -0
- package/lib/src/utils/branch-annotation-helpers.js +145 -0
- package/lib/tests/integration/cli-integration.test.js +3 -5
- package/lib/tests/maintenance/index.test.d.ts +1 -0
- package/lib/tests/maintenance/index.test.js +25 -0
- package/lib/tests/rules/error-reporting.test.d.ts +1 -0
- package/lib/tests/rules/error-reporting.test.js +47 -0
- package/lib/tests/rules/require-story-annotation.test.js +101 -25
- package/lib/tests/utils/branch-annotation-helpers.test.d.ts +1 -0
- package/lib/tests/utils/branch-annotation-helpers.test.js +46 -0
- package/package.json +1 -1
package/lib/src/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @req REQ-PLUGIN-STRUCTURE - Provide foundational plugin export and registration
|
|
5
5
|
*/
|
|
6
6
|
export declare const rules: {
|
|
7
|
-
"require-story-annotation":
|
|
7
|
+
"require-story-annotation": import("eslint").Rule.RuleModule;
|
|
8
8
|
"require-req-annotation": any;
|
|
9
9
|
"require-branch-annotation": import("eslint").Rule.RuleModule;
|
|
10
10
|
"valid-annotation-format": any;
|
|
@@ -41,7 +41,7 @@ export declare const configs: {
|
|
|
41
41
|
};
|
|
42
42
|
declare const _default: {
|
|
43
43
|
rules: {
|
|
44
|
-
"require-story-annotation":
|
|
44
|
+
"require-story-annotation": import("eslint").Rule.RuleModule;
|
|
45
45
|
"require-req-annotation": any;
|
|
46
46
|
"require-branch-annotation": import("eslint").Rule.RuleModule;
|
|
47
47
|
"valid-annotation-format": any;
|
|
@@ -1,41 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/* eslint-disable max-lines-per-function */
|
|
3
|
-
/****
|
|
4
|
-
* Rule to enforce @story and @req annotations on significant code branches
|
|
5
|
-
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
6
|
-
* @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
|
|
7
|
-
* @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
|
|
8
|
-
*/
|
|
9
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
const
|
|
11
|
-
"IfStatement",
|
|
12
|
-
"SwitchCase",
|
|
13
|
-
"TryStatement",
|
|
14
|
-
"CatchClause",
|
|
15
|
-
"ForStatement",
|
|
16
|
-
"ForOfStatement",
|
|
17
|
-
"ForInStatement",
|
|
18
|
-
"WhileStatement",
|
|
19
|
-
"DoWhileStatement",
|
|
20
|
-
];
|
|
21
|
-
/**
|
|
22
|
-
* Gather leading comments for a node, with fallback for SwitchCase.
|
|
23
|
-
*/
|
|
24
|
-
function gatherCommentText(sourceCode, node) {
|
|
25
|
-
if (node.type === "SwitchCase") {
|
|
26
|
-
const lines = sourceCode.lines;
|
|
27
|
-
const startLine = node.loc.start.line;
|
|
28
|
-
let i = startLine - 2;
|
|
29
|
-
const comments = [];
|
|
30
|
-
while (i >= 0 && /^\s*(\/\/|\/\*)/.test(lines[i])) {
|
|
31
|
-
comments.unshift(lines[i].trim());
|
|
32
|
-
i--;
|
|
33
|
-
}
|
|
34
|
-
return comments.join(" ");
|
|
35
|
-
}
|
|
36
|
-
const comments = sourceCode.getCommentsBefore(node) || [];
|
|
37
|
-
return comments.map((c) => c.value).join(" ");
|
|
38
|
-
}
|
|
3
|
+
const branch_annotation_helpers_1 = require("../utils/branch-annotation-helpers");
|
|
39
4
|
const rule = {
|
|
40
5
|
meta: {
|
|
41
6
|
type: "problem",
|
|
@@ -61,120 +26,29 @@ const rule = {
|
|
|
61
26
|
},
|
|
62
27
|
],
|
|
63
28
|
},
|
|
29
|
+
/**
|
|
30
|
+
* Create visitor for require-branch-annotation rule.
|
|
31
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
32
|
+
* @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
|
|
33
|
+
* @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
|
|
34
|
+
*/
|
|
64
35
|
create(context) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const invalidTypes = options.branchTypes.filter((t) => !DEFAULT_BRANCH_TYPES.includes(t));
|
|
69
|
-
if (invalidTypes.length > 0) {
|
|
70
|
-
return {
|
|
71
|
-
Program(node) {
|
|
72
|
-
invalidTypes.forEach((t) => {
|
|
73
|
-
context.report({
|
|
74
|
-
node,
|
|
75
|
-
message: `Value "${t}" should be equal to one of the allowed values: ${DEFAULT_BRANCH_TYPES.join(", ")}`,
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const branchTypes = Array.isArray(options.branchTypes)
|
|
83
|
-
? options.branchTypes
|
|
84
|
-
: Array.from(DEFAULT_BRANCH_TYPES);
|
|
85
|
-
let storyFixCount = 0;
|
|
86
|
-
function reportBranch(node) {
|
|
87
|
-
const text = gatherCommentText(sourceCode, node);
|
|
88
|
-
const missingStory = !/@story\b/.test(text);
|
|
89
|
-
const missingReq = !/@req\b/.test(text);
|
|
90
|
-
const indent = sourceCode.lines[node.loc.start.line - 1].match(/^(\s*)/)?.[1] || "";
|
|
91
|
-
const insertPos = sourceCode.getIndexFromLoc({
|
|
92
|
-
line: node.loc.start.line,
|
|
93
|
-
column: 0,
|
|
94
|
-
});
|
|
95
|
-
if (missingStory) {
|
|
96
|
-
if (storyFixCount === 0) {
|
|
97
|
-
context.report({
|
|
98
|
-
node,
|
|
99
|
-
messageId: "missingAnnotation",
|
|
100
|
-
data: { missing: "@story" },
|
|
101
|
-
fix: (fixer) => fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @story <story-file>.story.md\n`),
|
|
102
|
-
});
|
|
103
|
-
storyFixCount++;
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
context.report({
|
|
107
|
-
node,
|
|
108
|
-
messageId: "missingAnnotation",
|
|
109
|
-
data: { missing: "@story" },
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (missingReq) {
|
|
114
|
-
if (!missingStory) {
|
|
115
|
-
context.report({
|
|
116
|
-
node,
|
|
117
|
-
messageId: "missingAnnotation",
|
|
118
|
-
data: { missing: "@req" },
|
|
119
|
-
fix: (fixer) => fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @req <REQ-ID>\n`),
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
context.report({
|
|
124
|
-
node,
|
|
125
|
-
messageId: "missingAnnotation",
|
|
126
|
-
data: { missing: "@req" },
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
36
|
+
const branchTypesOrListener = (0, branch_annotation_helpers_1.validateBranchTypes)(context);
|
|
37
|
+
if (!Array.isArray(branchTypesOrListener)) {
|
|
38
|
+
return branchTypesOrListener;
|
|
130
39
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
SwitchCase(node) {
|
|
138
|
-
if (node.test == null || !branchTypes.includes(node.type))
|
|
139
|
-
return;
|
|
140
|
-
reportBranch(node);
|
|
141
|
-
},
|
|
142
|
-
TryStatement(node) {
|
|
143
|
-
if (!branchTypes.includes(node.type))
|
|
144
|
-
return;
|
|
145
|
-
reportBranch(node);
|
|
146
|
-
},
|
|
147
|
-
CatchClause(node) {
|
|
148
|
-
if (!branchTypes.includes(node.type))
|
|
40
|
+
const branchTypes = branchTypesOrListener;
|
|
41
|
+
const storyFixCountRef = { count: 0 };
|
|
42
|
+
const handlers = {};
|
|
43
|
+
branchTypes.forEach((type) => {
|
|
44
|
+
handlers[type] = (node) => {
|
|
45
|
+
if (type === "SwitchCase" && node.test == null) {
|
|
149
46
|
return;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
reportBranch(node);
|
|
156
|
-
},
|
|
157
|
-
ForOfStatement(node) {
|
|
158
|
-
if (!branchTypes.includes(node.type))
|
|
159
|
-
return;
|
|
160
|
-
reportBranch(node);
|
|
161
|
-
},
|
|
162
|
-
ForInStatement(node) {
|
|
163
|
-
if (!branchTypes.includes(node.type))
|
|
164
|
-
return;
|
|
165
|
-
reportBranch(node);
|
|
166
|
-
},
|
|
167
|
-
WhileStatement(node) {
|
|
168
|
-
if (!branchTypes.includes(node.type))
|
|
169
|
-
return;
|
|
170
|
-
reportBranch(node);
|
|
171
|
-
},
|
|
172
|
-
DoWhileStatement(node) {
|
|
173
|
-
if (!branchTypes.includes(node.type))
|
|
174
|
-
return;
|
|
175
|
-
reportBranch(node);
|
|
176
|
-
},
|
|
177
|
-
};
|
|
47
|
+
}
|
|
48
|
+
(0, branch_annotation_helpers_1.reportMissingAnnotations)(context, node, storyFixCountRef);
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
return handlers;
|
|
178
52
|
},
|
|
179
53
|
};
|
|
180
54
|
exports.default = rule;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* @req REQ-ANNOTATION-REQUIRED - Require @story annotation on functions
|
|
5
|
-
* @req REQ-OPTIONS-SCOPE - Support configuring which function types to enforce via options
|
|
6
|
-
* @req REQ-EXPORT-PRIORITY - Add exportPriority option to target exported or non-exported
|
|
7
|
-
* @req REQ-UNIFIED-CHECK - Implement unified checkNode for all supported node types
|
|
8
|
-
* @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions, arrow functions, and methods
|
|
9
|
-
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
10
|
-
*/
|
|
11
|
-
declare const _default: any;
|
|
12
|
-
export default _default;
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
declare const rule: Rule.RuleModule;
|
|
3
|
+
export default rule;
|
|
@@ -1,184 +1,164 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Rule to enforce @story annotation on functions, function expressions, arrow functions, and methods
|
|
4
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
5
|
-
* @req REQ-ANNOTATION-REQUIRED - Require @story annotation on functions
|
|
6
|
-
* @req REQ-OPTIONS-SCOPE - Support configuring which function types to enforce via options
|
|
7
|
-
* @req REQ-EXPORT-PRIORITY - Add exportPriority option to target exported or non-exported
|
|
8
|
-
* @req REQ-UNIFIED-CHECK - Implement unified checkNode for all supported node types
|
|
9
|
-
* @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions, arrow functions, and methods
|
|
10
|
-
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
11
|
-
*/
|
|
12
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// Default node types to check for function annotations
|
|
4
|
+
const DEFAULT_SCOPE = [
|
|
5
|
+
"FunctionDeclaration",
|
|
6
|
+
"FunctionExpression",
|
|
7
|
+
"ArrowFunctionExpression",
|
|
8
|
+
"MethodDefinition",
|
|
9
|
+
"TSDeclareFunction",
|
|
10
|
+
"TSMethodSignature",
|
|
11
|
+
];
|
|
12
|
+
const EXPORT_PRIORITY_VALUES = ["all", "exported", "non-exported"];
|
|
13
13
|
/**
|
|
14
|
-
* Determine if a node is
|
|
15
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
16
|
-
* @req REQ-EXPORT-PRIORITY - Determine if function node has export declaration ancestor
|
|
14
|
+
* Determine if a node is in an export declaration
|
|
17
15
|
*/
|
|
18
16
|
function isExportedNode(node) {
|
|
19
|
-
let
|
|
20
|
-
while (
|
|
21
|
-
if (
|
|
22
|
-
|
|
17
|
+
let p = node.parent;
|
|
18
|
+
while (p) {
|
|
19
|
+
if (p.type === "ExportNamedDeclaration" ||
|
|
20
|
+
p.type === "ExportDefaultDeclaration") {
|
|
23
21
|
return true;
|
|
24
22
|
}
|
|
25
|
-
|
|
23
|
+
p = p.parent;
|
|
26
24
|
}
|
|
27
25
|
return false;
|
|
28
26
|
}
|
|
27
|
+
// Path to the story file for annotations
|
|
28
|
+
const STORY_PATH = "docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md";
|
|
29
|
+
const ANNOTATION = `/** @story ${STORY_PATH} */`;
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
32
|
-
* @req REQ-OPTIONS-SCOPE - Support configuring which function types to enforce via options
|
|
31
|
+
* Check if @story annotation already present in JSDoc or preceding comments
|
|
33
32
|
*/
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return current;
|
|
39
|
-
}
|
|
40
|
-
current = current.parent;
|
|
33
|
+
function hasStoryAnnotation(sourceCode, node) {
|
|
34
|
+
const jsdoc = sourceCode.getJSDocComment(node);
|
|
35
|
+
if (jsdoc?.value.includes("@story")) {
|
|
36
|
+
return true;
|
|
41
37
|
}
|
|
42
|
-
|
|
38
|
+
const comments = sourceCode.getCommentsBefore(node) || [];
|
|
39
|
+
return comments.some((c) => c.value.includes("@story"));
|
|
43
40
|
}
|
|
44
41
|
/**
|
|
45
|
-
*
|
|
46
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
47
|
-
* @req REQ-OPTIONS-SCOPE
|
|
48
|
-
* @req REQ-EXPORT-PRIORITY
|
|
49
|
-
* @req REQ-UNIFIED-CHECK
|
|
42
|
+
* Get the name of the function-like node
|
|
50
43
|
*/
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
44
|
+
function getNodeName(node) {
|
|
45
|
+
let current = node;
|
|
46
|
+
while (current) {
|
|
47
|
+
if (current.type === "VariableDeclarator" &&
|
|
48
|
+
current.id &&
|
|
49
|
+
typeof current.id.name === "string") {
|
|
50
|
+
return current.id.name;
|
|
51
|
+
}
|
|
52
|
+
if ((current.type === "FunctionDeclaration" ||
|
|
53
|
+
current.type === "TSDeclareFunction") &&
|
|
54
|
+
current.id &&
|
|
55
|
+
typeof current.id.name === "string") {
|
|
56
|
+
return current.id.name;
|
|
57
|
+
}
|
|
58
|
+
if ((current.type === "MethodDefinition" ||
|
|
59
|
+
current.type === "TSMethodSignature") &&
|
|
60
|
+
current.key &&
|
|
61
|
+
typeof current.key.name === "string") {
|
|
62
|
+
return current.key.name;
|
|
63
|
+
}
|
|
64
|
+
current = current.parent;
|
|
63
65
|
}
|
|
64
|
-
return
|
|
66
|
+
return "<unknown>";
|
|
65
67
|
}
|
|
66
68
|
/**
|
|
67
|
-
*
|
|
68
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
69
|
-
* @req REQ-UNIFIED-CHECK
|
|
69
|
+
* Determine AST node where annotation should be inserted
|
|
70
70
|
*/
|
|
71
71
|
function resolveTargetNode(sourceCode, node) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"ExportNamedDeclaration",
|
|
76
|
-
"ExportDefaultDeclaration",
|
|
77
|
-
]);
|
|
78
|
-
if (exp) {
|
|
79
|
-
target = exp;
|
|
80
|
-
}
|
|
72
|
+
if (node.type === "TSMethodSignature") {
|
|
73
|
+
// Interface method signature -> insert on interface
|
|
74
|
+
return node.parent.parent;
|
|
81
75
|
}
|
|
82
|
-
|
|
76
|
+
if (node.type === "FunctionExpression" ||
|
|
83
77
|
node.type === "ArrowFunctionExpression") {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
target = exp;
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
const anc = findAncestorNode(node, [
|
|
93
|
-
"VariableDeclaration",
|
|
94
|
-
"ExpressionStatement",
|
|
95
|
-
]);
|
|
96
|
-
if (anc) {
|
|
97
|
-
target = anc;
|
|
78
|
+
const parent = node.parent;
|
|
79
|
+
if (parent.type === "VariableDeclarator") {
|
|
80
|
+
const varDecl = parent.parent;
|
|
81
|
+
if (varDecl.parent && varDecl.parent.type === "ExportNamedDeclaration") {
|
|
82
|
+
return varDecl.parent;
|
|
98
83
|
}
|
|
84
|
+
return varDecl;
|
|
99
85
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
86
|
+
if (parent.type === "ExportNamedDeclaration") {
|
|
87
|
+
return parent;
|
|
88
|
+
}
|
|
89
|
+
if (parent.type === "ExpressionStatement") {
|
|
90
|
+
return parent;
|
|
105
91
|
}
|
|
106
92
|
}
|
|
107
|
-
|
|
108
|
-
target = node;
|
|
109
|
-
}
|
|
110
|
-
return target;
|
|
93
|
+
return node;
|
|
111
94
|
}
|
|
112
95
|
/**
|
|
113
|
-
*
|
|
114
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
115
|
-
* @req REQ-ANNOTATION-REQUIRED
|
|
96
|
+
* Report missing @story annotation on function or method
|
|
116
97
|
*/
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return
|
|
98
|
+
function reportMissing(context, sourceCode, node, target) {
|
|
99
|
+
if (hasStoryAnnotation(sourceCode, node) ||
|
|
100
|
+
hasStoryAnnotation(sourceCode, target)) {
|
|
101
|
+
return;
|
|
121
102
|
}
|
|
122
|
-
|
|
123
|
-
|
|
103
|
+
let name = getNodeName(node);
|
|
104
|
+
if (node.type === "TSDeclareFunction" && node.id && node.id.name) {
|
|
105
|
+
name = node.id.name;
|
|
106
|
+
}
|
|
107
|
+
context.report({
|
|
108
|
+
node,
|
|
109
|
+
messageId: "missingStory",
|
|
110
|
+
data: { name },
|
|
111
|
+
suggest: [
|
|
112
|
+
{
|
|
113
|
+
desc: `Add JSDoc @story annotation for function '${name}', e.g., ${ANNOTATION}`,
|
|
114
|
+
fix: (fixer) => fixer.insertTextBefore(target, `${ANNOTATION}\n`),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
});
|
|
124
118
|
}
|
|
125
119
|
/**
|
|
126
|
-
*
|
|
127
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
128
|
-
* @req REQ-UNIFIED-CHECK
|
|
129
|
-
* @req REQ-ANNOTATION-REQUIRED
|
|
120
|
+
* Report missing @story annotation on class methods
|
|
130
121
|
*/
|
|
131
|
-
function
|
|
132
|
-
if (
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
// Special handling for TSMethodSignature: allow annotation on the method itself
|
|
136
|
-
if (node.type === "TSMethodSignature") {
|
|
137
|
-
// If annotated on the method signature, skip
|
|
138
|
-
if (hasStoryAnnotation(sourceCode, node)) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
// Otherwise, check on interface declaration
|
|
142
|
-
const intf = resolveTargetNode(sourceCode, node);
|
|
143
|
-
if (hasStoryAnnotation(sourceCode, intf)) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
// Missing annotation: report on the interface declaration
|
|
147
|
-
context.report({
|
|
148
|
-
node: intf,
|
|
149
|
-
messageId: "missingStory",
|
|
150
|
-
fix(fixer) {
|
|
151
|
-
const indentLevel = intf.loc.start.column;
|
|
152
|
-
const indent = " ".repeat(indentLevel);
|
|
153
|
-
const insertPos = intf.range[0] - indentLevel;
|
|
154
|
-
return fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}/** @story <story-file>.story.md */\n`);
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
const target = resolveTargetNode(sourceCode, node);
|
|
160
|
-
if (hasStoryAnnotation(sourceCode, target)) {
|
|
122
|
+
function reportMethod(context, sourceCode, node) {
|
|
123
|
+
if (hasStoryAnnotation(sourceCode, node)) {
|
|
161
124
|
return;
|
|
162
125
|
}
|
|
163
126
|
context.report({
|
|
164
127
|
node,
|
|
165
128
|
messageId: "missingStory",
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
129
|
+
data: { name: getNodeName(node) },
|
|
130
|
+
suggest: [
|
|
131
|
+
{
|
|
132
|
+
desc: `Add JSDoc @story annotation for function '${getNodeName(node)}', e.g., ${ANNOTATION}`,
|
|
133
|
+
fix: (fixer) => fixer.insertTextBefore(node, `${ANNOTATION}\n `),
|
|
134
|
+
},
|
|
135
|
+
],
|
|
172
136
|
});
|
|
173
137
|
}
|
|
174
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Check if this node is within scope and matches exportPriority
|
|
140
|
+
*/
|
|
141
|
+
function shouldProcessNode(node, scope, exportPriority) {
|
|
142
|
+
if (!scope.includes(node.type)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
const exported = isExportedNode(node);
|
|
146
|
+
if (exportPriority === "exported" && !exported) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
if (exportPriority === "non-exported" && exported) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
const rule = {
|
|
175
155
|
meta: {
|
|
176
156
|
type: "problem",
|
|
177
157
|
docs: {
|
|
178
|
-
description: "Require @story annotations on
|
|
158
|
+
description: "Require @story annotations on functions",
|
|
179
159
|
recommended: "error",
|
|
180
160
|
},
|
|
181
|
-
|
|
161
|
+
hasSuggestions: true,
|
|
182
162
|
messages: {
|
|
183
163
|
missingStory: "Missing @story annotation (REQ-ANNOTATION-REQUIRED)",
|
|
184
164
|
},
|
|
@@ -188,21 +168,10 @@ exports.default = {
|
|
|
188
168
|
properties: {
|
|
189
169
|
scope: {
|
|
190
170
|
type: "array",
|
|
191
|
-
items: {
|
|
192
|
-
enum: [
|
|
193
|
-
"FunctionDeclaration",
|
|
194
|
-
"FunctionExpression",
|
|
195
|
-
"ArrowFunctionExpression",
|
|
196
|
-
"MethodDefinition",
|
|
197
|
-
"TSDeclareFunction",
|
|
198
|
-
"TSMethodSignature",
|
|
199
|
-
],
|
|
200
|
-
},
|
|
171
|
+
items: { type: "string", enum: DEFAULT_SCOPE },
|
|
201
172
|
uniqueItems: true,
|
|
202
173
|
},
|
|
203
|
-
exportPriority: {
|
|
204
|
-
enum: ["all", "exported", "non-exported"],
|
|
205
|
-
},
|
|
174
|
+
exportPriority: { type: "string", enum: EXPORT_PRIORITY_VALUES },
|
|
206
175
|
},
|
|
207
176
|
additionalProperties: false,
|
|
208
177
|
},
|
|
@@ -210,39 +179,53 @@ exports.default = {
|
|
|
210
179
|
},
|
|
211
180
|
create(context) {
|
|
212
181
|
const sourceCode = context.getSourceCode();
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
"ArrowFunctionExpression",
|
|
218
|
-
"MethodDefinition",
|
|
219
|
-
"TSDeclareFunction",
|
|
220
|
-
"TSMethodSignature",
|
|
221
|
-
];
|
|
222
|
-
const exportPriority = options.exportPriority || "all";
|
|
182
|
+
const opts = context.options[0] ||
|
|
183
|
+
{};
|
|
184
|
+
const scope = opts.scope || DEFAULT_SCOPE;
|
|
185
|
+
const exportPriority = opts.exportPriority || "all";
|
|
223
186
|
return {
|
|
224
187
|
FunctionDeclaration(node) {
|
|
225
|
-
|
|
188
|
+
if (!shouldProcessNode(node, scope, exportPriority))
|
|
189
|
+
return;
|
|
190
|
+
let target = node;
|
|
191
|
+
if (node.parent &&
|
|
192
|
+
(node.parent.type === "ExportNamedDeclaration" ||
|
|
193
|
+
node.parent.type === "ExportDefaultDeclaration")) {
|
|
194
|
+
target = node.parent;
|
|
195
|
+
}
|
|
196
|
+
reportMissing(context, sourceCode, node, target);
|
|
226
197
|
},
|
|
227
198
|
FunctionExpression(node) {
|
|
228
|
-
|
|
199
|
+
if (!shouldProcessNode(node, scope, exportPriority))
|
|
200
|
+
return;
|
|
201
|
+
if (node.parent && node.parent.type === "MethodDefinition")
|
|
202
|
+
return;
|
|
203
|
+
const target = resolveTargetNode(sourceCode, node);
|
|
204
|
+
reportMissing(context, sourceCode, node, target);
|
|
229
205
|
},
|
|
230
206
|
ArrowFunctionExpression(node) {
|
|
231
|
-
|
|
207
|
+
if (!shouldProcessNode(node, scope, exportPriority))
|
|
208
|
+
return;
|
|
209
|
+
const target = resolveTargetNode(sourceCode, node);
|
|
210
|
+
reportMissing(context, sourceCode, node, target);
|
|
232
211
|
},
|
|
233
|
-
MethodDefinition(node) {
|
|
234
|
-
checkStoryAnnotation(sourceCode, context, node, scope, exportPriority);
|
|
235
|
-
},
|
|
236
|
-
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
237
|
-
// @req REQ-FUNCTION-DETECTION - Detect TS-specific function syntax
|
|
238
212
|
TSDeclareFunction(node) {
|
|
239
|
-
|
|
213
|
+
if (!shouldProcessNode(node, scope, exportPriority))
|
|
214
|
+
return;
|
|
215
|
+
reportMissing(context, sourceCode, node, node);
|
|
240
216
|
},
|
|
241
|
-
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
242
|
-
// @req REQ-FUNCTION-DETECTION - Detect TS-specific function syntax
|
|
243
217
|
TSMethodSignature(node) {
|
|
244
|
-
|
|
218
|
+
if (!shouldProcessNode(node, scope, exportPriority))
|
|
219
|
+
return;
|
|
220
|
+
const target = resolveTargetNode(sourceCode, node);
|
|
221
|
+
reportMissing(context, sourceCode, node, target);
|
|
222
|
+
},
|
|
223
|
+
MethodDefinition(node) {
|
|
224
|
+
if (!shouldProcessNode(node, scope, exportPriority))
|
|
225
|
+
return;
|
|
226
|
+
reportMethod(context, sourceCode, node);
|
|
245
227
|
},
|
|
246
228
|
};
|
|
247
229
|
},
|
|
248
230
|
};
|
|
231
|
+
exports.default = rule;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
/**
|
|
3
|
+
* Valid branch types for require-branch-annotation rule.
|
|
4
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
5
|
+
* @req REQ-SIGNIFICANCE-CRITERIA - Define criteria for which branches require annotations
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_BRANCH_TYPES: readonly ["IfStatement", "SwitchCase", "TryStatement", "CatchClause", "ForStatement", "ForOfStatement", "ForInStatement", "WhileStatement", "DoWhileStatement"];
|
|
8
|
+
/**
|
|
9
|
+
* Type for branch nodes supported by require-branch-annotation rule.
|
|
10
|
+
*/
|
|
11
|
+
export type BranchType = (typeof DEFAULT_BRANCH_TYPES)[number];
|
|
12
|
+
/**
|
|
13
|
+
* Validate branchTypes configuration option and return branch types to enforce,
|
|
14
|
+
* or return an ESLint listener if configuration is invalid.
|
|
15
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
16
|
+
* @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateBranchTypes(context: Rule.RuleContext): BranchType[] | Rule.RuleListener;
|
|
19
|
+
/**
|
|
20
|
+
* Gather leading comment text for a branch node.
|
|
21
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
22
|
+
* @req REQ-COMMENT-ASSOCIATION - Associate inline comments with their corresponding code branches
|
|
23
|
+
*/
|
|
24
|
+
export declare function gatherBranchCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any): string;
|
|
25
|
+
/**
|
|
26
|
+
* Report missing @story annotation on a branch node.
|
|
27
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
28
|
+
* @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
|
|
29
|
+
*/
|
|
30
|
+
export declare function reportMissingStory(context: Rule.RuleContext, node: any, indent: string, insertPos: number, storyFixCountRef: {
|
|
31
|
+
count: number;
|
|
32
|
+
}): void;
|
|
33
|
+
/**
|
|
34
|
+
* Report missing @req annotation on a branch node.
|
|
35
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
36
|
+
* @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
|
|
37
|
+
*/
|
|
38
|
+
export declare function reportMissingReq(context: Rule.RuleContext, node: any, indent: string, insertPos: number, missingStory: boolean): void;
|
|
39
|
+
/**
|
|
40
|
+
* Report missing annotations on a branch node.
|
|
41
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
42
|
+
* @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
|
|
43
|
+
*/
|
|
44
|
+
export declare function reportMissingAnnotations(context: Rule.RuleContext, node: any, storyFixCountRef: {
|
|
45
|
+
count: number;
|
|
46
|
+
}): void;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_BRANCH_TYPES = void 0;
|
|
4
|
+
exports.validateBranchTypes = validateBranchTypes;
|
|
5
|
+
exports.gatherBranchCommentText = gatherBranchCommentText;
|
|
6
|
+
exports.reportMissingStory = reportMissingStory;
|
|
7
|
+
exports.reportMissingReq = reportMissingReq;
|
|
8
|
+
exports.reportMissingAnnotations = reportMissingAnnotations;
|
|
9
|
+
/**
|
|
10
|
+
* Valid branch types for require-branch-annotation rule.
|
|
11
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
12
|
+
* @req REQ-SIGNIFICANCE-CRITERIA - Define criteria for which branches require annotations
|
|
13
|
+
*/
|
|
14
|
+
exports.DEFAULT_BRANCH_TYPES = [
|
|
15
|
+
"IfStatement",
|
|
16
|
+
"SwitchCase",
|
|
17
|
+
"TryStatement",
|
|
18
|
+
"CatchClause",
|
|
19
|
+
"ForStatement",
|
|
20
|
+
"ForOfStatement",
|
|
21
|
+
"ForInStatement",
|
|
22
|
+
"WhileStatement",
|
|
23
|
+
"DoWhileStatement",
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Validate branchTypes configuration option and return branch types to enforce,
|
|
27
|
+
* or return an ESLint listener if configuration is invalid.
|
|
28
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
29
|
+
* @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
|
|
30
|
+
*/
|
|
31
|
+
function validateBranchTypes(context) {
|
|
32
|
+
const options = context.options[0] || {};
|
|
33
|
+
if (Array.isArray(options.branchTypes)) {
|
|
34
|
+
const invalidTypes = options.branchTypes.filter((t) => !exports.DEFAULT_BRANCH_TYPES.includes(t));
|
|
35
|
+
if (invalidTypes.length > 0) {
|
|
36
|
+
return {
|
|
37
|
+
Program(node) {
|
|
38
|
+
invalidTypes.forEach((t) => {
|
|
39
|
+
context.report({
|
|
40
|
+
node,
|
|
41
|
+
message: `Value "${t}" should be equal to one of the allowed values: ${exports.DEFAULT_BRANCH_TYPES.join(", ")}`,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return Array.isArray(options.branchTypes)
|
|
49
|
+
? options.branchTypes
|
|
50
|
+
: Array.from(exports.DEFAULT_BRANCH_TYPES);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Gather leading comment text for a branch node.
|
|
54
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
55
|
+
* @req REQ-COMMENT-ASSOCIATION - Associate inline comments with their corresponding code branches
|
|
56
|
+
*/
|
|
57
|
+
function gatherBranchCommentText(sourceCode, node) {
|
|
58
|
+
if (node.type === "SwitchCase") {
|
|
59
|
+
const lines = sourceCode.lines;
|
|
60
|
+
const startLine = node.loc.start.line;
|
|
61
|
+
let i = startLine - 2;
|
|
62
|
+
const comments = [];
|
|
63
|
+
while (i >= 0 && /^\s*(\/\/|\/\*)/.test(lines[i])) {
|
|
64
|
+
comments.unshift(lines[i].trim());
|
|
65
|
+
i--;
|
|
66
|
+
}
|
|
67
|
+
return comments.join(" ");
|
|
68
|
+
}
|
|
69
|
+
const comments = sourceCode.getCommentsBefore(node) || [];
|
|
70
|
+
return comments.map((c) => c.value).join(" ");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Report missing @story annotation on a branch node.
|
|
74
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
75
|
+
* @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
|
|
76
|
+
*/
|
|
77
|
+
function reportMissingStory(context, node, indent, insertPos, storyFixCountRef) {
|
|
78
|
+
if (storyFixCountRef.count === 0) {
|
|
79
|
+
context.report({
|
|
80
|
+
node,
|
|
81
|
+
messageId: "missingAnnotation",
|
|
82
|
+
data: { missing: "@story" },
|
|
83
|
+
fix: (fixer) => fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @story <story-file>.story.md\n`),
|
|
84
|
+
});
|
|
85
|
+
storyFixCountRef.count++;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
context.report({
|
|
89
|
+
node,
|
|
90
|
+
messageId: "missingAnnotation",
|
|
91
|
+
data: { missing: "@story" },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Report missing @req annotation on a branch node.
|
|
97
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
98
|
+
* @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
|
|
99
|
+
*/
|
|
100
|
+
function reportMissingReq(context, node, indent, insertPos, missingStory) {
|
|
101
|
+
if (!missingStory) {
|
|
102
|
+
context.report({
|
|
103
|
+
node,
|
|
104
|
+
messageId: "missingAnnotation",
|
|
105
|
+
data: { missing: "@req" },
|
|
106
|
+
fix: (fixer) => fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @req <REQ-ID>\n`),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
context.report({
|
|
111
|
+
node,
|
|
112
|
+
messageId: "missingAnnotation",
|
|
113
|
+
data: { missing: "@req" },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Report missing annotations on a branch node.
|
|
119
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
120
|
+
* @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
|
|
121
|
+
*/
|
|
122
|
+
function reportMissingAnnotations(context, node, storyFixCountRef) {
|
|
123
|
+
const sourceCode = context.getSourceCode();
|
|
124
|
+
const text = gatherBranchCommentText(sourceCode, node);
|
|
125
|
+
const missingStory = !/@story\b/.test(text);
|
|
126
|
+
const missingReq = !/@req\b/.test(text);
|
|
127
|
+
const indent = sourceCode.lines[node.loc.start.line - 1].match(/^(\s*)/)?.[1] || "";
|
|
128
|
+
const insertPos = sourceCode.getIndexFromLoc({
|
|
129
|
+
line: node.loc.start.line,
|
|
130
|
+
column: 0,
|
|
131
|
+
});
|
|
132
|
+
const actions = [
|
|
133
|
+
{
|
|
134
|
+
missing: missingStory,
|
|
135
|
+
fn: reportMissingStory,
|
|
136
|
+
args: [context, node, indent, insertPos, storyFixCountRef],
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
missing: missingReq,
|
|
140
|
+
fn: reportMissingReq,
|
|
141
|
+
args: [context, node, indent, insertPos, missingStory],
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
actions.forEach(({ missing, fn, args }) => missing && fn(...args));
|
|
145
|
+
}
|
|
@@ -76,10 +76,8 @@ function baz() {}`,
|
|
|
76
76
|
});
|
|
77
77
|
return result;
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
expect(result.status).toBe(testCase.expectedStatus);
|
|
83
|
-
});
|
|
79
|
+
it.each(tests)("[REQ-PLUGIN-STRUCTURE] $name", ({ code, rule, expectedStatus }) => {
|
|
80
|
+
const result = runEslint(code, rule);
|
|
81
|
+
expect(result.status).toBe(expectedStatus);
|
|
84
82
|
});
|
|
85
83
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
5
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
6
|
+
* @req REQ-MAINT-SAFE - Ensure all maintenance tools are exported correctly
|
|
7
|
+
*/
|
|
8
|
+
const maintenance_1 = require("../../src/maintenance");
|
|
9
|
+
describe("Maintenance Tools Index Exports (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
10
|
+
it("[REQ-MAINT-DETECT] should export detectStaleAnnotations as a function", () => {
|
|
11
|
+
expect(typeof maintenance_1.detectStaleAnnotations).toBe("function");
|
|
12
|
+
});
|
|
13
|
+
it("[REQ-MAINT-UPDATE] should export updateAnnotationReferences as a function", () => {
|
|
14
|
+
expect(typeof maintenance_1.updateAnnotationReferences).toBe("function");
|
|
15
|
+
});
|
|
16
|
+
it("[REQ-MAINT-BATCH] should export batchUpdateAnnotations as a function", () => {
|
|
17
|
+
expect(typeof maintenance_1.batchUpdateAnnotations).toBe("function");
|
|
18
|
+
});
|
|
19
|
+
it("[REQ-MAINT-VERIFY] should export verifyAnnotations as a function", () => {
|
|
20
|
+
expect(typeof maintenance_1.verifyAnnotations).toBe("function");
|
|
21
|
+
});
|
|
22
|
+
it("[REQ-MAINT-REPORT] should export generateMaintenanceReport as a function", () => {
|
|
23
|
+
expect(typeof maintenance_1.generateMaintenanceReport).toBe("function");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
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/007.0-DEV-ERROR-REPORTING.story.md
|
|
8
|
+
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
9
|
+
* @req REQ-ERROR-SPECIFIC - Specific details about what annotation is missing or invalid
|
|
10
|
+
* @req REQ-ERROR-SUGGESTION - Suggest concrete steps to fix the issue
|
|
11
|
+
* @req REQ-ERROR-CONTEXT - Include relevant context in error messages
|
|
12
|
+
*/
|
|
13
|
+
const eslint_1 = require("eslint");
|
|
14
|
+
const require_story_annotation_1 = __importDefault(require("../../src/rules/require-story-annotation"));
|
|
15
|
+
const ruleTester = new eslint_1.RuleTester({
|
|
16
|
+
languageOptions: {
|
|
17
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
describe("Error Reporting Enhancements for require-story-annotation (Story 007.0-DEV-ERROR-REPORTING)", () => {
|
|
21
|
+
ruleTester.run("require-story-annotation", require_story_annotation_1.default, {
|
|
22
|
+
valid: [
|
|
23
|
+
{
|
|
24
|
+
name: "[007.0-DEV-ERROR-REPORTING] valid with existing annotation",
|
|
25
|
+
code: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */ function foo() {}`,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
invalid: [
|
|
29
|
+
{
|
|
30
|
+
name: "[REQ-ERROR-SPECIFIC] missing @story annotation should report specific details and suggestion",
|
|
31
|
+
code: `function bar() {}`,
|
|
32
|
+
errors: [
|
|
33
|
+
{
|
|
34
|
+
messageId: "missingStory",
|
|
35
|
+
data: { name: "bar" },
|
|
36
|
+
suggestions: [
|
|
37
|
+
{
|
|
38
|
+
desc: "Add JSDoc @story annotation for function 'bar', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
39
|
+
output: "/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// @ts-nocheck
|
|
6
7
|
/**
|
|
7
8
|
* Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
8
9
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
@@ -65,52 +66,100 @@ declare function tsDecl(): void;`,
|
|
|
65
66
|
{
|
|
66
67
|
name: "[REQ-ANNOTATION-REQUIRED] missing @story annotation on function",
|
|
67
68
|
code: `function bar() {}`,
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
errors: [
|
|
70
|
+
{
|
|
71
|
+
messageId: "missingStory",
|
|
72
|
+
suggestions: [
|
|
73
|
+
{
|
|
74
|
+
desc: `Add JSDoc @story annotation for function 'bar', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
75
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}`,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
70
80
|
},
|
|
71
81
|
{
|
|
72
82
|
name: "[REQ-ANNOTATION-REQUIRED] missing @story on function expression",
|
|
73
83
|
code: `const fnExpr = function() {};`,
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
errors: [
|
|
85
|
+
{
|
|
86
|
+
messageId: "missingStory",
|
|
87
|
+
suggestions: [
|
|
88
|
+
{
|
|
89
|
+
desc: `Add JSDoc @story annotation for function 'fnExpr', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
90
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
76
95
|
},
|
|
77
96
|
{
|
|
78
97
|
name: "[REQ-ANNOTATION-REQUIRED] missing @story on arrow function",
|
|
79
98
|
code: `const arrowFn = () => {};`,
|
|
80
|
-
|
|
81
|
-
|
|
99
|
+
errors: [
|
|
100
|
+
{
|
|
101
|
+
messageId: "missingStory",
|
|
102
|
+
suggestions: [
|
|
103
|
+
{
|
|
104
|
+
desc: `Add JSDoc @story annotation for function 'arrowFn', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
105
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst arrowFn = () => {};`,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
],
|
|
82
110
|
},
|
|
83
111
|
{
|
|
84
112
|
name: "[REQ-ANNOTATION-REQUIRED] missing @story on class method",
|
|
85
113
|
code: `class C {\n method() {}\n}`,
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
errors: [
|
|
115
|
+
{
|
|
116
|
+
messageId: "missingStory",
|
|
117
|
+
suggestions: [
|
|
118
|
+
{
|
|
119
|
+
desc: `Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
120
|
+
output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
],
|
|
88
125
|
},
|
|
89
126
|
{
|
|
90
127
|
name: "[REQ-ANNOTATION-REQUIRED] missing @story on TS declare function",
|
|
91
128
|
code: `declare function tsDecl(): void;`,
|
|
92
|
-
output: `/** @story <story-file>.story.md */
|
|
93
|
-
declare function tsDecl(): void;`,
|
|
94
129
|
languageOptions: {
|
|
95
130
|
parser: require("@typescript-eslint/parser"),
|
|
96
131
|
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
97
132
|
},
|
|
98
|
-
errors: [
|
|
133
|
+
errors: [
|
|
134
|
+
{
|
|
135
|
+
messageId: "missingStory",
|
|
136
|
+
suggestions: [
|
|
137
|
+
{
|
|
138
|
+
desc: `Add JSDoc @story annotation for function 'tsDecl', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
139
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
99
144
|
},
|
|
100
145
|
{
|
|
101
146
|
name: "[REQ-ANNOTATION-REQUIRED] missing @story on TS method signature",
|
|
102
|
-
code: `interface D {
|
|
103
|
-
method(): void;
|
|
104
|
-
}`,
|
|
105
|
-
output: `/** @story <story-file>.story.md */
|
|
106
|
-
interface D {
|
|
107
|
-
method(): void;
|
|
108
|
-
}`,
|
|
147
|
+
code: `interface D {\n method(): void;\n}`,
|
|
109
148
|
languageOptions: {
|
|
110
149
|
parser: require("@typescript-eslint/parser"),
|
|
111
150
|
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
112
151
|
},
|
|
113
|
-
errors: [
|
|
152
|
+
errors: [
|
|
153
|
+
{
|
|
154
|
+
messageId: "missingStory",
|
|
155
|
+
suggestions: [
|
|
156
|
+
{
|
|
157
|
+
desc: `Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
158
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
],
|
|
114
163
|
},
|
|
115
164
|
],
|
|
116
165
|
});
|
|
@@ -131,16 +180,34 @@ interface D {
|
|
|
131
180
|
{
|
|
132
181
|
name: "[exportPriority] exported function missing @story annotation",
|
|
133
182
|
code: `export function exportedMissing() {}`,
|
|
134
|
-
output: `/** @story <story-file>.story.md */\nexport function exportedMissing() {}`,
|
|
135
183
|
options: [{ exportPriority: "exported" }],
|
|
136
|
-
errors: [
|
|
184
|
+
errors: [
|
|
185
|
+
{
|
|
186
|
+
messageId: "missingStory",
|
|
187
|
+
suggestions: [
|
|
188
|
+
{
|
|
189
|
+
desc: `Add JSDoc @story annotation for function 'exportedMissing', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
190
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport function exportedMissing() {}`,
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
],
|
|
137
195
|
},
|
|
138
196
|
{
|
|
139
197
|
name: "[exportPriority] exported arrow function missing @story annotation",
|
|
140
198
|
code: `export const arrowExported = () => {};`,
|
|
141
|
-
output: `/** @story <story-file>.story.md */\nexport const arrowExported = () => {};`,
|
|
142
199
|
options: [{ exportPriority: "exported" }],
|
|
143
|
-
errors: [
|
|
200
|
+
errors: [
|
|
201
|
+
{
|
|
202
|
+
messageId: "missingStory",
|
|
203
|
+
suggestions: [
|
|
204
|
+
{
|
|
205
|
+
desc: `Add JSDoc @story annotation for function 'arrowExported', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
206
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport const arrowExported = () => {};`,
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
],
|
|
144
211
|
},
|
|
145
212
|
],
|
|
146
213
|
});
|
|
@@ -157,8 +224,17 @@ interface D {
|
|
|
157
224
|
name: "[scope] function declaration missing annotation when scope is FunctionDeclaration",
|
|
158
225
|
code: `function onlyDecl() {}`,
|
|
159
226
|
options: [{ scope: ["FunctionDeclaration"] }],
|
|
160
|
-
|
|
161
|
-
|
|
227
|
+
errors: [
|
|
228
|
+
{
|
|
229
|
+
messageId: "missingStory",
|
|
230
|
+
suggestions: [
|
|
231
|
+
{
|
|
232
|
+
desc: `Add JSDoc @story annotation for function 'onlyDecl', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
233
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction onlyDecl() {}`,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
],
|
|
162
238
|
},
|
|
163
239
|
],
|
|
164
240
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Unit tests for branch annotation helpers
|
|
5
|
+
* Tests for: docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
6
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
7
|
+
* @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
|
|
8
|
+
*/
|
|
9
|
+
const branch_annotation_helpers_1 = require("../../src/utils/branch-annotation-helpers");
|
|
10
|
+
describe("validateBranchTypes helper", () => {
|
|
11
|
+
let context;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
context = { options: [], report: jest.fn() };
|
|
14
|
+
});
|
|
15
|
+
it("should return default branch types when no options provided", () => {
|
|
16
|
+
const result = (0, branch_annotation_helpers_1.validateBranchTypes)(context);
|
|
17
|
+
expect(Array.isArray(result)).toBe(true);
|
|
18
|
+
expect(result).toEqual(branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES);
|
|
19
|
+
});
|
|
20
|
+
it("should return custom branch types when valid options provided", () => {
|
|
21
|
+
context.options = [{ branchTypes: ["IfStatement", "ForStatement"] }];
|
|
22
|
+
const result = (0, branch_annotation_helpers_1.validateBranchTypes)(context);
|
|
23
|
+
expect(Array.isArray(result)).toBe(true);
|
|
24
|
+
expect(result).toEqual(["IfStatement", "ForStatement"]);
|
|
25
|
+
});
|
|
26
|
+
it("should return listener when invalid branch types provided and report errors", () => {
|
|
27
|
+
const invalid = ["UnknownType", "Foo"];
|
|
28
|
+
context.options = [{ branchTypes: invalid }];
|
|
29
|
+
// Invoke helper
|
|
30
|
+
const result = (0, branch_annotation_helpers_1.validateBranchTypes)(context);
|
|
31
|
+
// Should return a listener object
|
|
32
|
+
expect(typeof result).toBe("object");
|
|
33
|
+
expect(result).toHaveProperty("Program");
|
|
34
|
+
// Call the Program listener
|
|
35
|
+
const fakeNode = {};
|
|
36
|
+
result.Program(fakeNode);
|
|
37
|
+
// report should be called for each invalid type
|
|
38
|
+
expect(context.report).toHaveBeenCalledTimes(invalid.length);
|
|
39
|
+
invalid.forEach((t) => {
|
|
40
|
+
expect(context.report).toHaveBeenCalledWith(expect.objectContaining({
|
|
41
|
+
node: fakeNode,
|
|
42
|
+
message: expect.stringContaining(`Value "${t}" should be equal to one of the allowed values:`),
|
|
43
|
+
}));
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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",
|