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.
Files changed (58) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +3 -3
  3. package/lib/src/index.d.ts +10 -5
  4. package/lib/src/index.js +71 -6
  5. package/lib/src/maintenance/commands.js +2 -3
  6. package/lib/src/maintenance/update.js +1 -14
  7. package/lib/src/rules/helpers/require-story-core.d.ts +12 -4
  8. package/lib/src/rules/helpers/require-story-core.js +59 -30
  9. package/lib/src/rules/helpers/require-story-helpers.d.ts +7 -41
  10. package/lib/src/rules/helpers/require-story-helpers.js +13 -70
  11. package/lib/src/rules/helpers/valid-annotation-format-internal.d.ts +12 -13
  12. package/lib/src/rules/helpers/valid-annotation-format-internal.js +21 -16
  13. package/lib/src/rules/helpers/valid-annotation-format-validators.d.ts +29 -3
  14. package/lib/src/rules/helpers/valid-annotation-format-validators.js +29 -3
  15. package/lib/src/rules/helpers/valid-annotation-utils.d.ts +3 -3
  16. package/lib/src/rules/helpers/valid-annotation-utils.js +10 -10
  17. package/lib/src/rules/helpers/valid-req-reference-helpers.d.ts +11 -0
  18. package/lib/src/rules/helpers/valid-req-reference-helpers.js +362 -0
  19. package/lib/src/rules/prefer-implements-annotation.js +7 -7
  20. package/lib/src/rules/require-story-annotation.d.ts +2 -0
  21. package/lib/src/rules/require-story-annotation.js +1 -1
  22. package/lib/src/rules/valid-req-reference.d.ts +4 -0
  23. package/lib/src/rules/valid-req-reference.js +5 -349
  24. package/lib/src/rules/valid-story-reference.d.ts +1 -1
  25. package/lib/src/rules/valid-story-reference.js +17 -10
  26. package/lib/src/utils/branch-annotation-helpers.d.ts +2 -2
  27. package/lib/src/utils/branch-annotation-helpers.js +96 -17
  28. package/lib/tests/cli-error-handling.test.js +1 -1
  29. package/lib/tests/config/eslint-config-validation.test.js +73 -0
  30. package/lib/tests/fixtures/stale/example.js +1 -1
  31. package/lib/tests/fixtures/update/example.js +1 -1
  32. package/lib/tests/integration/catch-annotation-prettier.integration.test.d.ts +1 -0
  33. package/lib/tests/integration/catch-annotation-prettier.integration.test.js +131 -0
  34. package/lib/tests/integration/dogfooding-validation.test.d.ts +1 -0
  35. package/lib/tests/integration/dogfooding-validation.test.js +94 -0
  36. package/lib/tests/maintenance/cli.test.js +37 -0
  37. package/lib/tests/maintenance/detect-isolated.test.js +5 -5
  38. package/lib/tests/perf/maintenance-cli-large-workspace.test.js +18 -0
  39. package/lib/tests/perf/require-branch-annotation-large-file.test.d.ts +1 -0
  40. package/lib/tests/perf/require-branch-annotation-large-file.test.js +67 -0
  41. package/lib/tests/perf/valid-annotation-format-large-file.test.d.ts +1 -0
  42. package/lib/tests/perf/valid-annotation-format-large-file.test.js +74 -0
  43. package/lib/tests/plugin-default-export-and-configs.test.js +1 -0
  44. package/lib/tests/plugin-setup.test.js +12 -1
  45. package/lib/tests/rules/prefer-implements-annotation.test.js +84 -70
  46. package/lib/tests/rules/require-branch-annotation.test.js +33 -1
  47. package/lib/tests/rules/valid-annotation-format-internal.test.d.ts +8 -0
  48. package/lib/tests/rules/valid-annotation-format-internal.test.js +47 -0
  49. package/lib/tests/utils/branch-annotation-catch-insert-position.test.d.ts +1 -0
  50. package/lib/tests/utils/branch-annotation-catch-insert-position.test.js +68 -0
  51. package/lib/tests/utils/branch-annotation-catch-position.test.d.ts +1 -0
  52. package/lib/tests/utils/branch-annotation-catch-position.test.js +115 -0
  53. package/lib/tests/utils/req-annotation-detection.test.d.ts +1 -0
  54. package/lib/tests/utils/req-annotation-detection.test.js +247 -0
  55. package/package.json +4 -4
  56. package/user-docs/api-reference.md +20 -12
  57. package/user-docs/examples.md +2 -1
  58. 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
- /* eslint-env node */
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
- * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
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 { Program: programListener(context) };
36
+ return {
37
+ Program: (0, valid_req_reference_helpers_1.createValidReqReferenceProgramVisitor)(context),
38
+ };
383
39
  },
384
40
  };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Rule to validate @story annotation references refer to existing story files
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
- * Extract the story path from the annotation line and delegate validation.
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
- * Handle existence status and report appropriate diagnostics for missing
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
- * Report any problems related to the existence or accessibility of the
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
- * Process and validate the story path for security, extension, and existence.
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
- * Existence check:
153
- * - Distinguish between missing files and filesystem errors.
154
- * - Filesystem and I/O errors are surfaced with a dedicated diagnostic.
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
- * Handle a single comment node by processing its lines.
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 "{{path}}" placeholder across file validation messages
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: iterate comments and validate @story annotations.
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 comments = sourceCode.getCommentsBefore(node) || [];
106
- /**
107
- * Mapper to extract the text value from a comment node.
108
- * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
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 comments.map(commentToValue).join(" ");
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 annotation above the branch.
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 annotation above the branch.
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(fixer) {
172
- return fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @req <REQ-ID>\n`);
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
- const indent = sourceCode.lines[node.loc.start.line - 1].match(/^(\s*)/)?.[1] || "";
199
- const insertPos = sourceCode.getIndexFromLoc({
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 (for example, docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md)");
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
  });