eslint-plugin-traceability 1.21.1 → 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 (57) hide show
  1. package/CHANGELOG.md +7 -2
  2. package/README.md +3 -4
  3. package/lib/src/maintenance/batch.js +0 -2
  4. package/lib/src/maintenance/cli.js +8 -11
  5. package/lib/src/maintenance/commands.d.ts +2 -2
  6. package/lib/src/maintenance/commands.js +2 -3
  7. package/lib/src/maintenance/detect.js +7 -8
  8. package/lib/src/maintenance/report.js +2 -3
  9. package/lib/src/maintenance/storyParser.d.ts +16 -0
  10. package/lib/src/maintenance/storyParser.js +167 -0
  11. package/lib/src/maintenance/update.js +0 -1
  12. package/lib/src/rules/helpers/pattern-validators.d.ts +42 -0
  13. package/lib/src/rules/helpers/pattern-validators.js +65 -0
  14. package/lib/src/rules/helpers/prefer-implements-inline.d.ts +16 -0
  15. package/lib/src/rules/helpers/prefer-implements-inline.js +146 -0
  16. package/lib/src/rules/helpers/require-story-comment-detection.d.ts +47 -0
  17. package/lib/src/rules/helpers/require-story-comment-detection.js +141 -0
  18. package/lib/src/rules/helpers/require-story-core.d.ts +6 -6
  19. package/lib/src/rules/helpers/require-story-core.js +10 -11
  20. package/lib/src/rules/helpers/require-story-helpers.d.ts +5 -63
  21. package/lib/src/rules/helpers/require-story-helpers.js +29 -337
  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/valid-annotation-format-internal.js +11 -3
  27. package/lib/src/rules/helpers/valid-annotation-options.d.ts +0 -10
  28. package/lib/src/rules/helpers/valid-annotation-options.js +22 -92
  29. package/lib/src/rules/helpers/valid-req-reference-helpers.js +0 -1
  30. package/lib/src/rules/no-redundant-annotation.js +4 -238
  31. package/lib/src/rules/prefer-implements-annotation.d.ts +12 -0
  32. package/lib/src/rules/prefer-implements-annotation.js +9 -164
  33. package/lib/src/rules/require-traceability.d.ts +8 -0
  34. package/lib/src/rules/require-traceability.js +8 -0
  35. package/lib/src/utils/annotation-checker.d.ts +3 -2
  36. package/lib/src/utils/annotation-checker.js +3 -2
  37. package/lib/src/utils/branch-annotation-catch-helpers.d.ts +22 -0
  38. package/lib/src/utils/branch-annotation-catch-helpers.js +70 -0
  39. package/lib/src/utils/branch-annotation-helpers.js +11 -187
  40. package/lib/src/utils/branch-annotation-if-helpers.d.ts +1 -0
  41. package/lib/src/utils/branch-annotation-if-helpers.js +59 -0
  42. package/lib/src/utils/branch-annotation-indent-helpers.d.ts +1 -1
  43. package/lib/src/utils/branch-annotation-switch-helpers.d.ts +8 -2
  44. package/lib/src/utils/branch-annotation-switch-helpers.js +10 -4
  45. package/lib/src/utils/branch-validation.d.ts +9 -0
  46. package/lib/src/utils/branch-validation.js +58 -0
  47. package/lib/src/utils/comment-text-helpers.d.ts +31 -0
  48. package/lib/src/utils/comment-text-helpers.js +54 -0
  49. package/lib/src/utils/redundancy-detector.d.ts +85 -0
  50. package/lib/src/utils/redundancy-detector.js +235 -0
  51. package/lib/tests/maintenance/storyParser.test.d.ts +8 -0
  52. package/lib/tests/maintenance/storyParser.test.js +505 -0
  53. package/lib/tests/rules/no-redundant-annotation.test.js +1 -0
  54. package/lib/tests/rules/require-story-helpers.test.js +3 -2
  55. package/lib/tests/rules/valid-req-reference.test.js +2 -0
  56. package/package.json +18 -10
  57. package/user-docs/api-reference.md +2 -2
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAnonymousArrowFunction = isAnonymousArrowFunction;
4
+ exports.isNestedFunction = isNestedFunction;
5
+ exports.isEffectivelyAnonymousFunction = isEffectivelyAnonymousFunction;
6
+ exports.isExportedNode = isExportedNode;
7
+ exports.resolveTargetNode = resolveTargetNode;
8
+ exports.resolveAnnotationTargetNode = resolveAnnotationTargetNode;
9
+ /**
10
+ * Node classification utilities for require-story rule
11
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
12
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
13
+ * @req REQ-ANNOTATION-REQUIRED - File-level header for node utility functions
14
+ */
15
+ const require_story_name_extraction_1 = require("./require-story-name-extraction");
16
+ /**
17
+ * Determine whether a node represents an anonymous arrow function expression
18
+ * where the parent variable declarator has no explicit Identifier name.
19
+ *
20
+ * @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-ARROW-FUNCTION-EXCLUDED
21
+ */
22
+ function isAnonymousArrowFunction(node) {
23
+ return !!node && node.type === "ArrowFunctionExpression";
24
+ }
25
+ /**
26
+ * Determine whether a function-like node is nested within another function.
27
+ *
28
+ * @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-NESTED-FUNCTION-INHERITANCE
29
+ */
30
+ function isNestedFunction(node) {
31
+ let current = node?.parent;
32
+ while (current) {
33
+ if (current.type === "FunctionDeclaration" ||
34
+ current.type === "FunctionExpression" ||
35
+ current.type === "ArrowFunctionExpression" ||
36
+ current.type === "MethodDefinition" ||
37
+ current.type === "TSDeclareFunction" ||
38
+ current.type === "TSMethodSignature") {
39
+ return true;
40
+ }
41
+ current = current.parent;
42
+ }
43
+ return false;
44
+ }
45
+ /**
46
+ * Determine whether a function-like node is effectively anonymous for the
47
+ * purposes of nested-function inheritance. Named functions must always carry
48
+ * their own annotations, while anonymous nested functions may inherit.
49
+ *
50
+ * @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-NESTED-FUNCTION-INHERITANCE
51
+ */
52
+ function isEffectivelyAnonymousFunction(node) {
53
+ const name = (0, require_story_name_extraction_1.getContainerKeyOrIdName)(node) ?? (0, require_story_name_extraction_1.getDirectIdentifierName)(node);
54
+ if (typeof name === "string" && name.length > 0 && name !== "(anonymous)") {
55
+ return false;
56
+ }
57
+ return true;
58
+ }
59
+ /**
60
+ * Determine if a node is in an export declaration
61
+ *
62
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
63
+ * @req REQ-ANNOTATION-REQUIRED - Check node ancestry to find export declarations
64
+ */
65
+ function isExportedNode(node) {
66
+ let p = node.parent;
67
+ while (p) {
68
+ if (p.type === "ExportNamedDeclaration" ||
69
+ p.type === "ExportDefaultDeclaration") {
70
+ return true;
71
+ }
72
+ p = p.parent;
73
+ }
74
+ return false;
75
+ }
76
+ /**
77
+ * Determine AST node where annotation should be inserted
78
+ *
79
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
80
+ * @req REQ-ANNOTATION-REQUIRED - Determine correct insertion target for annotation
81
+ */
82
+ function resolveTargetNode(sourceCode, node) {
83
+ if (node.type === "TSMethodSignature") {
84
+ // Interface method signature -> insert on interface
85
+ return node.parent.parent;
86
+ }
87
+ if (node.type === "FunctionExpression" ||
88
+ node.type === "ArrowFunctionExpression") {
89
+ const parent = node.parent;
90
+ if (parent.type === "VariableDeclarator") {
91
+ const varDecl = parent.parent;
92
+ if (varDecl.parent && varDecl.parent.type === "ExportNamedDeclaration") {
93
+ return varDecl.parent;
94
+ }
95
+ return varDecl;
96
+ }
97
+ if (parent.type === "ExportNamedDeclaration") {
98
+ return parent;
99
+ }
100
+ if (parent.type === "ExpressionStatement") {
101
+ return parent;
102
+ }
103
+ }
104
+ return node;
105
+ }
106
+ /**
107
+ * Resolve the node that should receive the `@story` annotation,
108
+ * respecting an explicitly passed target when provided.
109
+ *
110
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
111
+ * @req REQ-ANNOTATION-REQUIRED - Centralize annotation target node resolution
112
+ */
113
+ function resolveAnnotationTargetNode(sourceCode, node, passedTarget) {
114
+ return passedTarget ?? resolveTargetNode(sourceCode, node);
115
+ }
@@ -29,12 +29,20 @@ function normalizeCommentLine(rawLine) {
29
29
  // This ensures annotations that appear outside code spans are still
30
30
  // detected at their original indices.
31
31
  const filtered = trimmed.replace(/`[^`]*`/g, (match) => " ".repeat(match.length));
32
- const annotationMatch = filtered.match(/@story\b|@req\b|@supports\b/);
32
+ // Remove leading star first to normalize JSDoc format
33
+ const withoutLeadingStar = filtered.replace(/^\*\s?/, "");
34
+ // Check if the line starts with a non-traceability JSDoc tag (e.g., @param, @returns)
35
+ // If so, return the whole line as-is to avoid false positives where annotation
36
+ // keywords appear in the tag's description (e.g., "`@returns` ... `@story` annotations")
37
+ if (/^@(?!story\b|req\b|supports\b)/.test(withoutLeadingStar)) {
38
+ return withoutLeadingStar;
39
+ }
40
+ // Otherwise, check for traceability annotations and slice to them if found
41
+ const annotationMatch = withoutLeadingStar.match(/@story\b|@req\b|@supports\b/);
33
42
  if (!annotationMatch || annotationMatch.index === undefined) {
34
- const withoutLeadingStar = filtered.replace(/^\*\s?/, "");
35
43
  return withoutLeadingStar;
36
44
  }
37
- return filtered.slice(annotationMatch.index);
45
+ return withoutLeadingStar.slice(annotationMatch.index);
38
46
  }
39
47
  /**
40
48
  * Detect whether a normalized comment line starts with a non-traceability JSDoc tag.
@@ -1,13 +1,3 @@
1
- /**
2
- * Shared option handling for the valid-annotation-format rule.
3
- *
4
- * @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
5
- * @req REQ-PATTERN-CONFIG - Support configuration of custom story path and requirement ID patterns
6
- * @req REQ-REGEX-VALIDATION - Validate that configured patterns are valid regular expressions
7
- * @req REQ-BACKWARD-COMPAT - Maintain current behavior when no custom patterns configured
8
- * @req REQ-EXAMPLE-MESSAGES - Support optional example strings in error messages
9
- * @req REQ-SCHEMA-VALIDATION - Use JSON Schema to validate configuration options
10
- */
11
1
  export interface AnnotationRuleOptions {
12
2
  story?: {
13
3
  /**
@@ -5,6 +5,17 @@ exports.getResolvedDefaults = getResolvedDefaults;
5
5
  exports.getOptionErrors = getOptionErrors;
6
6
  exports.resolveOptions = resolveOptions;
7
7
  exports.getRuleSchema = getRuleSchema;
8
+ /**
9
+ * Shared option handling for the valid-annotation-format rule.
10
+ *
11
+ * @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
12
+ * @req REQ-PATTERN-CONFIG - Support configuration of custom story path and requirement ID patterns
13
+ * @req REQ-REGEX-VALIDATION - Validate that configured patterns are valid regular expressions
14
+ * @req REQ-BACKWARD-COMPAT - Maintain current behavior when no custom patterns configured
15
+ * @req REQ-EXAMPLE-MESSAGES - Support optional example strings in error messages
16
+ * @req REQ-SCHEMA-VALIDATION - Use JSON Schema to validate configuration options
17
+ */
18
+ const pattern_validators_1 = require("./pattern-validators");
8
19
  /**
9
20
  * Get the default regular expression used to validate story paths.
10
21
  *
@@ -84,92 +95,6 @@ function getOptionErrors() {
84
95
  * @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
85
96
  * @req REQ-PATTERN-CONFIG - Provide consistent regex validation diagnostics
86
97
  */
87
- function buildInvalidRegexError(field, pattern) {
88
- return `Invalid regular expression for option "${field}": "${pattern}"`;
89
- }
90
- /**
91
- * Normalize raw rule options into a single AnnotationRuleOptions object.
92
- *
93
- * @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
94
- * @req REQ-PATTERN-CONFIG
95
- * @req REQ-BACKWARD-COMPAT
96
- */
97
- function normalizeUserOptions(rawOptions) {
98
- if (!rawOptions || rawOptions.length === 0) {
99
- return undefined;
100
- }
101
- const first = rawOptions[0];
102
- if (!first || typeof first !== "object") {
103
- return undefined;
104
- }
105
- return first;
106
- }
107
- /**
108
- * Resolve a user-configured regex pattern, handling both nested and flat
109
- * configuration shapes and accumulating validation errors.
110
- *
111
- * @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
112
- * @req REQ-PATTERN-CONFIG
113
- * @req REQ-REGEX-VALIDATION
114
- * @req REQ-BACKWARD-COMPAT
115
- */
116
- function resolvePattern({ nestedPattern, nestedFieldName, flatPattern, flatFieldName, defaultPattern, }) {
117
- const effective = typeof nestedPattern === "string"
118
- ? { value: nestedPattern, field: nestedFieldName }
119
- : typeof flatPattern === "string"
120
- ? { value: flatPattern, field: flatFieldName }
121
- : null;
122
- if (!effective) {
123
- return defaultPattern;
124
- }
125
- try {
126
- return new RegExp(effective.value);
127
- }
128
- catch {
129
- optionErrors.push(buildInvalidRegexError(effective.field, effective.value));
130
- return defaultPattern;
131
- }
132
- }
133
- /**
134
- * Resolve an example string, preferring nested over flat configuration,
135
- * and falling back to the provided default when necessary.
136
- *
137
- * @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
138
- * @req REQ-EXAMPLE-MESSAGES
139
- * @req REQ-BACKWARD-COMPAT
140
- */
141
- function resolveExample(nestedExample, flatExample, defaultExample) {
142
- if (typeof nestedExample === "string" && nestedExample.trim()) {
143
- return nestedExample;
144
- }
145
- if (typeof flatExample === "string" && flatExample.trim()) {
146
- return flatExample;
147
- }
148
- return defaultExample;
149
- }
150
- /**
151
- * Extract and normalize user-provided options from the raw ESLint
152
- * options array into an AnnotationRuleOptions object.
153
- *
154
- * @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
155
- * @req REQ-PATTERN-CONFIG - Accept structured configuration for patterns
156
- * @req REQ-BACKWARD-COMPAT - Tolerate missing or malformed options
157
- */
158
- function getUserOptions(rawOptions) {
159
- return normalizeUserOptions(rawOptions);
160
- }
161
- /**
162
- * Resolve the auto-fix flag, defaulting to true when the option
163
- * is not explicitly provided by the user.
164
- *
165
- * @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
166
- * @req REQ-PATTERN-CONFIG - Support configuration of fix behavior
167
- * @req REQ-BACKWARD-COMPAT - Preserve default auto-fix behavior
168
- */
169
- function resolveAutoFixFlag(user) {
170
- const autoFixFlag = user?.autoFix;
171
- return typeof autoFixFlag === "boolean" ? autoFixFlag : true;
172
- }
173
98
  /**
174
99
  * Resolve the story path pattern from nested or flat configuration
175
100
  * fields, validating and falling back to the default as needed.
@@ -180,12 +105,13 @@ function resolveAutoFixFlag(user) {
180
105
  * @req REQ-BACKWARD-COMPAT - Use a default when no pattern is provided
181
106
  */
182
107
  function resolveStoryPattern(nestedStoryPattern, flatStoryPattern) {
183
- return resolvePattern({
108
+ return (0, pattern_validators_1.resolvePattern)({
184
109
  nestedPattern: nestedStoryPattern,
185
110
  nestedFieldName: "story.pattern",
186
111
  flatPattern: flatStoryPattern,
187
112
  flatFieldName: "storyPathPattern",
188
113
  defaultPattern: getDefaultStoryPattern(),
114
+ errors: optionErrors,
189
115
  });
190
116
  }
191
117
  /**
@@ -198,12 +124,13 @@ function resolveStoryPattern(nestedStoryPattern, flatStoryPattern) {
198
124
  * @req REQ-BACKWARD-COMPAT - Use a default when no pattern is provided
199
125
  */
200
126
  function resolveReqPattern(nestedReqPattern, flatReqPattern) {
201
- return resolvePattern({
127
+ return (0, pattern_validators_1.resolvePattern)({
202
128
  nestedPattern: nestedReqPattern,
203
129
  nestedFieldName: "req.pattern",
204
130
  flatPattern: flatReqPattern,
205
131
  flatFieldName: "requirementIdPattern",
206
132
  defaultPattern: getDefaultReqPattern(),
133
+ errors: optionErrors,
207
134
  });
208
135
  }
209
136
  /**
@@ -215,7 +142,7 @@ function resolveReqPattern(nestedReqPattern, flatReqPattern) {
215
142
  * @req REQ-BACKWARD-COMPAT - Use a default story example when omitted
216
143
  */
217
144
  function resolveStoryExample(nestedStoryExample, flatStoryExample) {
218
- return resolveExample(nestedStoryExample, flatStoryExample, getDefaultStoryExample());
145
+ return (0, pattern_validators_1.resolveExample)(nestedStoryExample, flatStoryExample, getDefaultStoryExample());
219
146
  }
220
147
  /**
221
148
  * Resolve the requirement ID example string from nested or flat configuration
@@ -226,7 +153,7 @@ function resolveStoryExample(nestedStoryExample, flatStoryExample) {
226
153
  * @req REQ-BACKWARD-COMPAT - Use a default requirement ID example when omitted
227
154
  */
228
155
  function resolveReqExample(nestedReqExample, flatReqExample) {
229
- return resolveExample(nestedReqExample, flatReqExample, getDefaultReqExample());
156
+ return (0, pattern_validators_1.resolveExample)(nestedReqExample, flatReqExample, getDefaultReqExample());
230
157
  }
231
158
  /**
232
159
  * Collect user-provided story pattern inputs from both nested and flat
@@ -298,7 +225,8 @@ function resolveOptionsInternal(user) {
298
225
  const { nestedStoryExample, flatStoryExample } = getStoryExampleInputs(user);
299
226
  const { nestedReqPattern, flatReqPattern } = getReqPatternInputs(user);
300
227
  const { nestedReqExample, flatReqExample } = getReqExampleInputs(user);
301
- const autoFix = resolveAutoFixFlag(user);
228
+ const autoFixFlag = user?.autoFix;
229
+ const autoFix = typeof autoFixFlag === "boolean" ? autoFixFlag : true;
302
230
  const storyPattern = resolveStoryPattern(nestedStoryPattern, flatStoryPattern);
303
231
  const reqPattern = resolveReqPattern(nestedReqPattern, flatReqPattern);
304
232
  const storyExample = resolveStoryExample(nestedStoryExample, flatStoryExample);
@@ -322,7 +250,9 @@ function resolveOptionsInternal(user) {
322
250
  */
323
251
  function resolveOptions(rawOptions) {
324
252
  optionErrors = [];
325
- const user = getUserOptions(rawOptions);
253
+ const user = rawOptions && rawOptions.length > 0 && typeof rawOptions[0] === "object"
254
+ ? rawOptions[0]
255
+ : undefined;
326
256
  const resolved = resolveOptionsInternal(user);
327
257
  resolvedDefaults = resolved;
328
258
  return resolvedDefaults;
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createValidReqReferenceProgramVisitor = createValidReqReferenceProgramVisitor;
7
7
  /* eslint-disable traceability/valid-annotation-format */
8
- /* eslint-env node */
9
8
  /**
10
9
  * Helper utilities for the "valid-req-reference" rule.
11
10
  *
@@ -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
  *