eslint-plugin-traceability 1.15.0 → 1.16.1

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 (34) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/README.md +61 -13
  3. package/lib/src/index.js +59 -0
  4. package/lib/src/rules/helpers/require-story-core.js +1 -1
  5. package/lib/src/rules/helpers/require-story-helpers.d.ts +10 -3
  6. package/lib/src/rules/helpers/require-story-helpers.js +84 -7
  7. package/lib/src/rules/no-redundant-annotation.js +73 -55
  8. package/lib/src/rules/require-branch-annotation.js +2 -2
  9. package/lib/src/rules/require-req-annotation.js +2 -2
  10. package/lib/src/rules/require-story-annotation.js +8 -3
  11. package/lib/src/rules/require-traceability.js +8 -9
  12. package/lib/src/utils/annotation-checker.js +23 -4
  13. package/lib/tests/cli-error-handling.test.js +10 -1
  14. package/lib/tests/integration/cli-integration.test.js +5 -0
  15. package/lib/tests/integration/require-traceability-aliases.integration.test.js +126 -0
  16. package/lib/tests/plugin-default-export-and-configs.test.js +23 -0
  17. package/lib/tests/rules/auto-fix-behavior-008.test.js +7 -7
  18. package/lib/tests/rules/error-reporting.test.js +1 -1
  19. package/lib/tests/rules/no-redundant-annotation.test.js +20 -0
  20. package/lib/tests/rules/require-story-annotation.test.js +75 -10
  21. package/lib/tests/rules/require-story-helpers.test.js +224 -0
  22. package/lib/tests/rules/require-story-utils.test.d.ts +7 -0
  23. package/lib/tests/rules/require-story-utils.test.js +158 -0
  24. package/lib/tests/utils/annotation-checker-branches.test.d.ts +5 -0
  25. package/lib/tests/utils/annotation-checker-branches.test.js +103 -0
  26. package/lib/tests/utils/annotation-scope-analyzer.test.js +134 -0
  27. package/lib/tests/utils/branch-annotation-helpers.test.js +66 -0
  28. package/package.json +2 -2
  29. package/user-docs/api-reference.md +71 -15
  30. package/user-docs/examples.md +24 -13
  31. package/user-docs/migration-guide.md +127 -4
  32. package/user-docs/traceability-overview.md +116 -0
  33. package/lib/tests/integration/dogfooding-validation.test.js +0 -129
  34. /package/lib/tests/integration/{dogfooding-validation.test.d.ts → require-traceability-aliases.integration.test.d.ts} +0 -0
@@ -27,21 +27,20 @@ const rule = {
27
27
  meta: {
28
28
  type: "problem",
29
29
  docs: {
30
- description: "Require @story and @req (or @supports) annotations on functions and methods",
30
+ description: "Require both story and requirement traceability annotations on functions and methods via the unified alias rule",
31
31
  recommended: "error",
32
32
  },
33
- hasSuggestions: Boolean(storyRule.meta?.hasSuggestions) ||
34
- Boolean(reqRule.meta?.hasSuggestions),
35
- fixable: (storyRule.meta && storyRule.meta.fixable) ||
36
- (reqRule.meta && reqRule.meta.fixable) ||
37
- undefined,
33
+ hasSuggestions: true,
34
+ fixable: undefined,
38
35
  messages: {
36
+ // Unified messageId for potential future direct use by this rule.
37
+ missingTraceability: "Function '{{name}}' must declare both story and requirement traceability annotations.",
38
+ // Preserve underlying rule messageIds so that composed listeners can
39
+ // continue to report using their original IDs.
39
40
  ...(storyRule.meta?.messages ?? {}),
40
41
  ...(reqRule.meta?.messages ?? {}),
41
42
  },
42
- schema: (storyRule.meta && storyRule.meta.schema) ??
43
- (reqRule.meta && reqRule.meta.schema) ??
44
- [],
43
+ schema: [],
45
44
  },
46
45
  create(context) {
47
46
  const storyListeners = storyRule.create(context) || {};
@@ -119,17 +119,19 @@ function getNameNodeForReqReport(node) {
119
119
  return node;
120
120
  }
121
121
  /**
122
- * Helper to report a missing @req annotation via the ESLint context API.
123
- * Uses getNodeName to provide a readable name for the node.
122
+ * Helper to build the report options object for missing traceability annotations.
123
+ * Uses getNodeName to provide a readable name for the node. @supports is the
124
+ * preferred format for expressing traceability to one or more requirements and
125
+ * stories, while @req is treated as a legacy shorthand for single-story usage.
124
126
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
125
127
  * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
126
- * @req REQ-ANNOTATION-REPORTING - Report missing @req annotation to context
128
+ * @req REQ-ANNOTATION-REPORTING - Report missing traceability annotations to context
127
129
  * @req REQ-ERROR-SPECIFIC - Provide specific error details including node name
128
130
  * @req REQ-ERROR-LOCATION - Include contextual location information in errors
129
131
  * @req REQ-ERROR-SUGGESTION - Provide actionable suggestions or fixes where possible
130
132
  * @req REQ-ERROR-CONTEXT - Include contextual hints to help understand the error
131
133
  */
132
- function reportMissing(context, node, enableFix = true) {
134
+ function buildMissingReqReportOptions(node, enableFix) {
133
135
  const parentNode = node?.parent;
134
136
  const name = getReportedName(node, parentNode);
135
137
  const nameNode = getNameNodeForReqReport(node);
@@ -144,6 +146,23 @@ function reportMissing(context, node, enableFix = true) {
144
146
  if (enableFix) {
145
147
  reportOptions.fix = createMissingReqFix(node);
146
148
  }
149
+ return reportOptions;
150
+ }
151
+ /**
152
+ * Helper to report missing traceability annotations via the ESLint context API.
153
+ * Uses getNodeName to provide a readable name for the node. @supports is the
154
+ * preferred format for expressing traceability to one or more requirements and
155
+ * stories, while @req is treated as a legacy shorthand for single-story usage.
156
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
157
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
158
+ * @req REQ-ANNOTATION-REPORTING - Report missing traceability annotations to context
159
+ * @req REQ-ERROR-SPECIFIC - Provide specific error details including node name
160
+ * @req REQ-ERROR-LOCATION - Include contextual location information in errors
161
+ * @req REQ-ERROR-SUGGESTION - Provide actionable suggestions or fixes where possible
162
+ * @req REQ-ERROR-CONTEXT - Include contextual hints to help understand the error
163
+ */
164
+ function reportMissing(context, node, enableFix = true) {
165
+ const reportOptions = buildMissingReqReportOptions(node, enableFix);
147
166
  context.report(reportOptions);
148
167
  }
149
168
  /**
@@ -11,12 +11,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
11
11
  */
12
12
  const child_process_1 = require("child_process");
13
13
  const path_1 = __importDefault(require("path"));
14
+ const originalNodePath = process.env.NODE_PATH;
14
15
  describe("CLI Error Handling for Traceability Plugin (Story 001.0-DEV-PLUGIN-SETUP)", () => {
15
16
  beforeAll(() => {
16
17
  // Simulate missing plugin build by deleting lib directory (if exist)
17
18
  // In tests, assume plugin built to lib/src/index.js; point plugin import to src/index.ts via env
18
19
  process.env.NODE_PATH = path_1.default.resolve(__dirname, "../src");
19
20
  });
21
+ afterAll(() => {
22
+ if (originalNodePath === undefined) {
23
+ delete process.env.NODE_PATH;
24
+ }
25
+ else {
26
+ process.env.NODE_PATH = originalNodePath;
27
+ }
28
+ });
20
29
  it("[REQ-ERROR-HANDLING] should exit with error when rule module missing", () => {
21
30
  const eslintPkgDir = path_1.default.dirname(require.resolve("eslint/package.json"));
22
31
  const eslintCliPath = path_1.default.join(eslintPkgDir, "bin", "eslint.js");
@@ -40,6 +49,6 @@ describe("CLI Error Handling for Traceability Plugin (Story 001.0-DEV-PLUGIN-SET
40
49
  });
41
50
  // Expect non-zero exit and missing annotation message on stdout
42
51
  expect(result.status).not.toBe(0);
43
- expect(result.stdout).toContain("Function 'foo' must have an explicit @story annotation. Add a JSDoc or line comment with @story that points to the implementing story file, such as docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md");
52
+ expect(result.stdout).toContain("Function 'foo' must declare a traceability annotation. Prefer adding an @supports line that links this function to at least one story (for example, '@supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED'), or, when you only need a single-story reference, add a legacy @story annotation that points to the implementing story file, such as docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md");
44
53
  });
45
54
  });
@@ -33,6 +33,7 @@ describe("CLI Integration (Story 001.0-DEV-PLUGIN-SETUP)", () => {
33
33
  name: "does not report error when @story annotation is present",
34
34
  code: `/**
35
35
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
36
+ * @req REQ-ANNOTATION-REQUIRED
36
37
  */
37
38
  function foo() {}`,
38
39
  rule: "traceability/require-story-annotation:error",
@@ -65,6 +66,10 @@ function baz() {}`,
65
66
  expectedStatus: 1,
66
67
  },
67
68
  ];
69
+ /**
70
+ * Helper to run ESLint CLI with a single rule for integration tests
71
+ * @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-PLUGIN-STRUCTURE
72
+ */
68
73
  function runEslint(code, rule) {
69
74
  const args = [
70
75
  "--no-config-lookup",
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ /**
37
+ * Integration tests for unified require-traceability rule and its legacy aliases.
38
+ *
39
+ * @supports docs/stories/010.4-DEV-UNIFIED-FUNCTION-RULE-AND-ALIASES.story.md REQ-UNIFIED-ALIAS-ENGINE REQ-SUPPORTS-FIRST-MODEL REQ-PRESETS-CANONICAL-RULE
40
+ */
41
+ const use_at_your_own_risk_1 = require("eslint/use-at-your-own-risk");
42
+ const index_1 = __importStar(require("../../src/index"));
43
+ async function lintTextWithConfig(text, filename, extraConfig) {
44
+ const baseConfig = {
45
+ plugins: {
46
+ traceability: index_1.default,
47
+ },
48
+ };
49
+ const eslint = new use_at_your_own_risk_1.FlatESLint({
50
+ overrideConfig: [baseConfig, ...extraConfig],
51
+ overrideConfigFile: true,
52
+ ignore: false,
53
+ });
54
+ const [result] = await eslint.lintText(text, { filePath: filename });
55
+ return result;
56
+ }
57
+ describe("Unified require-traceability and aliases integration (Story 010.4-DEV-UNIFIED-FUNCTION-RULE-AND-ALIASES)", () => {
58
+ const codeMissingAll = "function foo() {}";
59
+ const codeWithSupportsOnly = `/**\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction foo() {}`;
60
+ const codeWithStoryAndReq = `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED\n */\nfunction foo() {}`;
61
+ async function getDiagnosticsForRule(ruleKey, code) {
62
+ const config = [
63
+ {
64
+ rules: {
65
+ [ruleKey]: "error",
66
+ },
67
+ },
68
+ ];
69
+ const result = await lintTextWithConfig(code, "example.js", config);
70
+ return result.messages.map((m) => ({
71
+ ruleId: m.ruleId,
72
+ messageId: m.messageId,
73
+ }));
74
+ }
75
+ it("[REQ-UNIFIED-ALIAS-ENGINE] canonical and alias keys all report missing traceability on unannotated function", async () => {
76
+ const ruleKeys = [
77
+ "traceability/require-traceability",
78
+ "traceability/require-story-annotation",
79
+ "traceability/require-req-annotation",
80
+ ];
81
+ const results = await Promise.all(ruleKeys.map((ruleKey) => getDiagnosticsForRule(ruleKey, codeMissingAll)));
82
+ results.forEach((messages, index) => {
83
+ const ruleKey = ruleKeys[index];
84
+ expect(messages.length).toBeGreaterThan(0);
85
+ messages.forEach((msg) => {
86
+ expect(msg.ruleId).toBe(ruleKey);
87
+ });
88
+ });
89
+ });
90
+ it("[REQ-SUPPORTS-FIRST-MODEL] @supports-only annotation satisfies all three rule keys", async () => {
91
+ const ruleKeys = [
92
+ "traceability/require-traceability",
93
+ "traceability/require-story-annotation",
94
+ "traceability/require-req-annotation",
95
+ ];
96
+ const results = await Promise.all(ruleKeys.map((ruleKey) => getDiagnosticsForRule(ruleKey, codeWithSupportsOnly)));
97
+ results.forEach((messages) => {
98
+ expect(messages).toHaveLength(0);
99
+ });
100
+ });
101
+ it("[REQ-SUPPORTS-FIRST-MODEL] @story + @req annotation satisfies all three rule keys", async () => {
102
+ const ruleKeys = [
103
+ "traceability/require-traceability",
104
+ "traceability/require-story-annotation",
105
+ "traceability/require-req-annotation",
106
+ ];
107
+ const results = await Promise.all(ruleKeys.map((ruleKey) => getDiagnosticsForRule(ruleKey, codeWithStoryAndReq)));
108
+ results.forEach((messages) => {
109
+ expect(messages).toHaveLength(0);
110
+ });
111
+ });
112
+ it("[REQ-PRESETS-CANONICAL-RULE] recommended preset surfaces unified and legacy diagnostics together for missing annotations", async () => {
113
+ const result = await lintTextWithConfig(codeMissingAll, "example.js", index_1.configs.recommended);
114
+ const ruleIds = result.messages.map((m) => m.ruleId).sort();
115
+ expect(ruleIds).toContain("traceability/require-traceability");
116
+ expect(ruleIds).toContain("traceability/require-story-annotation");
117
+ expect(ruleIds).toContain("traceability/require-req-annotation");
118
+ });
119
+ it("[REQ-PRESETS-CANONICAL-RULE] strict preset surfaces unified and legacy diagnostics together for missing annotations", async () => {
120
+ const result = await lintTextWithConfig(codeMissingAll, "example.js", index_1.configs.strict);
121
+ const ruleIds = result.messages.map((m) => m.ruleId).sort();
122
+ expect(ruleIds).toContain("traceability/require-traceability");
123
+ expect(ruleIds).toContain("traceability/require-story-annotation");
124
+ expect(ruleIds).toContain("traceability/require-req-annotation");
125
+ });
126
+ });
@@ -95,4 +95,27 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
95
95
  const recommendedRules = index_1.configs.recommended[0].rules;
96
96
  expect(strictRules).toEqual(recommendedRules);
97
97
  });
98
+ describe("Unified function-annotation rule aliases (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", () => {
99
+ it("[REQ-ANNOTATION-REQUIRED] legacy rule names share the unified require-traceability implementation", () => {
100
+ const unified = index_1.rules["require-traceability"];
101
+ const storyAlias = index_1.rules["require-story-annotation"];
102
+ const reqAlias = index_1.rules["require-req-annotation"];
103
+ expect(typeof unified.create).toBe("function");
104
+ expect(storyAlias.create).toBe(unified.create);
105
+ expect(reqAlias.create).toBe(unified.create);
106
+ });
107
+ it("[REQ-CONFIGURABLE-SCOPE] alias rules preserve metadata needed for configuration and diagnostics", () => {
108
+ const unified = index_1.rules["require-traceability"];
109
+ const storyAlias = index_1.rules["require-story-annotation"];
110
+ const reqAlias = index_1.rules["require-req-annotation"];
111
+ // All variants should expose a schema and messages map so that options
112
+ // like scope/exportPriority and the core diagnostics remain available.
113
+ expect(unified.meta?.schema).toBeDefined();
114
+ expect(storyAlias.meta?.schema).toBeDefined();
115
+ expect(reqAlias.meta?.schema).toBeDefined();
116
+ expect(unified.meta?.messages).toBeDefined();
117
+ expect(storyAlias.meta?.messages).toBeDefined();
118
+ expect(reqAlias.meta?.messages).toBeDefined();
119
+ });
120
+ });
98
121
  });
@@ -46,7 +46,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
46
46
  messageId: "missingStory",
47
47
  suggestions: [
48
48
  {
49
- desc: "Add JSDoc @story annotation for function 'autoFixMe', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
49
+ desc: "Add traceability annotation for function 'autoFixMe' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
50
50
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction autoFixMe() {}`,
51
51
  },
52
52
  ],
@@ -62,7 +62,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
62
62
  messageId: "missingStory",
63
63
  suggestions: [
64
64
  {
65
- desc: "Add JSDoc @story annotation for function 'fnExpr', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
65
+ desc: "Add traceability annotation for function 'fnExpr' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
66
66
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
67
67
  },
68
68
  ],
@@ -78,7 +78,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
78
78
  messageId: "missingStory",
79
79
  suggestions: [
80
80
  {
81
- desc: "Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
81
+ desc: "Add traceability annotation for function 'method' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
82
82
  output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
83
83
  },
84
84
  ],
@@ -98,7 +98,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
98
98
  messageId: "missingStory",
99
99
  suggestions: [
100
100
  {
101
- desc: "Add JSDoc @story annotation for function 'tsDecl', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
101
+ desc: "Add traceability annotation for function 'tsDecl' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
102
102
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
103
103
  },
104
104
  ],
@@ -118,7 +118,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
118
118
  messageId: "missingStory",
119
119
  suggestions: [
120
120
  {
121
- desc: "Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
121
+ desc: "Add traceability annotation for function 'method' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
122
122
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
123
123
  },
124
124
  ],
@@ -220,7 +220,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
220
220
  messageId: "missingStory",
221
221
  suggestions: [
222
222
  {
223
- desc: "Add JSDoc @story annotation for function 'needsFixOnce', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
223
+ desc: "Add traceability annotation for function 'needsFixOnce' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
224
224
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction needsFixOnce() {}`,
225
225
  },
226
226
  ],
@@ -236,7 +236,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
236
236
  messageId: "missingStory",
237
237
  suggestions: [
238
238
  {
239
- desc: "Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
239
+ desc: "Add traceability annotation for function 'method' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
240
240
  output: `class F {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
241
241
  },
242
242
  ],
@@ -91,7 +91,7 @@ describe("Error Reporting Enhancements for require-story-annotation (Story 007.0
91
91
  expect(Array.isArray(error.suggest)).toBe(true);
92
92
  expect(error.suggest.length).toBeGreaterThanOrEqual(1);
93
93
  const suggestion = error.suggest[0];
94
- expect(suggestion.desc).toBe("Add JSDoc @story annotation for function 'bar', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */");
94
+ expect(suggestion.desc).toBe("Add traceability annotation for function 'bar' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */");
95
95
  expect(suggestion.fix).toBeDefined();
96
96
  });
97
97
  });
@@ -29,6 +29,14 @@ describe("no-redundant-annotation rule (Story 027.0-DEV-REDUNDANT-ANNOTATION-DET
29
29
  name: "[REQ-STATEMENT-SIGNIFICANCE] preserves annotation on complex nested branch",
30
30
  code: `function example() {\n // @story docs/stories/006.0-EXAMPLE.story.md\n // @req REQ-OUTER-CHECK\n if (enabled) {\n // @story docs/stories/006.0-EXAMPLE.story.md\n // @req REQ-INNER-VALIDATION\n if (validate) {\n validate(data);\n }\n }\n}`,
31
31
  },
32
+ {
33
+ name: "[REQ-SUPPORTS-COVERAGE] preserves non-redundant mixed @supports/@req pairs when only partially covered by scope",
34
+ code: `function example() {\n /**\n * @story docs/stories/010.0-EXAMPLE.story.md\n * @req REQ-FN-LEVEL\n * @supports REQ-SHARED\n */\n if (flag) {\n // @story docs/stories/010.0-EXAMPLE.story.md\n // @req REQ-BRANCH-SPECIFIC\n // @supports REQ-SHARED\n doThing();\n }\n}`,
35
+ },
36
+ {
37
+ name: "[REQ-SCOPE-ANALYSIS] preserves annotations on both branch and statement when they intentionally duplicate each other",
38
+ code: `function example() {\n if (condition) { // @story docs/stories/007.0-EXAMPLE.story.md @req REQ-BRANCH\n // @story docs/stories/007.0-EXAMPLE.story.md\n // @req REQ-BRANCH\n doBranchWork();\n }\n}`,
39
+ },
32
40
  ],
33
41
  invalid: [
34
42
  {
@@ -67,6 +75,18 @@ describe("no-redundant-annotation rule (Story 027.0-DEV-REDUNDANT-ANNOTATION-DET
67
75
  output: `function example() {\n const keep = 1;\n // @story docs/stories/003.0-EXAMPLE.story.md\n // @req REQ-INIT\n if (flag) {\n const value = 1;\n }\n}`,
68
76
  errors: [{ messageId: "redundantAnnotation" }],
69
77
  },
78
+ {
79
+ name: "[REQ-SCOPE-INHERITANCE] flags redundant statement annotation when scopePairs come from parent function JSDoc",
80
+ code: `/**\n * @story docs/stories/008.0-EXAMPLE.story.md\n * @req REQ-FUNC\n */\nfunction example() {\n // @story docs/stories/008.0-EXAMPLE.story.md\n // @req REQ-FUNC\n const result = compute();\n}`,
81
+ output: `/**\n * @story docs/stories/008.0-EXAMPLE.story.md\n * @req REQ-FUNC\n */\nfunction example() {\n const result = compute();\n}`,
82
+ errors: [{ messageId: "redundantAnnotation" }],
83
+ },
84
+ {
85
+ name: "[REQ-SUPPORTS-COVERAGE][REQ-DUPLICATION-DETECTION] flags redundant statement with multiple fully-covered @supports pairs",
86
+ code: `/**\n * @story docs/stories/009.0-EXAMPLE.story.md\n * @supports REQ-SUP-A, REQ-SUP-B\n */\nfunction example() {\n // @story docs/stories/009.0-EXAMPLE.story.md\n // @supports REQ-SUP-A, REQ-SUP-B\n const supported = checkSupport();\n}`,
87
+ output: `/**\n * @story docs/stories/009.0-EXAMPLE.story.md\n * @supports REQ-SUP-A, REQ-SUP-B\n */\nfunction example() {\n const supported = checkSupport();\n}`,
88
+ errors: [{ messageId: "redundantAnnotation" }],
89
+ },
70
90
  // TODO: rule implementation exists; full invalid-case behavior tests pending refinement
71
91
  // {
72
92
  // name: "[REQ-SCOPE-ANALYSIS][REQ-STATEMENT-SIGNIFICANCE] flags redundant annotation on simple return inside annotated if",
@@ -65,6 +65,41 @@ declare function tsDecl(): void;`,
65
65
  name: "[REQ-NESTED-FUNCTION-INHERITANCE] anonymous inner function inherits outer annotation",
66
66
  code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction outer() {\n const inner = function() {\n return 1;\n };\n return inner();\n}`,
67
67
  },
68
+ {
69
+ name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] default exclusion of Jest-style anonymous test callbacks",
70
+ code: `/**
71
+ * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-TEST-CALLBACK-EXCLUSION
72
+ */
73
+ describe('Feature X', () => {
74
+ it('does something', () => {});
75
+ });
76
+
77
+ // Mocha-style suite/context/specify examples
78
+ suite('Mocha suite', () => {
79
+ beforeEach(() => {});
80
+ afterEach(() => {});
81
+ before(() => {});
82
+ after(() => {});
83
+
84
+ test('Mocha test', () => {});
85
+ specify('Mocha specify', () => {});
86
+ context('Mocha context', () => {
87
+ it('nested it', () => {});
88
+ });
89
+ });
90
+
91
+ // Vitest-style APIs including hooks and bench
92
+ describe('Vitest suite', () => {
93
+ beforeEach(() => {});
94
+ afterEach(() => {});
95
+ beforeAll(() => {});
96
+ afterAll(() => {});
97
+
98
+ it('Vitest it', () => {});
99
+ test('Vitest test', () => {});
100
+ bench('Vitest bench', () => {});
101
+ });`,
102
+ },
68
103
  ],
69
104
  invalid: [
70
105
  {
@@ -77,7 +112,7 @@ declare function tsDecl(): void;`,
77
112
  messageId: "missingStory",
78
113
  suggestions: [
79
114
  {
80
- desc: `Add JSDoc @story annotation for function 'bar', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
115
+ desc: `Add traceability annotation for function 'bar' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
81
116
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}`,
82
117
  },
83
118
  ],
@@ -93,7 +128,7 @@ declare function tsDecl(): void;`,
93
128
  messageId: "missingStory",
94
129
  suggestions: [
95
130
  {
96
- desc: `Add JSDoc @story annotation for function 'fnExpr', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
131
+ desc: `Add traceability annotation for function 'fnExpr' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
97
132
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
98
133
  },
99
134
  ],
@@ -110,7 +145,7 @@ declare function tsDecl(): void;`,
110
145
  data: { name: "method", functionName: "method" },
111
146
  suggestions: [
112
147
  {
113
- desc: `Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
148
+ desc: `Add traceability annotation for function 'method' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
114
149
  output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
115
150
  },
116
151
  ],
@@ -126,7 +161,7 @@ declare function tsDecl(): void;`,
126
161
  messageId: "missingStory",
127
162
  suggestions: [
128
163
  {
129
- desc: `Add JSDoc @story annotation for function 'tsDecl', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
164
+ desc: `Add traceability annotation for function 'tsDecl' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
130
165
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
131
166
  },
132
167
  ],
@@ -142,7 +177,7 @@ declare function tsDecl(): void;`,
142
177
  messageId: "missingStory",
143
178
  suggestions: [
144
179
  {
145
- desc: `Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
180
+ desc: `Add traceability annotation for function 'method' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
146
181
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
147
182
  },
148
183
  ],
@@ -158,7 +193,7 @@ declare function tsDecl(): void;`,
158
193
  messageId: "missingStory",
159
194
  suggestions: [
160
195
  {
161
- desc: `Add JSDoc @story annotation for function 'handler', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
196
+ desc: `Add traceability annotation for function 'handler' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
162
197
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst handler = () => {};`,
163
198
  },
164
199
  ],
@@ -174,7 +209,7 @@ declare function tsDecl(): void;`,
174
209
  messageId: "missingStory",
175
210
  suggestions: [
176
211
  {
177
- desc: `Add JSDoc @story annotation for function 'innerNamed', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
212
+ desc: `Add traceability annotation for function 'innerNamed' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
178
213
  output: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction outer() {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction innerNamed() {\n return 1;\n }\n return innerNamed();\n}`,
179
214
  },
180
215
  ],
@@ -207,7 +242,7 @@ declare function tsDecl(): void;`,
207
242
  messageId: "missingStory",
208
243
  suggestions: [
209
244
  {
210
- desc: `Add JSDoc @story annotation for function 'exportedMissing', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
245
+ desc: `Add traceability annotation for function 'exportedMissing' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
211
246
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport function exportedMissing() {}`,
212
247
  },
213
248
  ],
@@ -224,7 +259,7 @@ declare function tsDecl(): void;`,
224
259
  messageId: "missingStory",
225
260
  suggestions: [
226
261
  {
227
- desc: `Add JSDoc @story annotation for function 'arrowExported', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
262
+ desc: `Add traceability annotation for function 'arrowExported' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
228
263
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport const arrowExported = () => {};`,
229
264
  },
230
265
  ],
@@ -252,7 +287,7 @@ declare function tsDecl(): void;`,
252
287
  messageId: "missingStory",
253
288
  suggestions: [
254
289
  {
255
- desc: `Add JSDoc @story annotation for function 'onlyDecl', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
290
+ desc: `Add traceability annotation for function 'onlyDecl' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
256
291
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction onlyDecl() {}`,
257
292
  },
258
293
  ],
@@ -261,4 +296,34 @@ declare function tsDecl(): void;`,
261
296
  },
262
297
  ],
263
298
  });
299
+ ruleTester.run("require-story-annotation with excludeTestCallbacks option", require_story_annotation_1.default, {
300
+ valid: [
301
+ {
302
+ name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] non-test arrow function annotated when excludeTestCallbacks=false",
303
+ code: `/**
304
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
305
+ */
306
+ const handler = () => {};`,
307
+ options: [{ excludeTestCallbacks: false }],
308
+ },
309
+ ],
310
+ invalid: [
311
+ {
312
+ name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] Jest-style it() callback requires annotation when excludeTestCallbacks=false",
313
+ code: `it('does something', () => {});`,
314
+ options: [{ excludeTestCallbacks: false, autoFix: false }],
315
+ errors: [
316
+ {
317
+ messageId: "missingStory",
318
+ suggestions: [
319
+ {
320
+ desc: `Add traceability annotation for function '(anonymous)' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
321
+ output: `it('does something', /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n() => {});`,
322
+ },
323
+ ],
324
+ },
325
+ ],
326
+ },
327
+ ],
328
+ });
264
329
  });