eslint-plugin-traceability 1.4.3 → 1.4.5

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.
Files changed (27) hide show
  1. package/lib/src/maintenance/batch.js +1 -0
  2. package/lib/src/maintenance/detect.js +4 -0
  3. package/lib/src/maintenance/report.js +2 -0
  4. package/lib/src/maintenance/update.js +18 -0
  5. package/lib/src/maintenance/utils.js +33 -2
  6. package/lib/src/rules/helpers/require-story-core.d.ts +35 -0
  7. package/lib/src/rules/helpers/require-story-core.js +139 -0
  8. package/lib/src/rules/helpers/require-story-helpers.d.ts +124 -0
  9. package/lib/src/rules/helpers/require-story-helpers.js +293 -0
  10. package/lib/src/rules/helpers/require-story-io.d.ts +35 -0
  11. package/lib/src/rules/helpers/require-story-io.js +107 -0
  12. package/lib/src/rules/helpers/require-story-visitors.d.ts +13 -0
  13. package/lib/src/rules/helpers/require-story-visitors.js +163 -0
  14. package/lib/src/rules/require-story-annotation.d.ts +15 -0
  15. package/lib/src/rules/require-story-annotation.js +27 -268
  16. package/lib/src/rules/valid-req-reference.js +9 -0
  17. package/lib/src/rules/valid-story-reference.js +4 -0
  18. package/lib/src/utils/branch-annotation-helpers.js +91 -31
  19. package/lib/tests/rules/require-story-core.branches.test.d.ts +1 -0
  20. package/lib/tests/rules/require-story-core.branches.test.js +69 -0
  21. package/lib/tests/rules/require-story-core.test.d.ts +1 -0
  22. package/lib/tests/rules/require-story-core.test.js +58 -0
  23. package/lib/tests/rules/require-story-helpers.test.d.ts +1 -0
  24. package/lib/tests/rules/require-story-helpers.test.js +173 -0
  25. package/lib/tests/rules/require-story-io.edgecases.test.d.ts +6 -0
  26. package/lib/tests/rules/require-story-io.edgecases.test.js +50 -0
  27. package/package.json +3 -1
@@ -0,0 +1,173 @@
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-ANNOTATION-REQUIRED - Verify helper functions in require-story helpers produce correct fixes and reporting behavior
7
+ */
8
+ const require_story_core_1 = require("../../src/rules/helpers/require-story-core");
9
+ const require_story_helpers_1 = require("../../src/rules/helpers/require-story-helpers");
10
+ describe("Require Story Helpers (Story 003.0)", () => {
11
+ test("createAddStoryFix uses parent range start when available", () => {
12
+ const target = {
13
+ type: "FunctionDeclaration",
14
+ range: [20, 40],
15
+ parent: { type: "ExportNamedDeclaration", range: [10, 50] },
16
+ };
17
+ const fixer = {
18
+ insertTextBeforeRange: jest.fn((r, t) => ({ r, t })),
19
+ };
20
+ const fixFn = (0, require_story_core_1.createAddStoryFix)(target);
21
+ const result = fixFn(fixer);
22
+ expect(fixer.insertTextBeforeRange).toHaveBeenCalledTimes(1);
23
+ const calledArgs = fixer.insertTextBeforeRange.mock.calls[0];
24
+ expect(calledArgs[0]).toEqual([10, 10]);
25
+ expect(calledArgs[1]).toBe(`${require_story_helpers_1.ANNOTATION}\n`);
26
+ expect(result).toEqual({ r: [10, 10], t: `${require_story_helpers_1.ANNOTATION}\n` });
27
+ });
28
+ test("createMethodFix falls back to node.range when parent not export", () => {
29
+ const node = {
30
+ type: "MethodDefinition",
31
+ range: [30, 60],
32
+ parent: { type: "ClassBody" },
33
+ };
34
+ const fixer = {
35
+ insertTextBeforeRange: jest.fn((r, t) => ({ r, t })),
36
+ };
37
+ const fixFn = (0, require_story_core_1.createMethodFix)(node);
38
+ const res = fixFn(fixer);
39
+ expect(fixer.insertTextBeforeRange.mock.calls[0][0]).toEqual([30, 30]);
40
+ expect(fixer.insertTextBeforeRange.mock.calls[0][1]).toBe(`${require_story_helpers_1.ANNOTATION}\n `);
41
+ expect(res).toEqual({ r: [30, 30], t: `${require_story_helpers_1.ANNOTATION}\n ` });
42
+ });
43
+ test("reportMissing does not call context.report if JSDoc contains @story", () => {
44
+ const node = {
45
+ type: "FunctionDeclaration",
46
+ id: { name: "fn" },
47
+ range: [0, 10],
48
+ };
49
+ const fakeSource = {
50
+ getJSDocComment: () => ({
51
+ value: "@story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md",
52
+ }),
53
+ getText: () => "",
54
+ };
55
+ const context = {
56
+ getSourceCode: () => fakeSource,
57
+ report: jest.fn(),
58
+ };
59
+ (0, require_story_core_1.reportMissing)(context, fakeSource, node, node);
60
+ expect(context.report).not.toHaveBeenCalled();
61
+ });
62
+ test("reportMissing calls context.report when no JSDoc story present", () => {
63
+ const node = {
64
+ type: "FunctionDeclaration",
65
+ id: { name: "fn2" },
66
+ range: [0, 10],
67
+ };
68
+ const fakeSource = {
69
+ getJSDocComment: () => null,
70
+ getText: () => "",
71
+ };
72
+ const context = {
73
+ getSourceCode: () => fakeSource,
74
+ report: jest.fn(),
75
+ };
76
+ (0, require_story_core_1.reportMissing)(context, fakeSource, node, node);
77
+ expect(context.report).toHaveBeenCalledTimes(1);
78
+ const call = context.report.mock.calls[0][0];
79
+ expect(call.node).toBe(node);
80
+ expect(call.messageId).toBe("missingStory");
81
+ });
82
+ /**
83
+ * Additional helper tests for story annotations and IO helpers
84
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
85
+ * @req REQ-ANNOTATION-REQUIRED - Verify resolveTargetNode/getNodeName/shouldProcessNode and IO helpers
86
+ */
87
+ test("resolveTargetNode prefers parent when parent is ExportNamedDeclaration", () => {
88
+ const fakeSource = { getText: () => "" };
89
+ const node = {
90
+ type: "FunctionExpression",
91
+ range: [5, 10],
92
+ parent: { type: "ExportNamedDeclaration", range: [1, 20] },
93
+ };
94
+ const resolved = (0, require_story_helpers_1.resolveTargetNode)(fakeSource, node);
95
+ expect(resolved).toBe(node.parent);
96
+ });
97
+ test("resolveTargetNode falls back to node when parent is not an export", () => {
98
+ const fakeSource = { getText: () => "" };
99
+ const node = {
100
+ type: "FunctionDeclaration",
101
+ range: [5, 10],
102
+ parent: { type: "ClassBody", range: [1, 20] },
103
+ };
104
+ const resolved = (0, require_story_helpers_1.resolveTargetNode)(fakeSource, node);
105
+ expect(resolved).toBe(node);
106
+ });
107
+ test("getNodeName extracts names from common node shapes", () => {
108
+ const funcNode = {
109
+ type: "FunctionDeclaration",
110
+ id: { name: "myFunc" },
111
+ };
112
+ const propNode = { type: "MethodDefinition", key: { name: "myProp" } };
113
+ expect((0, require_story_helpers_1.getNodeName)(funcNode)).toBe("myFunc");
114
+ expect((0, require_story_helpers_1.getNodeName)(propNode)).toBe("myProp");
115
+ });
116
+ test("shouldProcessNode returns booleans for typical node types", () => {
117
+ const funcDecl = { type: "FunctionDeclaration" };
118
+ const varDecl = { type: "VariableDeclaration" };
119
+ expect((0, require_story_helpers_1.shouldProcessNode)(funcDecl, require_story_helpers_1.DEFAULT_SCOPE)).toBeTruthy();
120
+ expect((0, require_story_helpers_1.shouldProcessNode)(varDecl, require_story_helpers_1.DEFAULT_SCOPE)).toBeFalsy();
121
+ });
122
+ test("linesBeforeHasStory detects preceding JSDoc story text", () => {
123
+ const jsdoc = "/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\n";
124
+ const rest = "function fn() {}\n";
125
+ const full = jsdoc + rest;
126
+ const fakeSource = {
127
+ getText: () => full,
128
+ getJSDocComment: () => ({
129
+ value: "@story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md",
130
+ }),
131
+ lines: full.split(/\r?\n/),
132
+ };
133
+ const nodeLine = fakeSource.lines.findIndex((l) => l.includes("function fn() {}")) + 1;
134
+ const node = {
135
+ type: "FunctionDeclaration",
136
+ range: [full.indexOf("function"), full.length],
137
+ loc: { start: { line: nodeLine } },
138
+ };
139
+ const has = (0, require_story_helpers_1.linesBeforeHasStory)(fakeSource, node);
140
+ expect(has).toBeTruthy();
141
+ });
142
+ test("fallbackTextBeforeHasStory returns boolean when called with source text and range", () => {
143
+ const jsdoc = "/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\n";
144
+ const rest = "function fnB() {}\n";
145
+ const full = jsdoc + rest;
146
+ const fakeSource = {
147
+ getText: () => full,
148
+ };
149
+ const node = {
150
+ type: "FunctionDeclaration",
151
+ range: [full.indexOf("function"), full.length],
152
+ };
153
+ const res = (0, require_story_helpers_1.fallbackTextBeforeHasStory)(fakeSource, node);
154
+ expect(typeof res).toBe("boolean");
155
+ expect(res).toBeTruthy();
156
+ });
157
+ test("parentChainHasStory returns true when ancestors have JSDoc story", () => {
158
+ const fakeSource = {
159
+ getCommentsBefore: () => [
160
+ {
161
+ type: "Block",
162
+ value: "@story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md",
163
+ },
164
+ ],
165
+ };
166
+ const node = {
167
+ type: "Identifier",
168
+ parent: { parent: { type: "ExportNamedDeclaration" } },
169
+ };
170
+ const res = (0, require_story_helpers_1.parentChainHasStory)(fakeSource, node);
171
+ expect(res).toBeTruthy();
172
+ });
173
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
3
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
+ * @req REQ-ANNOTATION-REQUIRED - Edge case tests for IO helpers (linesBeforeHasStory/fallbackTextBeforeHasStory/parentChainHasStory)
5
+ */
6
+ export {};
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
5
+ * @req REQ-ANNOTATION-REQUIRED - Edge case tests for IO helpers (linesBeforeHasStory/fallbackTextBeforeHasStory/parentChainHasStory)
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ const require_story_io_1 = require("../../src/rules/helpers/require-story-io");
9
+ describe("Require Story IO helpers - edge cases (Story 003.0)", () => {
10
+ test("linesBeforeHasStory returns false when source.lines missing or node.loc missing", () => {
11
+ const fakeSource = {};
12
+ const node = { loc: null };
13
+ expect((0, require_story_io_1.linesBeforeHasStory)(fakeSource, node)).toBe(false);
14
+ const fakeSource2 = { lines: ["line1", "line2"] };
15
+ const node2 = { loc: { start: { line: 100 } } };
16
+ expect((0, require_story_io_1.linesBeforeHasStory)(fakeSource2, node2)).toBe(false);
17
+ });
18
+ test("fallbackTextBeforeHasStory returns false when getText missing or node.range missing", () => {
19
+ const fakeSource = {};
20
+ const node = { range: null };
21
+ expect((0, require_story_io_1.fallbackTextBeforeHasStory)(fakeSource, node)).toBe(false);
22
+ const fakeSource2 = { getText: () => "no story here" };
23
+ const node2 = { range: [] };
24
+ expect((0, require_story_io_1.fallbackTextBeforeHasStory)(fakeSource2, node2)).toBe(false);
25
+ });
26
+ test("fallbackTextBeforeHasStory detects @story in text before node.range", () => {
27
+ const story = "@story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md";
28
+ const pre = `/* ${story} */\\n`;
29
+ const rest = "function x() {}";
30
+ const full = pre + rest;
31
+ const fakeSource = { getText: () => full };
32
+ const node = { range: [full.indexOf("function"), full.length] };
33
+ expect((0, require_story_io_1.fallbackTextBeforeHasStory)(fakeSource, node)).toBe(true);
34
+ });
35
+ test("parentChainHasStory returns true when ancestor comments contain @story", () => {
36
+ const fakeSource = {
37
+ getCommentsBefore: () => [
38
+ {
39
+ type: "Block",
40
+ value: "@story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md",
41
+ },
42
+ ],
43
+ };
44
+ const node = { parent: { parent: { type: "SomeParent" } } };
45
+ expect((0, require_story_io_1.parentChainHasStory)(fakeSource, node)).toBe(true);
46
+ const fakeSource2 = { getCommentsBefore: () => [] };
47
+ const node2 = { parent: null };
48
+ expect((0, require_story_io_1.parentChainHasStory)(fakeSource2, node2)).toBe(false);
49
+ });
50
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
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",
@@ -22,6 +22,8 @@
22
22
  "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
23
23
  "duplication": "jscpd src tests --reporters console --threshold 3 --ignore tests/utils/**",
24
24
  "audit:dev-high": "node scripts/generate-dev-deps-audit.js",
25
+ "safety:deps": "node scripts/ci-safety-deps.js",
26
+ "audit:ci": "node scripts/ci-audit.js",
25
27
  "smoke-test": "./scripts/smoke-test.sh",
26
28
  "prepare": "husky install"
27
29
  },