eslint-plugin-traceability 1.4.3 → 1.4.4

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.
@@ -1,198 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- // Default node types to check for function annotations
4
- const DEFAULT_SCOPE = [
5
- "FunctionDeclaration",
6
- "FunctionExpression",
7
- "ArrowFunctionExpression",
8
- "MethodDefinition",
9
- "TSDeclareFunction",
10
- "TSMethodSignature",
11
- ];
12
- const EXPORT_PRIORITY_VALUES = ["all", "exported", "non-exported"];
3
+ const require_story_visitors_1 = require("./helpers/require-story-visitors");
4
+ const require_story_helpers_1 = require("./helpers/require-story-helpers");
13
5
  /**
14
- * Determine if a node is in an export declaration
6
+ * ESLint rule to require @story annotations on functions/methods.
15
7
  *
16
8
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
17
9
  * @req REQ-ANNOTATION-REQUIRED
18
- * @param {any} node - AST node to check for export ancestry
19
- * @returns {boolean} true if node is within an export declaration
20
10
  */
21
- function isExportedNode(node) {
22
- let p = node.parent;
23
- while (p) {
24
- if (p.type === "ExportNamedDeclaration" ||
25
- p.type === "ExportDefaultDeclaration") {
26
- return true;
27
- }
28
- p = p.parent;
29
- }
30
- return false;
31
- }
32
- // Path to the story file for annotations
33
- const STORY_PATH = "docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md";
34
- const ANNOTATION = `/** @story ${STORY_PATH} */`;
35
- /**
36
- * Check if @story annotation already present in JSDoc or preceding comments
37
- *
38
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
39
- * @req REQ-ANNOTATION-REQUIRED
40
- * @param {any} sourceCode - ESLint sourceCode object
41
- * @param {any} node - AST node to inspect for existing annotations
42
- * @returns {boolean} true if @story annotation already present
43
- */
44
- function hasStoryAnnotation(sourceCode, node) {
45
- const jsdoc = sourceCode.getJSDocComment(node);
46
- if (jsdoc?.value.includes("@story")) {
47
- return true;
48
- }
49
- const comments = sourceCode.getCommentsBefore(node) || [];
50
- return comments.some((c) => c.value.includes("@story"));
51
- }
52
- /**
53
- * Get the name of the function-like node
54
- *
55
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
56
- * @req REQ-ANNOTATION-REQUIRED
57
- * @param {any} node - AST node representing a function-like construct
58
- * @returns {string} the resolved name or "<unknown>"
59
- */
60
- function getNodeName(node) {
61
- let current = node;
62
- while (current) {
63
- if (current.type === "VariableDeclarator" &&
64
- current.id &&
65
- typeof current.id.name === "string") {
66
- return current.id.name;
67
- }
68
- if ((current.type === "FunctionDeclaration" ||
69
- current.type === "TSDeclareFunction") &&
70
- current.id &&
71
- typeof current.id.name === "string") {
72
- return current.id.name;
73
- }
74
- if ((current.type === "MethodDefinition" ||
75
- current.type === "TSMethodSignature") &&
76
- current.key &&
77
- typeof current.key.name === "string") {
78
- return current.key.name;
79
- }
80
- current = current.parent;
81
- }
82
- return "<unknown>";
83
- }
84
- /**
85
- * Determine AST node where annotation should be inserted
86
- *
87
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
88
- * @req REQ-ANNOTATION-REQUIRED
89
- * @param {any} sourceCode - ESLint sourceCode object (unused but kept for parity)
90
- * @param {any} node - function-like AST node to resolve target for
91
- * @returns {any} AST node that should receive the annotation
92
- */
93
- function resolveTargetNode(sourceCode, node) {
94
- if (node.type === "TSMethodSignature") {
95
- // Interface method signature -> insert on interface
96
- return node.parent.parent;
97
- }
98
- if (node.type === "FunctionExpression" ||
99
- node.type === "ArrowFunctionExpression") {
100
- const parent = node.parent;
101
- if (parent.type === "VariableDeclarator") {
102
- const varDecl = parent.parent;
103
- if (varDecl.parent && varDecl.parent.type === "ExportNamedDeclaration") {
104
- return varDecl.parent;
105
- }
106
- return varDecl;
107
- }
108
- if (parent.type === "ExportNamedDeclaration") {
109
- return parent;
110
- }
111
- if (parent.type === "ExpressionStatement") {
112
- return parent;
113
- }
114
- }
115
- return node;
116
- }
117
- /**
118
- * Report missing @story annotation on function or method
119
- *
120
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
121
- * @req REQ-ANNOTATION-REQUIRED
122
- * @param {Rule.RuleContext} context - ESLint rule context
123
- * @param {any} sourceCode - ESLint sourceCode object
124
- * @param {any} node - function AST node missing annotation
125
- * @param {any} target - AST node where annotation should be inserted
126
- */
127
- function reportMissing(context, sourceCode, node, target) {
128
- if (hasStoryAnnotation(sourceCode, node) ||
129
- hasStoryAnnotation(sourceCode, target)) {
130
- return;
131
- }
132
- let name = getNodeName(node);
133
- if (node.type === "TSDeclareFunction" && node.id && node.id.name) {
134
- name = node.id.name;
135
- }
136
- context.report({
137
- node,
138
- messageId: "missingStory",
139
- data: { name },
140
- suggest: [
141
- {
142
- desc: `Add JSDoc @story annotation for function '${name}', e.g., ${ANNOTATION}`,
143
- fix: (fixer) => fixer.insertTextBefore(target, `${ANNOTATION}\n`),
144
- },
145
- ],
146
- });
147
- }
148
- /**
149
- * Report missing @story annotation on class methods
150
- *
151
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
152
- * @req REQ-ANNOTATION-REQUIRED
153
- * @param {Rule.RuleContext} context - ESLint rule context
154
- * @param {any} sourceCode - ESLint sourceCode object
155
- * @param {any} node - MethodDefinition AST node
156
- */
157
- function reportMethod(context, sourceCode, node) {
158
- if (hasStoryAnnotation(sourceCode, node)) {
159
- return;
160
- }
161
- context.report({
162
- node,
163
- messageId: "missingStory",
164
- data: { name: getNodeName(node) },
165
- suggest: [
166
- {
167
- desc: `Add JSDoc @story annotation for function '${getNodeName(node)}', e.g., ${ANNOTATION}`,
168
- fix: (fixer) => fixer.insertTextBefore(node, `${ANNOTATION}\n `),
169
- },
170
- ],
171
- });
172
- }
173
- /**
174
- * Check if this node is within scope and matches exportPriority
175
- *
176
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
177
- * @req REQ-ANNOTATION-REQUIRED
178
- * @param {any} node - AST node to evaluate
179
- * @param {string[]} scope - allowed node types
180
- * @param {string} exportPriority - 'all' | 'exported' | 'non-exported'
181
- * @returns {boolean} whether node should be processed
182
- */
183
- function shouldProcessNode(node, scope, exportPriority) {
184
- if (!scope.includes(node.type)) {
185
- return false;
186
- }
187
- const exported = isExportedNode(node);
188
- if (exportPriority === "exported" && !exported) {
189
- return false;
190
- }
191
- if (exportPriority === "non-exported" && exported) {
192
- return false;
193
- }
194
- return true;
195
- }
196
11
  const rule = {
197
12
  meta: {
198
13
  type: "problem",
@@ -210,99 +25,43 @@ const rule = {
210
25
  properties: {
211
26
  scope: {
212
27
  type: "array",
213
- items: { type: "string", enum: DEFAULT_SCOPE },
28
+ items: { type: "string", enum: require_story_helpers_1.DEFAULT_SCOPE },
214
29
  uniqueItems: true,
215
30
  },
216
- exportPriority: { type: "string", enum: EXPORT_PRIORITY_VALUES },
31
+ exportPriority: { type: "string", enum: require_story_helpers_1.EXPORT_PRIORITY_VALUES },
217
32
  },
218
33
  additionalProperties: false,
219
34
  },
220
35
  ],
221
36
  },
222
37
  /**
223
- * Create the rule visitor functions for require-story-annotation.
38
+ * Create the rule visitor functions.
39
+ *
224
40
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
225
- * @req REQ-CREATE-HOOK - Provide create(context) hook for rule behavior
41
+ * @req REQ-CREATE-HOOK
226
42
  */
227
43
  create(context) {
228
44
  const sourceCode = context.getSourceCode();
229
- const opts = context.options[0] ||
230
- {};
231
- const scope = opts.scope || DEFAULT_SCOPE;
45
+ const opts = (context.options && context.options[0]) || {};
46
+ const scope = opts.scope || require_story_helpers_1.DEFAULT_SCOPE;
232
47
  const exportPriority = opts.exportPriority || "all";
233
- return {
234
- FunctionDeclaration(node) {
235
- if (!shouldProcessNode(node, scope, exportPriority))
236
- return;
237
- let target = node;
238
- if (node.parent &&
239
- (node.parent.type === "ExportNamedDeclaration" ||
240
- node.parent.type === "ExportDefaultDeclaration")) {
241
- target = node.parent;
242
- }
243
- reportMissing(context, sourceCode, node, target);
244
- },
245
- /**
246
- * Handle FunctionExpression nodes
247
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
248
- * @req REQ-ANNOTATION-REQUIRED
249
- * @param {any} node - FunctionExpression AST node
250
- */
251
- FunctionExpression(node) {
252
- if (!shouldProcessNode(node, scope, exportPriority))
253
- return;
254
- if (node.parent && node.parent.type === "MethodDefinition")
255
- return;
256
- const target = resolveTargetNode(sourceCode, node);
257
- reportMissing(context, sourceCode, node, target);
258
- },
259
- /**
260
- * Handle ArrowFunctionExpression nodes
261
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
262
- * @req REQ-ANNOTATION-REQUIRED
263
- * @param {any} node - ArrowFunctionExpression AST node
264
- */
265
- ArrowFunctionExpression(node) {
266
- if (!shouldProcessNode(node, scope, exportPriority))
267
- return;
268
- const target = resolveTargetNode(sourceCode, node);
269
- reportMissing(context, sourceCode, node, target);
270
- },
271
- /**
272
- * Handle TSDeclareFunction nodes
273
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
274
- * @req REQ-ANNOTATION-REQUIRED
275
- * @param {any} node - TSDeclareFunction AST node
276
- */
277
- TSDeclareFunction(node) {
278
- if (!shouldProcessNode(node, scope, exportPriority))
279
- return;
280
- reportMissing(context, sourceCode, node, node);
281
- },
282
- /**
283
- * Handle TSMethodSignature nodes
284
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
285
- * @req REQ-ANNOTATION-REQUIRED
286
- * @param {any} node - TSMethodSignature AST node
287
- */
288
- TSMethodSignature(node) {
289
- if (!shouldProcessNode(node, scope, exportPriority))
290
- return;
291
- const target = resolveTargetNode(sourceCode, node);
292
- reportMissing(context, sourceCode, node, target);
293
- },
294
- /**
295
- * Handle MethodDefinition nodes
296
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
297
- * @req REQ-ANNOTATION-REQUIRED
298
- * @param {any} node - MethodDefinition AST node
299
- */
300
- MethodDefinition(node) {
301
- if (!shouldProcessNode(node, scope, exportPriority))
302
- return;
303
- reportMethod(context, sourceCode, node);
304
- },
305
- };
48
+ /**
49
+ * Debug log at the start of create to help diagnose rule activation in tests.
50
+ *
51
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
52
+ * @req REQ-DEBUG-LOG
53
+ */
54
+ console.debug("require-story-annotation:create", typeof context.getFilename === "function"
55
+ ? context.getFilename()
56
+ : "<unknown>");
57
+ // Local closure that binds configured scope and export priority to the helper.
58
+ const should = (node) => (0, require_story_helpers_1.shouldProcessNode)(node, scope, exportPriority);
59
+ // Delegate visitor construction to helper to keep this file concise.
60
+ return (0, require_story_visitors_1.buildVisitors)(context, sourceCode, {
61
+ shouldProcessNode: should,
62
+ scope,
63
+ exportPriority,
64
+ });
306
65
  },
307
66
  };
308
67
  exports.default = rule;
@@ -31,28 +31,47 @@ exports.DEFAULT_BRANCH_TYPES = [
31
31
  */
32
32
  function validateBranchTypes(context) {
33
33
  const options = context.options[0] || {};
34
+ /**
35
+ * Conditional branch checking whether branchTypes option was provided.
36
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
37
+ * @req REQ-TRACEABILITY-CONDITIONAL - Trace configuration branch existence check
38
+ */
34
39
  if (Array.isArray(options.branchTypes)) {
35
- // @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
36
- // @req REQ-TRACEABILITY-FILTER-CALLBACK - Trace filter callback for invalid branch type detection
37
- const invalidTypes = options.branchTypes.filter((t) => !exports.DEFAULT_BRANCH_TYPES.includes(t));
40
+ /**
41
+ * Predicate to determine whether a provided branch type is invalid.
42
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
43
+ * @req REQ-TRACEABILITY-FILTER-CALLBACK - Trace filter callback for invalid branch type detection
44
+ */
45
+ function isInvalidType(t) {
46
+ return !exports.DEFAULT_BRANCH_TYPES.includes(t);
47
+ }
48
+ const invalidTypes = options.branchTypes.filter(isInvalidType);
49
+ /**
50
+ * Conditional branch checking whether any invalid types were found.
51
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
52
+ * @req REQ-TRACEABILITY-INVALID-DETECTION - Trace handling when invalid types are detected
53
+ */
38
54
  if (invalidTypes.length > 0) {
39
55
  /**
40
56
  * Program listener produced when configuration is invalid.
41
57
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
42
58
  * @req REQ-TRACEABILITY-PROGRAM-LISTENER - Trace Program listener reporting invalid config values
43
59
  */
44
- return {
45
- Program(node) {
46
- // @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
47
- // @req REQ-TRACEABILITY-FOR-EACH-CALLBACK - Trace reporting for each invalid type
48
- invalidTypes.forEach((t) => {
49
- context.report({
50
- node,
51
- message: `Value "${t}" should be equal to one of the allowed values: ${exports.DEFAULT_BRANCH_TYPES.join(", ")}`,
52
- });
60
+ function ProgramHandler(node) {
61
+ /**
62
+ * Report a single invalid type for the given Program node.
63
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
64
+ * @req REQ-TRACEABILITY-FOR-EACH-CALLBACK - Trace reporting for each invalid type
65
+ */
66
+ function reportInvalidType(t) {
67
+ context.report({
68
+ node,
69
+ message: `Value "${t}" should be equal to one of the allowed values: ${exports.DEFAULT_BRANCH_TYPES.join(", ")}`,
53
70
  });
54
- },
55
- };
71
+ }
72
+ invalidTypes.forEach(reportInvalidType);
73
+ }
74
+ return { Program: ProgramHandler };
56
75
  }
57
76
  }
58
77
  return Array.isArray(options.branchTypes)
@@ -65,6 +84,11 @@ function validateBranchTypes(context) {
65
84
  * @req REQ-COMMENT-ASSOCIATION - Associate inline comments with their corresponding code branches
66
85
  */
67
86
  function gatherBranchCommentText(sourceCode, node) {
87
+ /**
88
+ * Conditional branch for SwitchCase nodes that may include inline comments.
89
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
90
+ * @req REQ-TRACEABILITY-SWITCHCASE-COMMENTS - Trace collection of preceding comments for SwitchCase
91
+ */
68
92
  if (node.type === "SwitchCase") {
69
93
  const lines = sourceCode.lines;
70
94
  const startLine = node.loc.start.line;
@@ -79,9 +103,15 @@ function gatherBranchCommentText(sourceCode, node) {
79
103
  return comments.join(" ");
80
104
  }
81
105
  const comments = sourceCode.getCommentsBefore(node) || [];
82
- // @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
83
- // @req REQ-TRACEABILITY-MAP-CALLBACK - Trace mapping of comment nodes to their text values
84
- return comments.map((c) => c.value).join(" ");
106
+ /**
107
+ * Mapper to extract the text value from a comment node.
108
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
109
+ * @req REQ-TRACEABILITY-MAP-CALLBACK - Trace mapping of comment nodes to their text values
110
+ */
111
+ function commentToValue(c) {
112
+ return c.value;
113
+ }
114
+ return comments.map(commentToValue).join(" ");
85
115
  }
86
116
  /**
87
117
  * Report missing @story annotation on a branch node.
@@ -90,15 +120,25 @@ function gatherBranchCommentText(sourceCode, node) {
90
120
  */
91
121
  function reportMissingStory(context, node, options) {
92
122
  const { indent, insertPos, storyFixCountRef } = options;
123
+ /**
124
+ * Conditional branch deciding whether to offer an auto-fix for the missing story.
125
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
126
+ * @req REQ-TRACEABILITY-FIX-DECISION - Trace decision to provide fixer for missing @story
127
+ */
93
128
  if (storyFixCountRef.count === 0) {
129
+ /**
130
+ * Fixer that inserts a default @story annotation above the branch.
131
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
132
+ * @req REQ-TRACEABILITY-FIX-ARROW - Trace fixer function used to insert missing @story
133
+ */
134
+ function insertStoryFixer(fixer) {
135
+ return fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @story <story-file>.story.md\n`);
136
+ }
94
137
  context.report({
95
138
  node,
96
139
  messageId: "missingAnnotation",
97
140
  data: { missing: "@story" },
98
- fix:
99
- // @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
100
- // @req REQ-TRACEABILITY-FIX-ARROW - Trace fixer arrow function used to insert missing @story
101
- (fixer) => fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @story <story-file>.story.md\n`),
141
+ fix: insertStoryFixer,
102
142
  });
103
143
  storyFixCountRef.count++;
104
144
  }
@@ -117,15 +157,25 @@ function reportMissingStory(context, node, options) {
117
157
  */
118
158
  function reportMissingReq(context, node, options) {
119
159
  const { indent, insertPos, missingStory } = options;
160
+ /**
161
+ * Conditional branch deciding whether to offer an auto-fix for the missing req.
162
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
163
+ * @req REQ-TRACEABILITY-FIX-DECISION - Trace decision to provide fixer for missing @req
164
+ */
120
165
  if (!missingStory) {
166
+ /**
167
+ * Fixer that inserts a default @req annotation above the branch.
168
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
169
+ * @req REQ-TRACEABILITY-FIX-ARROW - Trace fixer function used to insert missing @req
170
+ */
171
+ function insertReqFixer(fixer) {
172
+ return fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @req <REQ-ID>\n`);
173
+ }
121
174
  context.report({
122
175
  node,
123
176
  messageId: "missingAnnotation",
124
177
  data: { missing: "@req" },
125
- fix:
126
- // @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
127
- // @req REQ-TRACEABILITY-FIX-ARROW - Trace fixer arrow function used to insert missing @req
128
- (fixer) => fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @req <REQ-ID>\n`),
178
+ fix: insertReqFixer,
129
179
  });
130
180
  }
131
181
  else {
@@ -163,10 +213,20 @@ function reportMissingAnnotations(context, node, storyFixCountRef) {
163
213
  args: [context, node, { indent, insertPos, missingStory }],
164
214
  },
165
215
  ];
166
- // @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
167
- // @req REQ-TRACEABILITY-ACTIONS-FOREACH - Trace processing of actions array to report missing annotations
168
- actions.forEach(({ missing, fn, args }) =>
169
- // @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
170
- // @req REQ-TRACEABILITY-FOR-EACH-CALLBACK - Trace callback handling for each action item
171
- missing && fn(...args));
216
+ /**
217
+ * Process a single action from the actions array.
218
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
219
+ * @req REQ-TRACEABILITY-ACTIONS-FOREACH - Trace processing of actions array to report missing annotations
220
+ */
221
+ function processAction(item) {
222
+ /**
223
+ * Callback invoked for each action to decide and execute reporting.
224
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
225
+ * @req REQ-TRACEABILITY-FOR-EACH-CALLBACK - Trace callback handling for each action item
226
+ */
227
+ if (item.missing) {
228
+ item.fn(...item.args);
229
+ }
230
+ }
231
+ actions.forEach(processAction);
172
232
  }
@@ -0,0 +1 @@
1
+ export {};