eslint-plugin-traceability 1.11.1 → 1.11.3
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/README.md +3 -3
- package/lib/src/index.d.ts +10 -5
- package/lib/src/index.js +71 -6
- package/lib/src/maintenance/commands.js +2 -3
- package/lib/src/maintenance/update.js +1 -14
- package/lib/src/rules/helpers/require-story-core.d.ts +12 -4
- package/lib/src/rules/helpers/require-story-core.js +59 -30
- package/lib/src/rules/helpers/require-story-helpers.d.ts +7 -41
- package/lib/src/rules/helpers/require-story-helpers.js +13 -70
- package/lib/src/rules/helpers/valid-annotation-format-internal.d.ts +12 -13
- package/lib/src/rules/helpers/valid-annotation-format-internal.js +21 -16
- package/lib/src/rules/helpers/valid-annotation-format-validators.d.ts +29 -3
- package/lib/src/rules/helpers/valid-annotation-format-validators.js +29 -3
- package/lib/src/rules/helpers/valid-annotation-utils.d.ts +3 -3
- package/lib/src/rules/helpers/valid-annotation-utils.js +10 -10
- package/lib/src/rules/helpers/valid-req-reference-helpers.d.ts +11 -0
- package/lib/src/rules/helpers/valid-req-reference-helpers.js +362 -0
- package/lib/src/rules/prefer-implements-annotation.js +7 -7
- package/lib/src/rules/require-story-annotation.d.ts +2 -0
- package/lib/src/rules/require-story-annotation.js +1 -1
- package/lib/src/rules/valid-req-reference.d.ts +4 -0
- package/lib/src/rules/valid-req-reference.js +5 -349
- package/lib/src/rules/valid-story-reference.d.ts +1 -1
- package/lib/src/rules/valid-story-reference.js +17 -10
- package/lib/src/utils/branch-annotation-helpers.d.ts +2 -2
- package/lib/src/utils/branch-annotation-helpers.js +96 -17
- package/lib/tests/cli-error-handling.test.js +1 -1
- package/lib/tests/config/eslint-config-validation.test.js +73 -0
- package/lib/tests/fixtures/stale/example.js +1 -1
- package/lib/tests/fixtures/update/example.js +1 -1
- package/lib/tests/integration/catch-annotation-prettier.integration.test.d.ts +1 -0
- package/lib/tests/integration/catch-annotation-prettier.integration.test.js +131 -0
- package/lib/tests/integration/dogfooding-validation.test.d.ts +1 -0
- package/lib/tests/integration/dogfooding-validation.test.js +94 -0
- package/lib/tests/maintenance/cli.test.js +37 -0
- package/lib/tests/maintenance/detect-isolated.test.js +5 -5
- package/lib/tests/perf/maintenance-cli-large-workspace.test.js +18 -0
- package/lib/tests/perf/require-branch-annotation-large-file.test.d.ts +1 -0
- package/lib/tests/perf/require-branch-annotation-large-file.test.js +67 -0
- package/lib/tests/perf/valid-annotation-format-large-file.test.d.ts +1 -0
- package/lib/tests/perf/valid-annotation-format-large-file.test.js +74 -0
- package/lib/tests/plugin-default-export-and-configs.test.js +1 -0
- package/lib/tests/plugin-setup.test.js +12 -1
- package/lib/tests/rules/prefer-implements-annotation.test.js +84 -70
- package/lib/tests/rules/require-branch-annotation.test.js +33 -1
- package/lib/tests/rules/valid-annotation-format-internal.test.d.ts +8 -0
- package/lib/tests/rules/valid-annotation-format-internal.test.js +47 -0
- package/lib/tests/utils/branch-annotation-catch-insert-position.test.d.ts +1 -0
- package/lib/tests/utils/branch-annotation-catch-insert-position.test.js +68 -0
- package/lib/tests/utils/branch-annotation-catch-position.test.d.ts +1 -0
- package/lib/tests/utils/branch-annotation-catch-position.test.js +115 -0
- package/lib/tests/utils/req-annotation-detection.test.d.ts +1 -0
- package/lib/tests/utils/req-annotation-detection.test.js +247 -0
- package/package.json +4 -4
- package/user-docs/api-reference.md +20 -12
- package/user-docs/examples.md +2 -1
- package/user-docs/migration-guide.md +11 -7
|
@@ -1,348 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Rule to validate @req annotation references refer to existing requirements in story files
|
|
9
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
10
|
-
* @req REQ-DEEP-PARSE - Parse comments and extract story/requirement metadata
|
|
11
|
-
* @req REQ-DEEP-MATCH - Match @req annotations to story file requirements
|
|
12
|
-
* @req REQ-DEEP-CACHE - Cache requirement IDs per story file for efficient validation
|
|
13
|
-
* @req REQ-DEEP-PATH - Validate and resolve story file paths safely
|
|
14
|
-
*/
|
|
15
|
-
const fs_1 = __importDefault(require("fs"));
|
|
16
|
-
const path_1 = __importDefault(require("path"));
|
|
17
|
-
/**
|
|
18
|
-
* Token index configuration for @supports annotations.
|
|
19
|
-
* This clarifies the expected positions of the story path and first requirement ID
|
|
20
|
-
* and avoids hard-coded "magic number" indices in parsing logic.
|
|
21
|
-
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
22
|
-
*/
|
|
23
|
-
const IMPLEMENTS_TOKENS = {
|
|
24
|
-
STORY_INDEX: 1,
|
|
25
|
-
FIRST_REQ_INDEX: 2,
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Extract the story path from a JSDoc comment.
|
|
29
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
30
|
-
* @req REQ-DEEP-PARSE - Parse JSDoc comment lines to locate @story annotations
|
|
31
|
-
*/
|
|
32
|
-
function extractStoryPath(comment) {
|
|
33
|
-
const rawLines = comment.value.split(/\r?\n/);
|
|
34
|
-
for (const rawLine of rawLines) {
|
|
35
|
-
const line = rawLine.trim().replace(/^\*+\s*/, "");
|
|
36
|
-
if (line.startsWith("@story")) {
|
|
37
|
-
const parts = line.split(/\s+/);
|
|
38
|
-
return parts[1] || null;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Validate and resolve the referenced story path.
|
|
45
|
-
* Performs traversal/absolute checks and resolves to a disk path.
|
|
46
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
47
|
-
* @req REQ-DEEP-PATH - Validate and resolve referenced story file paths
|
|
48
|
-
*/
|
|
49
|
-
function validateAndResolveStoryPath(opts) {
|
|
50
|
-
const { comment, context, storyPath, cwd } = opts;
|
|
51
|
-
if (storyPath.includes("..") || path_1.default.isAbsolute(storyPath)) {
|
|
52
|
-
context.report({
|
|
53
|
-
node: comment,
|
|
54
|
-
messageId: "invalidPath",
|
|
55
|
-
data: { storyPath },
|
|
56
|
-
});
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
const resolvedStoryPath = path_1.default.resolve(cwd, storyPath);
|
|
60
|
-
if (!resolvedStoryPath.startsWith(cwd + path_1.default.sep) &&
|
|
61
|
-
resolvedStoryPath !== cwd) {
|
|
62
|
-
context.report({
|
|
63
|
-
node: comment,
|
|
64
|
-
messageId: "invalidPath",
|
|
65
|
-
data: { storyPath },
|
|
66
|
-
});
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
return resolvedStoryPath;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Load and cache requirement IDs from a story file.
|
|
73
|
-
* Reads the story file, extracts requirement IDs, and updates the cache.
|
|
74
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
75
|
-
* @req REQ-DEEP-CACHE - Cache requirement IDs discovered in story files
|
|
76
|
-
* @req REQ-DEEP-PARSE - Parse story file contents to extract requirement identifiers
|
|
77
|
-
*/
|
|
78
|
-
function loadAndCacheRequirements(opts) {
|
|
79
|
-
const { resolvedStoryPath, reqCache } = opts;
|
|
80
|
-
if (!reqCache.has(resolvedStoryPath)) {
|
|
81
|
-
try {
|
|
82
|
-
const content = fs_1.default.readFileSync(resolvedStoryPath, "utf8");
|
|
83
|
-
const found = new Set();
|
|
84
|
-
const regex = /REQ-[A-Z0-9-]+/g;
|
|
85
|
-
let match;
|
|
86
|
-
while ((match = regex.exec(content)) !== null) {
|
|
87
|
-
found.add(match[0]);
|
|
88
|
-
}
|
|
89
|
-
reqCache.set(resolvedStoryPath, found);
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
reqCache.set(resolvedStoryPath, new Set());
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return reqCache.get(resolvedStoryPath);
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Perform the final requirement existence check and report if missing.
|
|
99
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
100
|
-
* @req REQ-DEEP-MATCH - Verify that a referenced requirement ID exists in the story
|
|
101
|
-
*/
|
|
102
|
-
function checkRequirementExists(opts) {
|
|
103
|
-
const { comment, context, reqId, storyPath, reqSet } = opts;
|
|
104
|
-
if (!reqSet.has(reqId)) {
|
|
105
|
-
context.report({
|
|
106
|
-
node: comment,
|
|
107
|
-
messageId: "reqMissing",
|
|
108
|
-
data: { reqId, storyPath },
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Extract requirement ID from a @req line.
|
|
114
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
115
|
-
* @req REQ-DEEP-PARSE - Parse annotation lines to extract requirement IDs
|
|
116
|
-
*/
|
|
117
|
-
function extractReqIdFromLine(line) {
|
|
118
|
-
const parts = line.split(/\s+/);
|
|
119
|
-
return parts[1];
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Resolve story path and load requirements set for validation.
|
|
123
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
124
|
-
* @req REQ-DEEP-PATH - Validate and resolve referenced story file paths
|
|
125
|
-
* @req REQ-DEEP-CACHE - Cache requirement IDs discovered in story files
|
|
126
|
-
*/
|
|
127
|
-
function resolveStoryAndRequirements(opts) {
|
|
128
|
-
const { comment, context, storyPath, cwd, reqCache } = opts;
|
|
129
|
-
const resolvedStoryPath = validateAndResolveStoryPath({
|
|
130
|
-
comment,
|
|
131
|
-
context,
|
|
132
|
-
storyPath,
|
|
133
|
-
cwd,
|
|
134
|
-
});
|
|
135
|
-
if (!resolvedStoryPath) {
|
|
136
|
-
return { resolvedStoryPath: null, reqSet: null };
|
|
137
|
-
}
|
|
138
|
-
const reqSet = loadAndCacheRequirements({
|
|
139
|
-
resolvedStoryPath,
|
|
140
|
-
reqCache,
|
|
141
|
-
});
|
|
142
|
-
return { resolvedStoryPath, reqSet };
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Validate a @req annotation line against the extracted story content.
|
|
146
|
-
* Performs path validation, file reading, caching, and requirement existence checks.
|
|
147
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
148
|
-
* @req REQ-DEEP-PATH - Validate and resolve referenced story file paths
|
|
149
|
-
* @req REQ-DEEP-CACHE - Cache requirement IDs discovered in story files
|
|
150
|
-
* @req REQ-DEEP-MATCH - Verify that a referenced requirement ID exists in the story
|
|
151
|
-
* @req REQ-DEEP-PARSE - Parse story file contents to extract requirement identifiers
|
|
152
|
-
*/
|
|
153
|
-
function validateReqLine(opts) {
|
|
154
|
-
const { comment, context, line, storyPath, cwd, reqCache } = opts;
|
|
155
|
-
const reqId = extractReqIdFromLine(line);
|
|
156
|
-
if (!reqId || !storyPath) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
const { reqSet } = resolveStoryAndRequirements({
|
|
160
|
-
comment,
|
|
161
|
-
context,
|
|
162
|
-
storyPath,
|
|
163
|
-
cwd,
|
|
164
|
-
reqCache,
|
|
165
|
-
});
|
|
166
|
-
if (!reqSet) {
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
checkRequirementExists({
|
|
170
|
-
comment,
|
|
171
|
-
context,
|
|
172
|
-
reqId,
|
|
173
|
-
storyPath,
|
|
174
|
-
reqSet,
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Parse a @supports annotation line into its story path and requirement IDs.
|
|
179
|
-
* Expects the format: "@supports <storyPath> <REQ-ID-1> <REQ-ID-2> ..."
|
|
180
|
-
* Invalid formats (missing storyPath or reqIds) are ignored by this deep rule.
|
|
181
|
-
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
182
|
-
* @req REQ-IMPLEMENTS-VALIDATE - Support validation of @supports annotations
|
|
183
|
-
* @req REQ-MIXED-SUPPORT - Allow mixed @story/@req/@implements usage in the same comment
|
|
184
|
-
* @req REQ-SCOPED-IDS - Treat requirement IDs as scoped to the referenced story file
|
|
185
|
-
*/
|
|
186
|
-
function parseImplementsLine(line) {
|
|
187
|
-
const parts = line.split(/\s+/);
|
|
188
|
-
const storyPath = parts[IMPLEMENTS_TOKENS.STORY_INDEX];
|
|
189
|
-
const reqIds = parts.slice(IMPLEMENTS_TOKENS.FIRST_REQ_INDEX);
|
|
190
|
-
if (!storyPath || reqIds.length === 0) {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
return { storyPath, reqIds };
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Validate an @supports annotation line against the referenced story content.
|
|
197
|
-
* Performs path validation, file reading, caching, and requirement existence checks
|
|
198
|
-
* for each requirement ID listed on the line.
|
|
199
|
-
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
200
|
-
* @req REQ-IMPLEMENTS-VALIDATE - Validate that all @supports requirement IDs exist
|
|
201
|
-
* @req REQ-MIXED-SUPPORT - Ensure @supports can coexist with @story/@req annotations
|
|
202
|
-
* @req REQ-SCOPED-IDS - Validate requirement IDs in the scope of their explicit story
|
|
203
|
-
*/
|
|
204
|
-
function validateImplementsLine(opts) {
|
|
205
|
-
const { comment, context, line, cwd, reqCache } = opts;
|
|
206
|
-
const parsed = parseImplementsLine(line);
|
|
207
|
-
if (!parsed) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
const { storyPath, reqIds } = parsed;
|
|
211
|
-
const { reqSet } = resolveStoryAndRequirements({
|
|
212
|
-
comment,
|
|
213
|
-
context,
|
|
214
|
-
storyPath,
|
|
215
|
-
cwd,
|
|
216
|
-
reqCache,
|
|
217
|
-
});
|
|
218
|
-
if (!reqSet) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
for (const reqId of reqIds) {
|
|
222
|
-
checkRequirementExists({
|
|
223
|
-
comment,
|
|
224
|
-
context,
|
|
225
|
-
reqId,
|
|
226
|
-
storyPath,
|
|
227
|
-
reqSet,
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Handle a single annotation line for story or requirement metadata.
|
|
233
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
234
|
-
* @req REQ-DEEP-PARSE - Parse annotation lines for @story and @req tags
|
|
235
|
-
* @req REQ-DEEP-MATCH - Dispatch @req lines for validation against story requirements
|
|
236
|
-
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
237
|
-
* @req REQ-IMPLEMENTS-VALIDATE - Dispatch @supports lines for validation
|
|
238
|
-
* @req REQ-MIXED-SUPPORT - Support mixed annotation types without interfering with each other
|
|
239
|
-
*/
|
|
240
|
-
function handleAnnotationLine(opts) {
|
|
241
|
-
const { line, comment, context, cwd, reqCache, storyPath } = opts;
|
|
242
|
-
if (line.startsWith("@story")) {
|
|
243
|
-
const newPath = extractStoryPath(comment);
|
|
244
|
-
return newPath || storyPath;
|
|
245
|
-
}
|
|
246
|
-
else if (line.startsWith("@req")) {
|
|
247
|
-
validateReqLine({ comment, context, line, storyPath, cwd, reqCache });
|
|
248
|
-
return storyPath;
|
|
249
|
-
}
|
|
250
|
-
else if (line.startsWith("@supports")) {
|
|
251
|
-
validateImplementsLine({ comment, context, line, cwd, reqCache });
|
|
252
|
-
return storyPath;
|
|
253
|
-
}
|
|
254
|
-
return storyPath;
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Iterate over all raw lines in a comment and update storyPath as needed.
|
|
258
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
259
|
-
* @req REQ-DEEP-PARSE - Iterate comment lines to process @story/@req annotations
|
|
260
|
-
* @req REQ-DEEP-MATCH - Coordinate annotation handling across a comment block
|
|
261
|
-
*/
|
|
262
|
-
function processCommentLines(opts) {
|
|
263
|
-
const { comment, context, cwd, reqCache, initialStoryPath } = opts;
|
|
264
|
-
let storyPath = initialStoryPath;
|
|
265
|
-
const rawLines = comment.value.split(/\r?\n/);
|
|
266
|
-
for (const rawLine of rawLines) {
|
|
267
|
-
const line = rawLine.trim().replace(/^\*+\s*/, "");
|
|
268
|
-
storyPath = handleAnnotationLine({
|
|
269
|
-
line,
|
|
270
|
-
comment,
|
|
271
|
-
context,
|
|
272
|
-
cwd,
|
|
273
|
-
reqCache,
|
|
274
|
-
storyPath,
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
return storyPath;
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Handle JSDoc story and req annotations for a single comment block.
|
|
281
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
282
|
-
* @req REQ-DEEP-PARSE - Iterate comment lines to process @story/@req annotations
|
|
283
|
-
* @req REQ-DEEP-MATCH - Coordinate annotation handling across a comment block
|
|
284
|
-
* @req REQ-DEEP-CACHE - Maintain and reuse discovered story path across comments
|
|
285
|
-
*/
|
|
286
|
-
function handleComment(opts) {
|
|
287
|
-
const { comment, context, cwd, reqCache, rawStoryPath } = opts;
|
|
288
|
-
return processCommentLines({
|
|
289
|
-
comment,
|
|
290
|
-
context,
|
|
291
|
-
cwd,
|
|
292
|
-
reqCache,
|
|
293
|
-
initialStoryPath: rawStoryPath,
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Get all comments from source and drive comment-level handling.
|
|
298
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
299
|
-
* @req REQ-DEEP-PARSE - Collect all comments from the source code
|
|
300
|
-
* @req REQ-DEEP-MATCH - Drive comment-level handling for traceability checks
|
|
301
|
-
* @req REQ-DEEP-CACHE - Reuse story path and requirement cache across comments
|
|
302
|
-
*/
|
|
303
|
-
function processAllComments(opts) {
|
|
304
|
-
const { sourceCode, context, cwd, reqCache } = opts;
|
|
305
|
-
let rawStoryPath = opts.initialStoryPath;
|
|
306
|
-
const comments = sourceCode.getAllComments() || [];
|
|
307
|
-
comments.forEach((comment) => {
|
|
308
|
-
rawStoryPath = handleComment({
|
|
309
|
-
comment,
|
|
310
|
-
context,
|
|
311
|
-
cwd,
|
|
312
|
-
reqCache,
|
|
313
|
-
rawStoryPath,
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Create a Program listener that iterates comments and validates annotations.
|
|
319
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
320
|
-
* @req REQ-DEEP-CACHE - Initialize and share a requirement cache for the program
|
|
321
|
-
* @req REQ-DEEP-PATH - Derive the working directory context for path resolution
|
|
322
|
-
*/
|
|
323
|
-
function programListener(context) {
|
|
324
|
-
const sourceCode = context.getSourceCode();
|
|
325
|
-
const cwd = process.cwd();
|
|
326
|
-
const reqCache = new Map();
|
|
327
|
-
let rawStoryPath = null;
|
|
328
|
-
/**
|
|
329
|
-
* Program visitor that walks all comments to validate story/requirement references.
|
|
330
|
-
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
331
|
-
* @req REQ-DEEP-PARSE - Collect all comments from the source code
|
|
332
|
-
* @req REQ-DEEP-MATCH - Drive comment-level handling for traceability checks
|
|
333
|
-
* @req REQ-DEEP-CACHE - Reuse story path and requirement cache across comments
|
|
334
|
-
* @req REQ-DEEP-PATH - Ensure validation respects project-relative paths
|
|
335
|
-
*/
|
|
336
|
-
return function Program() {
|
|
337
|
-
processAllComments({
|
|
338
|
-
sourceCode,
|
|
339
|
-
context,
|
|
340
|
-
cwd,
|
|
341
|
-
reqCache,
|
|
342
|
-
initialStoryPath: rawStoryPath,
|
|
343
|
-
});
|
|
344
|
-
};
|
|
345
|
-
}
|
|
3
|
+
const valid_req_reference_helpers_1 = require("./helpers/valid-req-reference-helpers");
|
|
346
4
|
exports.default = {
|
|
347
5
|
meta: {
|
|
348
6
|
type: "problem",
|
|
@@ -372,13 +30,11 @@ exports.default = {
|
|
|
372
30
|
},
|
|
373
31
|
/**
|
|
374
32
|
* Rule create entrypoint that returns the Program visitor.
|
|
375
|
-
*
|
|
376
|
-
* @req REQ-DEEP-MATCH - Register the Program visitor with ESLint
|
|
377
|
-
* @req REQ-DEEP-PARSE - Integrate comment parsing into the ESLint rule lifecycle
|
|
378
|
-
* @req REQ-DEEP-CACHE - Ensure cache and context are wired into the listener
|
|
379
|
-
* @req REQ-DEEP-PATH - Propagate path context into the program listener
|
|
33
|
+
* Delegates to createValidReqReferenceProgramVisitor helper.
|
|
380
34
|
*/
|
|
381
35
|
create(context) {
|
|
382
|
-
return {
|
|
36
|
+
return {
|
|
37
|
+
Program: (0, valid_req_reference_helpers_1.createValidReqReferenceProgramVisitor)(context),
|
|
38
|
+
};
|
|
383
39
|
},
|
|
384
40
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* This rule validates that @story annotation references refer to existing story files.
|
|
3
3
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
4
4
|
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
5
5
|
* @req REQ-PATH-RESOLUTION - Resolve relative paths correctly and enforce configuration
|
|
@@ -21,7 +21,7 @@ function reportInvalidPath(opts) {
|
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* Extracts the story path from the annotation line and delegates validation.
|
|
25
25
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
26
26
|
* @req REQ-ANNOTATION-VALIDATION - Ensure each annotation line is parsed
|
|
27
27
|
*/
|
|
@@ -42,7 +42,7 @@ function validateStoryPath(opts) {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
45
|
+
* Handles existence status and reports appropriate diagnostics for missing
|
|
46
46
|
* or filesystem-error conditions, assuming project-boundary checks have
|
|
47
47
|
* already been applied.
|
|
48
48
|
*
|
|
@@ -85,7 +85,7 @@ function reportExistenceStatus(existenceResult, storyPath, commentNode, context)
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
88
|
+
* Reports any problems related to the existence or accessibility of the
|
|
89
89
|
* referenced story file. Filesystem and I/O errors are surfaced with a
|
|
90
90
|
* dedicated diagnostic that differentiates them from missing files.
|
|
91
91
|
*
|
|
@@ -115,7 +115,7 @@ function reportExistenceProblems(opts) {
|
|
|
115
115
|
reportExistenceStatus(existenceResult, storyPath, commentNode, context);
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
|
-
*
|
|
118
|
+
* Processes and validates the story path for security, extension, and existence.
|
|
119
119
|
* Filesystem and I/O errors are handled inside the underlying utilities
|
|
120
120
|
* (e.g. storyExists) and surfaced as missing-file or filesystem-error
|
|
121
121
|
* diagnostics where appropriate.
|
|
@@ -149,9 +149,9 @@ function processStoryPath(opts) {
|
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
151
|
/**
|
|
152
|
-
*
|
|
153
|
-
* -
|
|
154
|
-
* -
|
|
152
|
+
* Performs the existence check:
|
|
153
|
+
* - Distinguishes between missing files and filesystem errors.
|
|
154
|
+
* - Surfaces filesystem and I/O errors with a dedicated diagnostic.
|
|
155
155
|
*
|
|
156
156
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
157
157
|
* @req REQ-FILE-EXISTENCE - Ensure referenced files exist
|
|
@@ -166,7 +166,8 @@ function processStoryPath(opts) {
|
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
/**
|
|
169
|
-
*
|
|
169
|
+
* Handles a single comment node by processing its lines and looking for
|
|
170
|
+
* @story annotations that should be validated.
|
|
170
171
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
171
172
|
* @req REQ-ANNOTATION-VALIDATION - Ensure each annotation line is parsed
|
|
172
173
|
*/
|
|
@@ -175,6 +176,7 @@ function handleComment(opts) {
|
|
|
175
176
|
const lines = commentNode.value
|
|
176
177
|
.split(/\r?\n/)
|
|
177
178
|
/**
|
|
179
|
+
* Processes each line of the comment to extract and normalize @story annotations.
|
|
178
180
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
179
181
|
* @req REQ-ANNOTATION-VALIDATION - Ensure each annotation line is parsed
|
|
180
182
|
*/
|
|
@@ -202,6 +204,7 @@ exports.default = {
|
|
|
202
204
|
},
|
|
203
205
|
messages: {
|
|
204
206
|
/**
|
|
207
|
+
* Reports that a referenced story file could not be found.
|
|
205
208
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
206
209
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
207
210
|
* @req REQ-ERROR-SPECIFIC - Provide specific diagnostics when a referenced story file cannot be found
|
|
@@ -210,14 +213,17 @@ exports.default = {
|
|
|
210
213
|
*/
|
|
211
214
|
fileMissing: "Story file '{{path}}' not found",
|
|
212
215
|
/**
|
|
216
|
+
* Reports that the provided story file path has an invalid extension.
|
|
213
217
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
214
218
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
215
219
|
* @req REQ-ERROR-SPECIFIC - Indicate that the story file extension is invalid and what is expected
|
|
216
220
|
* @req REQ-ERROR-CONTEXT - Include the provided path so developers can see which reference is wrong
|
|
217
|
-
* @req REQ-ERROR-CONSISTENCY - Reuse the same pattern of
|
|
221
|
+
* @req REQ-ERROR-CONSISTENCY - Reuse the same pattern of '{{path}}' placeholder across file validation messages
|
|
218
222
|
*/
|
|
219
223
|
invalidExtension: "Invalid story file extension for '{{path}}', expected '.story.md'",
|
|
220
224
|
/**
|
|
225
|
+
* Reports that the referenced story path is invalid due to being absolute
|
|
226
|
+
* or containing unsafe traversal.
|
|
221
227
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
222
228
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
223
229
|
* @req REQ-ERROR-SPECIFIC - Explain that the story path is invalid due to absolute or unsafe traversal
|
|
@@ -226,6 +232,7 @@ exports.default = {
|
|
|
226
232
|
*/
|
|
227
233
|
invalidPath: "Invalid story path '{{path}}'",
|
|
228
234
|
/**
|
|
235
|
+
* Reports a filesystem error that occurred while validating the story file.
|
|
229
236
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
230
237
|
* @req REQ-ERROR-HANDLING - Provide clear diagnostics for filesystem errors
|
|
231
238
|
*/
|
|
@@ -251,7 +258,7 @@ exports.default = {
|
|
|
251
258
|
const requireExt = opts?.requireStoryExtension !== false;
|
|
252
259
|
return {
|
|
253
260
|
/**
|
|
254
|
-
* Program-level handler:
|
|
261
|
+
* Program-level handler: iterates comments and validates @story annotations.
|
|
255
262
|
* Filesystem and I/O errors are handled by underlying utilities and
|
|
256
263
|
* surfaced as missing-file or filesystem-error diagnostics where appropriate.
|
|
257
264
|
*
|
|
@@ -23,7 +23,7 @@ export declare function validateBranchTypes(context: Rule.RuleContext): BranchTy
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function gatherBranchCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any): string;
|
|
25
25
|
/**
|
|
26
|
-
* Report missing @story annotation on a branch node.
|
|
26
|
+
* Report missing @story annotation tag on a branch node when that branch lacks a corresponding @story reference in its comments.
|
|
27
27
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
28
28
|
* @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
|
|
29
29
|
*/
|
|
@@ -35,7 +35,7 @@ export declare function reportMissingStory(context: Rule.RuleContext, node: any,
|
|
|
35
35
|
};
|
|
36
36
|
}): void;
|
|
37
37
|
/**
|
|
38
|
-
* Report missing @req annotation on a branch node.
|
|
38
|
+
* Report missing @req annotation tag on a branch node when that branch has no linked requirement identifier in its associated comments.
|
|
39
39
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
40
40
|
* @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
|
|
41
41
|
*/
|
|
@@ -78,6 +78,61 @@ function validateBranchTypes(context) {
|
|
|
78
78
|
? options.branchTypes
|
|
79
79
|
: Array.from(exports.DEFAULT_BRANCH_TYPES);
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Extract the raw value from a comment node.
|
|
83
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
84
|
+
* @req REQ-TRACEABILITY-MAP-CALLBACK - Trace mapping of comment nodes to their text values
|
|
85
|
+
*/
|
|
86
|
+
function extractCommentValue(_c) {
|
|
87
|
+
return _c.value;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Gather annotation text for CatchClause nodes, supporting both before-catch and inside-catch positions.
|
|
91
|
+
* @story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md
|
|
92
|
+
* @req REQ-DUAL-POSITION-DETECTION
|
|
93
|
+
* @req REQ-FALLBACK-LOGIC
|
|
94
|
+
*/
|
|
95
|
+
function gatherCatchClauseCommentText(sourceCode, node, beforeText) {
|
|
96
|
+
if (/@story\b/.test(beforeText) || /@req\b/.test(beforeText)) {
|
|
97
|
+
return beforeText;
|
|
98
|
+
}
|
|
99
|
+
const getCommentsInside = sourceCode.getCommentsInside;
|
|
100
|
+
if (node.body && typeof getCommentsInside === "function") {
|
|
101
|
+
try {
|
|
102
|
+
const insideComments = getCommentsInside(node.body) || [];
|
|
103
|
+
const insideText = insideComments.map(extractCommentValue).join(" ");
|
|
104
|
+
if (insideText) {
|
|
105
|
+
return insideText;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// fall through to line-based fallback
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (node.body && node.body.loc && node.body.loc.start && node.body.loc.end) {
|
|
113
|
+
const lines = sourceCode.lines;
|
|
114
|
+
const startIndex = node.body.loc.start.line - 1;
|
|
115
|
+
const endIndex = node.body.loc.end.line - 1;
|
|
116
|
+
const comments = [];
|
|
117
|
+
let i = startIndex + 1;
|
|
118
|
+
while (i <= endIndex) {
|
|
119
|
+
const line = lines[i];
|
|
120
|
+
if (!line || !line.trim()) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
if (!/^\s*(\/\/|\/\*)/.test(line)) {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
comments.push(line.trim());
|
|
127
|
+
i++;
|
|
128
|
+
}
|
|
129
|
+
const insideText = comments.join(" ");
|
|
130
|
+
if (insideText) {
|
|
131
|
+
return insideText;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return beforeText;
|
|
135
|
+
}
|
|
81
136
|
/**
|
|
82
137
|
* Gather leading comment text for a branch node.
|
|
83
138
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
@@ -102,19 +157,15 @@ function gatherBranchCommentText(sourceCode, node) {
|
|
|
102
157
|
}
|
|
103
158
|
return comments.join(" ");
|
|
104
159
|
}
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
* @req REQ-TRACEABILITY-MAP-CALLBACK - Trace mapping of comment nodes to their text values
|
|
110
|
-
*/
|
|
111
|
-
function commentToValue(c) {
|
|
112
|
-
return c.value;
|
|
160
|
+
const beforeComments = sourceCode.getCommentsBefore(node) || [];
|
|
161
|
+
const beforeText = beforeComments.map(extractCommentValue).join(" ");
|
|
162
|
+
if (node.type === "CatchClause") {
|
|
163
|
+
return gatherCatchClauseCommentText(sourceCode, node, beforeText);
|
|
113
164
|
}
|
|
114
|
-
return
|
|
165
|
+
return beforeText;
|
|
115
166
|
}
|
|
116
167
|
/**
|
|
117
|
-
* Report missing @story annotation on a branch node.
|
|
168
|
+
* Report missing @story annotation tag on a branch node when that branch lacks a corresponding @story reference in its comments.
|
|
118
169
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
119
170
|
* @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
|
|
120
171
|
*/
|
|
@@ -127,7 +178,7 @@ function reportMissingStory(context, node, options) {
|
|
|
127
178
|
*/
|
|
128
179
|
if (storyFixCountRef.count === 0) {
|
|
129
180
|
/**
|
|
130
|
-
* Fixer that inserts a default @story
|
|
181
|
+
* Fixer that inserts a default @story tag above the branch.
|
|
131
182
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
132
183
|
* @req REQ-TRACEABILITY-FIX-ARROW - Trace fixer function used to insert missing @story
|
|
133
184
|
*/
|
|
@@ -151,7 +202,7 @@ function reportMissingStory(context, node, options) {
|
|
|
151
202
|
}
|
|
152
203
|
}
|
|
153
204
|
/**
|
|
154
|
-
* Report missing @req annotation on a branch node.
|
|
205
|
+
* Report missing @req annotation tag on a branch node when that branch has no linked requirement identifier in its associated comments.
|
|
155
206
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
156
207
|
* @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
|
|
157
208
|
*/
|
|
@@ -164,12 +215,12 @@ function reportMissingReq(context, node, options) {
|
|
|
164
215
|
*/
|
|
165
216
|
if (!missingStory) {
|
|
166
217
|
/**
|
|
167
|
-
* Fixer that inserts a default @req
|
|
218
|
+
* Fixer that inserts a default @req tag above the branch.
|
|
168
219
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
169
220
|
* @req REQ-TRACEABILITY-FIX-ARROW - Trace fixer function used to insert missing @req
|
|
170
221
|
*/
|
|
171
|
-
function insertReqFixer(
|
|
172
|
-
return
|
|
222
|
+
function insertReqFixer(fxer) {
|
|
223
|
+
return fxer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @req <REQ-ID>\n`);
|
|
173
224
|
}
|
|
174
225
|
context.report({
|
|
175
226
|
node,
|
|
@@ -195,11 +246,39 @@ function getBranchAnnotationInfo(sourceCode, node) {
|
|
|
195
246
|
const text = gatherBranchCommentText(sourceCode, node);
|
|
196
247
|
const missingStory = !/@story\b/.test(text);
|
|
197
248
|
const missingReq = !/@req\b/.test(text);
|
|
198
|
-
|
|
199
|
-
|
|
249
|
+
let indent = sourceCode.lines[node.loc.start.line - 1].match(/^(\s*)/)?.[1] || "";
|
|
250
|
+
let insertPos = sourceCode.getIndexFromLoc({
|
|
200
251
|
line: node.loc.start.line,
|
|
201
252
|
column: 0,
|
|
202
253
|
});
|
|
254
|
+
if (node.type === "CatchClause" && node.body) {
|
|
255
|
+
const bodyNode = node.body;
|
|
256
|
+
const bodyStatements = Array.isArray(bodyNode.body)
|
|
257
|
+
? bodyNode.body
|
|
258
|
+
: undefined;
|
|
259
|
+
const firstStatement = bodyStatements && bodyStatements.length > 0
|
|
260
|
+
? bodyStatements[0]
|
|
261
|
+
: undefined;
|
|
262
|
+
if (firstStatement && firstStatement.loc && firstStatement.loc.start) {
|
|
263
|
+
const firstLine = firstStatement.loc.start.line;
|
|
264
|
+
const innerIndent = sourceCode.lines[firstLine - 1].match(/^(\s*)/)?.[1] || "";
|
|
265
|
+
indent = innerIndent;
|
|
266
|
+
insertPos = sourceCode.getIndexFromLoc({
|
|
267
|
+
line: firstLine,
|
|
268
|
+
column: 0,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
else if (bodyNode.loc && bodyNode.loc.start) {
|
|
272
|
+
const blockLine = bodyNode.loc.start.line;
|
|
273
|
+
const blockIndent = sourceCode.lines[blockLine - 1].match(/^(\s*)/)?.[1] || "";
|
|
274
|
+
const innerIndent = `${blockIndent} `;
|
|
275
|
+
indent = innerIndent;
|
|
276
|
+
insertPos = sourceCode.getIndexFromLoc({
|
|
277
|
+
line: blockLine,
|
|
278
|
+
column: 0,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
203
282
|
return { missingStory, missingReq, indent, insertPos };
|
|
204
283
|
}
|
|
205
284
|
/**
|
|
@@ -40,6 +40,6 @@ describe("CLI Error Handling for Traceability Plugin (Story 001.0-DEV-PLUGIN-SET
|
|
|
40
40
|
});
|
|
41
41
|
// Expect non-zero exit and missing annotation message on stdout
|
|
42
42
|
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
|
|
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");
|
|
44
44
|
});
|
|
45
45
|
});
|