eslint-plugin-traceability 1.6.0 → 1.6.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.
- package/lib/src/rules/require-req-annotation.d.ts +9 -4
- package/lib/src/rules/require-req-annotation.js +69 -21
- package/lib/src/rules/require-story-annotation.d.ts +4 -0
- package/lib/src/rules/require-story-annotation.js +12 -1
- package/lib/src/rules/valid-annotation-format.js +20 -0
- package/lib/src/utils/annotation-checker.d.ts +7 -1
- package/lib/src/utils/annotation-checker.js +51 -8
- package/lib/tests/rules/auto-fix-behavior-008.test.js +76 -0
- package/lib/tests/rules/require-req-annotation.test.js +139 -4
- package/package.json +1 -1
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/****
|
|
2
|
+
* Rule to enforce @req annotation on functions
|
|
2
3
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
3
|
-
* @req REQ-RULE-EXPORT - Export the rule object for ESLint
|
|
4
4
|
* @req REQ-ANNOTATION-REQUIRED - Require @req annotation on functions
|
|
5
|
+
* @req REQ-FUNCTION-DETECTION - Detect function declarations, function expressions, and method definitions (including TypeScript declarations)
|
|
6
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
7
|
+
* @req REQ-CONFIGURABLE-SCOPE - Allow configuration of which exports are checked
|
|
8
|
+
* @req REQ-EXPORT-PRIORITY - Allow configuration of export priority behavior
|
|
5
9
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
10
|
+
import type { Rule } from "eslint";
|
|
11
|
+
declare const rule: Rule.RuleModule;
|
|
12
|
+
export default rule;
|
|
@@ -1,37 +1,59 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
4
|
-
* Rule to enforce @req annotation on functions
|
|
5
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
6
|
-
* @req REQ-ANNOTATION-REQUIRED - Require @req annotation on functions
|
|
7
|
-
* @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions, arrow functions, and methods
|
|
8
|
-
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
9
|
-
*/
|
|
3
|
+
const require_story_helpers_1 = require("./helpers/require-story-helpers");
|
|
10
4
|
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
|
-
*/
|
|
16
|
-
exports.default = {
|
|
5
|
+
const rule = {
|
|
17
6
|
meta: {
|
|
18
7
|
type: "problem",
|
|
19
8
|
fixable: "code",
|
|
20
9
|
docs: {
|
|
21
|
-
description: "Require @req annotations on functions",
|
|
10
|
+
description: "Require @req annotations on function-like exports (declarations, expressions, and methods, excluding arrow functions)",
|
|
22
11
|
recommended: "error",
|
|
23
12
|
},
|
|
24
13
|
messages: {
|
|
25
14
|
missingReq: "Missing @req annotation for function '{{name}}' (REQ-ANNOTATION-REQUIRED)",
|
|
26
15
|
},
|
|
27
|
-
schema: [
|
|
16
|
+
schema: [
|
|
17
|
+
{
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
scope: {
|
|
21
|
+
type: "array",
|
|
22
|
+
items: {
|
|
23
|
+
enum: require_story_helpers_1.DEFAULT_SCOPE,
|
|
24
|
+
},
|
|
25
|
+
uniqueItems: true,
|
|
26
|
+
},
|
|
27
|
+
exportPriority: {
|
|
28
|
+
enum: require_story_helpers_1.EXPORT_PRIORITY_VALUES,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
28
34
|
},
|
|
29
35
|
/**
|
|
30
36
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
31
37
|
* @req REQ-CREATE-HOOK - Provide create(context) hook for rule behavior
|
|
32
|
-
* @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions,
|
|
38
|
+
* @req REQ-FUNCTION-DETECTION - Detect function declarations, function expressions, and method definitions (including TS-specific nodes)
|
|
39
|
+
* @req REQ-CONFIGURABLE-SCOPE - Respect configurable scope of which exports are checked
|
|
40
|
+
* @req REQ-EXPORT-PRIORITY - Respect configurable export priority when determining which nodes to check
|
|
33
41
|
*/
|
|
34
42
|
create(context) {
|
|
43
|
+
const options = context.options?.[0] ?? {};
|
|
44
|
+
const rawScope = options?.scope;
|
|
45
|
+
const scope = Array.isArray(rawScope) && rawScope.length > 0 ? rawScope : require_story_helpers_1.DEFAULT_SCOPE;
|
|
46
|
+
const exportPriority = options?.exportPriority ?? "all";
|
|
47
|
+
const shouldCheck = (node) => (0, require_story_helpers_1.shouldProcessNode)(node, scope, exportPriority);
|
|
48
|
+
/**
|
|
49
|
+
* Helper to conditionally run the annotation check only when the node
|
|
50
|
+
* should be processed according to scope/exportPriority.
|
|
51
|
+
*/
|
|
52
|
+
const runCheck = (node) => {
|
|
53
|
+
if (!shouldCheck(node))
|
|
54
|
+
return;
|
|
55
|
+
(0, annotation_checker_1.checkReqAnnotation)(context, node, { enableFix: false });
|
|
56
|
+
};
|
|
35
57
|
return {
|
|
36
58
|
/**
|
|
37
59
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
@@ -39,18 +61,44 @@ exports.default = {
|
|
|
39
61
|
* @req REQ-ANNOTATION-REQUIRED - Enforce @req annotation on function declarations
|
|
40
62
|
*/
|
|
41
63
|
FunctionDeclaration(node) {
|
|
42
|
-
|
|
64
|
+
runCheck(node);
|
|
65
|
+
},
|
|
66
|
+
/**
|
|
67
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
68
|
+
* @req REQ-FUNCTION-DETECTION - Detect function expressions
|
|
69
|
+
* @req REQ-ANNOTATION-REQUIRED - Enforce @req annotation on function expressions
|
|
70
|
+
*/
|
|
71
|
+
FunctionExpression(node) {
|
|
72
|
+
if (node.parent && node.parent.type === "MethodDefinition") {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
runCheck(node);
|
|
43
76
|
},
|
|
44
77
|
/**
|
|
45
78
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
46
|
-
* @req REQ-
|
|
79
|
+
* @req REQ-FUNCTION-DETECTION - Detect method definitions
|
|
80
|
+
* @req REQ-ANNOTATION-REQUIRED - Enforce @req annotation on method definitions
|
|
47
81
|
*/
|
|
48
|
-
|
|
82
|
+
MethodDefinition(node) {
|
|
83
|
+
runCheck(node);
|
|
84
|
+
},
|
|
85
|
+
/**
|
|
86
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
87
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript declare functions
|
|
88
|
+
* @req REQ-ANNOTATION-REQUIRED - Enforce @req annotation on TS declare functions
|
|
89
|
+
*/
|
|
90
|
+
TSDeclareFunction(node) {
|
|
91
|
+
runCheck(node);
|
|
92
|
+
},
|
|
49
93
|
/**
|
|
50
94
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
51
|
-
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript
|
|
95
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript method signatures
|
|
96
|
+
* @req REQ-ANNOTATION-REQUIRED - Enforce @req annotation on TS method signatures
|
|
52
97
|
*/
|
|
53
|
-
TSMethodSignature
|
|
98
|
+
TSMethodSignature(node) {
|
|
99
|
+
runCheck(node);
|
|
100
|
+
},
|
|
54
101
|
};
|
|
55
102
|
},
|
|
56
103
|
};
|
|
104
|
+
exports.default = rule;
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
8
8
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
9
9
|
* @req REQ-ANNOTATION-REQUIRED
|
|
10
|
+
* @req REQ-AUTOFIX-MISSING - This rule supports auto-fixing missing @story annotations per Story 008.0 auto-fix behavior.
|
|
11
|
+
* @req REQ-AUTOFIX-SAFE - Auto-fix behavior only inserts @story annotation JSDoc comments and never changes executable or runtime code.
|
|
12
|
+
* @req REQ-AUTOFIX-PRESERVE - Auto-fix inserts a minimal placeholder JSDoc in a way that preserves existing surrounding formatting and structure.
|
|
10
13
|
*/
|
|
11
14
|
import type { Rule } from "eslint";
|
|
12
15
|
/**
|
|
@@ -15,6 +18,7 @@ import type { Rule } from "eslint";
|
|
|
15
18
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
16
19
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
17
20
|
* @req REQ-ANNOTATION-REQUIRED
|
|
21
|
+
* @req REQ-AUTOFIX-MISSING - This rule participates in auto-fix for missing @story annotations.
|
|
18
22
|
*/
|
|
19
23
|
declare const rule: Rule.RuleModule;
|
|
20
24
|
export default rule;
|
|
@@ -8,15 +8,25 @@ const require_story_helpers_1 = require("./helpers/require-story-helpers");
|
|
|
8
8
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
9
9
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
10
10
|
* @req REQ-ANNOTATION-REQUIRED
|
|
11
|
+
* @req REQ-AUTOFIX-MISSING - This rule participates in auto-fix for missing @story annotations.
|
|
11
12
|
*/
|
|
12
13
|
const rule = {
|
|
13
14
|
meta: {
|
|
14
15
|
type: "problem",
|
|
15
16
|
docs: {
|
|
16
|
-
description: "Require @story annotations on functions",
|
|
17
|
+
description: "Require @story annotations on functions and auto-fix missing annotations where possible",
|
|
17
18
|
recommended: "error",
|
|
18
19
|
},
|
|
19
20
|
hasSuggestions: true,
|
|
21
|
+
/**
|
|
22
|
+
* Auto-fix support for inserting @story annotations.
|
|
23
|
+
*
|
|
24
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
25
|
+
* @req REQ-ANNOTATION-REQUIRED
|
|
26
|
+
* @req REQ-AUTOFIX-MISSING - `fixable: \"code\"` is used to implement REQ-AUTOFIX-MISSING for missing @story annotations.
|
|
27
|
+
* @req REQ-AUTOFIX-SAFE - Auto-fix is conservative and only adds a single-line JSDoc @story annotation without modifying existing runtime expressions.
|
|
28
|
+
* @req REQ-AUTOFIX-PRESERVE - Auto-fix behavior preserves surrounding code formatting and indentation when inserting the placeholder JSDoc.
|
|
29
|
+
*/
|
|
20
30
|
fixable: "code",
|
|
21
31
|
messages: {
|
|
22
32
|
missingStory: "Missing @story annotation for function '{{name}}' (REQ-ANNOTATION-REQUIRED)",
|
|
@@ -42,6 +52,7 @@ const rule = {
|
|
|
42
52
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
43
53
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
44
54
|
* @req REQ-CREATE-HOOK
|
|
55
|
+
* @req REQ-AUTOFIX-MISSING - The create hook wires in visitors that are capable of providing auto-fix suggestions for missing @story annotations.
|
|
45
56
|
*/
|
|
46
57
|
create(context) {
|
|
47
58
|
const sourceCode = context.getSourceCode();
|
|
@@ -8,6 +8,7 @@ const STORY_EXAMPLE_PATH = "docs/stories/005.0-DEV-EXAMPLE.story.md";
|
|
|
8
8
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
9
9
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
10
10
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
11
|
+
* @req REQ-AUTOFIX-PRESERVE - Avoid risky text replacements when the annotation tag cannot be located
|
|
11
12
|
*/
|
|
12
13
|
const TAG_NOT_FOUND_INDEX = -1;
|
|
13
14
|
/**
|
|
@@ -89,6 +90,8 @@ function buildReqErrorMessage(kind, value) {
|
|
|
89
90
|
*
|
|
90
91
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
91
92
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
93
|
+
* @req REQ-AUTOFIX-SAFE - Auto-fix must be conservative and never broaden the referenced path
|
|
94
|
+
* @req REQ-AUTOFIX-PRESERVE - Preserve surrounding formatting when normalizing story path suffixes
|
|
92
95
|
*/
|
|
93
96
|
function getFixedStoryPath(original) {
|
|
94
97
|
if (original.includes("..")) {
|
|
@@ -124,10 +127,18 @@ function reportInvalidStoryFormat(context, comment, collapsed) {
|
|
|
124
127
|
* for common path suffix issues by locating and replacing the path text
|
|
125
128
|
* within the original comment.
|
|
126
129
|
*
|
|
130
|
+
* This helper:
|
|
131
|
+
* - only adjusts the story path suffix when a safe, well-understood
|
|
132
|
+
* transformation is available, satisfying REQ-AUTOFIX-SAFE.
|
|
133
|
+
* - preserves all surrounding comment formatting, spacing, and text
|
|
134
|
+
* outside the path substring, satisfying REQ-AUTOFIX-PRESERVE.
|
|
135
|
+
*
|
|
127
136
|
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
128
137
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
129
138
|
* @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
|
|
130
139
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
140
|
+
* @req REQ-AUTOFIX-SAFE - Auto-fix must be conservative and avoid changing semantics
|
|
141
|
+
* @req REQ-AUTOFIX-PRESERVE - Auto-fix must preserve surrounding formatting and comments
|
|
131
142
|
*/
|
|
132
143
|
function reportInvalidStoryFormatWithFix(context, comment, collapsed, fixed) {
|
|
133
144
|
const sourceCode = context.getSourceCode();
|
|
@@ -317,6 +328,15 @@ exports.default = {
|
|
|
317
328
|
invalidReqFormat: "{{details}}",
|
|
318
329
|
},
|
|
319
330
|
schema: [],
|
|
331
|
+
/**
|
|
332
|
+
* This rule's fixable support is limited to safe @story path suffix normalization per Story 008.0.
|
|
333
|
+
* Fixes are limited strictly to adjusting the suffix portion of the @story path (e.g., adding
|
|
334
|
+
* `.md` or `.story.md`), preserving all other comment text and whitespace exactly as written.
|
|
335
|
+
*
|
|
336
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
337
|
+
* @req REQ-AUTOFIX-SAFE
|
|
338
|
+
* @req REQ-AUTOFIX-PRESERVE
|
|
339
|
+
*/
|
|
320
340
|
fixable: "code",
|
|
321
341
|
},
|
|
322
342
|
/**
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Helper to check @req annotation presence on TS declare functions and method signatures.
|
|
3
|
+
* This helper is intentionally scope/exportPriority agnostic and focuses solely
|
|
4
|
+
* on detection and reporting of @req annotations for the given node.
|
|
3
5
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
4
6
|
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
7
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
8
|
+
* @req REQ-ANNOTATION-REPORTING - Report missing @req annotation to context
|
|
5
9
|
*/
|
|
6
|
-
export declare function checkReqAnnotation(context: any, node: any
|
|
10
|
+
export declare function checkReqAnnotation(context: any, node: any, options?: {
|
|
11
|
+
enableFix?: boolean;
|
|
12
|
+
}): void;
|
|
@@ -48,11 +48,41 @@ function commentContainsReq(c) {
|
|
|
48
48
|
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
49
49
|
*/
|
|
50
50
|
function hasReqAnnotation(jsdoc, comments) {
|
|
51
|
+
// BRANCH @req detection on JSDoc or comments
|
|
52
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
53
|
+
// @req REQ-ANNOTATION-REQ-DETECTION
|
|
51
54
|
return ((jsdoc &&
|
|
52
55
|
typeof jsdoc.value === "string" &&
|
|
53
56
|
jsdoc.value.includes("@req")) ||
|
|
54
57
|
comments.some(commentContainsReq));
|
|
55
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Determine the most appropriate node to attach an inserted JSDoc to.
|
|
61
|
+
* Prefers outer function-like constructs such as methods, variable declarators,
|
|
62
|
+
* or wrapping expression statements for function expressions.
|
|
63
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
64
|
+
* @req REQ-ANNOTATION-AUTOFIX - Provide autofix for missing @req annotation
|
|
65
|
+
*/
|
|
66
|
+
function getFixTargetNode(node) {
|
|
67
|
+
const parent = node && node.parent;
|
|
68
|
+
if (!parent) {
|
|
69
|
+
return node;
|
|
70
|
+
}
|
|
71
|
+
// If the node is part of a class/obj method definition, attach to the MethodDefinition
|
|
72
|
+
if (parent.type === "MethodDefinition") {
|
|
73
|
+
return parent;
|
|
74
|
+
}
|
|
75
|
+
// If the node is the init of a variable declarator, attach to the VariableDeclarator
|
|
76
|
+
if (parent.type === "VariableDeclarator" && parent.init === node) {
|
|
77
|
+
return parent;
|
|
78
|
+
}
|
|
79
|
+
// If the parent is an expression statement (e.g. IIFE or assigned via expression),
|
|
80
|
+
// attach to the outer ExpressionStatement.
|
|
81
|
+
if (parent.type === "ExpressionStatement") {
|
|
82
|
+
return parent;
|
|
83
|
+
}
|
|
84
|
+
return node;
|
|
85
|
+
}
|
|
56
86
|
/**
|
|
57
87
|
* Creates a fix function that inserts a missing @req JSDoc before the node.
|
|
58
88
|
* Returned function is a proper named function so no inline arrow is used.
|
|
@@ -60,8 +90,9 @@ function hasReqAnnotation(jsdoc, comments) {
|
|
|
60
90
|
* @req REQ-ANNOTATION-AUTOFIX - Provide autofix for missing @req annotation
|
|
61
91
|
*/
|
|
62
92
|
function createMissingReqFix(node) {
|
|
93
|
+
const target = getFixTargetNode(node);
|
|
63
94
|
return function missingReqFix(fixer) {
|
|
64
|
-
return fixer.insertTextBefore(
|
|
95
|
+
return fixer.insertTextBefore(target, "/** @req <REQ-ID> */\n");
|
|
65
96
|
};
|
|
66
97
|
}
|
|
67
98
|
/**
|
|
@@ -73,29 +104,41 @@ function createMissingReqFix(node) {
|
|
|
73
104
|
* @req REQ-ERROR-SPECIFIC - Provide specific error details including node name
|
|
74
105
|
* @req REQ-ERROR-LOCATION - Include contextual location information in errors
|
|
75
106
|
*/
|
|
76
|
-
function reportMissing(context, node) {
|
|
77
|
-
const rawName = (0, require_story_utils_1.getNodeName)(node);
|
|
107
|
+
function reportMissing(context, node, enableFix = true) {
|
|
108
|
+
const rawName = (0, require_story_utils_1.getNodeName)(node) ?? (node && (0, require_story_utils_1.getNodeName)(node.parent));
|
|
78
109
|
const name = rawName ?? "(anonymous)";
|
|
79
|
-
|
|
110
|
+
const reportOptions = {
|
|
80
111
|
node,
|
|
81
112
|
messageId: "missingReq",
|
|
82
113
|
data: { name },
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
};
|
|
115
|
+
if (enableFix) {
|
|
116
|
+
reportOptions.fix = createMissingReqFix(node);
|
|
117
|
+
}
|
|
118
|
+
context.report(reportOptions);
|
|
85
119
|
}
|
|
86
120
|
/**
|
|
87
121
|
* Helper to check @req annotation presence on TS declare functions and method signatures.
|
|
122
|
+
* This helper is intentionally scope/exportPriority agnostic and focuses solely
|
|
123
|
+
* on detection and reporting of @req annotations for the given node.
|
|
88
124
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
89
125
|
* @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
|
|
126
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
127
|
+
* @req REQ-ANNOTATION-REPORTING - Report missing @req annotation to context
|
|
90
128
|
*/
|
|
91
|
-
function checkReqAnnotation(context, node) {
|
|
129
|
+
function checkReqAnnotation(context, node, options) {
|
|
130
|
+
const { enableFix = true } = options ?? {};
|
|
92
131
|
const sourceCode = context.getSourceCode();
|
|
93
132
|
const jsdoc = getJsdocComment(sourceCode, node);
|
|
94
133
|
const leading = getLeadingComments(node);
|
|
95
134
|
const comments = getCommentsBefore(sourceCode, node);
|
|
96
135
|
const all = combineComments(leading, comments);
|
|
97
136
|
const hasReq = hasReqAnnotation(jsdoc, all);
|
|
137
|
+
// BRANCH when a @req annotation is missing and must be reported
|
|
138
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
139
|
+
// @req REQ-ANNOTATION-REQ-DETECTION
|
|
140
|
+
// @req REQ-ANNOTATION-REPORTING
|
|
98
141
|
if (!hasReq) {
|
|
99
|
-
reportMissing(context, node);
|
|
142
|
+
reportMissing(context, node, enableFix);
|
|
100
143
|
}
|
|
101
144
|
}
|
|
@@ -28,6 +28,10 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
|
28
28
|
name: "[REQ-AUTOFIX-MISSING] already annotated function is unchanged",
|
|
29
29
|
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction alreadyAnnotated() {}`,
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
name: "[REQ-AUTOFIX-MISSING] already annotated class method is unchanged",
|
|
33
|
+
code: `class A {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
34
|
+
},
|
|
31
35
|
],
|
|
32
36
|
invalid: [
|
|
33
37
|
{
|
|
@@ -46,6 +50,78 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
|
46
50
|
},
|
|
47
51
|
],
|
|
48
52
|
},
|
|
53
|
+
{
|
|
54
|
+
name: "[REQ-AUTOFIX-MISSING] adds @story before function expression when missing",
|
|
55
|
+
code: `const fnExpr = function() {};`,
|
|
56
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
|
|
57
|
+
errors: [
|
|
58
|
+
{
|
|
59
|
+
messageId: "missingStory",
|
|
60
|
+
suggestions: [
|
|
61
|
+
{
|
|
62
|
+
desc: "Add JSDoc @story annotation for function 'fnExpr', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
63
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "[REQ-AUTOFIX-MISSING] adds @story before class method when missing",
|
|
71
|
+
code: `class C {\n method() {}\n}`,
|
|
72
|
+
output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
73
|
+
errors: [
|
|
74
|
+
{
|
|
75
|
+
messageId: "missingStory",
|
|
76
|
+
suggestions: [
|
|
77
|
+
{
|
|
78
|
+
desc: "Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
79
|
+
output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "[REQ-AUTOFIX-MISSING] adds @story before TS declare function when missing",
|
|
87
|
+
code: `declare function tsDecl(): void;`,
|
|
88
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
|
|
89
|
+
languageOptions: {
|
|
90
|
+
parser: require("@typescript-eslint/parser"),
|
|
91
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
92
|
+
},
|
|
93
|
+
errors: [
|
|
94
|
+
{
|
|
95
|
+
messageId: "missingStory",
|
|
96
|
+
suggestions: [
|
|
97
|
+
{
|
|
98
|
+
desc: "Add JSDoc @story annotation for function 'tsDecl', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
99
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "[REQ-AUTOFIX-MISSING] adds @story before TS method signature when missing",
|
|
107
|
+
code: `interface D {\n method(): void;\n}`,
|
|
108
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
|
|
109
|
+
languageOptions: {
|
|
110
|
+
parser: require("@typescript-eslint/parser"),
|
|
111
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
112
|
+
},
|
|
113
|
+
errors: [
|
|
114
|
+
{
|
|
115
|
+
messageId: "missingStory",
|
|
116
|
+
suggestions: [
|
|
117
|
+
{
|
|
118
|
+
desc: "Add JSDoc @story annotation for function 'method', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
119
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
49
125
|
],
|
|
50
126
|
});
|
|
51
127
|
});
|
|
@@ -44,24 +44,80 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
44
44
|
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
|
+
{
|
|
48
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] valid FunctionExpression with @req annotation",
|
|
49
|
+
code: `const fn = /**\n * @req REQ-EXAMPLE\n */\nfunction() {};`,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] valid MethodDefinition with @req annotation",
|
|
53
|
+
code: `class C {\n /**\n * @req REQ-EXAMPLE\n */\n m() {}\n}`,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] valid TS FunctionExpression in variable declarator with @req",
|
|
57
|
+
code: `const fn = /**\n * @req REQ-EXAMPLE\n */\nfunction () {};`,
|
|
58
|
+
languageOptions: {
|
|
59
|
+
parser: require("@typescript-eslint/parser"),
|
|
60
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] valid exported TS FunctionExpression in variable declarator with @req",
|
|
65
|
+
code: `export const fn = /**\n * @req REQ-EXAMPLE\n */\nfunction () {};`,
|
|
66
|
+
languageOptions: {
|
|
67
|
+
parser: require("@typescript-eslint/parser"),
|
|
68
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "[REQ-CONFIGURABLE-SCOPE][Story 003.0] FunctionExpression ignored when scope only includes FunctionDeclaration",
|
|
73
|
+
code: `const fn = function () {};`,
|
|
74
|
+
options: [{ scope: ["FunctionDeclaration"] }],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported function ignored when exportPriority is 'exported'",
|
|
78
|
+
code: `function nonExported() {}`,
|
|
79
|
+
options: [{ exportPriority: "exported" }],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported function required when exportPriority is 'exported'",
|
|
83
|
+
code: `/** @req REQ-EXAMPLE */\nexport function exportedFn() {}`,
|
|
84
|
+
options: [{ exportPriority: "exported" }],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported function ignored when exportPriority is 'non-exported'",
|
|
88
|
+
code: `export function exportedFn() {}`,
|
|
89
|
+
options: [{ exportPriority: "non-exported" }],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported function required when exportPriority is 'non-exported'",
|
|
93
|
+
code: `/** @req REQ-EXAMPLE */\nfunction nonExported() {}`,
|
|
94
|
+
options: [{ exportPriority: "non-exported" }],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported method ignored when exportPriority is 'non-exported'",
|
|
98
|
+
code: `export class C {\n m() {}\n}`,
|
|
99
|
+
options: [{ exportPriority: "non-exported" }],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported method required when exportPriority is 'non-exported'",
|
|
103
|
+
code: `class C {\n /** @req REQ-EXAMPLE */\n m() {}\n}`,
|
|
104
|
+
options: [{ exportPriority: "non-exported" }],
|
|
105
|
+
},
|
|
47
106
|
],
|
|
48
107
|
invalid: [
|
|
49
108
|
{
|
|
50
109
|
name: "[REQ-ANNOTATION-REQUIRED] missing @req on function without JSDoc",
|
|
51
110
|
code: `function baz() {}`,
|
|
52
|
-
output: `/** @req <REQ-ID> */\nfunction baz() {}`,
|
|
53
111
|
errors: [{ messageId: "missingReq", data: { name: "baz" } }],
|
|
54
112
|
},
|
|
55
113
|
{
|
|
56
114
|
name: "[REQ-ANNOTATION-REQUIRED] missing @req on function with only @story annotation",
|
|
57
115
|
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction qux() {}`,
|
|
58
|
-
output: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\n/** @req <REQ-ID> */\nfunction qux() {}`,
|
|
59
116
|
errors: [{ messageId: "missingReq", data: { name: "qux" } }],
|
|
60
117
|
},
|
|
61
118
|
{
|
|
62
119
|
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSDeclareFunction",
|
|
63
120
|
code: `declare function baz(): void;`,
|
|
64
|
-
output: `/** @req <REQ-ID> */\ndeclare function baz(): void;`,
|
|
65
121
|
errors: [{ messageId: "missingReq", data: { name: "baz" } }],
|
|
66
122
|
languageOptions: {
|
|
67
123
|
parser: require("@typescript-eslint/parser"),
|
|
@@ -71,13 +127,92 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
71
127
|
{
|
|
72
128
|
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSMethodSignature",
|
|
73
129
|
code: `interface I { method(): void; }`,
|
|
74
|
-
output: `interface I { /** @req <REQ-ID> */\nmethod(): void; }`,
|
|
75
130
|
errors: [{ messageId: "missingReq", data: { name: "method" } }],
|
|
76
131
|
languageOptions: {
|
|
77
132
|
parser: require("@typescript-eslint/parser"),
|
|
78
133
|
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
79
134
|
},
|
|
80
135
|
},
|
|
136
|
+
{
|
|
137
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] missing @req on FunctionExpression assigned to variable",
|
|
138
|
+
code: `const fn = function () {};`,
|
|
139
|
+
errors: [{ messageId: "missingReq", data: { name: "fn" } }],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] missing @req on anonymous FunctionExpression (no variable name)",
|
|
143
|
+
code: `(function () {})();`,
|
|
144
|
+
errors: [{ messageId: "missingReq", data: { name: "(anonymous)" } }],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] missing @req on MethodDefinition in class",
|
|
148
|
+
code: `class C {\n m() {}\n}`,
|
|
149
|
+
errors: [{ messageId: "missingReq" }],
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "[REQ-FUNCTION-DETECTION][Story 003.0] missing @req on MethodDefinition in object literal",
|
|
153
|
+
code: `const o = { m() {} };`,
|
|
154
|
+
errors: [{ messageId: "missingReq" }],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] missing @req on TS FunctionExpression in variable declarator",
|
|
158
|
+
code: `const fn = function () {};`,
|
|
159
|
+
errors: [{ messageId: "missingReq", data: { name: "fn" } }],
|
|
160
|
+
languageOptions: {
|
|
161
|
+
parser: require("@typescript-eslint/parser"),
|
|
162
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] missing @req on exported TS FunctionExpression in variable declarator",
|
|
167
|
+
code: `export const fn = function () {};`,
|
|
168
|
+
errors: [{ messageId: "missingReq", data: { name: "fn" } }],
|
|
169
|
+
languageOptions: {
|
|
170
|
+
parser: require("@typescript-eslint/parser"),
|
|
171
|
+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "[REQ-CONFIGURABLE-SCOPE][Story 003.0] FunctionDeclaration still reported when scope only includes FunctionDeclaration",
|
|
176
|
+
code: `function scoped() {}`,
|
|
177
|
+
options: [{ scope: ["FunctionDeclaration"] }],
|
|
178
|
+
errors: [{ messageId: "missingReq", data: { name: "scoped" } }],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported function reported when exportPriority is 'exported'",
|
|
182
|
+
code: `export function exportedFn() {}`,
|
|
183
|
+
options: [{ exportPriority: "exported" }],
|
|
184
|
+
errors: [{ messageId: "missingReq", data: { name: "exportedFn" } }],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported function reported when exportPriority is 'non-exported'",
|
|
188
|
+
code: `function nonExported() {}`,
|
|
189
|
+
options: [{ exportPriority: "non-exported" }],
|
|
190
|
+
errors: [{ messageId: "missingReq", data: { name: "nonExported" } }],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported method reported when exportPriority is 'exported'",
|
|
194
|
+
code: `export class C {\n m() {}\n}`,
|
|
195
|
+
errors: [{ messageId: "missingReq" }],
|
|
196
|
+
options: [{ exportPriority: "exported" }],
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported method reported when exportPriority is 'non-exported'",
|
|
200
|
+
code: `class C {\n m() {}\n}`,
|
|
201
|
+
errors: [{ messageId: "missingReq" }],
|
|
202
|
+
options: [{ exportPriority: "non-exported" }],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] exported FunctionExpression reported when exportPriority is 'exported'",
|
|
206
|
+
code: `export const fn = function () {};`,
|
|
207
|
+
options: [{ exportPriority: "exported" }],
|
|
208
|
+
errors: [{ messageId: "missingReq", data: { name: "fn" } }],
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "[REQ-EXPORT-PRIORITY][Story 003.0] non-exported FunctionExpression reported when exportPriority is 'non-exported'",
|
|
212
|
+
code: `const fn = function () {};`,
|
|
213
|
+
options: [{ exportPriority: "non-exported" }],
|
|
214
|
+
errors: [{ messageId: "missingReq", data: { name: "fn" } }],
|
|
215
|
+
},
|
|
81
216
|
],
|
|
82
217
|
});
|
|
83
218
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
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",
|