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
@@ -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
+ }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.determineIsTestFile = determineIsTestFile;
4
4
  exports.ensureFileSupportsAnnotation = ensureFileSupportsAnnotation;
5
5
  exports.handleCallExpression = handleCallExpression;
6
+ /* eslint-disable traceability/valid-annotation-format */
6
7
  /**
7
8
  * Helper utilities for the require-test-traceability rule.
8
9
  *
@@ -19,7 +19,7 @@ export interface PendingAnnotation {
19
19
  * boundaries), keeps any annotation tags that appear later in the line, and
20
20
  * supports common JSDoc styles such as leading "*".
21
21
  *
22
- * It detects @story, @req, and @supports tags while preserving the rest
22
+ * It detects `@story`, `@req`, and `@supports` tags while preserving the rest
23
23
  * of the line for downstream logic.
24
24
  */
25
25
  export declare function normalizeCommentLine(rawLine: string): string;
@@ -27,7 +27,7 @@ export declare function normalizeCommentLine(rawLine: string): string;
27
27
  * Detect whether a normalized comment line starts with a non-traceability JSDoc tag.
28
28
  *
29
29
  * This is used to distinguish regular JSDoc tags (e.g. @param, @returns) from
30
- * traceability-related annotations such as @story, @req, and @supports.
30
+ * traceability-related annotations such as `@story`, `@req`, and `@supports`.
31
31
  *
32
32
  * Supports coexistence with JSDoc by:
33
33
  * - Detecting boundaries between traceability tags and other tags
@@ -15,7 +15,7 @@ exports.isNonTraceabilityJSDocTagLine = isNonTraceabilityJSDocTagLine;
15
15
  * boundaries), keeps any annotation tags that appear later in the line, and
16
16
  * supports common JSDoc styles such as leading "*".
17
17
  *
18
- * It detects @story, @req, and @supports tags while preserving the rest
18
+ * It detects `@story`, `@req`, and `@supports` tags while preserving the rest
19
19
  * of the line for downstream logic.
20
20
  */
21
21
  function normalizeCommentLine(rawLine) {
@@ -29,18 +29,26 @@ 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.
41
49
  *
42
50
  * This is used to distinguish regular JSDoc tags (e.g. @param, @returns) from
43
- * traceability-related annotations such as @story, @req, and @supports.
51
+ * traceability-related annotations such as `@story`, `@req`, and `@supports`.
44
52
  *
45
53
  * Supports coexistence with JSDoc by:
46
54
  * - Detecting boundaries between traceability tags and other tags
@@ -7,9 +7,9 @@
7
7
  * to read while still preserving all existing behavior.
8
8
  *
9
9
  * The implementation in this module supports:
10
- * - validation of @story annotations
11
- * - validation of @req annotations
12
- * - validation of @implements/@supports-style annotations
10
+ * - validation of `@story` annotations
11
+ * - validation of `@req` annotations
12
+ * - validation of `@implements`/`@supports`-style annotations
13
13
  * - safe, minimal auto-fixes for certain invalid formats
14
14
  *
15
15
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
@@ -33,9 +33,9 @@
33
33
  import type { ResolvedAnnotationOptions } from "./valid-annotation-options";
34
34
  import type { PendingAnnotation } from "./valid-annotation-format-internal";
35
35
  /**
36
- * Report an invalid @story annotation without applying a fix.
36
+ * Report an invalid `@story` annotation without applying a fix.
37
37
  *
38
- * The invalid @story annotation is detected and reported but left unchanged.
38
+ * The invalid `@story` annotation is detected and reported but left unchanged.
39
39
  *
40
40
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
41
41
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
@@ -44,10 +44,10 @@ import type { PendingAnnotation } from "./valid-annotation-format-internal";
44
44
  */
45
45
  export declare function reportInvalidStoryFormat(context: any, comment: any, collapsed: string, options: ResolvedAnnotationOptions): void;
46
46
  /**
47
- * Compute the text replacement for an invalid @story annotation within a comment.
47
+ * Compute the text replacement for an invalid `@story` annotation within a comment.
48
48
  *
49
49
  * This helper:
50
- * - finds the @story tag in the raw comment text,
50
+ * - finds the `@story` tag in the raw comment text,
51
51
  * - computes the character range of its value,
52
52
  * - and returns an ESLint fix that replaces only that range.
53
53
  *
@@ -59,7 +59,7 @@ export declare function reportInvalidStoryFormat(context: any, comment: any, col
59
59
  */
60
60
  export declare function createStoryFix(context: any, comment: any, fixed: string): null | (() => any);
61
61
  /**
62
- * Report an invalid @story annotation and attempt a minimal, safe auto-fix
62
+ * Report an invalid `@story` annotation and attempt a minimal, safe auto-fix
63
63
  * for common path suffix issues by locating and replacing the path text
64
64
  * within the original comment.
65
65
  *
@@ -76,10 +76,10 @@ export declare function createStoryFix(context: any, comment: any, fixed: string
76
76
  */
77
77
  export declare function reportInvalidStoryFormatWithFix(context: any, comment: any, collapsed: string, fixed: string): void;
78
78
  /**
79
- * Validate a @story annotation value and report detailed errors when needed.
79
+ * Validate a `@story` annotation value and report detailed errors when needed.
80
80
  * Where safe and unambiguous, apply an automatic fix for missing suffixes.
81
81
  *
82
- * Processing of @story values includes:
82
+ * Processing of `@story` values includes:
83
83
  * - trimming whitespace,
84
84
  * - collapsing multi-line text,
85
85
  * - matching against the configured story regex,
@@ -97,7 +97,7 @@ export declare function reportInvalidStoryFormatWithFix(context: any, comment: a
97
97
  */
98
98
  export declare function validateStoryAnnotation(context: any, comment: any, rawValue: string, options: ResolvedAnnotationOptions): void;
99
99
  /**
100
- * Validate a @req annotation value and report detailed errors when needed.
100
+ * Validate a `@req` annotation value and report detailed errors when needed.
101
101
  *
102
102
  * This behavior covers:
103
103
  * - detecting missing identifiers,
@@ -115,14 +115,14 @@ export declare function validateStoryAnnotation(context: any, comment: any, rawV
115
115
  */
116
116
  export declare function validateReqAnnotation(context: any, comment: any, rawValue: string, options: ResolvedAnnotationOptions): void;
117
117
  /**
118
- * Validate an @supports annotation value and report detailed errors when needed.
118
+ * Validate an `@supports` annotation value and report detailed errors when needed.
119
119
  *
120
120
  * Expected format:
121
- * @supports <storyPath> <REQ-ID> [<REQ-ID> ...]
121
+ * `@supports <storyPath> <REQ-ID> [<REQ-ID> ...]`
122
122
  *
123
123
  * Validation rules:
124
124
  * - Value must include at least a story path and one requirement ID.
125
- * - Story path must match the same storyPattern used for @story (no auto-fix).
125
+ * - Story path must match the same storyPattern used for `@story` (no auto-fix).
126
126
  * - Each subsequent token must match reqPattern and is validated individually.
127
127
  *
128
128
  * Story path issues are reported with "invalidImplementsFormat" and
@@ -8,9 +8,9 @@
8
8
  * to read while still preserving all existing behavior.
9
9
  *
10
10
  * The implementation in this module supports:
11
- * - validation of @story annotations
12
- * - validation of @req annotations
13
- * - validation of @implements/@supports-style annotations
11
+ * - validation of `@story` annotations
12
+ * - validation of `@req` annotations
13
+ * - validation of `@implements`/`@supports`-style annotations
14
14
  * - safe, minimal auto-fixes for certain invalid formats
15
15
  *
16
16
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
@@ -43,9 +43,9 @@ const valid_annotation_utils_1 = require("./valid-annotation-utils");
43
43
  const valid_implements_utils_1 = require("./valid-implements-utils");
44
44
  const valid_annotation_options_1 = require("./valid-annotation-options");
45
45
  /**
46
- * Report an invalid @story annotation without applying a fix.
46
+ * Report an invalid `@story` annotation without applying a fix.
47
47
  *
48
- * The invalid @story annotation is detected and reported but left unchanged.
48
+ * The invalid `@story` annotation is detected and reported but left unchanged.
49
49
  *
50
50
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
51
51
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
@@ -60,10 +60,10 @@ function reportInvalidStoryFormat(context, comment, collapsed, options) {
60
60
  });
61
61
  }
62
62
  /**
63
- * Compute the text replacement for an invalid @story annotation within a comment.
63
+ * Compute the text replacement for an invalid `@story` annotation within a comment.
64
64
  *
65
65
  * This helper:
66
- * - finds the @story tag in the raw comment text,
66
+ * - finds the `@story` tag in the raw comment text,
67
67
  * - computes the character range of its value,
68
68
  * - and returns an ESLint fix that replaces only that range.
69
69
  *
@@ -103,7 +103,7 @@ function createStoryFix(context, comment, fixed) {
103
103
  return () => (fixer) => fixer.replaceTextRange(fixRange, fixed);
104
104
  }
105
105
  /**
106
- * Report an invalid @story annotation and attempt a minimal, safe auto-fix
106
+ * Report an invalid `@story` annotation and attempt a minimal, safe auto-fix
107
107
  * for common path suffix issues by locating and replacing the path text
108
108
  * within the original comment.
109
109
  *
@@ -136,10 +136,10 @@ function reportInvalidStoryFormatWithFix(context, comment, collapsed, fixed) {
136
136
  });
137
137
  }
138
138
  /**
139
- * Validate a @story annotation value and report detailed errors when needed.
139
+ * Validate a `@story` annotation value and report detailed errors when needed.
140
140
  * Where safe and unambiguous, apply an automatic fix for missing suffixes.
141
141
  *
142
- * Processing of @story values includes:
142
+ * Processing of `@story` values includes:
143
143
  * - trimming whitespace,
144
144
  * - collapsing multi-line text,
145
145
  * - matching against the configured story regex,
@@ -194,7 +194,7 @@ function validateStoryAnnotation(context, comment, rawValue, options) {
194
194
  reportInvalidStoryFormat(context, comment, collapsed, options);
195
195
  }
196
196
  /**
197
- * Validate a @req annotation value and report detailed errors when needed.
197
+ * Validate a `@req` annotation value and report detailed errors when needed.
198
198
  *
199
199
  * This behavior covers:
200
200
  * - detecting missing identifiers,
@@ -222,33 +222,42 @@ function validateReqAnnotation(context, comment, rawValue, options) {
222
222
  });
223
223
  return;
224
224
  }
225
- const collapsed = (0, valid_annotation_utils_1.collapseAnnotationValue)(trimmed);
226
- // Allow mixed @req/@supports lines to pass without additional @req validation,
227
- // while still validating simple multi-line @req identifiers that collapse
228
- // to a single token.
229
- if (collapsed.includes("@supports")) {
225
+ // Allow mixed `@req`/`@supports` lines to pass without additional `@req` validation.
226
+ if (trimmed.includes("@supports")) {
230
227
  return;
231
228
  }
232
- const reqPattern = options.reqPattern;
229
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
230
+ let reqId = tokens[0] || trimmed;
231
+ for (let index = 1; index < tokens.length; index += 1) {
232
+ const token = tokens[index];
233
+ if (token === "-" || token === "–" || token === "—")
234
+ break;
235
+ const candidate = `${reqId}${token}`;
236
+ if (reqId.endsWith("-") || options.reqPattern.test(candidate)) {
237
+ reqId = candidate;
238
+ continue;
239
+ }
240
+ break;
241
+ }
233
242
  // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
234
243
  // @req REQ-REQ-FORMAT - Flag @req identifiers that do not match the configured pattern
235
- if (!reqPattern.test(collapsed)) {
244
+ if (!options.reqPattern.test(reqId)) {
236
245
  context.report({
237
246
  node: comment,
238
247
  messageId: "invalidReqFormat",
239
- data: { details: (0, valid_annotation_utils_1.buildReqErrorMessage)("invalid", collapsed, options) },
248
+ data: { details: (0, valid_annotation_utils_1.buildReqErrorMessage)("invalid", reqId, options) },
240
249
  });
241
250
  }
242
251
  }
243
252
  /**
244
- * Validate an @supports annotation value and report detailed errors when needed.
253
+ * Validate an `@supports` annotation value and report detailed errors when needed.
245
254
  *
246
255
  * Expected format:
247
- * @supports <storyPath> <REQ-ID> [<REQ-ID> ...]
256
+ * `@supports <storyPath> <REQ-ID> [<REQ-ID> ...]`
248
257
  *
249
258
  * Validation rules:
250
259
  * - Value must include at least a story path and one requirement ID.
251
- * - Story path must match the same storyPattern used for @story (no auto-fix).
260
+ * - Story path must match the same storyPattern used for `@story` (no auto-fix).
252
261
  * - Each subsequent token must match reqPattern and is validated individually.
253
262
  *
254
263
  * Story path issues are reported with "invalidImplementsFormat" and
@@ -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,6 +5,7 @@ exports.collapseAnnotationValue = collapseAnnotationValue;
5
5
  exports.getFixedStoryPath = getFixedStoryPath;
6
6
  exports.buildStoryErrorMessage = buildStoryErrorMessage;
7
7
  exports.buildReqErrorMessage = buildReqErrorMessage;
8
+ /* eslint-disable traceability/valid-annotation-format */
8
9
  const valid_annotation_options_1 = require("./valid-annotation-options");
9
10
  /**
10
11
  * Shared constants and helpers for annotation-format validation.
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createValidReqReferenceProgramVisitor = createValidReqReferenceProgramVisitor;
7
- /* eslint-env node */
7
+ /* eslint-disable traceability/valid-annotation-format */
8
8
  /**
9
9
  * Helper utilities for the "valid-req-reference" rule.
10
10
  *