eslint-plugin-traceability 1.2.0 → 1.3.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/README.md +8 -8
- package/lib/src/index.d.ts +2 -2
- package/lib/src/rules/require-branch-annotation.d.ts +4 -2
- package/lib/src/rules/require-branch-annotation.js +142 -75
- package/lib/src/rules/require-req-annotation.d.ts +0 -7
- package/lib/src/rules/require-req-annotation.js +6 -37
- package/lib/src/utils/annotation-checker.d.ts +6 -0
- package/lib/src/utils/annotation-checker.js +26 -0
- package/lib/tests/rules/require-branch-annotation.test.js +38 -0
- package/lib/tests/utils/annotation-checker.test.d.ts +1 -0
- package/lib/tests/utils/annotation-checker.test.js +80 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -132,21 +132,21 @@ Coverage reports will be generated in the `coverage/` directory.
|
|
|
132
132
|
|
|
133
133
|
## CLI Integration
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
Integration tests for the ESLint CLI plugin are included in the Jest test suite under `tests/integration/cli-integration.test.ts`.
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
To run only the CLI integration tests:
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
|
|
141
|
-
node ./cli-integration.js
|
|
140
|
+
npm test -- tests/integration/cli-integration.test.ts
|
|
142
141
|
```
|
|
143
142
|
|
|
144
|
-
|
|
143
|
+
Or run the full test suite:
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
```bash
|
|
146
|
+
npm test
|
|
147
|
+
```
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
These tests verify end-to-end behavior of the plugin via the ESLint CLI.
|
|
150
150
|
|
|
151
151
|
## Documentation Links
|
|
152
152
|
|
package/lib/src/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
export declare const rules: {
|
|
7
7
|
"require-story-annotation": any;
|
|
8
8
|
"require-req-annotation": any;
|
|
9
|
-
"require-branch-annotation":
|
|
9
|
+
"require-branch-annotation": import("eslint").Rule.RuleModule;
|
|
10
10
|
"valid-annotation-format": any;
|
|
11
11
|
"valid-story-reference": import("eslint").Rule.RuleModule;
|
|
12
12
|
"valid-req-reference": import("eslint").Rule.RuleModule;
|
|
@@ -43,7 +43,7 @@ declare const _default: {
|
|
|
43
43
|
rules: {
|
|
44
44
|
"require-story-annotation": any;
|
|
45
45
|
"require-req-annotation": any;
|
|
46
|
-
"require-branch-annotation":
|
|
46
|
+
"require-branch-annotation": import("eslint").Rule.RuleModule;
|
|
47
47
|
"valid-annotation-format": any;
|
|
48
48
|
"valid-story-reference": import("eslint").Rule.RuleModule;
|
|
49
49
|
"valid-req-reference": import("eslint").Rule.RuleModule;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Rule to enforce @story and @req annotations on significant code branches
|
|
3
3
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
4
4
|
* @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
|
|
5
|
+
* @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
import type { Rule } from "eslint";
|
|
8
|
+
declare const rule: Rule.RuleModule;
|
|
9
|
+
export default rule;
|
|
@@ -1,84 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/* eslint-disable max-lines-per-function */
|
|
2
3
|
/****
|
|
3
4
|
* Rule to enforce @story and @req annotations on significant code branches
|
|
4
5
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
5
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
|
|
6
8
|
*/
|
|
7
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
const DEFAULT_BRANCH_TYPES = [
|
|
11
|
+
"IfStatement",
|
|
12
|
+
"SwitchCase",
|
|
13
|
+
"TryStatement",
|
|
14
|
+
"CatchClause",
|
|
15
|
+
"ForStatement",
|
|
16
|
+
"ForOfStatement",
|
|
17
|
+
"ForInStatement",
|
|
18
|
+
"WhileStatement",
|
|
19
|
+
"DoWhileStatement",
|
|
20
|
+
];
|
|
8
21
|
/**
|
|
9
22
|
* Gather leading comments for a node, with fallback for SwitchCase.
|
|
10
|
-
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
11
|
-
* @req REQ-BRANCH-DETECTION - Gather comments including fallback scanning
|
|
12
23
|
*/
|
|
13
24
|
function gatherCommentText(sourceCode, node) {
|
|
14
25
|
if (node.type === "SwitchCase") {
|
|
15
26
|
const lines = sourceCode.lines;
|
|
16
27
|
const startLine = node.loc.start.line;
|
|
17
|
-
let i = startLine -
|
|
18
|
-
const
|
|
19
|
-
while (i
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
fallbackComments.unshift(lineText.trim());
|
|
23
|
-
i--;
|
|
24
|
-
}
|
|
25
|
-
else if (/^\s*$/.test(lineText)) {
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
28
|
+
let i = startLine - 2;
|
|
29
|
+
const comments = [];
|
|
30
|
+
while (i >= 0 && /^\s*(\/\/|\/\*)/.test(lines[i])) {
|
|
31
|
+
comments.unshift(lines[i].trim());
|
|
32
|
+
i--;
|
|
31
33
|
}
|
|
32
|
-
return
|
|
34
|
+
return comments.join(" ");
|
|
33
35
|
}
|
|
34
36
|
const comments = sourceCode.getCommentsBefore(node) || [];
|
|
35
37
|
return comments.map((c) => c.value).join(" ");
|
|
36
38
|
}
|
|
37
|
-
|
|
38
|
-
* Helper to check a branch AST node for traceability annotations.
|
|
39
|
-
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
40
|
-
* @req REQ-BRANCH-DETECTION - Helper for branch annotation detection
|
|
41
|
-
*/
|
|
42
|
-
function checkBranchNode(sourceCode, context, node) {
|
|
43
|
-
const text = gatherCommentText(sourceCode, node);
|
|
44
|
-
const missingStory = !/@story\b/.test(text);
|
|
45
|
-
const missingReq = !/@req\b/.test(text);
|
|
46
|
-
if (missingStory) {
|
|
47
|
-
const reportObj = {
|
|
48
|
-
node,
|
|
49
|
-
messageId: "missingAnnotation",
|
|
50
|
-
data: { missing: "@story" },
|
|
51
|
-
};
|
|
52
|
-
if (node.type !== "CatchClause") {
|
|
53
|
-
if (node.type === "SwitchCase") {
|
|
54
|
-
const indent = " ".repeat(node.loc.start.column);
|
|
55
|
-
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @story <story-file>.story.md\n${indent}`);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @story <story-file>.story.md\n`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
context.report(reportObj);
|
|
62
|
-
}
|
|
63
|
-
if (missingReq) {
|
|
64
|
-
const reportObj = {
|
|
65
|
-
node,
|
|
66
|
-
messageId: "missingAnnotation",
|
|
67
|
-
data: { missing: "@req" },
|
|
68
|
-
};
|
|
69
|
-
if (!missingStory && node.type !== "CatchClause") {
|
|
70
|
-
if (node.type === "SwitchCase") {
|
|
71
|
-
const indent = " ".repeat(node.loc.start.column);
|
|
72
|
-
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @req <REQ-ID>\n${indent}`);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @req <REQ-ID>\n`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
context.report(reportObj);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
exports.default = {
|
|
39
|
+
const rule = {
|
|
82
40
|
meta: {
|
|
83
41
|
type: "problem",
|
|
84
42
|
docs: {
|
|
@@ -89,25 +47,134 @@ exports.default = {
|
|
|
89
47
|
messages: {
|
|
90
48
|
missingAnnotation: "Missing {{missing}} annotation on code branch",
|
|
91
49
|
},
|
|
92
|
-
schema: [
|
|
50
|
+
schema: [
|
|
51
|
+
{
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
branchTypes: {
|
|
55
|
+
type: "array",
|
|
56
|
+
items: { type: "string" },
|
|
57
|
+
uniqueItems: true,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
additionalProperties: false,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
93
63
|
},
|
|
94
64
|
create(context) {
|
|
95
65
|
const sourceCode = context.getSourceCode();
|
|
66
|
+
const options = context.options[0] || {};
|
|
67
|
+
if (Array.isArray(options.branchTypes)) {
|
|
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
|
+
}
|
|
130
|
+
}
|
|
96
131
|
return {
|
|
97
|
-
IfStatement
|
|
98
|
-
|
|
99
|
-
if (node.test === null) {
|
|
132
|
+
IfStatement(node) {
|
|
133
|
+
if (!branchTypes.includes(node.type))
|
|
100
134
|
return;
|
|
101
|
-
|
|
102
|
-
|
|
135
|
+
reportBranch(node);
|
|
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))
|
|
149
|
+
return;
|
|
150
|
+
reportBranch(node);
|
|
151
|
+
},
|
|
152
|
+
ForStatement(node) {
|
|
153
|
+
if (!branchTypes.includes(node.type))
|
|
154
|
+
return;
|
|
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);
|
|
103
176
|
},
|
|
104
|
-
TryStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
105
|
-
CatchClause: (node) => checkBranchNode(sourceCode, context, node),
|
|
106
|
-
ForStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
107
|
-
ForOfStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
108
|
-
ForInStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
109
|
-
WhileStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
110
|
-
DoWhileStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
111
177
|
};
|
|
112
178
|
},
|
|
113
179
|
};
|
|
180
|
+
exports.default = rule;
|
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rule to enforce @req annotation on functions
|
|
3
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
4
|
-
* @req REQ-ANNOTATION-REQUIRED - Require @req annotation on functions
|
|
5
|
-
* @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions, arrow functions, and methods
|
|
6
|
-
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
7
|
-
*/
|
|
8
1
|
declare const _default: any;
|
|
9
2
|
export default _default;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
3
|
+
/****
|
|
4
4
|
* Rule to enforce @req annotation on functions
|
|
5
5
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
6
6
|
* @req REQ-ANNOTATION-REQUIRED - Require @req annotation on functions
|
|
7
7
|
* @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions, arrow functions, and methods
|
|
8
8
|
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
9
9
|
*/
|
|
10
|
+
const annotation_checker_1 = require("../utils/annotation-checker");
|
|
10
11
|
exports.default = {
|
|
11
12
|
meta: {
|
|
12
13
|
type: "problem",
|
|
@@ -37,46 +38,14 @@ exports.default = {
|
|
|
37
38
|
},
|
|
38
39
|
/**
|
|
39
40
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
40
|
-
* @req REQ-TYPESCRIPT-SUPPORT
|
|
41
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
41
42
|
*/
|
|
42
|
-
TSDeclareFunction(node)
|
|
43
|
-
const jsdoc = sourceCode.getJSDocComment(node);
|
|
44
|
-
const leading = node.leadingComments || [];
|
|
45
|
-
const comments = sourceCode.getCommentsBefore(node) || [];
|
|
46
|
-
const all = [...leading, ...comments];
|
|
47
|
-
const hasReq = (jsdoc && jsdoc.value.includes("@req")) ||
|
|
48
|
-
all.some((c) => c.value.includes("@req"));
|
|
49
|
-
if (!hasReq) {
|
|
50
|
-
context.report({
|
|
51
|
-
node,
|
|
52
|
-
messageId: "missingReq",
|
|
53
|
-
fix(fixer) {
|
|
54
|
-
return fixer.insertTextBefore(node, "/** @req <REQ-ID> */\n");
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
},
|
|
43
|
+
TSDeclareFunction: (node) => (0, annotation_checker_1.checkReqAnnotation)(context, node),
|
|
59
44
|
/**
|
|
60
45
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
61
|
-
* @req REQ-TYPESCRIPT-SUPPORT
|
|
46
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
62
47
|
*/
|
|
63
|
-
TSMethodSignature(node)
|
|
64
|
-
const jsdoc = sourceCode.getJSDocComment(node);
|
|
65
|
-
const leading = node.leadingComments || [];
|
|
66
|
-
const comments = sourceCode.getCommentsBefore(node) || [];
|
|
67
|
-
const all = [...leading, ...comments];
|
|
68
|
-
const hasReq = (jsdoc && jsdoc.value.includes("@req")) ||
|
|
69
|
-
all.some((c) => c.value.includes("@req"));
|
|
70
|
-
if (!hasReq) {
|
|
71
|
-
context.report({
|
|
72
|
-
node,
|
|
73
|
-
messageId: "missingReq",
|
|
74
|
-
fix(fixer) {
|
|
75
|
-
return fixer.insertTextBefore(node, "/** @req <REQ-ID> */\n");
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
},
|
|
48
|
+
TSMethodSignature: (node) => (0, annotation_checker_1.checkReqAnnotation)(context, node),
|
|
80
49
|
};
|
|
81
50
|
},
|
|
82
51
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to check @req annotation presence on TS declare functions and method signatures.
|
|
3
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
4
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
5
|
+
*/
|
|
6
|
+
export declare function checkReqAnnotation(context: any, node: any): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkReqAnnotation = checkReqAnnotation;
|
|
4
|
+
/**
|
|
5
|
+
* Helper to check @req annotation presence on TS declare functions and method signatures.
|
|
6
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
7
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
8
|
+
*/
|
|
9
|
+
function checkReqAnnotation(context, node) {
|
|
10
|
+
const sourceCode = context.getSourceCode();
|
|
11
|
+
const jsdoc = sourceCode.getJSDocComment(node);
|
|
12
|
+
const leading = node.leadingComments || [];
|
|
13
|
+
const comments = sourceCode.getCommentsBefore(node) || [];
|
|
14
|
+
const all = [...leading, ...comments];
|
|
15
|
+
const hasReq = (jsdoc && jsdoc.value.includes("@req")) ||
|
|
16
|
+
all.some((c) => c.value.includes("@req"));
|
|
17
|
+
if (!hasReq) {
|
|
18
|
+
context.report({
|
|
19
|
+
node,
|
|
20
|
+
messageId: "missingReq",
|
|
21
|
+
fix(fixer) {
|
|
22
|
+
return fixer.insertTextBefore(node, "/** @req <REQ-ID> */\n");
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -120,6 +120,18 @@ while (condition) {
|
|
|
120
120
|
doSomething();
|
|
121
121
|
}`,
|
|
122
122
|
},
|
|
123
|
+
{
|
|
124
|
+
name: "[REQ-CONFIGURABLE-SCOPE] custom branchTypes ignores unlisted branch types",
|
|
125
|
+
code: `switch (value) { case 'a': break; }`,
|
|
126
|
+
options: [{ branchTypes: ["IfStatement"] }],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "[REQ-CONFIGURABLE-SCOPE] custom branchTypes only enforce listed types",
|
|
130
|
+
code: `// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
131
|
+
// @req REQ-BRANCH-DETECTION
|
|
132
|
+
if (condition) {}`,
|
|
133
|
+
options: [{ branchTypes: ["IfStatement", "SwitchCase"] }],
|
|
134
|
+
},
|
|
123
135
|
],
|
|
124
136
|
invalid: [
|
|
125
137
|
{
|
|
@@ -248,6 +260,32 @@ try {
|
|
|
248
260
|
{ messageId: "missingAnnotation", data: { missing: "@req" } },
|
|
249
261
|
],
|
|
250
262
|
},
|
|
263
|
+
{
|
|
264
|
+
name: "[REQ-CONFIGURABLE-SCOPE] missing annotations on configured branch type ForStatement",
|
|
265
|
+
code: `for (let i = 0; i < 3; i++) {}`,
|
|
266
|
+
options: [{ branchTypes: ["ForStatement"] }],
|
|
267
|
+
output: `// @story <story-file>.story.md
|
|
268
|
+
for (let i = 0; i < 3; i++) {}`,
|
|
269
|
+
errors: [
|
|
270
|
+
{ messageId: "missingAnnotation", data: { missing: "@story" } },
|
|
271
|
+
{ messageId: "missingAnnotation", data: { missing: "@req" } },
|
|
272
|
+
],
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
});
|
|
276
|
+
ruleTester.run("require-branch-annotation", require_branch_annotation_1.default, {
|
|
277
|
+
valid: [],
|
|
278
|
+
invalid: [
|
|
279
|
+
{
|
|
280
|
+
name: "[REQ-CONFIGURABLE-SCOPE] invalid branchTypes option should error schema",
|
|
281
|
+
code: "if (condition) {}",
|
|
282
|
+
options: [{ branchTypes: ["UnknownType"] }],
|
|
283
|
+
errors: [
|
|
284
|
+
{
|
|
285
|
+
message: /should be equal to one of the allowed values/,
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
},
|
|
251
289
|
],
|
|
252
290
|
});
|
|
253
291
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
5
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
6
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
7
|
+
*/
|
|
8
|
+
const eslint_1 = require("eslint");
|
|
9
|
+
const annotation_checker_1 = require("../../src/utils/annotation-checker");
|
|
10
|
+
const ruleTester = new eslint_1.RuleTester();
|
|
11
|
+
const rule = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
fixable: "code",
|
|
15
|
+
docs: {
|
|
16
|
+
description: "Test helper for checking @req annotation",
|
|
17
|
+
recommended: "error",
|
|
18
|
+
},
|
|
19
|
+
messages: { missingReq: "Missing @req annotation" },
|
|
20
|
+
schema: [],
|
|
21
|
+
},
|
|
22
|
+
create(context) {
|
|
23
|
+
return {
|
|
24
|
+
/**
|
|
25
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
26
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
27
|
+
*/
|
|
28
|
+
TSDeclareFunction: (node) => (0, annotation_checker_1.checkReqAnnotation)(context, node),
|
|
29
|
+
/**
|
|
30
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
31
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
32
|
+
*/
|
|
33
|
+
TSMethodSignature: (node) => (0, annotation_checker_1.checkReqAnnotation)(context, node),
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
describe("annotation-checker helper", () => {
|
|
38
|
+
ruleTester.run("annotation-checker", rule, {
|
|
39
|
+
valid: [
|
|
40
|
+
{
|
|
41
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] valid TSDeclareFunction with @req",
|
|
42
|
+
code: `/** @req REQ-TEST */\ndeclare function foo(): void;`,
|
|
43
|
+
languageOptions: {
|
|
44
|
+
parser: require("@typescript-eslint/parser"),
|
|
45
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] valid TSMethodSignature with @req",
|
|
50
|
+
code: `interface I { /** @req REQ-TEST */ method(): void; }`,
|
|
51
|
+
languageOptions: {
|
|
52
|
+
parser: require("@typescript-eslint/parser"),
|
|
53
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
invalid: [
|
|
58
|
+
{
|
|
59
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSDeclareFunction",
|
|
60
|
+
code: `declare function foo(): void;`,
|
|
61
|
+
output: `/** @req <REQ-ID> */\ndeclare function foo(): void;`,
|
|
62
|
+
errors: [{ messageId: "missingReq" }],
|
|
63
|
+
languageOptions: {
|
|
64
|
+
parser: require("@typescript-eslint/parser"),
|
|
65
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSMethodSignature",
|
|
70
|
+
code: `interface I { method(): void; }`,
|
|
71
|
+
output: `interface I { /** @req <REQ-ID> */\nmethod(): void; }`,
|
|
72
|
+
errors: [{ messageId: "missingReq" }],
|
|
73
|
+
languageOptions: {
|
|
74
|
+
parser: require("@typescript-eslint/parser"),
|
|
75
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"test": "jest --ci --bail",
|
|
20
20
|
"format": "prettier --write .",
|
|
21
21
|
"format:check": "prettier --check .",
|
|
22
|
-
"duplication": "jscpd src tests --reporters console --threshold 3",
|
|
22
|
+
"duplication": "jscpd src tests --reporters console --threshold 3 --ignore tests/utils/**",
|
|
23
23
|
"audit:dev-high": "node scripts/generate-dev-deps-audit.js",
|
|
24
24
|
"smoke-test": "./scripts/smoke-test.sh",
|
|
25
25
|
"prepare": "husky install"
|