eslint-plugin-traceability 1.16.1 → 1.17.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 (29) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/lib/src/index.js +53 -33
  3. package/lib/src/maintenance/commands.d.ts +4 -0
  4. package/lib/src/maintenance/commands.js +4 -0
  5. package/lib/src/maintenance/index.d.ts +1 -0
  6. package/lib/src/maintenance/index.js +1 -0
  7. package/lib/src/maintenance/report.js +2 -2
  8. package/lib/src/maintenance/update.js +4 -2
  9. package/lib/src/rules/helpers/require-story-helpers.d.ts +5 -11
  10. package/lib/src/rules/helpers/require-story-helpers.js +7 -74
  11. package/lib/src/rules/helpers/test-callback-exclusion.d.ts +43 -0
  12. package/lib/src/rules/helpers/test-callback-exclusion.js +100 -0
  13. package/lib/src/rules/helpers/valid-annotation-format-validators.js +8 -2
  14. package/lib/src/rules/no-redundant-annotation.js +4 -0
  15. package/lib/src/rules/prefer-implements-annotation.js +25 -20
  16. package/lib/src/rules/require-story-annotation.js +14 -1
  17. package/lib/src/rules/valid-annotation-format.js +62 -42
  18. package/lib/tests/integration/no-redundant-annotation.integration.test.js +31 -0
  19. package/lib/tests/integration/require-traceability-test-callbacks.integration.test.d.ts +1 -0
  20. package/lib/tests/integration/require-traceability-test-callbacks.integration.test.js +148 -0
  21. package/lib/tests/maintenance/detect-isolated.test.js +22 -14
  22. package/lib/tests/perf/maintenance-cli-large-workspace.test.js +145 -64
  23. package/lib/tests/perf/maintenance-large-workspace.test.js +65 -46
  24. package/lib/tests/rules/no-redundant-annotation.test.js +5 -0
  25. package/lib/tests/rules/require-story-annotation.test.js +21 -0
  26. package/lib/tests/rules/require-story-helpers.test.js +69 -0
  27. package/lib/tests/utils/{annotation-checker-branches.test.d.ts → annotation-checker-autofix-behavior.test.d.ts} +1 -1
  28. package/lib/tests/utils/{annotation-checker-branches.test.js → annotation-checker-autofix-behavior.test.js} +2 -2
  29. package/package.json +2 -2
@@ -42,6 +42,8 @@ const os = __importStar(require("os"));
42
42
  const path = __importStar(require("path"));
43
43
  const perf_hooks_1 = require("perf_hooks");
44
44
  const cli_1 = require("../../src/maintenance/cli");
45
+ // Performance budget documented in docs/maintenance-performance-tests.md
46
+ const CLI_LARGE_WORKSPACE_PERF_BUDGET_MS = 5000;
45
47
  function createCliLargeWorkspace() {
46
48
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "traceability-cli-large-"));
47
49
  // Create a modestly sized workspace reusing the same shape as the core perf tests,
@@ -71,78 +73,157 @@ export function cli_example_${moduleIndex}_${fileIndex}() {}
71
73
  },
72
74
  };
73
75
  }
76
+ function createDeepNestedCliWorkspace() {
77
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "traceability-cli-deep-nested-"));
78
+ // Create a deeply nested directory structure with a small number of files.
79
+ for (let branchIndex = 0; branchIndex < 3; branchIndex += 1) {
80
+ const level1 = path.join(root, `branch-${branchIndex.toString().padStart(3, "0")}`);
81
+ fs.mkdirSync(level1);
82
+ const level2 = path.join(level1, "deep", "nested", "structure");
83
+ fs.mkdirSync(path.join(level1, "deep"), { recursive: true });
84
+ fs.mkdirSync(path.join(level1, "deep", "nested"), { recursive: true });
85
+ fs.mkdirSync(level2, { recursive: true });
86
+ for (let fileIndex = 0; fileIndex < 3; fileIndex += 1) {
87
+ const filePath = path.join(level2, `deep-file-${fileIndex.toString().padStart(3, "0")}.ts`);
88
+ const validStory = "cli-valid.story.md";
89
+ const staleStory = "cli-deep-stale.story.md";
90
+ const content = `/**
91
+ * @story ${validStory}
92
+ * @story ${staleStory}
93
+ */
94
+ export function cli_deep_example_${branchIndex}_${fileIndex}() {}
95
+ `;
96
+ fs.writeFileSync(filePath, content, "utf8");
97
+ }
98
+ }
99
+ // Create the valid story file so that only the stale entries are reported.
100
+ fs.writeFileSync(path.join(root, "cli-valid.story.md"), "# cli valid", "utf8");
101
+ return {
102
+ root,
103
+ cleanup: () => {
104
+ fs.rmSync(root, { recursive: true, force: true });
105
+ },
106
+ };
107
+ }
74
108
  describe("Maintenance CLI on large workspaces (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
75
- let workspace;
76
- let originalCwd;
77
- beforeAll(() => {
78
- originalCwd = process.cwd();
79
- workspace = createCliLargeWorkspace();
80
- process.chdir(workspace.root);
81
- });
82
- afterAll(() => {
83
- process.chdir(originalCwd);
84
- workspace.cleanup();
85
- });
86
109
  it("[REQ-MAINT-DETECT] detect --json completes within a generous time budget and returns JSON payload", () => {
110
+ const { root, cleanup } = createCliLargeWorkspace();
111
+ const originalCwd = process.cwd();
112
+ process.chdir(root);
87
113
  const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
88
- const start = perf_hooks_1.performance.now();
89
- const exitCode = (0, cli_1.runMaintenanceCli)([
90
- "node",
91
- "traceability-maint",
92
- "detect",
93
- "--root",
94
- workspace.root,
95
- "--json",
96
- ]);
97
- const durationMs = perf_hooks_1.performance.now() - start;
98
- expect(exitCode === 0 || exitCode === 1).toBe(true);
99
- expect(durationMs).toBeLessThan(5000);
100
- expect(logSpy).toHaveBeenCalledTimes(1);
101
- const payloadRaw = String(logSpy.mock.calls[0][0]);
102
- const payload = JSON.parse(payloadRaw);
103
- expect(payload.root).toBe(workspace.root);
104
- expect(Array.isArray(payload.stale)).toBe(true);
105
- expect(payload.stale.length).toBeGreaterThan(0);
106
- logSpy.mockRestore();
114
+ try {
115
+ const start = perf_hooks_1.performance.now();
116
+ const exitCode = (0, cli_1.runMaintenanceCli)([
117
+ "node",
118
+ "traceability-maint",
119
+ "detect",
120
+ "--root",
121
+ root,
122
+ "--json",
123
+ ]);
124
+ const durationMs = perf_hooks_1.performance.now() - start;
125
+ expect(exitCode === 0 || exitCode === 1).toBe(true);
126
+ expect(durationMs).toBeLessThan(CLI_LARGE_WORKSPACE_PERF_BUDGET_MS);
127
+ expect(logSpy).toHaveBeenCalledTimes(1);
128
+ const payloadRaw = String(logSpy.mock.calls[0][0]);
129
+ const payload = JSON.parse(payloadRaw);
130
+ expect(payload.root).toBe(root);
131
+ expect(Array.isArray(payload.stale)).toBe(true);
132
+ expect(payload.stale.length).toBeGreaterThan(0);
133
+ }
134
+ finally {
135
+ logSpy.mockRestore();
136
+ process.chdir(originalCwd);
137
+ cleanup();
138
+ }
107
139
  });
108
140
  it("[REQ-MAINT-REPORT] report --format=json completes within a generous time budget", () => {
141
+ const { root, cleanup } = createCliLargeWorkspace();
142
+ const originalCwd = process.cwd();
143
+ process.chdir(root);
109
144
  const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
110
- const start = perf_hooks_1.performance.now();
111
- const exitCode = (0, cli_1.runMaintenanceCli)([
112
- "node",
113
- "traceability-maint",
114
- "report",
115
- "--root",
116
- workspace.root,
117
- "--format",
118
- "json",
119
- ]);
120
- const durationMs = perf_hooks_1.performance.now() - start;
121
- expect(exitCode).toBe(0);
122
- expect(durationMs).toBeLessThan(5000);
123
- expect(logSpy).toHaveBeenCalledTimes(1);
124
- const payloadRaw = String(logSpy.mock.calls[0][0]);
125
- const payload = JSON.parse(payloadRaw);
126
- expect(payload.root).toBe(workspace.root);
127
- expect(typeof payload.report).toBe("string");
128
- logSpy.mockRestore();
145
+ try {
146
+ const start = perf_hooks_1.performance.now();
147
+ const exitCode = (0, cli_1.runMaintenanceCli)([
148
+ "node",
149
+ "traceability-maint",
150
+ "report",
151
+ "--root",
152
+ root,
153
+ "--format",
154
+ "json",
155
+ ]);
156
+ const durationMs = perf_hooks_1.performance.now() - start;
157
+ expect(exitCode).toBe(0);
158
+ expect(durationMs).toBeLessThan(CLI_LARGE_WORKSPACE_PERF_BUDGET_MS);
159
+ expect(logSpy).toHaveBeenCalledTimes(1);
160
+ const payloadRaw = String(logSpy.mock.calls[0][0]);
161
+ const payload = JSON.parse(payloadRaw);
162
+ expect(payload.root).toBe(root);
163
+ expect(typeof payload.report).toBe("string");
164
+ }
165
+ finally {
166
+ logSpy.mockRestore();
167
+ process.chdir(originalCwd);
168
+ cleanup();
169
+ }
129
170
  });
130
171
  it("[REQ-MAINT-VERIFY] verify completes within a generous time budget and reports stale annotations", () => {
172
+ const { root, cleanup } = createCliLargeWorkspace();
173
+ const originalCwd = process.cwd();
174
+ process.chdir(root);
131
175
  const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
132
- const start = perf_hooks_1.performance.now();
133
- const exitCode = (0, cli_1.runMaintenanceCli)([
134
- "node",
135
- "traceability-maint",
136
- "verify",
137
- "--root",
138
- workspace.root,
139
- ]);
140
- const durationMs = perf_hooks_1.performance.now() - start;
141
- expect(exitCode).toBe(1);
142
- expect(durationMs).toBeLessThan(5000);
143
- expect(logSpy).toHaveBeenCalledTimes(1);
144
- const message = String(logSpy.mock.calls[0][0]);
145
- expect(message).toContain("Stale or invalid traceability annotations detected under");
146
- logSpy.mockRestore();
176
+ try {
177
+ const start = perf_hooks_1.performance.now();
178
+ const exitCode = (0, cli_1.runMaintenanceCli)([
179
+ "node",
180
+ "traceability-maint",
181
+ "verify",
182
+ "--root",
183
+ root,
184
+ ]);
185
+ const durationMs = perf_hooks_1.performance.now() - start;
186
+ expect(exitCode).toBe(1);
187
+ expect(durationMs).toBeLessThan(CLI_LARGE_WORKSPACE_PERF_BUDGET_MS);
188
+ expect(logSpy).toHaveBeenCalledTimes(1);
189
+ const message = String(logSpy.mock.calls[0][0]);
190
+ expect(message).toContain("Stale or invalid traceability annotations detected under");
191
+ }
192
+ finally {
193
+ logSpy.mockRestore();
194
+ process.chdir(originalCwd);
195
+ cleanup();
196
+ }
197
+ });
198
+ it("[REQ-MAINT-DETECT] detect traverses deeply nested directories within a generous time budget", () => {
199
+ const { root, cleanup } = createDeepNestedCliWorkspace();
200
+ const originalCwd = process.cwd();
201
+ process.chdir(root);
202
+ const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
203
+ try {
204
+ const start = perf_hooks_1.performance.now();
205
+ const exitCode = (0, cli_1.runMaintenanceCli)([
206
+ "node",
207
+ "traceability-maint",
208
+ "detect",
209
+ "--root",
210
+ root,
211
+ "--json",
212
+ ]);
213
+ const durationMs = perf_hooks_1.performance.now() - start;
214
+ expect(exitCode === 0 || exitCode === 1).toBe(true);
215
+ expect(durationMs).toBeLessThan(CLI_LARGE_WORKSPACE_PERF_BUDGET_MS);
216
+ expect(logSpy).toHaveBeenCalledTimes(1);
217
+ const payloadRaw = String(logSpy.mock.calls[0][0]);
218
+ const payload = JSON.parse(payloadRaw);
219
+ expect(payload.root).toBe(root);
220
+ expect(Array.isArray(payload.stale)).toBe(true);
221
+ expect(payload.stale.length).toBeGreaterThan(0);
222
+ }
223
+ finally {
224
+ logSpy.mockRestore();
225
+ process.chdir(originalCwd);
226
+ cleanup();
227
+ }
147
228
  });
148
229
  });
@@ -45,6 +45,8 @@ const detect_1 = require("../../src/maintenance/detect");
45
45
  const batch_1 = require("../../src/maintenance/batch");
46
46
  const report_1 = require("../../src/maintenance/report");
47
47
  const update_1 = require("../../src/maintenance/update");
48
+ // Performance budget for large-workspace maintenance tests; documented in docs/maintenance-performance-tests.md.
49
+ const LARGE_WORKSPACE_PERF_BUDGET_MS = 5000;
48
50
  /**
49
51
  * Shape of the synthetic large workspace:
50
52
  * - 10 modules (module-000 .. module-009)
@@ -92,58 +94,75 @@ export function example_${moduleIndex}_${fileIndex}() {}
92
94
  };
93
95
  }
94
96
  describe("Maintenance tools on large workspaces (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
95
- let workspace;
96
- beforeAll(() => {
97
- workspace = createLargeWorkspace();
98
- });
99
- afterAll(() => {
100
- workspace.cleanup();
101
- });
102
97
  it("[REQ-MAINT-DETECT] detectStaleAnnotations completes within a generous time budget", () => {
103
- const start = perf_hooks_1.performance.now();
104
- const stale = (0, detect_1.detectStaleAnnotations)(workspace.root);
105
- const durationMs = perf_hooks_1.performance.now() - start;
106
- // Sanity check: we expect at least some stale entries due to the generated stale-story-* references.
107
- expect(stale.length).toBeGreaterThan(0);
108
- // Guardrail: this operation should remain comfortably under ~5 seconds on CI hardware.
109
- expect(durationMs).toBeLessThan(5000);
98
+ const workspace = createLargeWorkspace();
99
+ try {
100
+ const start = perf_hooks_1.performance.now();
101
+ const stale = (0, detect_1.detectStaleAnnotations)(workspace.root);
102
+ const durationMs = perf_hooks_1.performance.now() - start;
103
+ // Sanity check: we expect at least some stale entries due to the generated stale-story-* references.
104
+ expect(stale.length).toBeGreaterThan(0);
105
+ // Guardrail: this operation should remain comfortably under ~5 seconds on CI hardware.
106
+ expect(durationMs).toBeLessThan(LARGE_WORKSPACE_PERF_BUDGET_MS);
107
+ }
108
+ finally {
109
+ workspace.cleanup();
110
+ }
110
111
  });
111
112
  it("[REQ-MAINT-VERIFY] verifyAnnotations remains fast on large workspaces", () => {
112
- const start = perf_hooks_1.performance.now();
113
- const result = (0, batch_1.verifyAnnotations)(workspace.root);
114
- const durationMs = perf_hooks_1.performance.now() - start;
115
- // With both valid and stale references, verification should report false.
116
- expect(result).toBe(false);
117
- expect(durationMs).toBeLessThan(5000);
113
+ const workspace = createLargeWorkspace();
114
+ try {
115
+ const start = perf_hooks_1.performance.now();
116
+ const result = (0, batch_1.verifyAnnotations)(workspace.root);
117
+ const durationMs = perf_hooks_1.performance.now() - start;
118
+ // With both valid and stale references, verification should report false.
119
+ expect(result).toBe(false);
120
+ expect(durationMs).toBeLessThan(LARGE_WORKSPACE_PERF_BUDGET_MS);
121
+ }
122
+ finally {
123
+ workspace.cleanup();
124
+ }
118
125
  });
119
126
  it("[REQ-MAINT-REPORT] generateMaintenanceReport produces output within a generous time budget", () => {
120
- const start = perf_hooks_1.performance.now();
121
- const report = (0, report_1.generateMaintenanceReport)(workspace.root);
122
- const durationMs = perf_hooks_1.performance.now() - start;
123
- expect(report).not.toBe("");
124
- expect(durationMs).toBeLessThan(5000);
127
+ const workspace = createLargeWorkspace();
128
+ try {
129
+ const start = perf_hooks_1.performance.now();
130
+ const report = (0, report_1.generateMaintenanceReport)(workspace.root);
131
+ const durationMs = perf_hooks_1.performance.now() - start;
132
+ expect(report).not.toBe("");
133
+ expect(durationMs).toBeLessThan(LARGE_WORKSPACE_PERF_BUDGET_MS);
134
+ }
135
+ finally {
136
+ workspace.cleanup();
137
+ }
125
138
  });
126
139
  it("[REQ-MAINT-UPDATE] updateAnnotationReferences and batchUpdateAnnotations remain tractable", () => {
127
- const exampleOldPath = "stale-story-0000.story.md";
128
- const exampleNewPath = "updated-story-0000.story.md";
129
- const singleStart = perf_hooks_1.performance.now();
130
- const updatedCount = (0, update_1.updateAnnotationReferences)(workspace.root, exampleOldPath, exampleNewPath);
131
- const singleDuration = perf_hooks_1.performance.now() - singleStart;
132
- expect(updatedCount).toBeGreaterThan(0);
133
- expect(singleDuration).toBeLessThan(5000);
134
- const batchStart = perf_hooks_1.performance.now();
135
- const totalUpdated = (0, batch_1.batchUpdateAnnotations)(workspace.root, [
136
- {
137
- oldPath: "stale-story-0001.story.md",
138
- newPath: "updated-story-0001.story.md",
139
- },
140
- {
141
- oldPath: "stale-story-0002.story.md",
142
- newPath: "updated-story-0002.story.md",
143
- },
144
- ]);
145
- const batchDuration = perf_hooks_1.performance.now() - batchStart;
146
- expect(totalUpdated).toBeGreaterThanOrEqual(2);
147
- expect(batchDuration).toBeLessThan(5000);
140
+ const workspace = createLargeWorkspace();
141
+ try {
142
+ const exampleOldPath = "stale-story-0000.story.md";
143
+ const exampleNewPath = "updated-story-0000.story.md";
144
+ const singleStart = perf_hooks_1.performance.now();
145
+ const updatedCount = (0, update_1.updateAnnotationReferences)(workspace.root, exampleOldPath, exampleNewPath);
146
+ const singleDuration = perf_hooks_1.performance.now() - singleStart;
147
+ expect(updatedCount).toBeGreaterThan(0);
148
+ expect(singleDuration).toBeLessThan(LARGE_WORKSPACE_PERF_BUDGET_MS);
149
+ const batchStart = perf_hooks_1.performance.now();
150
+ const totalUpdated = (0, batch_1.batchUpdateAnnotations)(workspace.root, [
151
+ {
152
+ oldPath: "stale-story-0001.story.md",
153
+ newPath: "updated-story-0001.story.md",
154
+ },
155
+ {
156
+ oldPath: "stale-story-0002.story.md",
157
+ newPath: "updated-story-0002.story.md",
158
+ },
159
+ ]);
160
+ const batchDuration = perf_hooks_1.performance.now() - batchStart;
161
+ expect(totalUpdated).toBeGreaterThanOrEqual(2);
162
+ expect(batchDuration).toBeLessThan(LARGE_WORKSPACE_PERF_BUDGET_MS);
163
+ }
164
+ finally {
165
+ workspace.cleanup();
166
+ }
148
167
  });
149
168
  });
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
11
11
  * @req REQ-STATEMENT-SIGNIFICANCE - Verify that simple statements are treated as redundant when covered by scope
12
12
  * @req REQ-SAFE-REMOVAL - Verify that auto-fix removes only redundant annotations and preserves code
13
13
  * @req REQ-DIFFERENT-REQUIREMENTS - Verify that annotations with different requirement IDs are preserved
14
+ * @req REQ-CATCH-BLOCK-HANDLING - Verify that catch block annotations are not incorrectly treated as redundant
14
15
  */
15
16
  const eslint_1 = require("eslint");
16
17
  const no_redundant_annotation_1 = __importDefault(require("../../src/rules/no-redundant-annotation"));
@@ -37,6 +38,10 @@ describe("no-redundant-annotation rule (Story 027.0-DEV-REDUNDANT-ANNOTATION-DET
37
38
  name: "[REQ-SCOPE-ANALYSIS] preserves annotations on both branch and statement when they intentionally duplicate each other",
38
39
  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
40
  },
41
+ {
42
+ name: "[REQ-CATCH-BLOCK-HANDLING] preserves catch block annotation from issue #6 scenario",
43
+ code: `async function example() {\n try {\n // @story prompts/004.0-DEV-FILTER-VULNERABLE-VERSIONS.story.md\n // @supports prompts/004.0-DEV-FILTER-VULNERABLE-VERSIONS.md REQ-SAFE-ONLY\n if (isSafeVersion({ version, vulnerabilityData })) {\n return version;\n }\n\n // @story prompts/004.0-DEV-FILTER-VULNERABLE-VERSIONS.story.md\n // @supports prompts/004.0-DEV-FILTER-VULNERABLE-VERSIONS.md REQ-SAFE-ONLY\n if (!vulnerabilityData.isVulnerable) {\n return version;\n }\n } catch (error) {\n // @story prompts/004.0-DEV-FILTER-VULNERABLE-VERSIONS.story.md\n // @supports prompts/004.0-DEV-FILTER-VULNERABLE-VERSIONS.md REQ-SAFE-ONLY\n return null;\n }\n}`,
44
+ },
40
45
  ],
41
46
  invalid: [
42
47
  {
@@ -100,6 +100,11 @@ describe('Vitest suite', () => {
100
100
  bench('Vitest bench', () => {});
101
101
  });`,
102
102
  },
103
+ {
104
+ name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] additionalTestHelperNames excludes configured helper callbacks when excludeTestCallbacks=true",
105
+ code: `withTestCase("does something", () => {});`,
106
+ options: [{ additionalTestHelperNames: ["withTestCase"] }],
107
+ },
103
108
  ],
104
109
  invalid: [
105
110
  {
@@ -216,6 +221,22 @@ describe('Vitest suite', () => {
216
221
  },
217
222
  ],
218
223
  },
224
+ {
225
+ name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] bench callback still reported even when included in additionalTestHelperNames",
226
+ code: `bench("bench case", () => {});`,
227
+ options: [{ additionalTestHelperNames: ["bench"], autoFix: false }],
228
+ errors: [
229
+ {
230
+ messageId: "missingStory",
231
+ suggestions: [
232
+ {
233
+ 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 */`,
234
+ output: `bench("bench case", /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n() => {});`,
235
+ },
236
+ ],
237
+ },
238
+ ],
239
+ },
219
240
  ],
220
241
  });
221
242
  ruleTester.run("require-story-annotation with exportPriority option", require_story_annotation_1.default, {
@@ -401,4 +401,73 @@ describe("Require Story Helpers (Story 003.0)", () => {
401
401
  });
402
402
  expect(result).toBeTruthy();
403
403
  });
404
+ /**
405
+ * Additional coverage for nested and helper-wrapped test callbacks.
406
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
407
+ * @req REQ-TEST-CALLBACK-EXCLUSION - Document how nested and wrapper-based callbacks interact with exclusion logic
408
+ */
409
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Nested anonymous arrow inside it() callback is excluded via nested-function inheritance", () => {
410
+ const outerCallback = {
411
+ type: "ArrowFunctionExpression",
412
+ parent: {
413
+ type: "CallExpression",
414
+ callee: { type: "Identifier", name: "it" },
415
+ },
416
+ };
417
+ const innerCallback = {
418
+ type: "ArrowFunctionExpression",
419
+ parent: {
420
+ type: "BlockStatement",
421
+ parent: outerCallback,
422
+ },
423
+ };
424
+ // Outer callback is treated as a test framework callback and excluded.
425
+ const outerResult = (0, require_story_helpers_1.shouldProcessNode)(outerCallback, require_story_helpers_1.DEFAULT_SCOPE);
426
+ // Inner anonymous arrow inherits from its nested parent and is also excluded.
427
+ const innerResult = (0, require_story_helpers_1.shouldProcessNode)(innerCallback, require_story_helpers_1.DEFAULT_SCOPE);
428
+ expect(outerResult).toBeFalsy();
429
+ expect(innerResult).toBeFalsy();
430
+ });
431
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow callback passed to local wrapper around describe() is not treated as a test callback", () => {
432
+ const node = {
433
+ type: "ArrowFunctionExpression",
434
+ parent: {
435
+ type: "CallExpression",
436
+ callee: { type: "Identifier", name: "withDescribe" },
437
+ },
438
+ };
439
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE);
440
+ expect(result).toBeTruthy();
441
+ });
442
+ /**
443
+ * Additional coverage for configurable test helper names.
444
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
445
+ * @req REQ-TEST-CALLBACK-EXCLUSION - Verify additionalTestHelperNames interacts correctly with exclusion logic
446
+ */
447
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow callback passed to configured additionalTestHelperNames helper is excluded by default", () => {
448
+ const node = {
449
+ type: "ArrowFunctionExpression",
450
+ parent: {
451
+ type: "CallExpression",
452
+ callee: { type: "Identifier", name: "withTest" },
453
+ },
454
+ };
455
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
456
+ additionalTestHelperNames: ["withTest"],
457
+ });
458
+ expect(result).toBeFalsy();
459
+ });
460
+ test("[REQ-TEST-CALLBACK-EXCLUSION] bench callback is never excluded even when included in additionalTestHelperNames", () => {
461
+ const node = {
462
+ type: "ArrowFunctionExpression",
463
+ parent: {
464
+ type: "CallExpression",
465
+ callee: { type: "Identifier", name: "bench" },
466
+ },
467
+ };
468
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
469
+ additionalTestHelperNames: ["bench"],
470
+ });
471
+ expect(result).toBeTruthy();
472
+ });
404
473
  });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Focused branch coverage tests for annotation-checker helper.
2
+ * Focused autofix behavior tests for annotation-checker helper.
3
3
  * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-AUTOFIX REQ-ANNOTATION-REPORTING
4
4
  */
5
5
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Focused branch coverage tests for annotation-checker helper.
3
+ * Focused autofix behavior tests for annotation-checker helper.
4
4
  * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-AUTOFIX REQ-ANNOTATION-REPORTING
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -34,7 +34,7 @@ function createContextStub() {
34
34
  };
35
35
  return { context, report };
36
36
  }
37
- describe("annotation-checker helper branch coverage (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", () => {
37
+ describe("annotation-checker helper autofix behavior (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", () => {
38
38
  it("[REQ-ANNOTATION-AUTOFIX] attaches fix directly to node when parent is missing", () => {
39
39
  const { context, report } = createContextStub();
40
40
  const node = { type: "FunctionDeclaration" }; // no parent property
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.16.1",
3
+ "version": "1.17.1",
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",
@@ -88,7 +88,7 @@
88
88
  "jest": "^30.2.0",
89
89
  "jscpd": "^4.0.5",
90
90
  "lint-staged": "^16.2.7",
91
- "prettier": "^3.6.2",
91
+ "prettier": "^3.7.4",
92
92
  "semantic-release": "25.0.2",
93
93
  "ts-jest": "^29.4.6",
94
94
  "typescript": "^5.9.3",