eslint-plugin-traceability 1.14.1 → 1.16.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/README.md +61 -13
  3. package/lib/src/index.js +61 -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 +66 -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.d.ts +19 -0
  12. package/lib/src/rules/require-traceability.js +72 -0
  13. package/lib/src/utils/annotation-checker.js +23 -4
  14. package/lib/tests/cli-error-handling.test.js +10 -1
  15. package/lib/tests/config/flat-config-presets-integration.test.js +2 -0
  16. package/lib/tests/integration/cli-integration.test.js +5 -0
  17. package/lib/tests/integration/require-traceability-aliases.integration.test.js +126 -0
  18. package/lib/tests/plugin-default-export-and-configs.test.js +26 -0
  19. package/lib/tests/rules/auto-fix-behavior-008.test.js +7 -7
  20. package/lib/tests/rules/error-reporting.test.js +1 -1
  21. package/lib/tests/rules/no-redundant-annotation.test.js +20 -0
  22. package/lib/tests/rules/require-story-annotation.test.js +49 -10
  23. package/lib/tests/rules/require-story-helpers.test.js +32 -0
  24. package/lib/tests/rules/require-story-utils.test.d.ts +7 -0
  25. package/lib/tests/rules/require-story-utils.test.js +158 -0
  26. package/lib/tests/utils/annotation-checker-branches.test.d.ts +5 -0
  27. package/lib/tests/utils/annotation-checker-branches.test.js +103 -0
  28. package/lib/tests/utils/annotation-scope-analyzer.test.js +134 -0
  29. package/lib/tests/utils/branch-annotation-helpers.test.js +66 -0
  30. package/package.json +2 -2
  31. package/user-docs/api-reference.md +80 -21
  32. package/user-docs/examples.md +24 -13
  33. package/user-docs/migration-guide.md +127 -4
  34. package/user-docs/traceability-overview.md +116 -0
  35. package/lib/tests/integration/dogfooding-validation.test.js +0 -129
  36. /package/lib/tests/integration/{dogfooding-validation.test.d.ts → require-traceability-aliases.integration.test.d.ts} +0 -0
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ /**
3
+ * Composite ESLint rule that enforces both story and requirement traceability
4
+ * annotations on functions and methods.
5
+ *
6
+ * Implements Story 003.0-DEV-FUNCTION-ANNOTATIONS with:
7
+ * - REQ-ANNOTATION-REQUIRED
8
+ * - REQ-FUNCTION-DETECTION
9
+ * - REQ-CONFIGURABLE-SCOPE
10
+ * - REQ-EXPORT-PRIORITY
11
+ * - REQ-ERROR-LOCATION
12
+ * - REQ-TYPESCRIPT-SUPPORT
13
+ *
14
+ * via composition of:
15
+ * - ./require-story-annotation
16
+ * - ./require-req-annotation
17
+ */
18
+ var __importDefault = (this && this.__importDefault) || function (mod) {
19
+ return (mod && mod.__esModule) ? mod : { "default": mod };
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ const require_story_annotation_1 = __importDefault(require("./require-story-annotation"));
23
+ const require_req_annotation_1 = __importDefault(require("./require-req-annotation"));
24
+ const storyRule = require_story_annotation_1.default;
25
+ const reqRule = require_req_annotation_1.default;
26
+ const rule = {
27
+ meta: {
28
+ type: "problem",
29
+ docs: {
30
+ description: "Require both story and requirement traceability annotations on functions and methods via the unified alias rule",
31
+ recommended: "error",
32
+ },
33
+ hasSuggestions: true,
34
+ fixable: undefined,
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.
40
+ ...(storyRule.meta?.messages ?? {}),
41
+ ...(reqRule.meta?.messages ?? {}),
42
+ },
43
+ schema: [],
44
+ },
45
+ create(context) {
46
+ const storyListeners = storyRule.create(context) || {};
47
+ const reqListeners = reqRule.create(context) || {};
48
+ const mergedListener = {};
49
+ const allEventNames = new Set([
50
+ ...Object.keys(storyListeners),
51
+ ...Object.keys(reqListeners),
52
+ ]);
53
+ for (const eventName of allEventNames) {
54
+ const storyHandler = storyListeners[eventName];
55
+ const reqHandler = reqListeners[eventName];
56
+ if (storyHandler && reqHandler) {
57
+ mergedListener[eventName] = function mergedHandler(...args) {
58
+ storyHandler.apply(this, args);
59
+ reqHandler.apply(this, args);
60
+ };
61
+ }
62
+ else if (storyHandler) {
63
+ mergedListener[eventName] = storyHandler;
64
+ }
65
+ else if (reqHandler) {
66
+ mergedListener[eventName] = reqHandler;
67
+ }
68
+ }
69
+ return mergedListener;
70
+ },
71
+ };
72
+ exports.default = rule;
@@ -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
  });
@@ -61,6 +61,7 @@ describe("Flat config presets integration (Story 002.0-DEV-ESLINT-CONFIG)", () =
61
61
  const code = "function foo() {}";
62
62
  const result = await lintTextWithConfig(code, config);
63
63
  const ruleIds = result.messages.map((m) => m.ruleId).sort();
64
+ expect(ruleIds).toContain("traceability/require-traceability");
64
65
  expect(ruleIds).toContain("traceability/require-story-annotation");
65
66
  });
66
67
  it("[REQ-CONFIG-PRESETS] strict preset also enables traceability rules via documented usage", async () => {
@@ -68,6 +69,7 @@ describe("Flat config presets integration (Story 002.0-DEV-ESLINT-CONFIG)", () =
68
69
  const code = "function bar() {}";
69
70
  const result = await lintTextWithConfig(code, config);
70
71
  const ruleIds = result.messages.map((m) => m.ruleId).sort();
72
+ expect(ruleIds).toContain("traceability/require-traceability");
71
73
  expect(ruleIds).toContain("traceability/require-story-annotation");
72
74
  });
73
75
  });
@@ -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
+ });
@@ -51,6 +51,7 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
51
51
  it("[REQ-PLUGIN-STRUCTURE] rules object has correct rule names", () => {
52
52
  // Arrange: expected rule names in insertion order
53
53
  const expected = [
54
+ "require-traceability",
54
55
  "require-story-annotation",
55
56
  "require-req-annotation",
56
57
  "require-branch-annotation",
@@ -69,6 +70,7 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
69
70
  });
70
71
  it("[REQ-RULE-REGISTRY] configs.recommended contains correct rule configuration", () => {
71
72
  const recommendedRules = index_1.configs.recommended[0].rules;
73
+ expect(recommendedRules).toHaveProperty("traceability/require-traceability", "error");
72
74
  expect(recommendedRules).toHaveProperty("traceability/require-story-annotation", "error");
73
75
  expect(recommendedRules).toHaveProperty("traceability/require-req-annotation", "error");
74
76
  expect(recommendedRules).toHaveProperty("traceability/require-branch-annotation", "error");
@@ -80,6 +82,7 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
80
82
  it("[REQ-ERROR-SEVERITY] configs.recommended maps valid-annotation-format to warn and others to error", () => {
81
83
  const recommendedRules = index_1.configs.recommended[0].rules;
82
84
  expect(recommendedRules).toHaveProperty("traceability/valid-annotation-format", "warn");
85
+ expect(recommendedRules).toHaveProperty("traceability/require-traceability", "error");
83
86
  expect(recommendedRules).toHaveProperty("traceability/require-story-annotation", "error");
84
87
  expect(recommendedRules).toHaveProperty("traceability/require-req-annotation", "error");
85
88
  expect(recommendedRules).toHaveProperty("traceability/require-branch-annotation", "error");
@@ -92,4 +95,27 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
92
95
  const recommendedRules = index_1.configs.recommended[0].rules;
93
96
  expect(strictRules).toEqual(recommendedRules);
94
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
+ });
95
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,15 @@ 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
+ },
68
77
  ],
69
78
  invalid: [
70
79
  {
@@ -77,7 +86,7 @@ declare function tsDecl(): void;`,
77
86
  messageId: "missingStory",
78
87
  suggestions: [
79
88
  {
80
- desc: `Add JSDoc @story annotation for function 'bar', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
89
+ 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
90
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}`,
82
91
  },
83
92
  ],
@@ -93,7 +102,7 @@ declare function tsDecl(): void;`,
93
102
  messageId: "missingStory",
94
103
  suggestions: [
95
104
  {
96
- desc: `Add JSDoc @story annotation for function 'fnExpr', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
105
+ 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
106
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
98
107
  },
99
108
  ],
@@ -110,7 +119,7 @@ declare function tsDecl(): void;`,
110
119
  data: { name: "method", functionName: "method" },
111
120
  suggestions: [
112
121
  {
113
- desc: `Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
122
+ 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
123
  output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
115
124
  },
116
125
  ],
@@ -126,7 +135,7 @@ declare function tsDecl(): void;`,
126
135
  messageId: "missingStory",
127
136
  suggestions: [
128
137
  {
129
- desc: `Add JSDoc @story annotation for function 'tsDecl', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
138
+ 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
139
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
131
140
  },
132
141
  ],
@@ -142,7 +151,7 @@ declare function tsDecl(): void;`,
142
151
  messageId: "missingStory",
143
152
  suggestions: [
144
153
  {
145
- desc: `Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
154
+ 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
155
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
147
156
  },
148
157
  ],
@@ -158,7 +167,7 @@ declare function tsDecl(): void;`,
158
167
  messageId: "missingStory",
159
168
  suggestions: [
160
169
  {
161
- desc: `Add JSDoc @story annotation for function 'handler', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
170
+ 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
171
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst handler = () => {};`,
163
172
  },
164
173
  ],
@@ -174,7 +183,7 @@ declare function tsDecl(): void;`,
174
183
  messageId: "missingStory",
175
184
  suggestions: [
176
185
  {
177
- desc: `Add JSDoc @story annotation for function 'innerNamed', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
186
+ 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
187
  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
188
  },
180
189
  ],
@@ -207,7 +216,7 @@ declare function tsDecl(): void;`,
207
216
  messageId: "missingStory",
208
217
  suggestions: [
209
218
  {
210
- desc: `Add JSDoc @story annotation for function 'exportedMissing', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
219
+ 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
220
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport function exportedMissing() {}`,
212
221
  },
213
222
  ],
@@ -224,7 +233,7 @@ declare function tsDecl(): void;`,
224
233
  messageId: "missingStory",
225
234
  suggestions: [
226
235
  {
227
- desc: `Add JSDoc @story annotation for function 'arrowExported', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
236
+ 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
237
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport const arrowExported = () => {};`,
229
238
  },
230
239
  ],
@@ -252,7 +261,7 @@ declare function tsDecl(): void;`,
252
261
  messageId: "missingStory",
253
262
  suggestions: [
254
263
  {
255
- desc: `Add JSDoc @story annotation for function 'onlyDecl', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
264
+ 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
265
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction onlyDecl() {}`,
257
266
  },
258
267
  ],
@@ -261,4 +270,34 @@ declare function tsDecl(): void;`,
261
270
  },
262
271
  ],
263
272
  });
273
+ ruleTester.run("require-story-annotation with excludeTestCallbacks option", require_story_annotation_1.default, {
274
+ valid: [
275
+ {
276
+ name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] non-test arrow function annotated when excludeTestCallbacks=false",
277
+ code: `/**
278
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
279
+ */
280
+ const handler = () => {};`,
281
+ options: [{ excludeTestCallbacks: false }],
282
+ },
283
+ ],
284
+ invalid: [
285
+ {
286
+ name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] Jest-style it() callback requires annotation when excludeTestCallbacks=false",
287
+ code: `it('does something', () => {});`,
288
+ options: [{ excludeTestCallbacks: false, autoFix: false }],
289
+ errors: [
290
+ {
291
+ messageId: "missingStory",
292
+ suggestions: [
293
+ {
294
+ 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 */`,
295
+ output: `it('does something', /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n() => {});`,
296
+ },
297
+ ],
298
+ },
299
+ ],
300
+ },
301
+ ],
302
+ });
264
303
  });