eslint-plugin-traceability 1.11.0 → 1.11.2

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 (84) hide show
  1. package/CHANGELOG.md +3 -4
  2. package/README.md +1 -1
  3. package/lib/src/index.d.ts +12 -1
  4. package/lib/src/index.js +43 -6
  5. package/lib/src/maintenance/commands.js +2 -3
  6. package/lib/src/maintenance/flags.js +111 -25
  7. package/lib/src/maintenance/update.js +1 -14
  8. package/lib/src/rules/helpers/require-story-core.d.ts +67 -0
  9. package/lib/src/rules/helpers/require-story-core.js +142 -23
  10. package/lib/src/rules/helpers/require-story-helpers.d.ts +9 -88
  11. package/lib/src/rules/helpers/require-story-helpers.js +118 -166
  12. package/lib/src/rules/helpers/require-story-io.js +51 -31
  13. package/lib/src/rules/helpers/valid-annotation-format-internal.d.ts +12 -13
  14. package/lib/src/rules/helpers/valid-annotation-format-internal.js +21 -16
  15. package/lib/src/rules/helpers/valid-annotation-format-validators.d.ts +29 -3
  16. package/lib/src/rules/helpers/valid-annotation-format-validators.js +29 -3
  17. package/lib/src/rules/helpers/valid-annotation-options.d.ts +3 -0
  18. package/lib/src/rules/helpers/valid-annotation-options.js +64 -21
  19. package/lib/src/rules/helpers/valid-annotation-utils.d.ts +3 -3
  20. package/lib/src/rules/helpers/valid-annotation-utils.js +10 -10
  21. package/lib/src/rules/helpers/valid-req-reference-helpers.d.ts +11 -0
  22. package/lib/src/rules/helpers/valid-req-reference-helpers.js +362 -0
  23. package/lib/src/rules/prefer-implements-annotation.js +7 -7
  24. package/lib/src/rules/require-story-annotation.d.ts +2 -0
  25. package/lib/src/rules/require-story-annotation.js +1 -1
  26. package/lib/src/rules/valid-req-reference.d.ts +4 -0
  27. package/lib/src/rules/valid-req-reference.js +5 -349
  28. package/lib/src/rules/valid-story-reference.d.ts +1 -1
  29. package/lib/src/rules/valid-story-reference.js +17 -10
  30. package/lib/src/utils/annotation-checker.js +31 -7
  31. package/lib/src/utils/branch-annotation-helpers.d.ts +2 -2
  32. package/lib/src/utils/branch-annotation-helpers.js +4 -4
  33. package/lib/src/utils/reqAnnotationDetection.js +36 -22
  34. package/lib/tests/cli-error-handling.test.js +2 -1
  35. package/lib/tests/config/eslint-config-validation.test.d.ts +8 -0
  36. package/lib/tests/config/eslint-config-validation.test.js +81 -0
  37. package/lib/tests/config/flat-config-presets-integration.test.js +1 -3
  38. package/lib/tests/config/require-story-annotation-config.test.d.ts +9 -0
  39. package/lib/tests/config/require-story-annotation-config.test.js +9 -0
  40. package/lib/tests/fixtures/stale/example.js +1 -1
  41. package/lib/tests/fixtures/update/example.js +1 -1
  42. package/lib/tests/integration/cli-integration.test.js +9 -1
  43. package/lib/tests/integration/dogfooding-validation.test.d.ts +1 -0
  44. package/lib/tests/integration/dogfooding-validation.test.js +94 -0
  45. package/lib/tests/maintenance/batch.test.js +1 -0
  46. package/lib/tests/maintenance/cli.test.js +38 -0
  47. package/lib/tests/maintenance/detect-isolated.test.js +6 -5
  48. package/lib/tests/maintenance/detect.test.js +1 -0
  49. package/lib/tests/maintenance/index.test.js +1 -0
  50. package/lib/tests/maintenance/report.test.js +1 -0
  51. package/lib/tests/maintenance/update-isolated.test.js +1 -0
  52. package/lib/tests/maintenance/update.test.js +1 -0
  53. package/lib/tests/perf/maintenance-cli-large-workspace.test.js +18 -0
  54. package/lib/tests/perf/require-branch-annotation-large-file.test.d.ts +1 -0
  55. package/lib/tests/perf/require-branch-annotation-large-file.test.js +67 -0
  56. package/lib/tests/plugin-default-export-and-configs.test.js +2 -0
  57. package/lib/tests/plugin-setup-error.test.d.ts +1 -0
  58. package/lib/tests/plugin-setup-error.test.js +1 -0
  59. package/lib/tests/plugin-setup.test.js +12 -1
  60. package/lib/tests/rules/auto-fix-behavior-008.test.js +16 -0
  61. package/lib/tests/rules/error-reporting.test.js +1 -0
  62. package/lib/tests/rules/prefer-implements-annotation.test.js +8 -0
  63. package/lib/tests/rules/require-branch-annotation.test.js +34 -0
  64. package/lib/tests/rules/require-story-core-edgecases.test.js +1 -0
  65. package/lib/tests/rules/require-story-core.autofix.test.js +1 -0
  66. package/lib/tests/rules/require-story-core.test.js +1 -0
  67. package/lib/tests/rules/require-story-helpers-edgecases.test.d.ts +1 -0
  68. package/lib/tests/rules/require-story-helpers-edgecases.test.js +1 -0
  69. package/lib/tests/rules/require-story-helpers.test.js +4 -3
  70. package/lib/tests/rules/require-story-io-behavior.test.d.ts +1 -0
  71. package/lib/tests/rules/require-story-io-behavior.test.js +1 -0
  72. package/lib/tests/rules/require-story-io.edgecases.test.d.ts +1 -0
  73. package/lib/tests/rules/require-story-io.edgecases.test.js +1 -0
  74. package/lib/tests/rules/require-story-visitors-edgecases.test.d.ts +1 -0
  75. package/lib/tests/rules/require-story-visitors-edgecases.test.js +1 -0
  76. package/lib/tests/rules/valid-annotation-format-internal.test.d.ts +8 -0
  77. package/lib/tests/rules/valid-annotation-format-internal.test.js +47 -0
  78. package/lib/tests/rules/valid-story-reference.test.js +2 -0
  79. package/lib/tests/utils/annotation-checker.test.js +2 -1
  80. package/lib/tests/utils/branch-annotation-helpers.test.js +2 -1
  81. package/package.json +2 -2
  82. package/user-docs/api-reference.md +115 -8
  83. package/user-docs/examples.md +1 -1
  84. package/user-docs/migration-guide.md +35 -2
@@ -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
  *
@@ -90,6 +90,34 @@ function createMissingReqFix(node) {
90
90
  return fixer.insertTextBefore(target, "/** @req <REQ-ID> */\n");
91
91
  };
92
92
  }
93
+ /**
94
+ * Resolve the display name used when reporting a missing @req annotation.
95
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
96
+ * @req REQ-ANNOTATION-REPORTING - Use consistent naming when reporting missing @req
97
+ * @req REQ-ERROR-SPECIFIC - Derive a specific, human-readable name for the node
98
+ */
99
+ function getReportedName(contextNode, parentNode) {
100
+ const rawName = (0, require_story_utils_1.getNodeName)(contextNode) ?? (0, require_story_utils_1.getNodeName)(parentNode);
101
+ return rawName ?? "(anonymous)";
102
+ }
103
+ /**
104
+ * Determine the AST sub-node that should be used as the location for reporting.
105
+ * Prefers Identifier nodes (id or key) over the broader function-like node.
106
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
107
+ * @req REQ-ANNOTATION-REPORTING - Report missing @req on the most relevant node
108
+ * @req REQ-ERROR-SPECIFIC - Target the identifier when available for precise errors
109
+ */
110
+ function getNameNodeForReqReport(node) {
111
+ const candidateId = node.id;
112
+ if (candidateId && candidateId.type === "Identifier") {
113
+ return candidateId;
114
+ }
115
+ const candidateKey = node.key;
116
+ if (candidateKey && candidateKey.type === "Identifier") {
117
+ return candidateKey;
118
+ }
119
+ return node;
120
+ }
93
121
  /**
94
122
  * Helper to report a missing @req annotation via the ESLint context API.
95
123
  * Uses getNodeName to provide a readable name for the node.
@@ -102,13 +130,9 @@ function createMissingReqFix(node) {
102
130
  * @req REQ-ERROR-CONTEXT - Include contextual hints to help understand the error
103
131
  */
104
132
  function reportMissing(context, node, enableFix = true) {
105
- const rawName = (0, require_story_utils_1.getNodeName)(node) ?? (node && (0, require_story_utils_1.getNodeName)(node.parent));
106
- const name = rawName ?? "(anonymous)";
107
- const nameNode = (node && node.id && node.id.type === "Identifier"
108
- ? node.id
109
- : node && node.key && node.key.type === "Identifier"
110
- ? node.key
111
- : node) ?? node;
133
+ const parentNode = node?.parent;
134
+ const name = getReportedName(node, parentNode);
135
+ const nameNode = getNameNodeForReqReport(node);
112
136
  const reportOptions = {
113
137
  node: nameNode,
114
138
  messageId: "missingReq",
@@ -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
  */
@@ -114,7 +114,7 @@ function gatherBranchCommentText(sourceCode, node) {
114
114
  return comments.map(commentToValue).join(" ");
115
115
  }
116
116
  /**
117
- * Report missing @story annotation on a branch node.
117
+ * Report missing @story annotation tag on a branch node when that branch lacks a corresponding @story reference in its comments.
118
118
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
119
119
  * @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
120
120
  */
@@ -127,7 +127,7 @@ function reportMissingStory(context, node, options) {
127
127
  */
128
128
  if (storyFixCountRef.count === 0) {
129
129
  /**
130
- * Fixer that inserts a default @story annotation above the branch.
130
+ * Fixer that inserts a default @story tag above the branch.
131
131
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
132
132
  * @req REQ-TRACEABILITY-FIX-ARROW - Trace fixer function used to insert missing @story
133
133
  */
@@ -151,7 +151,7 @@ function reportMissingStory(context, node, options) {
151
151
  }
152
152
  }
153
153
  /**
154
- * Report missing @req annotation on a branch node.
154
+ * Report missing @req annotation tag on a branch node when that branch has no linked requirement identifier in its associated comments.
155
155
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
156
156
  * @req REQ-ANNOTATION-PARSING - Parse @story and @req annotations from branch comments
157
157
  */
@@ -164,7 +164,7 @@ function reportMissingReq(context, node, options) {
164
164
  */
165
165
  if (!missingStory) {
166
166
  /**
167
- * Fixer that inserts a default @req annotation above the branch.
167
+ * Fixer that inserts a default @req tag above the branch.
168
168
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
169
169
  * @req REQ-TRACEABILITY-FIX-ARROW - Trace fixer function used to insert missing @req
170
170
  */
@@ -138,6 +138,38 @@ function fallbackTextBeforeHasReq(sourceCode, node) {
138
138
  }
139
139
  return false;
140
140
  }
141
+ /**
142
+ * Helper to combine advanced, location-based heuristics for requirement detection.
143
+ * Uses preceding lines, parent-chain comments, and fallback text windows to find @req/@supports.
144
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
145
+ * @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
146
+ * @req REQ-ANNOTATION-REQ-DETECTION - Use multiple heuristics to detect @req markers around the node
147
+ * @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Use multiple heuristics to detect @supports markers around the node
148
+ */
149
+ function hasReqInAdvancedHeuristics(sourceCode, node) {
150
+ if (!sourceCode || !node) {
151
+ return false;
152
+ }
153
+ return (linesBeforeHasReq(sourceCode, node) ||
154
+ parentChainHasReq(sourceCode, node) ||
155
+ fallbackTextBeforeHasReq(sourceCode, node));
156
+ }
157
+ /**
158
+ * Helper to check JSDoc and nearby comments for requirement annotations.
159
+ * Accepts both @req and @supports markers as evidence of requirement coverage.
160
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
161
+ * @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
162
+ * @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation in JSDoc/comments
163
+ * @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @supports as requirement coverage in JSDoc/comments
164
+ */
165
+ function hasReqInJsdocOrComments(jsdoc, comments) {
166
+ if (jsdoc &&
167
+ typeof jsdoc.value === "string" &&
168
+ (jsdoc.value.includes("@req") || jsdoc.value.includes("@supports"))) {
169
+ return true;
170
+ }
171
+ return comments.some(commentContainsReq);
172
+ }
141
173
  /**
142
174
  * Helper to determine whether a JSDoc or any nearby comments contain a requirement annotation.
143
175
  * Treats both @req and @supports annotations as evidence of requirement coverage.
@@ -151,30 +183,12 @@ function hasReqAnnotation(jsdoc, comments, context, node) {
151
183
  const sourceCode = context && typeof context.getSourceCode === "function"
152
184
  ? context.getSourceCode()
153
185
  : undefined;
154
- // Prefer robust, location-based heuristics when sourceCode and node are available.
155
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
156
- // @req REQ-ANNOTATION-REQ-DETECTION - Use multiple heuristics to detect @req markers around the node
157
- // @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Use multiple heuristics to detect @supports markers around the node
158
- if (sourceCode && node) {
159
- if (linesBeforeHasReq(sourceCode, node) ||
160
- parentChainHasReq(sourceCode, node) ||
161
- fallbackTextBeforeHasReq(sourceCode, node)) {
162
- return true;
163
- }
186
+ if (hasReqInAdvancedHeuristics(sourceCode, node)) {
187
+ return true;
164
188
  }
165
189
  }
166
190
  catch {
167
- // Swallow detection errors and fall through to simple checks.
168
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
169
- // @req REQ-ANNOTATION-REQ-DETECTION - Fail gracefully when advanced detection heuristics throw
191
+ // swallow and fall through to simple checks
170
192
  }
171
- // BRANCH requirement detection on JSDoc or comments, accepting both @req and @supports.
172
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
173
- // @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
174
- // @req REQ-ANNOTATION-REQ-DETECTION
175
- // @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS
176
- return ((jsdoc &&
177
- typeof jsdoc.value === "string" &&
178
- (jsdoc.value.includes("@req") || jsdoc.value.includes("@supports"))) ||
179
- comments.some(commentContainsReq));
193
+ return hasReqInJsdocOrComments(jsdoc, comments);
180
194
  }
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  * Tests for CLI error handling when plugin loading fails
8
8
  * @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
9
9
  * @req REQ-ERROR-HANDLING - Plugin CLI should exit with error on rule load failure
10
+ * @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-ERROR-HANDLING
10
11
  */
11
12
  const child_process_1 = require("child_process");
12
13
  const path_1 = __importDefault(require("path"));
@@ -39,6 +40,6 @@ describe("CLI Error Handling for Traceability Plugin (Story 001.0-DEV-PLUGIN-SET
39
40
  });
40
41
  // Expect non-zero exit and missing annotation message on stdout
41
42
  expect(result.status).not.toBe(0);
42
- 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");
43
44
  });
44
45
  });