eslint-plugin-traceability 1.3.0 → 1.4.1

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 (30) hide show
  1. package/README.md +2 -1
  2. package/lib/src/index.d.ts +12 -17
  3. package/lib/src/index.js +69 -24
  4. package/lib/src/maintenance/utils.js +5 -0
  5. package/lib/src/rules/require-branch-annotation.js +27 -147
  6. package/lib/src/rules/require-req-annotation.d.ts +5 -0
  7. package/lib/src/rules/require-req-annotation.js +20 -0
  8. package/lib/src/rules/require-story-annotation.d.ts +3 -12
  9. package/lib/src/rules/require-story-annotation.js +192 -162
  10. package/lib/src/rules/valid-annotation-format.js +11 -0
  11. package/lib/src/rules/valid-req-reference.js +65 -25
  12. package/lib/src/rules/valid-story-reference.js +55 -58
  13. package/lib/src/utils/annotation-checker.js +80 -13
  14. package/lib/src/utils/branch-annotation-helpers.d.ts +54 -0
  15. package/lib/src/utils/branch-annotation-helpers.js +148 -0
  16. package/lib/src/utils/storyReferenceUtils.d.ts +47 -0
  17. package/lib/src/utils/storyReferenceUtils.js +111 -0
  18. package/lib/tests/cli-error-handling.test.d.ts +1 -0
  19. package/lib/tests/cli-error-handling.test.js +44 -0
  20. package/lib/tests/integration/cli-integration.test.js +3 -5
  21. package/lib/tests/maintenance/index.test.d.ts +1 -0
  22. package/lib/tests/maintenance/index.test.js +25 -0
  23. package/lib/tests/plugin-setup-error.test.d.ts +5 -0
  24. package/lib/tests/plugin-setup-error.test.js +37 -0
  25. package/lib/tests/rules/error-reporting.test.d.ts +1 -0
  26. package/lib/tests/rules/error-reporting.test.js +47 -0
  27. package/lib/tests/rules/require-story-annotation.test.js +101 -25
  28. package/lib/tests/utils/branch-annotation-helpers.test.d.ts +1 -0
  29. package/lib/tests/utils/branch-annotation-helpers.test.js +46 -0
  30. package/package.json +4 -3
package/README.md CHANGED
@@ -8,7 +8,7 @@ Created autonomously by [voder.ai](https://voder.ai).
8
8
 
9
9
  ## Installation
10
10
 
11
- Prerequisites: Node.js v12+ and ESLint v9+.
11
+ Prerequisites: Node.js >=14 and ESLint v9+.
12
12
 
13
13
  1. Using npm
14
14
  npm install --save-dev eslint-plugin-traceability
@@ -154,6 +154,7 @@ These tests verify end-to-end behavior of the plugin via the ESLint CLI.
154
154
  - Plugin Development Guide: docs/eslint-plugin-development-guide.md
155
155
  - API Reference: user-docs/api-reference.md
156
156
  - Examples: user-docs/examples.md
157
+ - Migration Guide: user-docs/migration-guide.md
157
158
  - Full README: https://github.com/voder-ai/eslint-plugin-traceability#readme
158
159
  - Rule: require-story-annotation: docs/rules/require-story-annotation.md
159
160
  - Rule: require-req-annotation: docs/rules/require-req-annotation.md
@@ -2,16 +2,17 @@
2
2
  * ESLint Traceability Plugin
3
3
  * @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
4
4
  * @req REQ-PLUGIN-STRUCTURE - Provide foundational plugin export and registration
5
+ * @req REQ-ERROR-HANDLING - Gracefully handles plugin loading errors and missing dependencies
5
6
  */
6
- export declare const rules: {
7
- "require-story-annotation": any;
8
- "require-req-annotation": any;
9
- "require-branch-annotation": import("eslint").Rule.RuleModule;
10
- "valid-annotation-format": any;
11
- "valid-story-reference": import("eslint").Rule.RuleModule;
12
- "valid-req-reference": import("eslint").Rule.RuleModule;
13
- };
14
- export declare const configs: {
7
+ import type { Rule } from "eslint";
8
+ /**
9
+ * @story docs/stories/001.2-RULE-NAMES-DECLARATION.story.md
10
+ * @req REQ-RULE-LIST - Enumerate supported rule file names for plugin discovery
11
+ */
12
+ declare const RULE_NAMES: readonly ["require-story-annotation", "require-req-annotation", "require-branch-annotation", "valid-annotation-format", "valid-story-reference", "valid-req-reference"];
13
+ type RuleName = (typeof RULE_NAMES)[number];
14
+ declare const rules: Record<RuleName, Rule.RuleModule>;
15
+ declare const configs: {
15
16
  recommended: {
16
17
  plugins: {
17
18
  traceability: {};
@@ -39,15 +40,9 @@ export declare const configs: {
39
40
  };
40
41
  }[];
41
42
  };
43
+ export { rules, configs };
42
44
  declare const _default: {
43
- rules: {
44
- "require-story-annotation": any;
45
- "require-req-annotation": any;
46
- "require-branch-annotation": import("eslint").Rule.RuleModule;
47
- "valid-annotation-format": any;
48
- "valid-story-reference": import("eslint").Rule.RuleModule;
49
- "valid-req-reference": import("eslint").Rule.RuleModule;
50
- };
45
+ rules: Record<"require-story-annotation" | "require-req-annotation" | "require-branch-annotation" | "valid-annotation-format" | "valid-story-reference" | "valid-req-reference", Rule.RuleModule>;
51
46
  configs: {
52
47
  recommended: {
53
48
  plugins: {
package/lib/src/index.js CHANGED
@@ -1,29 +1,73 @@
1
1
  "use strict";
2
- /**
3
- * ESLint Traceability Plugin
4
- * @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
5
- * @req REQ-PLUGIN-STRUCTURE - Provide foundational plugin export and registration
6
- */
7
- var __importDefault = (this && this.__importDefault) || function (mod) {
8
- return (mod && mod.__esModule) ? mod : { "default": mod };
9
- };
10
2
  Object.defineProperty(exports, "__esModule", { value: true });
11
3
  exports.configs = exports.rules = void 0;
12
- const require_story_annotation_1 = __importDefault(require("./rules/require-story-annotation"));
13
- const require_req_annotation_1 = __importDefault(require("./rules/require-req-annotation"));
14
- const require_branch_annotation_1 = __importDefault(require("./rules/require-branch-annotation"));
15
- const valid_annotation_format_1 = __importDefault(require("./rules/valid-annotation-format"));
16
- const valid_story_reference_1 = __importDefault(require("./rules/valid-story-reference"));
17
- const valid_req_reference_1 = __importDefault(require("./rules/valid-req-reference"));
18
- exports.rules = {
19
- "require-story-annotation": require_story_annotation_1.default,
20
- "require-req-annotation": require_req_annotation_1.default,
21
- "require-branch-annotation": require_branch_annotation_1.default,
22
- "valid-annotation-format": valid_annotation_format_1.default,
23
- "valid-story-reference": valid_story_reference_1.default,
24
- "valid-req-reference": valid_req_reference_1.default,
25
- };
26
- exports.configs = {
4
+ /**
5
+ * @story docs/stories/001.2-RULE-NAMES-DECLARATION.story.md
6
+ * @req REQ-RULE-LIST - Enumerate supported rule file names for plugin discovery
7
+ */
8
+ const RULE_NAMES = [
9
+ "require-story-annotation",
10
+ "require-req-annotation",
11
+ "require-branch-annotation",
12
+ "valid-annotation-format",
13
+ "valid-story-reference",
14
+ "valid-req-reference",
15
+ ];
16
+ const rules = {};
17
+ exports.rules = rules;
18
+ RULE_NAMES.forEach(
19
+ /**
20
+ * @story docs/stories/002.0-DYNAMIC-RULE-LOADING.story.md
21
+ * @req REQ-DYNAMIC-LOADING - Support dynamic rule loading by name at runtime
22
+ * @param {RuleName} name - Rule file base name used to discover and load rule module
23
+ */
24
+ (name) => {
25
+ /**
26
+ * @story docs/stories/002.0-DYNAMIC-RULE-LOADING.story.md
27
+ * @req REQ-DYNAMIC-LOADING - Support dynamic rule loading by name at runtime
28
+ */
29
+ try {
30
+ /**
31
+ * @story docs/stories/002.0-DYNAMIC-RULE-LOADING.story.md
32
+ * @req REQ-DYNAMIC-LOADING - Support dynamic rule loading by name at runtime
33
+ */
34
+ // Dynamically require rule module
35
+ const mod = require(`./rules/${name}`);
36
+ // Support ESModule default export
37
+ rules[name] = mod.default ?? mod;
38
+ }
39
+ catch (error) {
40
+ /**
41
+ * @story docs/stories/003.0-RULE-LOAD-ERROR-HANDLING.story.md
42
+ * @req REQ-ERROR-HANDLING - Provide fallback rule module and surface errors when rule loading fails
43
+ */
44
+ /**
45
+ * @story docs/stories/003.0-RULE-LOAD-ERROR-HANDLING.story.md
46
+ * @req REQ-ERROR-HANDLING - Provide fallback rule module and surface errors when rule loading fails
47
+ */
48
+ console.error(`[eslint-plugin-traceability] Failed to load rule "${name}": ${error.message}`);
49
+ rules[name] = {
50
+ meta: {
51
+ type: "problem",
52
+ docs: {
53
+ description: `Failed to load rule '${name}'`,
54
+ },
55
+ schema: [],
56
+ },
57
+ create(context) {
58
+ return {
59
+ Program(node) {
60
+ context.report({
61
+ node,
62
+ message: `eslint-plugin-traceability: Error loading rule "${name}": ${error.message}`,
63
+ });
64
+ },
65
+ };
66
+ },
67
+ };
68
+ }
69
+ });
70
+ const configs = {
27
71
  recommended: [
28
72
  {
29
73
  plugins: {
@@ -55,4 +99,5 @@ exports.configs = {
55
99
  },
56
100
  ],
57
101
  };
58
- exports.default = { rules: exports.rules, configs: exports.configs };
102
+ exports.configs = configs;
103
+ exports.default = { rules, configs };
@@ -46,6 +46,11 @@ function getAllFiles(dir) {
46
46
  if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
47
47
  return fileList;
48
48
  }
49
+ /**
50
+ * Recursively traverse a directory and collect file paths.
51
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
52
+ * @req REQ-MAINT-UTILS-TRAVERSE - Helper traversal function used by getAllFiles
53
+ */
49
54
  function traverse(currentDir) {
50
55
  const entries = fs.readdirSync(currentDir);
51
56
  for (const entry of entries) {
@@ -1,41 +1,6 @@
1
1
  "use strict";
2
- /* eslint-disable max-lines-per-function */
3
- /****
4
- * Rule to enforce @story and @req annotations on significant code branches
5
- * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
6
- * @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
7
- * @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
8
- */
9
2
  Object.defineProperty(exports, "__esModule", { value: true });
10
- const DEFAULT_BRANCH_TYPES = [
11
- "IfStatement",
12
- "SwitchCase",
13
- "TryStatement",
14
- "CatchClause",
15
- "ForStatement",
16
- "ForOfStatement",
17
- "ForInStatement",
18
- "WhileStatement",
19
- "DoWhileStatement",
20
- ];
21
- /**
22
- * Gather leading comments for a node, with fallback for SwitchCase.
23
- */
24
- function gatherCommentText(sourceCode, node) {
25
- if (node.type === "SwitchCase") {
26
- const lines = sourceCode.lines;
27
- const startLine = node.loc.start.line;
28
- let i = startLine - 2;
29
- const comments = [];
30
- while (i >= 0 && /^\s*(\/\/|\/\*)/.test(lines[i])) {
31
- comments.unshift(lines[i].trim());
32
- i--;
33
- }
34
- return comments.join(" ");
35
- }
36
- const comments = sourceCode.getCommentsBefore(node) || [];
37
- return comments.map((c) => c.value).join(" ");
38
- }
3
+ const branch_annotation_helpers_1 = require("../utils/branch-annotation-helpers");
39
4
  const rule = {
40
5
  meta: {
41
6
  type: "problem",
@@ -61,120 +26,35 @@ const rule = {
61
26
  },
62
27
  ],
63
28
  },
29
+ /**
30
+ * Create visitor for require-branch-annotation rule.
31
+ * @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
34
+ */
64
35
  create(context) {
65
- const sourceCode = context.getSourceCode();
66
- const options = context.options[0] || {};
67
- if (Array.isArray(options.branchTypes)) {
68
- const invalidTypes = options.branchTypes.filter((t) => !DEFAULT_BRANCH_TYPES.includes(t));
69
- if (invalidTypes.length > 0) {
70
- return {
71
- Program(node) {
72
- invalidTypes.forEach((t) => {
73
- context.report({
74
- node,
75
- message: `Value "${t}" should be equal to one of the allowed values: ${DEFAULT_BRANCH_TYPES.join(", ")}`,
76
- });
77
- });
78
- },
79
- };
80
- }
81
- }
82
- const branchTypes = Array.isArray(options.branchTypes)
83
- ? options.branchTypes
84
- : Array.from(DEFAULT_BRANCH_TYPES);
85
- let storyFixCount = 0;
86
- function reportBranch(node) {
87
- const text = gatherCommentText(sourceCode, node);
88
- const missingStory = !/@story\b/.test(text);
89
- const missingReq = !/@req\b/.test(text);
90
- const indent = sourceCode.lines[node.loc.start.line - 1].match(/^(\s*)/)?.[1] || "";
91
- const insertPos = sourceCode.getIndexFromLoc({
92
- line: node.loc.start.line,
93
- column: 0,
94
- });
95
- if (missingStory) {
96
- if (storyFixCount === 0) {
97
- context.report({
98
- node,
99
- messageId: "missingAnnotation",
100
- data: { missing: "@story" },
101
- fix: (fixer) => fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @story <story-file>.story.md\n`),
102
- });
103
- storyFixCount++;
104
- }
105
- else {
106
- context.report({
107
- node,
108
- messageId: "missingAnnotation",
109
- data: { missing: "@story" },
110
- });
111
- }
112
- }
113
- if (missingReq) {
114
- if (!missingStory) {
115
- context.report({
116
- node,
117
- messageId: "missingAnnotation",
118
- data: { missing: "@req" },
119
- fix: (fixer) => fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @req <REQ-ID>\n`),
120
- });
121
- }
122
- else {
123
- context.report({
124
- node,
125
- messageId: "missingAnnotation",
126
- data: { missing: "@req" },
127
- });
128
- }
129
- }
36
+ const branchTypesOrListener = (0, branch_annotation_helpers_1.validateBranchTypes)(context);
37
+ if (!Array.isArray(branchTypesOrListener)) {
38
+ return branchTypesOrListener;
130
39
  }
131
- return {
132
- IfStatement(node) {
133
- if (!branchTypes.includes(node.type))
134
- return;
135
- reportBranch(node);
136
- },
137
- SwitchCase(node) {
138
- if (node.test == null || !branchTypes.includes(node.type))
139
- return;
140
- reportBranch(node);
141
- },
142
- TryStatement(node) {
143
- if (!branchTypes.includes(node.type))
144
- return;
145
- reportBranch(node);
146
- },
147
- CatchClause(node) {
148
- if (!branchTypes.includes(node.type))
40
+ const branchTypes = branchTypesOrListener;
41
+ const storyFixCountRef = { count: 0 };
42
+ const handlers = {};
43
+ branchTypes.forEach((type) => {
44
+ /**
45
+ * Handler for a specific branch node type.
46
+ * @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
49
+ */
50
+ handlers[type] = function branchHandler(node) {
51
+ if (type === "SwitchCase" && node.test == null) {
149
52
  return;
150
- reportBranch(node);
151
- },
152
- ForStatement(node) {
153
- if (!branchTypes.includes(node.type))
154
- return;
155
- reportBranch(node);
156
- },
157
- ForOfStatement(node) {
158
- if (!branchTypes.includes(node.type))
159
- return;
160
- reportBranch(node);
161
- },
162
- ForInStatement(node) {
163
- if (!branchTypes.includes(node.type))
164
- return;
165
- reportBranch(node);
166
- },
167
- WhileStatement(node) {
168
- if (!branchTypes.includes(node.type))
169
- return;
170
- reportBranch(node);
171
- },
172
- DoWhileStatement(node) {
173
- if (!branchTypes.includes(node.type))
174
- return;
175
- reportBranch(node);
176
- },
177
- };
53
+ }
54
+ (0, branch_annotation_helpers_1.reportMissingAnnotations)(context, node, storyFixCountRef);
55
+ };
56
+ });
57
+ return handlers;
178
58
  },
179
59
  };
180
60
  exports.default = rule;
@@ -1,2 +1,7 @@
1
+ /**
2
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
3
+ * @req REQ-RULE-EXPORT - Export the rule object for ESLint
4
+ * @req REQ-ANNOTATION-REQUIRED - Require @req annotation on functions
5
+ */
1
6
  declare const _default: any;
2
7
  export default _default;
@@ -8,6 +8,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
9
9
  */
10
10
  const annotation_checker_1 = require("../utils/annotation-checker");
11
+ /**
12
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
13
+ * @req REQ-RULE-EXPORT - Export the rule object for ESLint
14
+ * @req REQ-ANNOTATION-REQUIRED - Require @req annotation on functions
15
+ */
11
16
  exports.default = {
12
17
  meta: {
13
18
  type: "problem",
@@ -21,15 +26,30 @@ exports.default = {
21
26
  },
22
27
  schema: [],
23
28
  },
29
+ /**
30
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
31
+ * @req REQ-CREATE-HOOK - Provide create(context) hook for rule behavior
32
+ * @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions, arrow functions, and methods
33
+ */
24
34
  create(context) {
25
35
  const sourceCode = context.getSourceCode();
26
36
  return {
37
+ /**
38
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
39
+ * @req REQ-FUNCTION-DETECTION - Detect function declarations
40
+ * @req REQ-ANNOTATION-REQUIRED - Enforce @req annotation on function declarations
41
+ */
27
42
  FunctionDeclaration(node) {
28
43
  const jsdoc = sourceCode.getJSDocComment(node);
29
44
  if (!jsdoc || !jsdoc.value.includes("@req")) {
30
45
  context.report({
31
46
  node,
32
47
  messageId: "missingReq",
48
+ /**
49
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
50
+ * @req REQ-AUTOFIX - Provide automatic fix to insert @req annotation
51
+ * @req REQ-ANNOTATION-REQUIRED - Ensure inserted fix contains @req placeholder
52
+ */
33
53
  fix(fixer) {
34
54
  return fixer.insertTextBefore(node, "/** @req <REQ-ID> */\n");
35
55
  },
@@ -1,12 +1,3 @@
1
- /**
2
- * Rule to enforce @story annotation on functions, function expressions, arrow functions, and methods
3
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
- * @req REQ-ANNOTATION-REQUIRED - Require @story annotation on functions
5
- * @req REQ-OPTIONS-SCOPE - Support configuring which function types to enforce via options
6
- * @req REQ-EXPORT-PRIORITY - Add exportPriority option to target exported or non-exported
7
- * @req REQ-UNIFIED-CHECK - Implement unified checkNode for all supported node types
8
- * @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions, arrow functions, and methods
9
- * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
10
- */
11
- declare const _default: any;
12
- export default _default;
1
+ import type { Rule } from "eslint";
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;