eslint-plugin-traceability 1.1.10 → 1.2.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 +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/tests/integration/cli-integration.test.d.ts +1 -0
- package/lib/tests/integration/cli-integration.test.js +85 -0
- package/lib/tests/maintenance/detect.test.js +18 -0
- package/lib/tests/maintenance/update-isolated.test.js +4 -0
- package/lib/tests/rules/require-req-annotation.test.js +41 -1
- package/package.json +2 -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
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
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 CLI integration functionality
|
|
8
|
+
* @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
9
|
+
* @req REQ-PLUGIN-STRUCTURE - Validate plugin registers via CLI
|
|
10
|
+
*/
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
describe("[docs/stories/001.0-DEV-PLUGIN-SETUP.story.md] CLI Integration (traceability plugin)", () => {
|
|
14
|
+
const eslintPkgDir = path_1.default.dirname(require.resolve("eslint/package.json"));
|
|
15
|
+
const eslintCliPath = path_1.default.join(eslintPkgDir, "bin", "eslint.js");
|
|
16
|
+
const configPath = path_1.default.resolve(__dirname, "../../eslint.config.js");
|
|
17
|
+
const tests = [
|
|
18
|
+
{
|
|
19
|
+
name: "reports error when @story annotation is missing",
|
|
20
|
+
code: "function foo() {}",
|
|
21
|
+
rule: "traceability/require-story-annotation:error",
|
|
22
|
+
expectedStatus: 1,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "does not report error when @story annotation is present",
|
|
26
|
+
code: `/**
|
|
27
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
28
|
+
*/
|
|
29
|
+
function foo() {}`,
|
|
30
|
+
rule: "traceability/require-story-annotation:error",
|
|
31
|
+
expectedStatus: 0,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "reports error when @req annotation is missing",
|
|
35
|
+
code: "function bar() {}",
|
|
36
|
+
rule: "traceability/require-req-annotation:error",
|
|
37
|
+
expectedStatus: 1,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "reports error when @story annotation uses path traversal and @req annotation uses path traversal",
|
|
41
|
+
code: `/**
|
|
42
|
+
* @story ../docs/stories/invalid.story.md
|
|
43
|
+
* @req ../docs/requirements/REQ-INVALID.md
|
|
44
|
+
*/
|
|
45
|
+
function bar() {}`,
|
|
46
|
+
rule: "traceability/valid-req-reference:error",
|
|
47
|
+
expectedStatus: 1,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "reports error when @story annotation uses absolute path and @req annotation uses absolute path",
|
|
51
|
+
code: `/**
|
|
52
|
+
* @story /absolute/path/to/story.story.md
|
|
53
|
+
* @req /etc/passwd
|
|
54
|
+
*/
|
|
55
|
+
function baz() {}`,
|
|
56
|
+
rule: "traceability/valid-req-reference:error",
|
|
57
|
+
expectedStatus: 1,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
function runEslint(code, rule) {
|
|
61
|
+
const args = [
|
|
62
|
+
"--no-config-lookup",
|
|
63
|
+
"--config",
|
|
64
|
+
configPath,
|
|
65
|
+
"--stdin",
|
|
66
|
+
"--stdin-filename",
|
|
67
|
+
"foo.js",
|
|
68
|
+
"--rule",
|
|
69
|
+
"no-unused-vars:off",
|
|
70
|
+
"--rule",
|
|
71
|
+
rule,
|
|
72
|
+
];
|
|
73
|
+
const result = (0, child_process_1.spawnSync)(process.execPath, [eslintCliPath, ...args], {
|
|
74
|
+
encoding: "utf-8",
|
|
75
|
+
input: code,
|
|
76
|
+
});
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
tests.forEach((testCase) => {
|
|
80
|
+
it(`[REQ-PLUGIN-STRUCTURE] ${testCase.name}`, () => {
|
|
81
|
+
const result = runEslint(testCase.code, testCase.rule);
|
|
82
|
+
expect(result.status).toBe(testCase.expectedStatus);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -24,4 +24,22 @@ describe("detectStaleAnnotations (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
|
24
24
|
fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
|
+
it("[REQ-MAINT-DETECT] should detect stale annotation references", () => {
|
|
28
|
+
const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "detect-stale-"));
|
|
29
|
+
try {
|
|
30
|
+
const filePath = path_1.default.join(tmpDir, "file.ts");
|
|
31
|
+
const storyName = "stale.story.md";
|
|
32
|
+
const content = `
|
|
33
|
+
/**
|
|
34
|
+
* @story ${storyName}
|
|
35
|
+
*/`;
|
|
36
|
+
fs_1.default.writeFileSync(filePath, content, "utf8");
|
|
37
|
+
const result = (0, detect_1.detectStaleAnnotations)(tmpDir);
|
|
38
|
+
expect(result).toHaveLength(1);
|
|
39
|
+
expect(result).toContain(storyName);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
27
45
|
});
|
|
@@ -63,4 +63,8 @@ function foo() {}
|
|
|
63
63
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
64
64
|
}
|
|
65
65
|
});
|
|
66
|
+
it("[REQ-MAINT-UPDATE] should return 0 when directory does not exist", () => {
|
|
67
|
+
const count = (0, update_1.updateAnnotationReferences)("non-existent-dir", "old.md", "new.md");
|
|
68
|
+
expect(count).toBe(0);
|
|
69
|
+
});
|
|
66
70
|
});
|
|
@@ -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
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
"doc": "docs"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"prelint": "npm run build",
|
|
17
16
|
"build": "tsc -p tsconfig.json",
|
|
18
17
|
"type-check": "tsc --noEmit -p tsconfig.json",
|
|
19
18
|
"lint": "eslint \"src/**/*.{js,ts}\" \"tests/**/*.{js,ts}\" --max-warnings=0",
|
|
@@ -60,7 +59,7 @@
|
|
|
60
59
|
"actionlint": "^2.0.6",
|
|
61
60
|
"eslint": "^9.39.1",
|
|
62
61
|
"husky": "^9.1.7",
|
|
63
|
-
"jest": "^
|
|
62
|
+
"jest": "^30.2.0",
|
|
64
63
|
"jscpd": "^4.0.5",
|
|
65
64
|
"lint-staged": "^16.2.6",
|
|
66
65
|
"prettier": "^3.6.2",
|