eslint-plugin-traceability 1.4.4 → 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.
@@ -56,12 +56,22 @@ function parentChainHasStory(sourceCode, node) {
56
56
  ? sourceCode.getCommentsBefore(p) || []
57
57
  : [];
58
58
  if (Array.isArray(pComments) &&
59
- pComments.some((c) => typeof c.value === "string" && c.value.includes("@story"))) {
59
+ pComments.some(
60
+ /**
61
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
62
+ * @req REQ-ANNOTATION-REQUIRED - Detect @story in parent comments via value inspection
63
+ */
64
+ (c) => typeof c.value === "string" && c.value.includes("@story"))) {
60
65
  return true;
61
66
  }
62
67
  const pLeading = p.leadingComments || [];
63
68
  if (Array.isArray(pLeading) &&
64
- pLeading.some((c) => typeof c.value === "string" && c.value.includes("@story"))) {
69
+ pLeading.some(
70
+ /**
71
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
72
+ * @req REQ-ANNOTATION-REQUIRED - Detect @story in parent leadingComments via value inspection
73
+ */
74
+ (c) => typeof c.value === "string" && c.value.includes("@story"))) {
65
75
  return true;
66
76
  }
67
77
  p = p.parent;
@@ -162,6 +162,15 @@ function programListener(context) {
162
162
  let rawStoryPath = null;
163
163
  return function Program() {
164
164
  const comments = sourceCode.getAllComments() || [];
165
+ /**
166
+ * Process each comment to handle story and requirement annotations.
167
+ *
168
+ * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
169
+ * @req REQ-DEEP-PARSE - Parse annotations from comment blocks
170
+ * @req REQ-DEEP-MATCH - Validate @req references found in comments
171
+ * @req REQ-DEEP-CACHE - Use cache for parsed story files to avoid repeated IO
172
+ * @req REQ-DEEP-PATH - Enforce path validation when resolving story files
173
+ */
165
174
  comments.forEach((comment) => {
166
175
  rawStoryPath = handleComment({
167
176
  comment,
@@ -94,6 +94,10 @@ function handleComment(opts) {
94
94
  const { commentNode, context, cwd, storyDirs, allowAbsolute, requireExt } = opts;
95
95
  const lines = commentNode.value
96
96
  .split(/\r?\n/)
97
+ /**
98
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
99
+ * @req REQ-ANNOTATION-VALIDATION - Ensure each annotation line is parsed
100
+ */
97
101
  .map((l) => l.replace(/^[^@]*/, "").trim());
98
102
  for (const line of lines) {
99
103
  if (line.startsWith("@story")) {
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Branch 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-AUTOFIX - Cover additional branch cases in require-story-core (addStoryFixer/reportMissing)
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 Core branches (Story 003.0)", () => {
11
+ test("createAddStoryFix falls back to 0 when target is falsy", () => {
12
+ const fixer = {
13
+ insertTextBeforeRange: jest.fn((r, t) => ({ r, t })),
14
+ };
15
+ const fixFn = (0, require_story_core_1.createAddStoryFix)(null);
16
+ const res = fixFn(fixer);
17
+ expect(fixer.insertTextBeforeRange).toHaveBeenCalledTimes(1);
18
+ const args = fixer.insertTextBeforeRange.mock.calls[0];
19
+ expect(args[0]).toEqual([0, 0]);
20
+ expect(args[1]).toBe(`${require_story_helpers_1.ANNOTATION}\n`);
21
+ expect(res).toEqual({ r: [0, 0], t: `${require_story_helpers_1.ANNOTATION}\n` });
22
+ });
23
+ test("createAddStoryFix uses target.range when parent not export and parent.range missing", () => {
24
+ const target = {
25
+ type: "FunctionDeclaration",
26
+ range: [21, 33],
27
+ parent: { type: "ClassBody" },
28
+ };
29
+ const fixer = {
30
+ insertTextBeforeRange: jest.fn((r, t) => ({ r, t })),
31
+ };
32
+ const fixFn = (0, require_story_core_1.createAddStoryFix)(target);
33
+ const res = fixFn(fixer);
34
+ expect(fixer.insertTextBeforeRange.mock.calls[0][0]).toEqual([21, 21]);
35
+ expect(fixer.insertTextBeforeRange.mock.calls[0][1]).toBe(`${require_story_helpers_1.ANNOTATION}\n`);
36
+ expect(res).toEqual({ r: [21, 21], t: `${require_story_helpers_1.ANNOTATION}\n` });
37
+ });
38
+ test("createAddStoryFix prefers ExportDefaultDeclaration parent.range when present", () => {
39
+ const target = {
40
+ type: "FunctionDeclaration",
41
+ range: [50, 70],
42
+ parent: { type: "ExportDefaultDeclaration", range: [5, 100] },
43
+ };
44
+ const fixer = {
45
+ insertTextBeforeRange: jest.fn((r, t) => ({ r, t })),
46
+ };
47
+ const fixFn = (0, require_story_core_1.createAddStoryFix)(target);
48
+ const res = fixFn(fixer);
49
+ expect(fixer.insertTextBeforeRange.mock.calls[0][0]).toEqual([5, 5]);
50
+ expect(fixer.insertTextBeforeRange.mock.calls[0][1]).toBe(`${require_story_helpers_1.ANNOTATION}\n`);
51
+ expect(res).toEqual({ r: [5, 5], t: `${require_story_helpers_1.ANNOTATION}\n` });
52
+ });
53
+ test("reportMissing uses context.getSourceCode fallback when sourceCode not provided and still reports", () => {
54
+ const node = {
55
+ type: "FunctionDeclaration",
56
+ id: { name: "fnX" },
57
+ range: [0, 10],
58
+ };
59
+ const fakeSource = {
60
+ /* intentionally missing getJSDocComment to exercise branch */ getText: () => "",
61
+ };
62
+ const context = { getSourceCode: () => fakeSource, report: jest.fn() };
63
+ (0, require_story_core_1.reportMissing)(context, undefined, node, node);
64
+ expect(context.report).toHaveBeenCalledTimes(1);
65
+ const call = context.report.mock.calls[0][0];
66
+ expect(call.node).toBe(node);
67
+ expect(call.messageId).toBe("missingStory");
68
+ });
69
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
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-AUTOFIX - Verify createMethodFix and reportMethod behaviors
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 Core (Story 003.0)", () => {
11
+ test("createMethodFix uses parent range start when parent is export", () => {
12
+ const node = {
13
+ type: "MethodDefinition",
14
+ range: [30, 60],
15
+ parent: { type: "ExportNamedDeclaration", range: [12, 90] },
16
+ };
17
+ const fixer = {
18
+ insertTextBeforeRange: jest.fn((r, t) => ({ r, t })),
19
+ };
20
+ const fixFn = (0, require_story_core_1.createMethodFix)(node);
21
+ const result = fixFn(fixer);
22
+ expect(fixer.insertTextBeforeRange).toHaveBeenCalledTimes(1);
23
+ const calledArgs = fixer.insertTextBeforeRange.mock.calls[0];
24
+ expect(calledArgs[0]).toEqual([12, 12]);
25
+ expect(calledArgs[1]).toBe(`${require_story_helpers_1.ANNOTATION}\n `);
26
+ expect(result).toEqual({ r: [12, 12], t: `${require_story_helpers_1.ANNOTATION}\n ` });
27
+ });
28
+ test("reportMethod calls context.report with proper data and suggest.fix works", () => {
29
+ const node = {
30
+ type: "MethodDefinition",
31
+ key: { name: "myMethod" },
32
+ range: [40, 80],
33
+ parent: { type: "ClassBody" },
34
+ };
35
+ const fakeSource = { getText: () => "" };
36
+ const context = {
37
+ getSourceCode: () => fakeSource,
38
+ report: jest.fn(),
39
+ };
40
+ (0, require_story_core_1.reportMethod)(context, fakeSource, node, node);
41
+ expect(context.report).toHaveBeenCalledTimes(1);
42
+ const call = context.report.mock.calls[0][0];
43
+ expect(call.messageId).toBe("missingStory");
44
+ expect(call.data).toEqual({ name: "myMethod" });
45
+ // The suggest fix should be a function; exercise it with a mock fixer
46
+ expect(Array.isArray(call.suggest)).toBe(true);
47
+ expect(typeof call.suggest[0].fix).toBe("function");
48
+ const fixer = {
49
+ insertTextBeforeRange: jest.fn((r, t) => ({ r, t })),
50
+ };
51
+ const fixResult = call.suggest[0].fix(fixer);
52
+ expect(fixer.insertTextBeforeRange).toHaveBeenCalled();
53
+ const args = fixer.insertTextBeforeRange.mock.calls[0];
54
+ expect(args[0]).toEqual([40, 40]);
55
+ expect(args[1]).toBe(`${require_story_helpers_1.ANNOTATION}\n `);
56
+ expect(fixResult).toEqual({ r: [40, 40], t: `${require_story_helpers_1.ANNOTATION}\n ` });
57
+ });
58
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.4.4",
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",