eslint-plugin-traceability 1.21.0 → 1.22.0

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 (83) hide show
  1. package/CHANGELOG.md +7 -2
  2. package/README.md +6 -6
  3. package/lib/src/maintenance/batch.js +0 -1
  4. package/lib/src/maintenance/cli.js +8 -10
  5. package/lib/src/maintenance/commands.d.ts +2 -2
  6. package/lib/src/maintenance/commands.js +2 -2
  7. package/lib/src/maintenance/detect.js +7 -7
  8. package/lib/src/maintenance/report.js +2 -2
  9. package/lib/src/maintenance/storyParser.d.ts +16 -0
  10. package/lib/src/maintenance/storyParser.js +167 -0
  11. package/lib/src/rules/helpers/pattern-validators.d.ts +42 -0
  12. package/lib/src/rules/helpers/pattern-validators.js +65 -0
  13. package/lib/src/rules/helpers/prefer-implements-inline.d.ts +16 -0
  14. package/lib/src/rules/helpers/prefer-implements-inline.js +146 -0
  15. package/lib/src/rules/helpers/require-story-comment-detection.d.ts +47 -0
  16. package/lib/src/rules/helpers/require-story-comment-detection.js +141 -0
  17. package/lib/src/rules/helpers/require-story-core.d.ts +6 -6
  18. package/lib/src/rules/helpers/require-story-core.js +10 -10
  19. package/lib/src/rules/helpers/require-story-helpers.d.ts +5 -63
  20. package/lib/src/rules/helpers/require-story-helpers.js +29 -337
  21. package/lib/src/rules/helpers/require-story-io.js +1 -0
  22. package/lib/src/rules/helpers/require-story-name-extraction.d.ts +35 -0
  23. package/lib/src/rules/helpers/require-story-name-extraction.js +107 -0
  24. package/lib/src/rules/helpers/require-story-node-utils.d.ts +43 -0
  25. package/lib/src/rules/helpers/require-story-node-utils.js +115 -0
  26. package/lib/src/rules/helpers/require-test-traceability-helpers.js +1 -0
  27. package/lib/src/rules/helpers/valid-annotation-format-internal.d.ts +2 -2
  28. package/lib/src/rules/helpers/valid-annotation-format-internal.js +13 -5
  29. package/lib/src/rules/helpers/valid-annotation-format-validators.d.ts +14 -14
  30. package/lib/src/rules/helpers/valid-annotation-format-validators.js +31 -22
  31. package/lib/src/rules/helpers/valid-annotation-options.d.ts +0 -10
  32. package/lib/src/rules/helpers/valid-annotation-options.js +22 -92
  33. package/lib/src/rules/helpers/valid-annotation-utils.js +1 -0
  34. package/lib/src/rules/helpers/valid-req-reference-helpers.js +1 -1
  35. package/lib/src/rules/no-redundant-annotation.js +4 -238
  36. package/lib/src/rules/prefer-implements-annotation.d.ts +12 -0
  37. package/lib/src/rules/prefer-implements-annotation.js +9 -164
  38. package/lib/src/rules/require-traceability.d.ts +8 -0
  39. package/lib/src/rules/require-traceability.js +8 -0
  40. package/lib/src/rules/valid-annotation-format.js +14 -10
  41. package/lib/src/utils/annotation-checker.d.ts +3 -2
  42. package/lib/src/utils/annotation-checker.js +4 -2
  43. package/lib/src/utils/branch-annotation-catch-helpers.d.ts +22 -0
  44. package/lib/src/utils/branch-annotation-catch-helpers.js +70 -0
  45. package/lib/src/utils/branch-annotation-helpers.js +11 -187
  46. package/lib/src/utils/branch-annotation-if-helpers.d.ts +1 -0
  47. package/lib/src/utils/branch-annotation-if-helpers.js +59 -0
  48. package/lib/src/utils/branch-annotation-indent-helpers.d.ts +1 -1
  49. package/lib/src/utils/branch-annotation-switch-helpers.d.ts +8 -2
  50. package/lib/src/utils/branch-annotation-switch-helpers.js +10 -4
  51. package/lib/src/utils/branch-validation.d.ts +9 -0
  52. package/lib/src/utils/branch-validation.js +58 -0
  53. package/lib/src/utils/comment-text-helpers.d.ts +31 -0
  54. package/lib/src/utils/comment-text-helpers.js +54 -0
  55. package/lib/src/utils/redundancy-detector.d.ts +85 -0
  56. package/lib/src/utils/redundancy-detector.js +235 -0
  57. package/lib/src/utils/reqAnnotationDetection.js +1 -0
  58. package/lib/tests/config/eslint-config-validation.test.js +1 -0
  59. package/lib/tests/config/flat-config-presets-integration.test.js +1 -0
  60. package/lib/tests/config/require-story-annotation-config.test.js +1 -0
  61. package/lib/tests/fixtures/stale/example.js +1 -0
  62. package/lib/tests/fixtures/update/example.js +1 -0
  63. package/lib/tests/integration/annotation-placement-inside-prettier.integration.test.js +1 -0
  64. package/lib/tests/integration/catch-annotation-prettier.integration.test.js +1 -0
  65. package/lib/tests/integration/else-if-annotation-prettier.integration.test.js +1 -0
  66. package/lib/tests/integration/prettier-test-helpers.js +1 -0
  67. package/lib/tests/integration/require-traceability-test-callbacks.integration.test.js +1 -0
  68. package/lib/tests/maintenance/detect-isolated.test.js +1 -0
  69. package/lib/tests/maintenance/storyParser.test.d.ts +8 -0
  70. package/lib/tests/maintenance/storyParser.test.js +505 -0
  71. package/lib/tests/perf/maintenance-large-workspace.test.js +1 -0
  72. package/lib/tests/perf/valid-annotation-format-large-file.test.js +1 -0
  73. package/lib/tests/plugin-setup.test.js +1 -0
  74. package/lib/tests/rules/error-reporting.test.js +1 -0
  75. package/lib/tests/rules/no-redundant-annotation.test.js +1 -0
  76. package/lib/tests/rules/require-story-helpers.test.js +3 -2
  77. package/lib/tests/rules/require-test-traceability.test.js +1 -0
  78. package/lib/tests/rules/valid-req-reference.test.js +2 -0
  79. package/lib/tests/utils/branch-annotation-catch-insert-position.test.js +1 -0
  80. package/lib/tests/utils/branch-annotation-else-if-insert-position.test.js +1 -0
  81. package/lib/tests/utils/branch-annotation-helpers.test.js +1 -0
  82. package/package.json +18 -10
  83. package/user-docs/api-reference.md +2 -2
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const branch_annotation_helpers_1 = require("../utils/branch-annotation-helpers");
4
- const annotation_scope_analyzer_1 = require("../utils/annotation-scope-analyzer");
3
+ const redundancy_detector_1 = require("../utils/redundancy-detector");
5
4
  /**
6
5
  * ESLint rule to detect redundant traceability annotations on statements
7
6
  * that are already covered by their containing scope.
@@ -47,239 +46,6 @@ function normalizeOptions(raw) {
47
46
  alwaysCovered,
48
47
  };
49
48
  }
50
- /**
51
- * Collect comments around a scope node using JSDoc, leading comments,
52
- * and any comments that appear immediately before the node.
53
- *
54
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE
55
- */
56
- function getScopeCommentsFromJSDocAndLeading(sourceCode, scopeNode) {
57
- const comments = [];
58
- const jsdoc = sourceCode.getJSDocComment
59
- ? sourceCode.getJSDocComment(scopeNode)
60
- : null;
61
- const before = sourceCode.getCommentsBefore
62
- ? sourceCode.getCommentsBefore(scopeNode) || []
63
- : [];
64
- if (jsdoc) {
65
- comments.push(jsdoc);
66
- }
67
- if (Array.isArray(scopeNode.leadingComments)) {
68
- comments.push(...scopeNode.leadingComments);
69
- }
70
- comments.push(...before);
71
- return comments;
72
- }
73
- /**
74
- * Compute the story/requirement pairs for annotations that apply to the
75
- * given scope node.
76
- *
77
- * For branch scopes we reuse the same comment-gathering helper used by
78
- * the require-branch-annotation rule so that REQ-SCOPE-INHERITANCE
79
- * aligns with existing behavior. For non-branch scopes, we reuse a
80
- * shared helper that collects JSDoc, leading, and immediately-before
81
- * comments around the scope node.
82
- *
83
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE
84
- */
85
- function getScopePairs(context, scopeNode, parent) {
86
- const sourceCode = context.getSourceCode();
87
- // Branch-style scope: use the branch helpers to collect comment text.
88
- if (branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES.includes(scopeNode.type)) {
89
- /**
90
- * Inside-brace annotations used as branch-level indicators (inside placement
91
- * mode) should not be folded into scopePairs for redundancy purposes; only
92
- * before-brace annotations define the covering scope here.
93
- *
94
- * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-NON-REDUNDANT-INSIDE REQ-PLACEMENT-CONFIG
95
- */
96
- const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, scopeNode, parent, "before");
97
- return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromText)(text);
98
- }
99
- const comments = getScopeCommentsFromJSDocAndLeading(sourceCode, scopeNode);
100
- return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromComments)(comments);
101
- }
102
- /**
103
- * Collect the comments directly associated with a statement node.
104
- *
105
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-STATEMENT-SIGNIFICANCE REQ-SCOPE-ANALYSIS
106
- */
107
- function getStatementComments(context, node) {
108
- const sourceCode = context.getSourceCode();
109
- const comments = [];
110
- if (sourceCode.getCommentsBefore) {
111
- comments.push(...(sourceCode.getCommentsBefore(node) || []));
112
- }
113
- if (Array.isArray(node.leadingComments)) {
114
- comments.push(...node.leadingComments);
115
- }
116
- return comments;
117
- }
118
- /**
119
- * Debug helper for logging scope-level pairs in TRACEABILITY_DEBUG mode.
120
- *
121
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS
122
- */
123
- function debugScopePairs(scopeNode, scopePairs) {
124
- if (process.env.TRACEABILITY_DEBUG !== "1") {
125
- return;
126
- }
127
- console.log("[no-redundant-annotation] Scope node type=%s pairs=%o", scopeNode && scopeNode.type, Array.from(scopePairs));
128
- }
129
- /**
130
- * Walk up enclosing scopes starting from the given scope node and
131
- * accumulate all story/requirement pairs, limited by maxScopeDepth.
132
- *
133
- * This keeps REQ-SCOPE-INHERITANCE and REQ-CONFIGURABLE-STRICTNESS
134
- * aligned with the story's configuration model while delegating the
135
- * actual comment parsing to getScopePairs.
136
- *
137
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE REQ-CONFIGURABLE-STRICTNESS
138
- */
139
- function collectScopePairs(context, startingScopeNode, maxScopeDepth) {
140
- const result = new Set();
141
- if (!startingScopeNode || maxScopeDepth <= 0) {
142
- return result;
143
- }
144
- let current = startingScopeNode;
145
- let depth = 0;
146
- while (current && depth < maxScopeDepth) {
147
- const parent = current.parent;
148
- const pairs = getScopePairs(context, current, parent);
149
- for (const key of pairs) {
150
- result.add(key);
151
- }
152
- current = parent;
153
- depth += 1;
154
- }
155
- return result;
156
- }
157
- /**
158
- * Extract statement-level comments and story/requirement pairs that are
159
- * relevant for redundancy analysis within a given scope.
160
- *
161
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-STATEMENT-SIGNIFICANCE REQ-SCOPE-ANALYSIS
162
- */
163
- function getStatementPairsForRedundancy(context, stmt, scopePairs, options) {
164
- if (scopePairs.size === 0) {
165
- return null;
166
- }
167
- if (!(0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)(stmt, options, branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES)) {
168
- return null;
169
- }
170
- const stmtComments = getStatementComments(context, stmt);
171
- if (stmtComments.length === 0) {
172
- return null;
173
- }
174
- const stmtPairs = (0, annotation_scope_analyzer_1.extractStoryReqPairsFromComments)(stmtComments);
175
- if (process.env.TRACEABILITY_DEBUG === "1") {
176
- console.log("[no-redundant-annotation] Statement type=%s eligible=%s commentCount=%d pairs=%o", stmt && stmt.type, (0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)(stmt, options, branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES), stmtComments.length, Array.from(stmtPairs));
177
- }
178
- if (stmtPairs.size === 0) {
179
- return null;
180
- }
181
- return { comments: stmtComments, pairs: stmtPairs };
182
- }
183
- /**
184
- * Decide whether the provided statement-level pairs should be considered
185
- * redundant within the given scope, respecting configuration options.
186
- *
187
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-CONFIGURABLE-STRICTNESS
188
- */
189
- function isStatementRedundantWithinScope(stmtPairs, scopePairs, options) {
190
- if (options.allowEmphasisDuplication &&
191
- stmtPairs.size === 1 &&
192
- (0, annotation_scope_analyzer_1.arePairsFullyCovered)(stmtPairs, scopePairs)) {
193
- return false;
194
- }
195
- if (!(0, annotation_scope_analyzer_1.arePairsFullyCovered)(stmtPairs, scopePairs)) {
196
- return false;
197
- }
198
- return true;
199
- }
200
- /**
201
- * Filter a list of comments down to those that contain traceability
202
- * annotations relevant for redundancy detection.
203
- *
204
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SAFE-REMOVAL REQ-REDUNDANCY-PATTERNS
205
- */
206
- function getAnnotationCommentsFromStatement(comments) {
207
- return comments.filter((comment) => {
208
- const commentText = typeof comment.value === "string" ? comment.value : "";
209
- return /@story\b|@req\b|@supports\b/.test(commentText);
210
- });
211
- }
212
- /**
213
- * Determine whether a statement is redundant relative to the provided
214
- * scopePairs and options, using helper functions to gather statement
215
- * pairs, apply redundancy rules, and collect the associated annotation
216
- * comments. Returns null when the statement should not be treated as
217
- * redundant.
218
- *
219
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-STATEMENT-SIGNIFICANCE REQ-CONFIGURABLE-STRICTNESS
220
- */
221
- function getRedundantStatementContext(context, stmt, scopePairs, options) {
222
- const stmtInfo = getStatementPairsForRedundancy(context, stmt, scopePairs, options);
223
- if (!stmtInfo) {
224
- return null;
225
- }
226
- const { comments, pairs } = stmtInfo;
227
- if (!isStatementRedundantWithinScope(pairs, scopePairs, options)) {
228
- return null;
229
- }
230
- const annotationComments = getAnnotationCommentsFromStatement(comments);
231
- if (annotationComments.length === 0) {
232
- return null;
233
- }
234
- return { comments: annotationComments };
235
- }
236
- /**
237
- * Compute unique removal ranges for the given annotation comments.
238
- *
239
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SAFE-REMOVAL
240
- */
241
- function getRemovalRangesForAnnotationComments(comments, sourceCode) {
242
- const rangeMap = new Map();
243
- for (const comment of comments) {
244
- const [removalStart, removalEnd] = (0, annotation_scope_analyzer_1.getCommentRemovalRange)(comment, sourceCode);
245
- const key = `${removalStart}:${removalEnd}`;
246
- if (!rangeMap.has(key)) {
247
- rangeMap.set(key, [removalStart, removalEnd]);
248
- }
249
- }
250
- return Array.from(rangeMap.values()).sort((a, b) => b[0] - a[0]);
251
- }
252
- /**
253
- * Analyze a block's statements and report redundant traceability annotations.
254
- *
255
- * This helper encapsulates the iteration and reporting logic so that the
256
- * BlockStatement visitor remains small and focused on scope setup.
257
- *
258
- * @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-STATEMENT-SIGNIFICANCE
259
- */
260
- function reportRedundantAnnotationsInBlock(context, blockNode, scopePairs, options) {
261
- const statements = Array.isArray(blockNode.body) ? blockNode.body : [];
262
- if (statements.length === 0 || scopePairs.size === 0)
263
- return;
264
- const sourceCode = context.getSourceCode();
265
- for (const stmt of statements) {
266
- const info = getRedundantStatementContext(context, stmt, scopePairs, options);
267
- if (!info) {
268
- continue;
269
- }
270
- const ranges = getRemovalRangesForAnnotationComments(info.comments, sourceCode);
271
- if (ranges.length === 0) {
272
- continue;
273
- }
274
- context.report({
275
- node: stmt,
276
- messageId: "redundantAnnotation",
277
- fix(fixer) {
278
- return ranges.map(([start, end]) => fixer.removeRange([start, end]));
279
- },
280
- });
281
- }
282
- }
283
49
  const rule = {
284
50
  meta: {
285
51
  type: "suggestion",
@@ -337,11 +103,11 @@ const rule = {
337
103
  if (parent && parent.type === "CatchClause") {
338
104
  return;
339
105
  }
340
- const scopePairs = collectScopePairs(context, parent, options.maxScopeDepth);
341
- debugScopePairs(parent, scopePairs);
106
+ const scopePairs = (0, redundancy_detector_1.collectScopePairs)(context, parent, options.maxScopeDepth);
107
+ (0, redundancy_detector_1.debugScopePairs)(parent, scopePairs);
342
108
  if (scopePairs.size === 0)
343
109
  return;
344
- reportRedundantAnnotationsInBlock(context, node, scopePairs, options);
110
+ (0, redundancy_detector_1.reportRedundantAnnotationsInBlock)(context, node, scopePairs, options);
345
111
  },
346
112
  };
347
113
  },
@@ -23,6 +23,18 @@
23
23
  * @req REQ-AUTO-FIX - Provide safe, opt-in auto-fix for simple legacy patterns
24
24
  */
25
25
  import type { Rule } from "eslint";
26
+ /**
27
+ * ESLint rule: prefer-implements-annotation
28
+ *
29
+ * Recommend migrating from legacy `@story` + `@req` annotations to the
30
+ * newer `@supports` format. This rule is **disabled by default** and
31
+ * is intended as an optional, opt-in migration aid.
32
+ *
33
+ * @story docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
34
+ * @req REQ-OPTIONAL-WARNING - Emit configurable recommendation diagnostics for legacy @story/@req usage
35
+ * @req REQ-MULTI-STORY-DETECT - Detect multi-story patterns that cannot be auto-fixed
36
+ * @req REQ-BACKWARD-COMP-VALIDATION - Keep legacy @story/@req annotations valid when the rule is disabled
37
+ */
26
38
  /**
27
39
  * ESLint rule: prefer-implements-annotation
28
40
  *
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const valid_annotation_format_internal_1 = require("./helpers/valid-annotation-format-internal");
4
+ const prefer_implements_inline_1 = require("./helpers/prefer-implements-inline");
4
5
  // Maximum number of distinct @story paths allowed before treating as "multi-story".
5
6
  // @req REQ-MULTI-STORY-DETECT - Centralized threshold constant for detecting multi-story patterns
6
7
  const MULTI_STORY_THRESHOLD = 1;
@@ -223,173 +224,17 @@ function processBlockComment(comment, context) {
223
224
  });
224
225
  }
225
226
  /**
226
- * Extract the leading whitespace and `//` prefix from a line comment's full
227
- * source text so that new inline annotations can be inserted with matching
228
- * indentation and formatting.
229
- *
230
- * @story docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
231
- * @req REQ-MIGRATE-INLINE
232
- */
233
- function getLinePrefixFromText(fullText) {
234
- const match = fullText.match(/^(\s*\/\/\s*)/);
235
- return match ? match[1] : "";
236
- }
237
- /**
238
- * Attempt to construct an inline auto-fix that replaces a contiguous
239
- * sequence of `@story` and `@req` line comments with a single `@supports`
240
- * annotation while preserving the original comment prefix.
241
- *
242
- * @story docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
243
- * @req REQ-MIGRATE-INLINE
244
- */
245
- function tryBuildInlineAutoFix(context, comments, storyIndex, reqIndices) {
246
- const sourceCode = context.getSourceCode();
247
- const storyComment = comments[storyIndex];
248
- const storyNormalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(storyComment.value || "");
249
- if (!storyNormalized || !/^@story\b/.test(storyNormalized)) {
250
- return null;
251
- }
252
- const storyParts = storyNormalized.split(/\s+/);
253
- if (storyParts.length !== MIN_STORY_TOKENS) {
254
- return null;
255
- }
256
- const storyPath = storyParts[1];
257
- const reqIds = [];
258
- for (const idx of reqIndices) {
259
- const reqComment = comments[idx];
260
- const reqNormalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(reqComment.value || "");
261
- if (!reqNormalized || !/^@req\b/.test(reqNormalized)) {
262
- return null;
263
- }
264
- const reqParts = reqNormalized.split(/\s+/);
265
- if (reqParts.length !== MIN_REQ_TOKENS) {
266
- return null;
267
- }
268
- reqIds.push(reqParts[1]);
269
- }
270
- if (!reqIds.length) {
271
- return null;
272
- }
273
- const fullText = sourceCode.text.slice(storyComment.range[0], storyComment.range[1]);
274
- const linePrefix = getLinePrefixFromText(fullText);
275
- const implAnnotation = `@supports ${storyPath} ${reqIds.join(" ")}`;
276
- const implLine = `${linePrefix}${implAnnotation}`;
277
- const start = storyComment.range[0];
278
- const end = comments[reqIndices[reqIndices.length - 1]].range[1];
279
- return (fixer) => fixer.replaceTextRange([start, end], implLine);
280
- }
281
- /**
282
- * Coordinate detection and optional migration of a single inline `@story`
283
- * comment and its following `@req` comments, reporting diagnostics and
284
- * scheduling auto-fixes where safe.
227
+ * ESLint rule: prefer-implements-annotation
285
228
  *
286
- * @story docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
287
- * @req REQ-MIGRATE-INLINE
288
- */
289
- function collectReqIndicesAfterStory(group, startIndex) {
290
- const n = group.length;
291
- const reqIndices = [];
292
- let j = startIndex + 1;
293
- while (j < n) {
294
- const next = group[j];
295
- const nextNormalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(next.value || "");
296
- if (!nextNormalized || /^@supports\b/.test(nextNormalized)) {
297
- break;
298
- }
299
- if (/^@req\b/.test(nextNormalized)) {
300
- reqIndices.push(j);
301
- j += 1;
302
- continue;
303
- }
304
- break;
305
- }
306
- return { reqIndices, nextIndex: j };
307
- }
308
- function handleInlineStorySequence(context, group, startIndex) {
309
- const current = group[startIndex];
310
- const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(current.value || "");
311
- if (!normalized || !/^@story\b/.test(normalized)) {
312
- return startIndex + 1;
313
- }
314
- if (/^@supports\b/.test(normalized)) {
315
- return startIndex + 1;
316
- }
317
- const storyIndex = startIndex;
318
- const { reqIndices, nextIndex } = collectReqIndicesAfterStory(group, startIndex);
319
- if (reqIndices.length === 0) {
320
- context.report({
321
- node: current,
322
- messageId: "preferImplements",
323
- });
324
- return startIndex + 1;
325
- }
326
- const fix = tryBuildInlineAutoFix(context, group, storyIndex, reqIndices);
327
- if (fix) {
328
- context.report({
329
- node: current,
330
- messageId: "preferImplements",
331
- fix,
332
- });
333
- }
334
- else {
335
- context.report({
336
- node: current,
337
- messageId: "preferImplements",
338
- });
339
- }
340
- return nextIndex;
341
- }
342
- /**
343
- * Process a contiguous group of inline line comments, identifying legacy
344
- * `@story`/`@req` sequences and scheduling the corresponding diagnostics
345
- * and potential auto-fixes for migration to `@supports`.
229
+ * Recommend migrating from legacy `@story` + `@req` annotations to the
230
+ * newer `@supports` format. This rule is **disabled by default** and
231
+ * is intended as an optional, opt-in migration aid.
346
232
  *
347
233
  * @story docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
348
- * @req REQ-MIGRATE-INLINE
349
- */
350
- function advanceInlineGroupIndex(context, group, currentIndex) {
351
- const current = group[currentIndex];
352
- const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(current.value || "");
353
- if (!normalized || !/^@story\b/.test(normalized)) {
354
- return currentIndex + 1;
355
- }
356
- return handleInlineStorySequence(context, group, currentIndex);
357
- }
358
- function processInlineGroup(context, group) {
359
- if (group.length === 0)
360
- return;
361
- let i = 0;
362
- while (i < group.length) {
363
- i = advanceInlineGroupIndex(context, group, i);
364
- }
365
- }
366
- /**
367
- * Scan sequences of Line comments for inline legacy @story/@req patterns and
368
- * report diagnostics and optional auto-fixes.
234
+ * @req REQ-OPTIONAL-WARNING - Emit configurable recommendation diagnostics for legacy @story/@req usage
235
+ * @req REQ-MULTI-STORY-DETECT - Detect multi-story patterns that cannot be auto-fixed
236
+ * @req REQ-BACKWARD-COMP-VALIDATION - Keep legacy @story/@req annotations valid when the rule is disabled
369
237
  */
370
- function processInlineComments(context, lineComments) {
371
- if (!lineComments.length)
372
- return;
373
- // Group by contiguous line numbers
374
- let group = [lineComments[0]];
375
- const flushGroup = () => {
376
- processInlineGroup(context, group);
377
- group = [];
378
- };
379
- for (let idx = 1; idx < lineComments.length; idx++) {
380
- const prev = lineComments[idx - 1];
381
- const curr = lineComments[idx];
382
- if (curr.loc.start.line === prev.loc.start.line + 1 &&
383
- curr.loc.start.column === prev.loc.start.column) {
384
- group.push(curr);
385
- }
386
- else {
387
- flushGroup();
388
- group.push(curr);
389
- }
390
- }
391
- flushGroup();
392
- }
393
238
  /**
394
239
  * ESLint rule: prefer-implements-annotation
395
240
  *
@@ -472,7 +317,7 @@ const preferImplementsAnnotationRule = {
472
317
  processBlockComment(comment, context);
473
318
  });
474
319
  const lineComments = comments.filter((comment) => comment.type === "Line");
475
- processInlineComments(context, lineComments);
320
+ (0, prefer_implements_inline_1.processInlineComments)(context, lineComments);
476
321
  },
477
322
  };
478
323
  },
@@ -2,6 +2,14 @@
2
2
  * Composite ESLint rule that enforces both story and requirement traceability
3
3
  * annotations on functions and methods.
4
4
  *
5
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
6
+ * @req REQ-ANNOTATION-REQUIRED - Require both @story and @req annotations via composition
7
+ * @req REQ-FUNCTION-DETECTION - Detect functions via composed rules
8
+ * @req REQ-CONFIGURABLE-SCOPE - Support scope configuration through underlying rules
9
+ * @req REQ-EXPORT-PRIORITY - Support export priority through underlying rules
10
+ * @req REQ-ERROR-LOCATION - Report errors at function locations via composed rules
11
+ * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript syntax via composed rules
12
+ *
5
13
  * Implements Story 003.0-DEV-FUNCTION-ANNOTATIONS with:
6
14
  * - REQ-ANNOTATION-REQUIRED
7
15
  * - REQ-FUNCTION-DETECTION
@@ -3,6 +3,14 @@
3
3
  * Composite ESLint rule that enforces both story and requirement traceability
4
4
  * annotations on functions and methods.
5
5
  *
6
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
7
+ * @req REQ-ANNOTATION-REQUIRED - Require both @story and @req annotations via composition
8
+ * @req REQ-FUNCTION-DETECTION - Detect functions via composed rules
9
+ * @req REQ-CONFIGURABLE-SCOPE - Support scope configuration through underlying rules
10
+ * @req REQ-EXPORT-PRIORITY - Support export priority through underlying rules
11
+ * @req REQ-ERROR-LOCATION - Report errors at function locations via composed rules
12
+ * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript syntax via composed rules
13
+ *
6
14
  * Implements Story 003.0-DEV-FUNCTION-ANNOTATIONS with:
7
15
  * - REQ-ANNOTATION-REQUIRED
8
16
  * - REQ-FUNCTION-DETECTION
@@ -5,7 +5,9 @@ const valid_annotation_format_internal_1 = require("./helpers/valid-annotation-f
5
5
  const valid_annotation_format_validators_1 = require("./helpers/valid-annotation-format-validators");
6
6
  function handleImplementsLine(normalized, pending, deps) {
7
7
  const { context, comment, options } = deps;
8
- const isImplements = /@supports\b/.test(normalized);
8
+ // Only match `@supports` at the START of the normalized line to avoid
9
+ // false matches when this keyword appears in prose
10
+ const isImplements = /^@supports\b/.test(normalized);
9
11
  if (!isImplements) {
10
12
  return pending;
11
13
  }
@@ -15,8 +17,10 @@ function handleImplementsLine(normalized, pending, deps) {
15
17
  }
16
18
  function handleStoryOrReqLine(normalized, pending, deps) {
17
19
  const { context, comment, options } = deps;
18
- const isStory = /@story\b/.test(normalized);
19
- const isReq = /@req\b/.test(normalized);
20
+ // Only match `@story`/`@req` at the START of the normalized line to avoid
21
+ // false matches when these keywords appear in prose (e.g., "@returns ... `@story` annotations")
22
+ const isStory = /^@story\b/.test(normalized);
23
+ const isReq = /^@req\b/.test(normalized);
20
24
  if (!isStory && !isReq) {
21
25
  return pending;
22
26
  }
@@ -81,7 +85,7 @@ function processCommentLine({ normalized, pending, context, comment, options, })
81
85
  if (afterStoryOrReq !== pending) {
82
86
  return afterStoryOrReq;
83
87
  }
84
- // Implement JSDoc tag coexistence behavior: terminate @story/@req values when a new non-traceability JSDoc tag line (e.g., @param, @returns) is encountered.
88
+ // Implement JSDoc tag coexistence behavior: terminate `@story`/`@req` values when a new non-traceability JSDoc tag line (e.g., @param, @returns) is encountered.
85
89
  // @supports docs/stories/022.0-DEV-JSDOC-COEXISTENCE.story.md REQ-ANNOTATION-TERMINATION REQ-CONTINUATION-LOGIC
86
90
  if ((0, valid_annotation_format_internal_1.isNonTraceabilityJSDocTagLine)(normalized)) {
87
91
  (0, valid_annotation_format_validators_1.finalizePendingAnnotation)(context, comment, options, pending);
@@ -96,13 +100,13 @@ function processCommentLine({ normalized, pending, context, comment, options, })
96
100
  return extendPendingAnnotation(normalized, pending);
97
101
  }
98
102
  /**
99
- * Process a single comment node and validate any @story/@req/@supports annotations it contains.
103
+ * Process a single comment node and validate any `@story`/`@req`/`@supports` annotations it contains.
100
104
  *
101
- * Supports @story and @req annotations whose values span multiple lines within the same
105
+ * Supports `@story` and `@req` annotations whose values span multiple lines within the same
102
106
  * comment block, collapsing whitespace so that the logical value can be
103
107
  * validated against the configured patterns.
104
108
  *
105
- * @supports annotations are validated immediately per-line and are not
109
+ * `@supports` annotations are validated immediately per-line and are not
106
110
  * accumulated into pending multi-line state.
107
111
  *
108
112
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
@@ -172,8 +176,8 @@ exports.default = {
172
176
  },
173
177
  schema: (0, valid_annotation_options_1.getRuleSchema)(),
174
178
  /**
175
- * This rule's fixable support is limited to safe @story path suffix normalization per Story 008.0.
176
- * Fixes are limited strictly to adjusting the suffix portion of the @story path (e.g., adding
179
+ * This rule's fixable support is limited to safe `@story` path suffix normalization per Story 008.0.
180
+ * Fixes are limited strictly to adjusting the suffix portion of the `@story` path (e.g., adding
177
181
  * `.md` or `.story.md`), preserving all other comment text and whitespace exactly as written.
178
182
  *
179
183
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
@@ -204,7 +208,7 @@ exports.default = {
204
208
  const optionErrors = (0, valid_annotation_options_1.getOptionErrors)();
205
209
  return {
206
210
  /**
207
- * Program-level handler that inspects all comments for @story, @req, and @supports tags
211
+ * Program-level handler that inspects all comments for `@story`, `@req`, and `@supports` tags
208
212
  *
209
213
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
210
214
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * Helper to check @req annotation presence on TS declare functions and method signatures.
3
+ *
3
4
  * This helper is intentionally scope/exportPriority agnostic and focuses solely
4
5
  * on detection and reporting of @req annotations for the given node.
6
+ *
5
7
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
6
8
  * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
7
9
  * @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
8
10
  * @req REQ-ANNOTATION-REPORTING - Report missing @req annotation to context
9
11
  * @param context - ESLint rule context used to obtain source and report problems
10
12
  * @param node - Function-like AST node whose surrounding comments should be inspected
11
- * @param options - Optional configuration controlling behaviour (e.g., enableFix)
12
- * @returns void
13
+ * @param options - Optional configuration controlling behaviour (e.g., enableFix, annotationPlacement)
13
14
  */
14
15
  export declare function checkReqAnnotation(context: any, node: any, options?: {
15
16
  enableFix?: boolean;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.checkReqAnnotation = checkReqAnnotation;
4
+ /* eslint-disable traceability/valid-annotation-format */
4
5
  const require_story_utils_1 = require("../rules/helpers/require-story-utils");
5
6
  const reqAnnotationDetection_1 = require("./reqAnnotationDetection");
6
7
  const function_annotation_helpers_1 = require("./function-annotation-helpers");
@@ -168,16 +169,17 @@ function reportMissing(context, node, enableFix = true) {
168
169
  }
169
170
  /**
170
171
  * Helper to check @req annotation presence on TS declare functions and method signatures.
172
+ *
171
173
  * This helper is intentionally scope/exportPriority agnostic and focuses solely
172
174
  * on detection and reporting of @req annotations for the given node.
175
+ *
173
176
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
174
177
  * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
175
178
  * @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
176
179
  * @req REQ-ANNOTATION-REPORTING - Report missing @req annotation to context
177
180
  * @param context - ESLint rule context used to obtain source and report problems
178
181
  * @param node - Function-like AST node whose surrounding comments should be inspected
179
- * @param options - Optional configuration controlling behaviour (e.g., enableFix)
180
- * @returns void
182
+ * @param options - Optional configuration controlling behaviour (e.g., enableFix, annotationPlacement)
181
183
  */
182
184
  function checkReqAnnotation(context, node, options) {
183
185
  const { enableFix = true } = options ?? {};
@@ -0,0 +1,22 @@
1
+ import type { Rule } from "eslint";
2
+ /**
3
+ * Gather comment text from inside a CatchClause body.
4
+ *
5
+ * Uses dual-position detection strategy: first attempts getCommentsInside API,
6
+ * then falls back to line-based scanning if API is unavailable or returns empty.
7
+ *
8
+ * @story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md
9
+ * @req REQ-DUAL-POSITION-DETECTION REQ-FALLBACK-LOGIC
10
+ * @param sourceCode - ESLint source code object providing comment access APIs
11
+ * @param node - CatchClause AST node whose body will be scanned for comments
12
+ * @returns Concatenated comment text from inside the catch body, or empty string if none found
13
+ */
14
+ export declare function getInsideCatchCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any): string;
15
+ /**
16
+ * Gather comment text from the first contiguous comment lines inside a TryStatement block body.
17
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-INSIDE-BRACE-PLACEMENT
18
+ * @param sourceCode - ESLint source code object providing line access
19
+ * @param node - TryStatement AST node whose block will be scanned for comments
20
+ * @returns Concatenated comment text from inside the try block, or empty string if none found
21
+ */
22
+ export declare function getInsideTryBlockCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any): string;