eslint-plugin-traceability 1.5.1 → 1.6.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.
package/README.md CHANGED
@@ -55,6 +55,8 @@ module.exports = [
55
55
  - `traceability/valid-story-reference` Validates that `@story` references point to existing story files. ([Documentation](docs/rules/valid-story-reference.md))
56
56
  - `traceability/valid-req-reference` Validates that `@req` references point to existing requirement IDs. ([Documentation](docs/rules/valid-req-reference.md))
57
57
 
58
+ Configuration options: For detailed per-rule options (such as scopes, branch types, and story directory settings), see the individual rule docs in `docs/rules/` and the consolidated [API Reference](user-docs/api-reference.md).
59
+
58
60
  For development and contribution guidelines, see docs/eslint-plugin-development-guide.md.
59
61
 
60
62
  ## Quick Start
@@ -100,7 +100,9 @@ declare function shouldProcessNode(node: any, scope: string[], exportPriority?:
100
100
  * Report a missing @story annotation for a function-like node
101
101
  * Provides a suggestion to add the annotation.
102
102
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
103
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
103
104
  * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing annotations with suggestion
105
+ * @req REQ-AUTOFIX-MISSING - Provide autofix for missing annotations while preserving suggestions
104
106
  * @param {Rule.RuleContext} context - ESLint rule context used to report
105
107
  * @param {any} sourceCode - ESLint sourceCode object
106
108
  * @param {any} node - AST node that is missing the annotation
@@ -111,7 +113,9 @@ declare function reportMissing(context: Rule.RuleContext, sourceCode: any, node:
111
113
  * Report a missing @story annotation for a method-like node
112
114
  * Provides a suggestion to update the method/interface with the annotation.
113
115
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
116
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
114
117
  * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing method/interface annotations with suggestion
118
+ * @req REQ-AUTOFIX-MISSING - Provide autofix for missing method/interface annotations while preserving suggestions
115
119
  * @param {Rule.RuleContext} context - ESLint rule context to report
116
120
  * @param {any} sourceCode - ESLint sourceCode object
117
121
  * @param {any} node - AST node that is missing the annotation
@@ -238,7 +238,9 @@ function shouldProcessNode(node, scope, exportPriority = "all") {
238
238
  * Report a missing @story annotation for a function-like node
239
239
  * Provides a suggestion to add the annotation.
240
240
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
241
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
241
242
  * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing annotations with suggestion
243
+ * @req REQ-AUTOFIX-MISSING - Provide autofix for missing annotations while preserving suggestions
242
244
  * @param {Rule.RuleContext} context - ESLint rule context used to report
243
245
  * @param {any} sourceCode - ESLint sourceCode object
244
246
  * @param {any} node - AST node that is missing the annotation
@@ -259,6 +261,7 @@ function reportMissing(context, sourceCode, node, passedTarget) {
259
261
  node: nameNode,
260
262
  messageId: "missingStory",
261
263
  data: { name },
264
+ fix: (0, require_story_core_1.createAddStoryFix)(resolvedTarget),
262
265
  suggest: [
263
266
  {
264
267
  desc: `Add JSDoc @story annotation for function '${name}', e.g., ${ANNOTATION}`,
@@ -275,7 +278,9 @@ function reportMissing(context, sourceCode, node, passedTarget) {
275
278
  * Report a missing @story annotation for a method-like node
276
279
  * Provides a suggestion to update the method/interface with the annotation.
277
280
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
281
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
278
282
  * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing method/interface annotations with suggestion
283
+ * @req REQ-AUTOFIX-MISSING - Provide autofix for missing method/interface annotations while preserving suggestions
279
284
  * @param {Rule.RuleContext} context - ESLint rule context to report
280
285
  * @param {any} sourceCode - ESLint sourceCode object
281
286
  * @param {any} node - AST node that is missing the annotation
@@ -293,6 +298,7 @@ function reportMethod(context, sourceCode, node, passedTarget) {
293
298
  node: nameNode,
294
299
  messageId: "missingStory",
295
300
  data: { name },
301
+ fix: (0, require_story_core_1.createMethodFix)(resolvedTarget),
296
302
  suggest: [
297
303
  {
298
304
  desc: `Add JSDoc @story annotation for function '${name}', e.g., ${ANNOTATION}`,
@@ -1,9 +1,16 @@
1
- /****
2
- * Rule to enforce @story and @req annotations on significant code branches
1
+ /**
2
+ * Rule to enforce @story and @req annotations on significant code branches.
3
+ *
3
4
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
4
- * @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
5
- * @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
5
+ * @req REQ-BRANCH-DETECTION
6
+ * @req REQ-CONFIGURABLE-SCOPE
6
7
  */
7
8
  import type { Rule } from "eslint";
9
+ /**
10
+ * ESLint rule definition for require-branch-annotation.
11
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
12
+ * @req REQ-BRANCH-DETECTION - Enforce @story/@req presence on configured branch types
13
+ * @req REQ-CONFIGURABLE-SCOPE - Respect configurable branchTypes option
14
+ */
8
15
  declare const rule: Rule.RuleModule;
9
16
  export default rule;
@@ -1,6 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const branch_annotation_helpers_1 = require("../utils/branch-annotation-helpers");
4
+ /**
5
+ * ESLint rule definition for require-branch-annotation.
6
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
7
+ * @req REQ-BRANCH-DETECTION - Enforce @story/@req presence on configured branch types
8
+ * @req REQ-CONFIGURABLE-SCOPE - Respect configurable branchTypes option
9
+ */
4
10
  const rule = {
5
11
  meta: {
6
12
  type: "problem",
@@ -28,12 +34,20 @@ const rule = {
28
34
  },
29
35
  /**
30
36
  * Create visitor for require-branch-annotation rule.
37
+ *
31
38
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
32
- * @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
33
- * @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
39
+ * @req REQ-BRANCH-DETECTION
40
+ * @req REQ-CONFIGURABLE-SCOPE
34
41
  */
35
42
  create(context) {
36
43
  const branchTypesOrListener = (0, branch_annotation_helpers_1.validateBranchTypes)(context);
44
+ /**
45
+ * Branch configuration guard: if validation returns a listener, use it directly
46
+ * instead of branch-type iteration.
47
+ *
48
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
49
+ * @req REQ-CONFIGURABLE-SCOPE
50
+ */
37
51
  if (!Array.isArray(branchTypesOrListener)) {
38
52
  return branchTypesOrListener;
39
53
  }
@@ -44,8 +58,8 @@ const rule = {
44
58
  /**
45
59
  * Handler for a specific branch node type.
46
60
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
47
- * @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
48
- * @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
61
+ * @req REQ-BRANCH-DETECTION
62
+ * @req REQ-CONFIGURABLE-SCOPE
49
63
  */
50
64
  handlers[type] = function branchHandler(node) {
51
65
  if (type === "SwitchCase" && node.test == null) {
@@ -5,6 +5,7 @@
5
5
  * on functions and methods according to configured scope and export priority.
6
6
  *
7
7
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
8
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
8
9
  * @req REQ-ANNOTATION-REQUIRED
9
10
  */
10
11
  import type { Rule } from "eslint";
@@ -12,6 +13,7 @@ import type { Rule } from "eslint";
12
13
  * ESLint rule to require @story annotations on functions/methods.
13
14
  *
14
15
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
16
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
15
17
  * @req REQ-ANNOTATION-REQUIRED
16
18
  */
17
19
  declare const rule: Rule.RuleModule;
@@ -6,6 +6,7 @@ const require_story_helpers_1 = require("./helpers/require-story-helpers");
6
6
  * ESLint rule to require @story annotations on functions/methods.
7
7
  *
8
8
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
9
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
9
10
  * @req REQ-ANNOTATION-REQUIRED
10
11
  */
11
12
  const rule = {
@@ -16,6 +17,7 @@ const rule = {
16
17
  recommended: "error",
17
18
  },
18
19
  hasSuggestions: true,
20
+ fixable: "code",
19
21
  messages: {
20
22
  missingStory: "Missing @story annotation for function '{{name}}' (REQ-ANNOTATION-REQUIRED)",
21
23
  },
@@ -38,6 +40,7 @@ const rule = {
38
40
  * Create the rule visitor functions.
39
41
  *
40
42
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
43
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
41
44
  * @req REQ-CREATE-HOOK
42
45
  */
43
46
  create(context) {
@@ -1,6 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const STORY_EXAMPLE_PATH = "docs/stories/005.0-DEV-EXAMPLE.story.md";
4
+ /**
5
+ * Constant to represent the "tag not found" index when searching
6
+ * for @story or @req within a comment.
7
+ *
8
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
9
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
10
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
11
+ */
12
+ const TAG_NOT_FOUND_INDEX = -1;
4
13
  /**
5
14
  * Normalize a raw comment line to make annotation parsing more robust.
6
15
  *
@@ -8,7 +17,9 @@ const STORY_EXAMPLE_PATH = "docs/stories/005.0-DEV-EXAMPLE.story.md";
8
17
  * later in the line, and supports common JSDoc styles such as leading "*".
9
18
  *
10
19
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
20
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
11
21
  * @req REQ-FLEXIBLE-PARSING - Support reasonable variations in whitespace and formatting
22
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
12
23
  */
13
24
  function normalizeCommentLine(rawLine) {
14
25
  const trimmed = rawLine.trim();
@@ -31,7 +42,9 @@ function normalizeCommentLine(rawLine) {
31
42
  * multiple lines will be collapsed before validation.
32
43
  *
33
44
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
45
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
34
46
  * @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
47
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
35
48
  */
36
49
  function collapseAnnotationValue(value) {
37
50
  return value.replace(/\s+/g, "");
@@ -40,7 +53,9 @@ function collapseAnnotationValue(value) {
40
53
  * Build a detailed error message for invalid @story annotations.
41
54
  *
42
55
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
56
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
43
57
  * @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
58
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
44
59
  */
45
60
  function buildStoryErrorMessage(kind, value) {
46
61
  if (kind === "missing") {
@@ -52,7 +67,9 @@ function buildStoryErrorMessage(kind, value) {
52
67
  * Build a detailed error message for invalid @req annotations.
53
68
  *
54
69
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
70
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
55
71
  * @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
72
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
56
73
  */
57
74
  function buildReqErrorMessage(kind, value) {
58
75
  if (kind === "missing") {
@@ -60,12 +77,101 @@ function buildReqErrorMessage(kind, value) {
60
77
  }
61
78
  return `Invalid requirement ID "${value ?? ""}" for @req annotation. Expected an identifier like "REQ-EXAMPLE" (uppercase letters, numbers, and dashes only).`;
62
79
  }
80
+ /**
81
+ * Attempt a minimal, safe auto-fix for common @story path suffix issues.
82
+ *
83
+ * Only handles:
84
+ * - missing ".md"
85
+ * - missing ".story.md"
86
+ * and skips any paths with traversal segments (e.g. "..").
87
+ *
88
+ * Returns the fixed path when safe, or null if no fix should be applied.
89
+ *
90
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
91
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
92
+ */
93
+ function getFixedStoryPath(original) {
94
+ if (original.includes("..")) {
95
+ return null;
96
+ }
97
+ if (/\.story\.md$/.test(original)) {
98
+ return null;
99
+ }
100
+ if (/\.story$/.test(original)) {
101
+ return `${original}.md`;
102
+ }
103
+ if (/\.md$/.test(original)) {
104
+ return original.replace(/\.md$/, ".story.md");
105
+ }
106
+ return `${original}.story.md`;
107
+ }
108
+ /**
109
+ * Report an invalid @story annotation without applying a fix.
110
+ *
111
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
112
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
113
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
114
+ */
115
+ function reportInvalidStoryFormat(context, comment, collapsed) {
116
+ context.report({
117
+ node: comment,
118
+ messageId: "invalidStoryFormat",
119
+ data: { details: buildStoryErrorMessage("invalid", collapsed) },
120
+ });
121
+ }
122
+ /**
123
+ * Report an invalid @story annotation and attempt a minimal, safe auto-fix
124
+ * for common path suffix issues by locating and replacing the path text
125
+ * within the original comment.
126
+ *
127
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
128
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
129
+ * @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
130
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
131
+ */
132
+ function reportInvalidStoryFormatWithFix(context, comment, collapsed, fixed) {
133
+ const sourceCode = context.getSourceCode();
134
+ const commentText = sourceCode.getText(comment);
135
+ const search = "@story";
136
+ const tagIndex = commentText.indexOf(search);
137
+ if (tagIndex === TAG_NOT_FOUND_INDEX) {
138
+ reportInvalidStoryFormat(context, comment, collapsed);
139
+ return;
140
+ }
141
+ const afterTagIndex = tagIndex + search.length;
142
+ const rest = commentText.slice(afterTagIndex);
143
+ const valueMatch = rest.match(/[^\S\r\n]*([^\r\n*]+)/);
144
+ if (!valueMatch || valueMatch.index === undefined) {
145
+ reportInvalidStoryFormat(context, comment, collapsed);
146
+ return;
147
+ }
148
+ const valueStartInComment = afterTagIndex +
149
+ valueMatch.index +
150
+ (valueMatch[0].length - valueMatch[1].length);
151
+ const valueEndInComment = valueStartInComment + valueMatch[1].length;
152
+ const start = comment.range[0];
153
+ const fixRange = [
154
+ start + valueStartInComment,
155
+ start + valueEndInComment,
156
+ ];
157
+ context.report({
158
+ node: comment,
159
+ messageId: "invalidStoryFormat",
160
+ data: { details: buildStoryErrorMessage("invalid", collapsed) },
161
+ fix(fixer) {
162
+ return fixer.replaceTextRange(fixRange, fixed);
163
+ },
164
+ });
165
+ }
63
166
  /**
64
167
  * Validate a @story annotation value and report detailed errors when needed.
168
+ * Where safe and unambiguous, apply an automatic fix for missing suffixes.
65
169
  *
66
170
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
171
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
67
172
  * @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
68
173
  * @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
174
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
69
175
  */
70
176
  function validateStoryAnnotation(context, comment, rawValue) {
71
177
  const trimmed = rawValue.trim();
@@ -79,18 +185,25 @@ function validateStoryAnnotation(context, comment, rawValue) {
79
185
  }
80
186
  const collapsed = collapseAnnotationValue(trimmed);
81
187
  const pathPattern = /^docs\/stories\/[0-9]+\.[0-9]+-DEV-[\w-]+\.story\.md$/;
82
- if (!pathPattern.test(collapsed)) {
83
- context.report({
84
- node: comment,
85
- messageId: "invalidStoryFormat",
86
- data: { details: buildStoryErrorMessage("invalid", collapsed) },
87
- });
188
+ if (pathPattern.test(collapsed)) {
189
+ return;
190
+ }
191
+ if (/\s/.test(trimmed)) {
192
+ reportInvalidStoryFormat(context, comment, collapsed);
193
+ return;
194
+ }
195
+ const fixed = getFixedStoryPath(collapsed);
196
+ if (fixed && pathPattern.test(fixed)) {
197
+ reportInvalidStoryFormatWithFix(context, comment, collapsed, fixed);
198
+ return;
88
199
  }
200
+ reportInvalidStoryFormat(context, comment, collapsed);
89
201
  }
90
202
  /**
91
203
  * Validate a @req annotation value and report detailed errors when needed.
92
204
  *
93
205
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
206
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
94
207
  * @req REQ-REQ-FORMAT - Validate @req identifiers follow expected patterns
95
208
  * @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
96
209
  */
@@ -122,8 +235,10 @@ function validateReqAnnotation(context, comment, rawValue) {
122
235
  * validated against the configured patterns.
123
236
  *
124
237
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
238
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
125
239
  * @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
126
240
  * @req REQ-FLEXIBLE-PARSING - Support reasonable variations in whitespace and formatting
241
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
127
242
  */
128
243
  function processComment(context, comment) {
129
244
  const rawLines = (comment.value || "").split(/\r?\n/);
@@ -132,14 +247,18 @@ function processComment(context, comment) {
132
247
  * Finalize and validate the currently pending annotation, if any.
133
248
  *
134
249
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
250
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
135
251
  * @req REQ-SYNTAX-VALIDATION - Validate annotation syntax matches specification
252
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
136
253
  */
137
254
  function finalizePending() {
138
255
  if (!pending) {
139
256
  return;
140
257
  }
141
258
  // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
259
+ // @story docs/stories/008.0-DEV-AUTO-FIX.story.md
142
260
  // @req REQ-SYNTAX-VALIDATION - Dispatch validation based on annotation type
261
+ // @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
143
262
  if (pending.type === "story") {
144
263
  validateStoryAnnotation(context, comment, pending.value);
145
264
  }
@@ -156,7 +275,9 @@ function processComment(context, comment) {
156
275
  const isStory = /@story\b/.test(normalized);
157
276
  const isReq = /@req\b/.test(normalized);
158
277
  // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
278
+ // @story docs/stories/008.0-DEV-AUTO-FIX.story.md
159
279
  // @req REQ-SYNTAX-VALIDATION - Start new pending annotation when a tag is found
280
+ // @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
160
281
  if (isStory || isReq) {
161
282
  finalizePending();
162
283
  const value = normalized.replace(/^@story\b|^@req\b/, "").trim();
@@ -168,7 +289,9 @@ function processComment(context, comment) {
168
289
  return;
169
290
  }
170
291
  // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
292
+ // @story docs/stories/008.0-DEV-AUTO-FIX.story.md
171
293
  // @req REQ-MULTILINE-SUPPORT - Treat subsequent lines as continuation for pending annotation
294
+ // @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
172
295
  if (pending) {
173
296
  const continuation = normalized.trim();
174
297
  if (!continuation) {
@@ -194,11 +317,14 @@ exports.default = {
194
317
  invalidReqFormat: "{{details}}",
195
318
  },
196
319
  schema: [],
320
+ fixable: "code",
197
321
  },
198
322
  /**
199
323
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
324
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
200
325
  * @req REQ-SYNTAX-VALIDATION - Ensure rule create function validates annotations syntax
201
326
  * @req REQ-FORMAT-SPECIFICATION - Implement formatting checks per specification
327
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
202
328
  */
203
329
  create(context) {
204
330
  const sourceCode = context.getSourceCode();
@@ -207,8 +333,10 @@ exports.default = {
207
333
  * Program-level handler that inspects all comments for @story and @req tags
208
334
  *
209
335
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
336
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
210
337
  * @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
211
338
  * @req REQ-REQ-FORMAT - Validate @req identifiers follow expected patterns
339
+ * @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
212
340
  */
213
341
  Program() {
214
342
  const comments = sourceCode.getAllComments() || [];
@@ -7,21 +7,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  /**
8
8
  * Rule to validate @req annotation references refer to existing requirements in story files
9
9
  * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
10
- * @req REQ-DEEP-PARSE - Parse story files to extract requirement identifiers
11
- * @req REQ-DEEP-MATCH - Validate @req references against story file content
12
- * @req REQ-DEEP-CACHE - Cache parsed story content for performance
13
- * @req REQ-DEEP-PATH - Protect against path traversal in story paths
10
+ * @req REQ-DEEP-PARSE - Parse comments and extract story/requirement metadata
11
+ * @req REQ-DEEP-MATCH - Match @req annotations to story file requirements
12
+ * @req REQ-DEEP-CACHE - Cache requirement IDs per story file for efficient validation
13
+ * @req REQ-DEEP-PATH - Validate and resolve story file paths safely
14
14
  */
15
15
  const fs_1 = __importDefault(require("fs"));
16
16
  const path_1 = __importDefault(require("path"));
17
17
  /**
18
18
  * Extract the story path from a JSDoc comment.
19
- * Parses comment.value lines for @story annotation.
20
- * @param comment any JSDoc comment node
21
- * @returns story path or null if not found
22
- *
23
19
  * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
24
- * @req REQ-DEEP-PARSE - Extracts @story annotation from comment content
20
+ * @req REQ-DEEP-PARSE - Parse JSDoc comment lines to locate @story annotations
25
21
  */
26
22
  function extractStoryPath(comment) {
27
23
  const rawLines = comment.value.split(/\r?\n/);
@@ -37,14 +33,11 @@ function extractStoryPath(comment) {
37
33
  /**
38
34
  * Validate a @req annotation line against the extracted story content.
39
35
  * Performs path validation, file reading, caching, and requirement existence checks.
40
- *
41
- * @param opts options bag
42
- *
43
36
  * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
44
- * @req REQ-DEEP-PATH - Validates and protects against path traversal and absolute paths
45
- * @req REQ-DEEP-CACHE - Caches parsed story files to avoid repeated IO
46
- * @req REQ-DEEP-MATCH - Ensures referenced requirement IDs exist in the story file
47
- * @req REQ-DEEP-PARSE - Parses story file content to find REQ- identifiers
37
+ * @req REQ-DEEP-PATH - Validate and resolve referenced story file paths
38
+ * @req REQ-DEEP-CACHE - Cache requirement IDs discovered in story files
39
+ * @req REQ-DEEP-MATCH - Verify that a referenced requirement ID exists in the story
40
+ * @req REQ-DEEP-PARSE - Parse story file contents to extract requirement identifiers
48
41
  */
49
42
  function validateReqLine(opts) {
50
43
  const { comment, context, line, storyPath, cwd, reqCache } = opts;
@@ -96,15 +89,10 @@ function validateReqLine(opts) {
96
89
  }
97
90
  }
98
91
  /**
99
- * Handle a single annotation line.
100
- * @story Updates the current story path when encountering an @story annotation
101
- * @req Validates the requirement reference against the current story content
102
- *
103
- * @param opts handler options
104
- *
92
+ * Handle a single annotation line for story or requirement metadata.
105
93
  * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
106
- * @req REQ-DEEP-PARSE - Recognizes @story and @req annotation lines
107
- * @req REQ-DEEP-MATCH - Delegates @req validation to validateReqLine
94
+ * @req REQ-DEEP-PARSE - Parse annotation lines for @story and @req tags
95
+ * @req REQ-DEEP-MATCH - Dispatch @req lines for validation against story requirements
108
96
  */
109
97
  function handleAnnotationLine(opts) {
110
98
  const { line, comment, context, cwd, reqCache, storyPath } = opts;
@@ -119,14 +107,11 @@ function handleAnnotationLine(opts) {
119
107
  return storyPath;
120
108
  }
121
109
  /**
122
- * Handle JSDoc story and req annotations.
123
- *
124
- * @param opts options for comment handling
125
- *
110
+ * Handle JSDoc story and req annotations for a single comment block.
126
111
  * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
127
- * @req REQ-DEEP-PARSE - Parses comment blocks to extract annotation lines
128
- * @req REQ-DEEP-MATCH - Uses handleAnnotationLine to validate @req entries
129
- * @req REQ-DEEP-CACHE - Passes shared cache for parsed story files
112
+ * @req REQ-DEEP-PARSE - Iterate comment lines to process @story/@req annotations
113
+ * @req REQ-DEEP-MATCH - Coordinate annotation handling across a comment block
114
+ * @req REQ-DEEP-CACHE - Maintain and reuse discovered story path across comments
130
115
  */
131
116
  function handleComment(opts) {
132
117
  const { comment, context, cwd, reqCache, rawStoryPath } = opts;
@@ -147,30 +132,25 @@ function handleComment(opts) {
147
132
  }
148
133
  /**
149
134
  * Create a Program listener that iterates comments and validates annotations.
150
- *
151
- * @param context ESLint rule context
152
- * @returns Program visitor function
153
- *
154
135
  * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
155
- * @req REQ-DEEP-CACHE - Maintains a cache across comment processing
156
- * @req REQ-DEEP-PATH - Resolves and protects story paths against traversal
136
+ * @req REQ-DEEP-CACHE - Initialize and share a requirement cache for the program
137
+ * @req REQ-DEEP-PATH - Derive the working directory context for path resolution
157
138
  */
158
139
  function programListener(context) {
159
140
  const sourceCode = context.getSourceCode();
160
141
  const cwd = process.cwd();
161
142
  const reqCache = new Map();
162
143
  let rawStoryPath = null;
144
+ /**
145
+ * Program visitor that walks all comments to validate story/requirement references.
146
+ * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
147
+ * @req REQ-DEEP-PARSE - Collect all comments from the source code
148
+ * @req REQ-DEEP-MATCH - Drive comment-level handling for traceability checks
149
+ * @req REQ-DEEP-CACHE - Reuse story path and requirement cache across comments
150
+ * @req REQ-DEEP-PATH - Ensure validation respects project-relative paths
151
+ */
163
152
  return function Program() {
164
153
  const comments = sourceCode.getAllComments() || [];
165
- /**
166
- * Process each comment to handle story and requirement annotations.
167
- *
168
- * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
169
- * @req REQ-DEEP-PARSE - Parse annotations from comment blocks
170
- * @req REQ-DEEP-MATCH - Validate @req references found in comments
171
- * @req REQ-DEEP-CACHE - Use cache for parsed story files to avoid repeated IO
172
- * @req REQ-DEEP-PATH - Enforce path validation when resolving story files
173
- */
174
154
  comments.forEach((comment) => {
175
155
  rawStoryPath = handleComment({
176
156
  comment,
@@ -197,12 +177,11 @@ exports.default = {
197
177
  },
198
178
  /**
199
179
  * Rule create entrypoint that returns the Program visitor.
200
- *
201
180
  * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
202
- * @req REQ-DEEP-MATCH - Entrypoint orchestrates validation of @req annotations
203
- * @req REQ-DEEP-PARSE - Uses parsing helpers to extract annotations and story paths
204
- * @req REQ-DEEP-CACHE - Establishes cache used during validation
205
- * @req REQ-DEEP-PATH - Ensures path validation is applied during checks
181
+ * @req REQ-DEEP-MATCH - Register the Program visitor with ESLint
182
+ * @req REQ-DEEP-PARSE - Integrate comment parsing into the ESLint rule lifecycle
183
+ * @req REQ-DEEP-CACHE - Ensure cache and context are wired into the listener
184
+ * @req REQ-DEEP-PATH - Propagate path context into the program listener
206
185
  */
207
186
  create(context) {
208
187
  return { Program: programListener(context) };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ /**
7
+ * Tests for: docs/stories/008.0-DEV-AUTO-FIX.story.md
8
+ * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
9
+ * @req REQ-AUTOFIX-MISSING - Verify ESLint --fix automatically adds missing @story annotations to functions
10
+ * @req REQ-AUTOFIX-FORMAT - Verify ESLint --fix corrects simple annotation format issues for @story annotations
11
+ */
12
+ const eslint_1 = require("eslint");
13
+ const require_story_annotation_1 = __importDefault(require("../../src/rules/require-story-annotation"));
14
+ const valid_annotation_format_1 = __importDefault(require("../../src/rules/valid-annotation-format"));
15
+ const functionRuleTester = new eslint_1.RuleTester({
16
+ languageOptions: {
17
+ parserOptions: { ecmaVersion: 2020, sourceType: "module" },
18
+ },
19
+ });
20
+ const formatRuleTester = new eslint_1.RuleTester({
21
+ languageOptions: { parserOptions: { ecmaVersion: 2020 } },
22
+ });
23
+ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
24
+ describe("[REQ-AUTOFIX-MISSING] require-story-annotation auto-fix", () => {
25
+ functionRuleTester.run("require-story-annotation --fix", require_story_annotation_1.default, {
26
+ valid: [
27
+ {
28
+ name: "[REQ-AUTOFIX-MISSING] already annotated function is unchanged",
29
+ code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction alreadyAnnotated() {}`,
30
+ },
31
+ ],
32
+ invalid: [
33
+ {
34
+ name: "[REQ-AUTOFIX-MISSING] adds @story before function declaration when missing",
35
+ code: `function autoFixMe() {}`,
36
+ output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction autoFixMe() {}`,
37
+ errors: [
38
+ {
39
+ messageId: "missingStory",
40
+ suggestions: [
41
+ {
42
+ desc: "Add JSDoc @story annotation for function 'autoFixMe', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
43
+ output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction autoFixMe() {}`,
44
+ },
45
+ ],
46
+ },
47
+ ],
48
+ },
49
+ ],
50
+ });
51
+ });
52
+ describe("[REQ-AUTOFIX-FORMAT] valid-annotation-format auto-fix", () => {
53
+ formatRuleTester.run("valid-annotation-format --fix simple @story extension issues", valid_annotation_format_1.default, {
54
+ valid: [
55
+ {
56
+ name: "[REQ-AUTOFIX-FORMAT] already-correct story path is unchanged",
57
+ code: `// @story docs/stories/005.0-DEV-EXAMPLE.story.md`,
58
+ },
59
+ ],
60
+ invalid: [
61
+ {
62
+ name: "[REQ-AUTOFIX-FORMAT] adds .md extension for .story path",
63
+ code: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story`,
64
+ output: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md`,
65
+ errors: [
66
+ {
67
+ messageId: "invalidStoryFormat",
68
+ },
69
+ ],
70
+ },
71
+ {
72
+ name: "[REQ-AUTOFIX-FORMAT] adds .story.md extension when missing entirely",
73
+ code: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION`,
74
+ output: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md`,
75
+ errors: [
76
+ {
77
+ messageId: "invalidStoryFormat",
78
+ },
79
+ ],
80
+ },
81
+ ],
82
+ });
83
+ });
84
+ });
@@ -29,6 +29,7 @@ describe("Error Reporting Enhancements for require-story-annotation (Story 007.0
29
29
  {
30
30
  name: "[REQ-ERROR-SPECIFIC] missing @story annotation should report specific details and suggestion",
31
31
  code: `function bar() {}`,
32
+ output: "/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}",
32
33
  errors: [
33
34
  {
34
35
  messageId: "missingStory",
@@ -69,6 +69,7 @@ declare function tsDecl(): void;`,
69
69
  {
70
70
  name: "[REQ-ANNOTATION-REQUIRED] missing @story annotation on function",
71
71
  code: `function bar() {}`,
72
+ output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}`,
72
73
  errors: [
73
74
  {
74
75
  messageId: "missingStory",
@@ -84,6 +85,7 @@ declare function tsDecl(): void;`,
84
85
  {
85
86
  name: "[REQ-ANNOTATION-REQUIRED] missing @story on function expression",
86
87
  code: `const fnExpr = function() {};`,
88
+ output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
87
89
  errors: [
88
90
  {
89
91
  messageId: "missingStory",
@@ -99,6 +101,7 @@ declare function tsDecl(): void;`,
99
101
  {
100
102
  name: "[REQ-ANNOTATION-REQUIRED] missing @story on class method",
101
103
  code: `class C {\n method() {}\n}`,
104
+ output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
102
105
  errors: [
103
106
  {
104
107
  messageId: "missingStory",
@@ -114,6 +117,7 @@ declare function tsDecl(): void;`,
114
117
  {
115
118
  name: "[REQ-ANNOTATION-REQUIRED] missing @story on TS declare function",
116
119
  code: `declare function tsDecl(): void;`,
120
+ output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
117
121
  languageOptions: {
118
122
  parser: require("@typescript-eslint/parser"),
119
123
  parserOptions: { ecmaVersion: 2020, sourceType: "module" },
@@ -133,6 +137,7 @@ declare function tsDecl(): void;`,
133
137
  {
134
138
  name: "[REQ-ANNOTATION-REQUIRED] missing @story on TS method signature",
135
139
  code: `interface D {\n method(): void;\n}`,
140
+ output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
136
141
  languageOptions: {
137
142
  parser: require("@typescript-eslint/parser"),
138
143
  parserOptions: { ecmaVersion: 2020, sourceType: "module" },
@@ -173,6 +178,7 @@ declare function tsDecl(): void;`,
173
178
  {
174
179
  name: "[exportPriority] exported function missing @story annotation",
175
180
  code: `export function exportedMissing() {}`,
181
+ output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport function exportedMissing() {}`,
176
182
  options: [{ exportPriority: "exported" }],
177
183
  errors: [
178
184
  {
@@ -200,6 +206,7 @@ declare function tsDecl(): void;`,
200
206
  {
201
207
  name: "[scope] function declaration missing annotation when scope is FunctionDeclaration",
202
208
  code: `function onlyDecl() {}`,
209
+ output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction onlyDecl() {}`,
203
210
  options: [{ scope: ["FunctionDeclaration"] }],
204
211
  errors: [
205
212
  {
@@ -69,6 +69,7 @@ describe("Valid Annotation Format Rule (Story 005.0-DEV-ANNOTATION-VALIDATION)",
69
69
  {
70
70
  name: "[REQ-PATH-FORMAT] invalid story file extension",
71
71
  code: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story`,
72
+ output: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md`,
72
73
  errors: [
73
74
  {
74
75
  messageId: "invalidStoryFormat",
@@ -81,6 +82,7 @@ describe("Valid Annotation Format Rule (Story 005.0-DEV-ANNOTATION-VALIDATION)",
81
82
  {
82
83
  name: "[REQ-PATH-FORMAT] missing extension in story path",
83
84
  code: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION`,
85
+ output: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md`,
84
86
  errors: [
85
87
  {
86
88
  messageId: "invalidStoryFormat",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "A customizable ESLint plugin that enforces traceability annotations in your code, ensuring each implementation is linked to its requirement or test case.",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "lint": "eslint --config eslint.config.js \"src/**/*.{js,ts}\" \"tests/**/*.{js,ts}\" --max-warnings=0",
23
23
  "test": "jest --ci --bail",
24
24
  "ci-verify": "npm run type-check && npm run lint && npm run format:check && npm run duplication && npm run check:traceability && npm test && npm run audit:ci && npm run safety:deps",
25
- "ci-verify:full": "npm run check:traceability && npm run safety:deps && npm run audit:ci && npm run build && npm run type-check && npm run lint-plugin-check && npm run lint -- --max-warnings=0 && npm run duplication && npm run test -- --coverage && npm run format:check && npm audit --production --audit-level=high && npm run audit:dev-high",
25
+ "ci-verify:full": "npm run check:traceability && npm run safety:deps && npm run audit:ci && npm run build && npm run type-check && npm run lint-plugin-check && npm run lint -- --max-warnings=0 && npm run duplication && npm run test -- --coverage && npm run format:check && npm audit --omit=dev --audit-level=high && npm run audit:dev-high",
26
26
  "ci-verify:fast": "npm run type-check && npm run check:traceability && npm run duplication && jest --ci --bail --passWithNoTests --testPathPatterns 'tests/(unit|fast)'",
27
27
  "format": "prettier --write .",
28
28
  "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
@@ -30,8 +30,7 @@
30
30
  "audit:dev-high": "node scripts/generate-dev-deps-audit.js",
31
31
  "safety:deps": "node scripts/ci-safety-deps.js",
32
32
  "audit:ci": "node scripts/ci-audit.js",
33
- "smoke-test": "./scripts/smoke-test.sh",
34
- "prepare": "husky install"
33
+ "smoke-test": "./scripts/smoke-test.sh"
35
34
  },
36
35
  "lint-staged": {
37
36
  "src/**/*.{js,jsx,ts,tsx,json,md}": [