eslint-plugin-traceability 1.1.9 → 1.1.11
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 +5 -0
- package/lib/src/rules/require-req-annotation.d.ts +2 -0
- package/lib/src/rules/require-req-annotation.js +44 -0
- package/lib/src/rules/require-story-annotation.d.ts +2 -0
- package/lib/src/rules/require-story-annotation.js +46 -0
- package/lib/tests/rules/require-req-annotation.test.js +41 -1
- package/lib/tests/rules/require-story-annotation.test.js +46 -0
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -21,6 +21,11 @@ For detailed setup with ESLint v9, see user-docs/eslint-9-setup-guide.md.
|
|
|
21
21
|
|
|
22
22
|
Add the plugin to your ESLint configuration and enable the rules.
|
|
23
23
|
|
|
24
|
+
Additional ESLint v9 configuration guidance:
|
|
25
|
+
|
|
26
|
+
- For detailed configuration examples, see [Common Configuration Patterns](user-docs/eslint-9-setup-guide.md#common-configuration-patterns) in the ESLint 9 Setup Guide.
|
|
27
|
+
- For troubleshooting ESLint flat-config errors, see [Troubleshooting ESLint Configuration](user-docs/eslint-9-setup-guide.md#troubleshooting-eslint-configuration).
|
|
28
|
+
|
|
24
29
|
Example eslint.config.js (ESLint v9 flat config):
|
|
25
30
|
|
|
26
31
|
```js
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Rule to enforce @req annotation on functions
|
|
3
3
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
4
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
|
|
5
7
|
*/
|
|
6
8
|
declare const _default: any;
|
|
7
9
|
export default _default;
|
|
@@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
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
|
+
* @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions, arrow functions, and methods
|
|
8
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
7
9
|
*/
|
|
8
10
|
exports.default = {
|
|
9
11
|
meta: {
|
|
@@ -33,6 +35,48 @@ exports.default = {
|
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
},
|
|
38
|
+
/**
|
|
39
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
40
|
+
* @req REQ-TYPESCRIPT-SUPPORT
|
|
41
|
+
*/
|
|
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
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
61
|
+
* @req REQ-TYPESCRIPT-SUPPORT
|
|
62
|
+
*/
|
|
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
|
+
},
|
|
36
80
|
};
|
|
37
81
|
},
|
|
38
82
|
};
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* @req REQ-OPTIONS-SCOPE - Support configuring which function types to enforce via options
|
|
6
6
|
* @req REQ-EXPORT-PRIORITY - Add exportPriority option to target exported or non-exported
|
|
7
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
|
|
8
10
|
*/
|
|
9
11
|
declare const _default: any;
|
|
10
12
|
export default _default;
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* @req REQ-OPTIONS-SCOPE - Support configuring which function types to enforce via options
|
|
7
7
|
* @req REQ-EXPORT-PRIORITY - Add exportPriority option to target exported or non-exported
|
|
8
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
|
|
9
11
|
*/
|
|
10
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
13
|
/**
|
|
@@ -96,6 +98,12 @@ function resolveTargetNode(sourceCode, node) {
|
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
}
|
|
101
|
+
else if (node.type === "TSMethodSignature") {
|
|
102
|
+
const exp = findAncestorNode(node, ["TSInterfaceDeclaration"]);
|
|
103
|
+
if (exp) {
|
|
104
|
+
target = exp;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
99
107
|
else if (node.type === "MethodDefinition") {
|
|
100
108
|
target = node;
|
|
101
109
|
}
|
|
@@ -124,6 +132,30 @@ function checkStoryAnnotation(sourceCode, context, node, scope, exportPriority)
|
|
|
124
132
|
if (!shouldCheckNode(node, scope, exportPriority)) {
|
|
125
133
|
return;
|
|
126
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
|
+
}
|
|
127
159
|
const target = resolveTargetNode(sourceCode, node);
|
|
128
160
|
if (hasStoryAnnotation(sourceCode, target)) {
|
|
129
161
|
return;
|
|
@@ -162,6 +194,8 @@ exports.default = {
|
|
|
162
194
|
"FunctionExpression",
|
|
163
195
|
"ArrowFunctionExpression",
|
|
164
196
|
"MethodDefinition",
|
|
197
|
+
"TSDeclareFunction",
|
|
198
|
+
"TSMethodSignature",
|
|
165
199
|
],
|
|
166
200
|
},
|
|
167
201
|
uniqueItems: true,
|
|
@@ -182,6 +216,8 @@ exports.default = {
|
|
|
182
216
|
"FunctionExpression",
|
|
183
217
|
"ArrowFunctionExpression",
|
|
184
218
|
"MethodDefinition",
|
|
219
|
+
"TSDeclareFunction",
|
|
220
|
+
"TSMethodSignature",
|
|
185
221
|
];
|
|
186
222
|
const exportPriority = options.exportPriority || "all";
|
|
187
223
|
return {
|
|
@@ -197,6 +233,16 @@ exports.default = {
|
|
|
197
233
|
MethodDefinition(node) {
|
|
198
234
|
checkStoryAnnotation(sourceCode, context, node, scope, exportPriority);
|
|
199
235
|
},
|
|
236
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
237
|
+
// @req REQ-FUNCTION-DETECTION - Detect TS-specific function syntax
|
|
238
|
+
TSDeclareFunction(node) {
|
|
239
|
+
checkStoryAnnotation(sourceCode, context, node, scope, exportPriority);
|
|
240
|
+
},
|
|
241
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
242
|
+
// @req REQ-FUNCTION-DETECTION - Detect TS-specific function syntax
|
|
243
|
+
TSMethodSignature(node) {
|
|
244
|
+
checkStoryAnnotation(sourceCode, context, node, scope, exportPriority);
|
|
245
|
+
},
|
|
200
246
|
};
|
|
201
247
|
},
|
|
202
248
|
};
|
|
@@ -10,7 +10,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
10
|
*/
|
|
11
11
|
const eslint_1 = require("eslint");
|
|
12
12
|
const require_req_annotation_1 = __importDefault(require("../../src/rules/require-req-annotation"));
|
|
13
|
-
const ruleTester = new eslint_1.RuleTester(
|
|
13
|
+
const ruleTester = new eslint_1.RuleTester({
|
|
14
|
+
languageOptions: {
|
|
15
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
16
|
+
},
|
|
17
|
+
});
|
|
14
18
|
describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", () => {
|
|
15
19
|
ruleTester.run("require-req-annotation", require_req_annotation_1.default, {
|
|
16
20
|
valid: [
|
|
@@ -22,6 +26,22 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
22
26
|
name: "[REQ-ANNOTATION-REQUIRED] valid with @story and @req annotations",
|
|
23
27
|
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-EXAMPLE\n */\nfunction bar() {}`,
|
|
24
28
|
},
|
|
29
|
+
{
|
|
30
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] valid with @req annotation on TSDeclareFunction",
|
|
31
|
+
code: `/**\n * @req REQ-EXAMPLE\n */\ndeclare function foo(): void;`,
|
|
32
|
+
languageOptions: {
|
|
33
|
+
parser: require("@typescript-eslint/parser"),
|
|
34
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] valid with @req annotation on TSMethodSignature",
|
|
39
|
+
code: `interface I {\n /**\n * @req REQ-EXAMPLE\n */\n method(): void;\n}`,
|
|
40
|
+
languageOptions: {
|
|
41
|
+
parser: require("@typescript-eslint/parser"),
|
|
42
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
25
45
|
],
|
|
26
46
|
invalid: [
|
|
27
47
|
{
|
|
@@ -36,6 +56,26 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
36
56
|
output: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\n/** @req <REQ-ID> */\nfunction qux() {}`,
|
|
37
57
|
errors: [{ messageId: "missingReq" }],
|
|
38
58
|
},
|
|
59
|
+
{
|
|
60
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSDeclareFunction",
|
|
61
|
+
code: `declare function baz(): void;`,
|
|
62
|
+
output: `/** @req <REQ-ID> */\ndeclare function baz(): void;`,
|
|
63
|
+
errors: [{ messageId: "missingReq" }],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parser: require("@typescript-eslint/parser"),
|
|
66
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSMethodSignature",
|
|
71
|
+
code: `interface I { method(): void; }`,
|
|
72
|
+
output: `interface I { /** @req <REQ-ID> */\nmethod(): void; }`,
|
|
73
|
+
errors: [{ messageId: "missingReq" }],
|
|
74
|
+
languageOptions: {
|
|
75
|
+
parser: require("@typescript-eslint/parser"),
|
|
76
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
39
79
|
],
|
|
40
80
|
});
|
|
41
81
|
});
|
|
@@ -40,6 +40,26 @@ const arrowFn = () => {};`,
|
|
|
40
40
|
name: "[REQ-ANNOTATION-REQUIRED] valid on class method with annotation",
|
|
41
41
|
code: `class A {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
42
42
|
},
|
|
43
|
+
{
|
|
44
|
+
name: "[REQ-FUNCTION-DETECTION] valid with annotation on TS declare function",
|
|
45
|
+
code: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
46
|
+
declare function tsDecl(): void;`,
|
|
47
|
+
languageOptions: {
|
|
48
|
+
parser: require("@typescript-eslint/parser"),
|
|
49
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "[REQ-FUNCTION-DETECTION] valid with annotation on TS method signature",
|
|
54
|
+
code: `interface C {
|
|
55
|
+
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
56
|
+
method(): void;
|
|
57
|
+
}`,
|
|
58
|
+
languageOptions: {
|
|
59
|
+
parser: require("@typescript-eslint/parser"),
|
|
60
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
43
63
|
],
|
|
44
64
|
invalid: [
|
|
45
65
|
{
|
|
@@ -66,6 +86,32 @@ const arrowFn = () => {};`,
|
|
|
66
86
|
output: `class C {\n /** @story <story-file>.story.md */\n method() {}\n}`,
|
|
67
87
|
errors: [{ messageId: "missingStory" }],
|
|
68
88
|
},
|
|
89
|
+
{
|
|
90
|
+
name: "[REQ-ANNOTATION-REQUIRED] missing @story on TS declare function",
|
|
91
|
+
code: `declare function tsDecl(): void;`,
|
|
92
|
+
output: `/** @story <story-file>.story.md */
|
|
93
|
+
declare function tsDecl(): void;`,
|
|
94
|
+
languageOptions: {
|
|
95
|
+
parser: require("@typescript-eslint/parser"),
|
|
96
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
97
|
+
},
|
|
98
|
+
errors: [{ messageId: "missingStory" }],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
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
|
+
}`,
|
|
109
|
+
languageOptions: {
|
|
110
|
+
parser: require("@typescript-eslint/parser"),
|
|
111
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
112
|
+
},
|
|
113
|
+
errors: [{ messageId: "missingStory" }],
|
|
114
|
+
},
|
|
69
115
|
],
|
|
70
116
|
});
|
|
71
117
|
ruleTester.run("require-story-annotation with exportPriority option", require_story_annotation_1.default, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
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",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"build": "tsc -p tsconfig.json",
|
|
18
18
|
"type-check": "tsc --noEmit -p tsconfig.json",
|
|
19
19
|
"lint": "eslint \"src/**/*.{js,ts}\" \"tests/**/*.{js,ts}\" --max-warnings=0",
|
|
20
|
-
"test": "jest --ci --bail",
|
|
20
|
+
"test": "jest --ci --bail && node tests/integration/cli-integration.js",
|
|
21
21
|
"format": "prettier --write .",
|
|
22
22
|
"format:check": "prettier --check .",
|
|
23
23
|
"duplication": "jscpd src tests --reporters console --threshold 3",
|
|
@@ -75,6 +75,11 @@
|
|
|
75
75
|
"node": ">=14"
|
|
76
76
|
},
|
|
77
77
|
"overrides": {
|
|
78
|
-
"glob": "
|
|
78
|
+
"glob": "12.0.0",
|
|
79
|
+
"http-cache-semantics": ">=4.1.1",
|
|
80
|
+
"ip": ">=2.0.2",
|
|
81
|
+
"semver": ">=7.5.2",
|
|
82
|
+
"socks": ">=2.7.2",
|
|
83
|
+
"tar": ">=6.1.12"
|
|
79
84
|
}
|
|
80
85
|
}
|