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.
- package/CHANGELOG.md +2 -2
- package/lib/src/index.js +53 -33
- package/lib/src/maintenance/commands.d.ts +4 -0
- package/lib/src/maintenance/commands.js +4 -0
- package/lib/src/maintenance/index.d.ts +1 -0
- package/lib/src/maintenance/index.js +1 -0
- package/lib/src/maintenance/report.js +2 -2
- package/lib/src/maintenance/update.js +4 -2
- package/lib/src/rules/helpers/require-story-helpers.d.ts +5 -11
- package/lib/src/rules/helpers/require-story-helpers.js +7 -74
- package/lib/src/rules/helpers/test-callback-exclusion.d.ts +43 -0
- package/lib/src/rules/helpers/test-callback-exclusion.js +100 -0
- package/lib/src/rules/helpers/valid-annotation-format-validators.js +8 -2
- package/lib/src/rules/no-redundant-annotation.js +4 -0
- package/lib/src/rules/prefer-implements-annotation.js +25 -20
- package/lib/src/rules/require-story-annotation.js +14 -1
- package/lib/src/rules/valid-annotation-format.js +62 -42
- package/lib/tests/integration/no-redundant-annotation.integration.test.js +31 -0
- package/lib/tests/integration/require-traceability-test-callbacks.integration.test.d.ts +1 -0
- package/lib/tests/integration/require-traceability-test-callbacks.integration.test.js +148 -0
- package/lib/tests/maintenance/detect-isolated.test.js +22 -14
- package/lib/tests/perf/maintenance-cli-large-workspace.test.js +145 -64
- package/lib/tests/perf/maintenance-large-workspace.test.js +65 -46
- package/lib/tests/rules/no-redundant-annotation.test.js +5 -0
- package/lib/tests/rules/require-story-annotation.test.js +21 -0
- package/lib/tests/rules/require-story-helpers.test.js +69 -0
- package/lib/tests/utils/{annotation-checker-branches.test.d.ts → annotation-checker-autofix-behavior.test.d.ts} +1 -1
- package/lib/tests/utils/{annotation-checker-branches.test.js → annotation-checker-autofix-behavior.test.js} +2 -2
- 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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Focused
|
|
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
|
|
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.
|
|
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.
|
|
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",
|