eslint-plugin-traceability 1.21.1 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -2
- package/README.md +3 -4
- package/lib/src/maintenance/batch.js +0 -2
- package/lib/src/maintenance/cli.js +8 -11
- package/lib/src/maintenance/commands.d.ts +2 -2
- package/lib/src/maintenance/commands.js +2 -3
- package/lib/src/maintenance/detect.js +7 -8
- package/lib/src/maintenance/report.js +2 -3
- package/lib/src/maintenance/storyParser.d.ts +16 -0
- package/lib/src/maintenance/storyParser.js +167 -0
- package/lib/src/maintenance/update.js +0 -1
- package/lib/src/rules/helpers/pattern-validators.d.ts +42 -0
- package/lib/src/rules/helpers/pattern-validators.js +65 -0
- package/lib/src/rules/helpers/prefer-implements-inline.d.ts +16 -0
- package/lib/src/rules/helpers/prefer-implements-inline.js +146 -0
- package/lib/src/rules/helpers/require-story-comment-detection.d.ts +47 -0
- package/lib/src/rules/helpers/require-story-comment-detection.js +141 -0
- package/lib/src/rules/helpers/require-story-core.d.ts +6 -6
- package/lib/src/rules/helpers/require-story-core.js +10 -11
- package/lib/src/rules/helpers/require-story-helpers.d.ts +5 -63
- package/lib/src/rules/helpers/require-story-helpers.js +29 -337
- package/lib/src/rules/helpers/require-story-name-extraction.d.ts +35 -0
- package/lib/src/rules/helpers/require-story-name-extraction.js +107 -0
- package/lib/src/rules/helpers/require-story-node-utils.d.ts +43 -0
- package/lib/src/rules/helpers/require-story-node-utils.js +115 -0
- package/lib/src/rules/helpers/valid-annotation-format-internal.js +11 -3
- package/lib/src/rules/helpers/valid-annotation-options.d.ts +0 -10
- package/lib/src/rules/helpers/valid-annotation-options.js +22 -92
- package/lib/src/rules/helpers/valid-req-reference-helpers.js +0 -1
- package/lib/src/rules/no-redundant-annotation.js +4 -238
- package/lib/src/rules/prefer-implements-annotation.d.ts +12 -0
- package/lib/src/rules/prefer-implements-annotation.js +9 -164
- package/lib/src/rules/require-traceability.d.ts +8 -0
- package/lib/src/rules/require-traceability.js +8 -0
- package/lib/src/utils/annotation-checker.d.ts +3 -2
- package/lib/src/utils/annotation-checker.js +3 -2
- package/lib/src/utils/branch-annotation-catch-helpers.d.ts +22 -0
- package/lib/src/utils/branch-annotation-catch-helpers.js +70 -0
- package/lib/src/utils/branch-annotation-helpers.js +11 -187
- package/lib/src/utils/branch-annotation-if-helpers.d.ts +1 -0
- package/lib/src/utils/branch-annotation-if-helpers.js +59 -0
- package/lib/src/utils/branch-annotation-indent-helpers.d.ts +1 -1
- package/lib/src/utils/branch-annotation-switch-helpers.d.ts +8 -2
- package/lib/src/utils/branch-annotation-switch-helpers.js +10 -4
- package/lib/src/utils/branch-validation.d.ts +9 -0
- package/lib/src/utils/branch-validation.js +58 -0
- package/lib/src/utils/comment-text-helpers.d.ts +31 -0
- package/lib/src/utils/comment-text-helpers.js +54 -0
- package/lib/src/utils/redundancy-detector.d.ts +85 -0
- package/lib/src/utils/redundancy-detector.js +235 -0
- package/lib/tests/maintenance/storyParser.test.d.ts +8 -0
- package/lib/tests/maintenance/storyParser.test.js +505 -0
- package/lib/tests/rules/no-redundant-annotation.test.js +1 -0
- package/lib/tests/rules/require-story-helpers.test.js +3 -2
- package/lib/tests/rules/valid-req-reference.test.js +2 -0
- package/package.json +18 -10
- package/user-docs/api-reference.md +2 -2
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAnonymousArrowFunction = isAnonymousArrowFunction;
|
|
4
|
+
exports.isNestedFunction = isNestedFunction;
|
|
5
|
+
exports.isEffectivelyAnonymousFunction = isEffectivelyAnonymousFunction;
|
|
6
|
+
exports.isExportedNode = isExportedNode;
|
|
7
|
+
exports.resolveTargetNode = resolveTargetNode;
|
|
8
|
+
exports.resolveAnnotationTargetNode = resolveAnnotationTargetNode;
|
|
9
|
+
/**
|
|
10
|
+
* Node classification utilities for require-story rule
|
|
11
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
12
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
13
|
+
* @req REQ-ANNOTATION-REQUIRED - File-level header for node utility functions
|
|
14
|
+
*/
|
|
15
|
+
const require_story_name_extraction_1 = require("./require-story-name-extraction");
|
|
16
|
+
/**
|
|
17
|
+
* Determine whether a node represents an anonymous arrow function expression
|
|
18
|
+
* where the parent variable declarator has no explicit Identifier name.
|
|
19
|
+
*
|
|
20
|
+
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-ARROW-FUNCTION-EXCLUDED
|
|
21
|
+
*/
|
|
22
|
+
function isAnonymousArrowFunction(node) {
|
|
23
|
+
return !!node && node.type === "ArrowFunctionExpression";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Determine whether a function-like node is nested within another function.
|
|
27
|
+
*
|
|
28
|
+
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-NESTED-FUNCTION-INHERITANCE
|
|
29
|
+
*/
|
|
30
|
+
function isNestedFunction(node) {
|
|
31
|
+
let current = node?.parent;
|
|
32
|
+
while (current) {
|
|
33
|
+
if (current.type === "FunctionDeclaration" ||
|
|
34
|
+
current.type === "FunctionExpression" ||
|
|
35
|
+
current.type === "ArrowFunctionExpression" ||
|
|
36
|
+
current.type === "MethodDefinition" ||
|
|
37
|
+
current.type === "TSDeclareFunction" ||
|
|
38
|
+
current.type === "TSMethodSignature") {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
current = current.parent;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Determine whether a function-like node is effectively anonymous for the
|
|
47
|
+
* purposes of nested-function inheritance. Named functions must always carry
|
|
48
|
+
* their own annotations, while anonymous nested functions may inherit.
|
|
49
|
+
*
|
|
50
|
+
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-NESTED-FUNCTION-INHERITANCE
|
|
51
|
+
*/
|
|
52
|
+
function isEffectivelyAnonymousFunction(node) {
|
|
53
|
+
const name = (0, require_story_name_extraction_1.getContainerKeyOrIdName)(node) ?? (0, require_story_name_extraction_1.getDirectIdentifierName)(node);
|
|
54
|
+
if (typeof name === "string" && name.length > 0 && name !== "(anonymous)") {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Determine if a node is in an export declaration
|
|
61
|
+
*
|
|
62
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
63
|
+
* @req REQ-ANNOTATION-REQUIRED - Check node ancestry to find export declarations
|
|
64
|
+
*/
|
|
65
|
+
function isExportedNode(node) {
|
|
66
|
+
let p = node.parent;
|
|
67
|
+
while (p) {
|
|
68
|
+
if (p.type === "ExportNamedDeclaration" ||
|
|
69
|
+
p.type === "ExportDefaultDeclaration") {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
p = p.parent;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Determine AST node where annotation should be inserted
|
|
78
|
+
*
|
|
79
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
80
|
+
* @req REQ-ANNOTATION-REQUIRED - Determine correct insertion target for annotation
|
|
81
|
+
*/
|
|
82
|
+
function resolveTargetNode(sourceCode, node) {
|
|
83
|
+
if (node.type === "TSMethodSignature") {
|
|
84
|
+
// Interface method signature -> insert on interface
|
|
85
|
+
return node.parent.parent;
|
|
86
|
+
}
|
|
87
|
+
if (node.type === "FunctionExpression" ||
|
|
88
|
+
node.type === "ArrowFunctionExpression") {
|
|
89
|
+
const parent = node.parent;
|
|
90
|
+
if (parent.type === "VariableDeclarator") {
|
|
91
|
+
const varDecl = parent.parent;
|
|
92
|
+
if (varDecl.parent && varDecl.parent.type === "ExportNamedDeclaration") {
|
|
93
|
+
return varDecl.parent;
|
|
94
|
+
}
|
|
95
|
+
return varDecl;
|
|
96
|
+
}
|
|
97
|
+
if (parent.type === "ExportNamedDeclaration") {
|
|
98
|
+
return parent;
|
|
99
|
+
}
|
|
100
|
+
if (parent.type === "ExpressionStatement") {
|
|
101
|
+
return parent;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return node;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Resolve the node that should receive the `@story` annotation,
|
|
108
|
+
* respecting an explicitly passed target when provided.
|
|
109
|
+
*
|
|
110
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
111
|
+
* @req REQ-ANNOTATION-REQUIRED - Centralize annotation target node resolution
|
|
112
|
+
*/
|
|
113
|
+
function resolveAnnotationTargetNode(sourceCode, node, passedTarget) {
|
|
114
|
+
return passedTarget ?? resolveTargetNode(sourceCode, node);
|
|
115
|
+
}
|
|
@@ -29,12 +29,20 @@ function normalizeCommentLine(rawLine) {
|
|
|
29
29
|
// This ensures annotations that appear outside code spans are still
|
|
30
30
|
// detected at their original indices.
|
|
31
31
|
const filtered = trimmed.replace(/`[^`]*`/g, (match) => " ".repeat(match.length));
|
|
32
|
-
|
|
32
|
+
// Remove leading star first to normalize JSDoc format
|
|
33
|
+
const withoutLeadingStar = filtered.replace(/^\*\s?/, "");
|
|
34
|
+
// Check if the line starts with a non-traceability JSDoc tag (e.g., @param, @returns)
|
|
35
|
+
// If so, return the whole line as-is to avoid false positives where annotation
|
|
36
|
+
// keywords appear in the tag's description (e.g., "`@returns` ... `@story` annotations")
|
|
37
|
+
if (/^@(?!story\b|req\b|supports\b)/.test(withoutLeadingStar)) {
|
|
38
|
+
return withoutLeadingStar;
|
|
39
|
+
}
|
|
40
|
+
// Otherwise, check for traceability annotations and slice to them if found
|
|
41
|
+
const annotationMatch = withoutLeadingStar.match(/@story\b|@req\b|@supports\b/);
|
|
33
42
|
if (!annotationMatch || annotationMatch.index === undefined) {
|
|
34
|
-
const withoutLeadingStar = filtered.replace(/^\*\s?/, "");
|
|
35
43
|
return withoutLeadingStar;
|
|
36
44
|
}
|
|
37
|
-
return
|
|
45
|
+
return withoutLeadingStar.slice(annotationMatch.index);
|
|
38
46
|
}
|
|
39
47
|
/**
|
|
40
48
|
* Detect whether a normalized comment line starts with a non-traceability JSDoc tag.
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared option handling for the valid-annotation-format rule.
|
|
3
|
-
*
|
|
4
|
-
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
5
|
-
* @req REQ-PATTERN-CONFIG - Support configuration of custom story path and requirement ID patterns
|
|
6
|
-
* @req REQ-REGEX-VALIDATION - Validate that configured patterns are valid regular expressions
|
|
7
|
-
* @req REQ-BACKWARD-COMPAT - Maintain current behavior when no custom patterns configured
|
|
8
|
-
* @req REQ-EXAMPLE-MESSAGES - Support optional example strings in error messages
|
|
9
|
-
* @req REQ-SCHEMA-VALIDATION - Use JSON Schema to validate configuration options
|
|
10
|
-
*/
|
|
11
1
|
export interface AnnotationRuleOptions {
|
|
12
2
|
story?: {
|
|
13
3
|
/**
|
|
@@ -5,6 +5,17 @@ exports.getResolvedDefaults = getResolvedDefaults;
|
|
|
5
5
|
exports.getOptionErrors = getOptionErrors;
|
|
6
6
|
exports.resolveOptions = resolveOptions;
|
|
7
7
|
exports.getRuleSchema = getRuleSchema;
|
|
8
|
+
/**
|
|
9
|
+
* Shared option handling for the valid-annotation-format rule.
|
|
10
|
+
*
|
|
11
|
+
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
12
|
+
* @req REQ-PATTERN-CONFIG - Support configuration of custom story path and requirement ID patterns
|
|
13
|
+
* @req REQ-REGEX-VALIDATION - Validate that configured patterns are valid regular expressions
|
|
14
|
+
* @req REQ-BACKWARD-COMPAT - Maintain current behavior when no custom patterns configured
|
|
15
|
+
* @req REQ-EXAMPLE-MESSAGES - Support optional example strings in error messages
|
|
16
|
+
* @req REQ-SCHEMA-VALIDATION - Use JSON Schema to validate configuration options
|
|
17
|
+
*/
|
|
18
|
+
const pattern_validators_1 = require("./pattern-validators");
|
|
8
19
|
/**
|
|
9
20
|
* Get the default regular expression used to validate story paths.
|
|
10
21
|
*
|
|
@@ -84,92 +95,6 @@ function getOptionErrors() {
|
|
|
84
95
|
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
85
96
|
* @req REQ-PATTERN-CONFIG - Provide consistent regex validation diagnostics
|
|
86
97
|
*/
|
|
87
|
-
function buildInvalidRegexError(field, pattern) {
|
|
88
|
-
return `Invalid regular expression for option "${field}": "${pattern}"`;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Normalize raw rule options into a single AnnotationRuleOptions object.
|
|
92
|
-
*
|
|
93
|
-
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
94
|
-
* @req REQ-PATTERN-CONFIG
|
|
95
|
-
* @req REQ-BACKWARD-COMPAT
|
|
96
|
-
*/
|
|
97
|
-
function normalizeUserOptions(rawOptions) {
|
|
98
|
-
if (!rawOptions || rawOptions.length === 0) {
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
|
-
const first = rawOptions[0];
|
|
102
|
-
if (!first || typeof first !== "object") {
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
return first;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Resolve a user-configured regex pattern, handling both nested and flat
|
|
109
|
-
* configuration shapes and accumulating validation errors.
|
|
110
|
-
*
|
|
111
|
-
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
112
|
-
* @req REQ-PATTERN-CONFIG
|
|
113
|
-
* @req REQ-REGEX-VALIDATION
|
|
114
|
-
* @req REQ-BACKWARD-COMPAT
|
|
115
|
-
*/
|
|
116
|
-
function resolvePattern({ nestedPattern, nestedFieldName, flatPattern, flatFieldName, defaultPattern, }) {
|
|
117
|
-
const effective = typeof nestedPattern === "string"
|
|
118
|
-
? { value: nestedPattern, field: nestedFieldName }
|
|
119
|
-
: typeof flatPattern === "string"
|
|
120
|
-
? { value: flatPattern, field: flatFieldName }
|
|
121
|
-
: null;
|
|
122
|
-
if (!effective) {
|
|
123
|
-
return defaultPattern;
|
|
124
|
-
}
|
|
125
|
-
try {
|
|
126
|
-
return new RegExp(effective.value);
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
optionErrors.push(buildInvalidRegexError(effective.field, effective.value));
|
|
130
|
-
return defaultPattern;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Resolve an example string, preferring nested over flat configuration,
|
|
135
|
-
* and falling back to the provided default when necessary.
|
|
136
|
-
*
|
|
137
|
-
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
138
|
-
* @req REQ-EXAMPLE-MESSAGES
|
|
139
|
-
* @req REQ-BACKWARD-COMPAT
|
|
140
|
-
*/
|
|
141
|
-
function resolveExample(nestedExample, flatExample, defaultExample) {
|
|
142
|
-
if (typeof nestedExample === "string" && nestedExample.trim()) {
|
|
143
|
-
return nestedExample;
|
|
144
|
-
}
|
|
145
|
-
if (typeof flatExample === "string" && flatExample.trim()) {
|
|
146
|
-
return flatExample;
|
|
147
|
-
}
|
|
148
|
-
return defaultExample;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Extract and normalize user-provided options from the raw ESLint
|
|
152
|
-
* options array into an AnnotationRuleOptions object.
|
|
153
|
-
*
|
|
154
|
-
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
155
|
-
* @req REQ-PATTERN-CONFIG - Accept structured configuration for patterns
|
|
156
|
-
* @req REQ-BACKWARD-COMPAT - Tolerate missing or malformed options
|
|
157
|
-
*/
|
|
158
|
-
function getUserOptions(rawOptions) {
|
|
159
|
-
return normalizeUserOptions(rawOptions);
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Resolve the auto-fix flag, defaulting to true when the option
|
|
163
|
-
* is not explicitly provided by the user.
|
|
164
|
-
*
|
|
165
|
-
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
166
|
-
* @req REQ-PATTERN-CONFIG - Support configuration of fix behavior
|
|
167
|
-
* @req REQ-BACKWARD-COMPAT - Preserve default auto-fix behavior
|
|
168
|
-
*/
|
|
169
|
-
function resolveAutoFixFlag(user) {
|
|
170
|
-
const autoFixFlag = user?.autoFix;
|
|
171
|
-
return typeof autoFixFlag === "boolean" ? autoFixFlag : true;
|
|
172
|
-
}
|
|
173
98
|
/**
|
|
174
99
|
* Resolve the story path pattern from nested or flat configuration
|
|
175
100
|
* fields, validating and falling back to the default as needed.
|
|
@@ -180,12 +105,13 @@ function resolveAutoFixFlag(user) {
|
|
|
180
105
|
* @req REQ-BACKWARD-COMPAT - Use a default when no pattern is provided
|
|
181
106
|
*/
|
|
182
107
|
function resolveStoryPattern(nestedStoryPattern, flatStoryPattern) {
|
|
183
|
-
return resolvePattern({
|
|
108
|
+
return (0, pattern_validators_1.resolvePattern)({
|
|
184
109
|
nestedPattern: nestedStoryPattern,
|
|
185
110
|
nestedFieldName: "story.pattern",
|
|
186
111
|
flatPattern: flatStoryPattern,
|
|
187
112
|
flatFieldName: "storyPathPattern",
|
|
188
113
|
defaultPattern: getDefaultStoryPattern(),
|
|
114
|
+
errors: optionErrors,
|
|
189
115
|
});
|
|
190
116
|
}
|
|
191
117
|
/**
|
|
@@ -198,12 +124,13 @@ function resolveStoryPattern(nestedStoryPattern, flatStoryPattern) {
|
|
|
198
124
|
* @req REQ-BACKWARD-COMPAT - Use a default when no pattern is provided
|
|
199
125
|
*/
|
|
200
126
|
function resolveReqPattern(nestedReqPattern, flatReqPattern) {
|
|
201
|
-
return resolvePattern({
|
|
127
|
+
return (0, pattern_validators_1.resolvePattern)({
|
|
202
128
|
nestedPattern: nestedReqPattern,
|
|
203
129
|
nestedFieldName: "req.pattern",
|
|
204
130
|
flatPattern: flatReqPattern,
|
|
205
131
|
flatFieldName: "requirementIdPattern",
|
|
206
132
|
defaultPattern: getDefaultReqPattern(),
|
|
133
|
+
errors: optionErrors,
|
|
207
134
|
});
|
|
208
135
|
}
|
|
209
136
|
/**
|
|
@@ -215,7 +142,7 @@ function resolveReqPattern(nestedReqPattern, flatReqPattern) {
|
|
|
215
142
|
* @req REQ-BACKWARD-COMPAT - Use a default story example when omitted
|
|
216
143
|
*/
|
|
217
144
|
function resolveStoryExample(nestedStoryExample, flatStoryExample) {
|
|
218
|
-
return resolveExample(nestedStoryExample, flatStoryExample, getDefaultStoryExample());
|
|
145
|
+
return (0, pattern_validators_1.resolveExample)(nestedStoryExample, flatStoryExample, getDefaultStoryExample());
|
|
219
146
|
}
|
|
220
147
|
/**
|
|
221
148
|
* Resolve the requirement ID example string from nested or flat configuration
|
|
@@ -226,7 +153,7 @@ function resolveStoryExample(nestedStoryExample, flatStoryExample) {
|
|
|
226
153
|
* @req REQ-BACKWARD-COMPAT - Use a default requirement ID example when omitted
|
|
227
154
|
*/
|
|
228
155
|
function resolveReqExample(nestedReqExample, flatReqExample) {
|
|
229
|
-
return resolveExample(nestedReqExample, flatReqExample, getDefaultReqExample());
|
|
156
|
+
return (0, pattern_validators_1.resolveExample)(nestedReqExample, flatReqExample, getDefaultReqExample());
|
|
230
157
|
}
|
|
231
158
|
/**
|
|
232
159
|
* Collect user-provided story pattern inputs from both nested and flat
|
|
@@ -298,7 +225,8 @@ function resolveOptionsInternal(user) {
|
|
|
298
225
|
const { nestedStoryExample, flatStoryExample } = getStoryExampleInputs(user);
|
|
299
226
|
const { nestedReqPattern, flatReqPattern } = getReqPatternInputs(user);
|
|
300
227
|
const { nestedReqExample, flatReqExample } = getReqExampleInputs(user);
|
|
301
|
-
const
|
|
228
|
+
const autoFixFlag = user?.autoFix;
|
|
229
|
+
const autoFix = typeof autoFixFlag === "boolean" ? autoFixFlag : true;
|
|
302
230
|
const storyPattern = resolveStoryPattern(nestedStoryPattern, flatStoryPattern);
|
|
303
231
|
const reqPattern = resolveReqPattern(nestedReqPattern, flatReqPattern);
|
|
304
232
|
const storyExample = resolveStoryExample(nestedStoryExample, flatStoryExample);
|
|
@@ -322,7 +250,9 @@ function resolveOptionsInternal(user) {
|
|
|
322
250
|
*/
|
|
323
251
|
function resolveOptions(rawOptions) {
|
|
324
252
|
optionErrors = [];
|
|
325
|
-
const user =
|
|
253
|
+
const user = rawOptions && rawOptions.length > 0 && typeof rawOptions[0] === "object"
|
|
254
|
+
? rawOptions[0]
|
|
255
|
+
: undefined;
|
|
326
256
|
const resolved = resolveOptionsInternal(user);
|
|
327
257
|
resolvedDefaults = resolved;
|
|
328
258
|
return resolvedDefaults;
|
|
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createValidReqReferenceProgramVisitor = createValidReqReferenceProgramVisitor;
|
|
7
7
|
/* eslint-disable traceability/valid-annotation-format */
|
|
8
|
-
/* eslint-env node */
|
|
9
8
|
/**
|
|
10
9
|
* Helper utilities for the "valid-req-reference" rule.
|
|
11
10
|
*
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
const annotation_scope_analyzer_1 = require("../utils/annotation-scope-analyzer");
|
|
3
|
+
const redundancy_detector_1 = require("../utils/redundancy-detector");
|
|
5
4
|
/**
|
|
6
5
|
* ESLint rule to detect redundant traceability annotations on statements
|
|
7
6
|
* that are already covered by their containing scope.
|
|
@@ -47,239 +46,6 @@ function normalizeOptions(raw) {
|
|
|
47
46
|
alwaysCovered,
|
|
48
47
|
};
|
|
49
48
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Collect comments around a scope node using JSDoc, leading comments,
|
|
52
|
-
* and any comments that appear immediately before the node.
|
|
53
|
-
*
|
|
54
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE
|
|
55
|
-
*/
|
|
56
|
-
function getScopeCommentsFromJSDocAndLeading(sourceCode, scopeNode) {
|
|
57
|
-
const comments = [];
|
|
58
|
-
const jsdoc = sourceCode.getJSDocComment
|
|
59
|
-
? sourceCode.getJSDocComment(scopeNode)
|
|
60
|
-
: null;
|
|
61
|
-
const before = sourceCode.getCommentsBefore
|
|
62
|
-
? sourceCode.getCommentsBefore(scopeNode) || []
|
|
63
|
-
: [];
|
|
64
|
-
if (jsdoc) {
|
|
65
|
-
comments.push(jsdoc);
|
|
66
|
-
}
|
|
67
|
-
if (Array.isArray(scopeNode.leadingComments)) {
|
|
68
|
-
comments.push(...scopeNode.leadingComments);
|
|
69
|
-
}
|
|
70
|
-
comments.push(...before);
|
|
71
|
-
return comments;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Compute the story/requirement pairs for annotations that apply to the
|
|
75
|
-
* given scope node.
|
|
76
|
-
*
|
|
77
|
-
* For branch scopes we reuse the same comment-gathering helper used by
|
|
78
|
-
* the require-branch-annotation rule so that REQ-SCOPE-INHERITANCE
|
|
79
|
-
* aligns with existing behavior. For non-branch scopes, we reuse a
|
|
80
|
-
* shared helper that collects JSDoc, leading, and immediately-before
|
|
81
|
-
* comments around the scope node.
|
|
82
|
-
*
|
|
83
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE
|
|
84
|
-
*/
|
|
85
|
-
function getScopePairs(context, scopeNode, parent) {
|
|
86
|
-
const sourceCode = context.getSourceCode();
|
|
87
|
-
// Branch-style scope: use the branch helpers to collect comment text.
|
|
88
|
-
if (branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES.includes(scopeNode.type)) {
|
|
89
|
-
/**
|
|
90
|
-
* Inside-brace annotations used as branch-level indicators (inside placement
|
|
91
|
-
* mode) should not be folded into scopePairs for redundancy purposes; only
|
|
92
|
-
* before-brace annotations define the covering scope here.
|
|
93
|
-
*
|
|
94
|
-
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-NON-REDUNDANT-INSIDE REQ-PLACEMENT-CONFIG
|
|
95
|
-
*/
|
|
96
|
-
const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, scopeNode, parent, "before");
|
|
97
|
-
return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromText)(text);
|
|
98
|
-
}
|
|
99
|
-
const comments = getScopeCommentsFromJSDocAndLeading(sourceCode, scopeNode);
|
|
100
|
-
return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromComments)(comments);
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Collect the comments directly associated with a statement node.
|
|
104
|
-
*
|
|
105
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-STATEMENT-SIGNIFICANCE REQ-SCOPE-ANALYSIS
|
|
106
|
-
*/
|
|
107
|
-
function getStatementComments(context, node) {
|
|
108
|
-
const sourceCode = context.getSourceCode();
|
|
109
|
-
const comments = [];
|
|
110
|
-
if (sourceCode.getCommentsBefore) {
|
|
111
|
-
comments.push(...(sourceCode.getCommentsBefore(node) || []));
|
|
112
|
-
}
|
|
113
|
-
if (Array.isArray(node.leadingComments)) {
|
|
114
|
-
comments.push(...node.leadingComments);
|
|
115
|
-
}
|
|
116
|
-
return comments;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Debug helper for logging scope-level pairs in TRACEABILITY_DEBUG mode.
|
|
120
|
-
*
|
|
121
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS
|
|
122
|
-
*/
|
|
123
|
-
function debugScopePairs(scopeNode, scopePairs) {
|
|
124
|
-
if (process.env.TRACEABILITY_DEBUG !== "1") {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
console.log("[no-redundant-annotation] Scope node type=%s pairs=%o", scopeNode && scopeNode.type, Array.from(scopePairs));
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Walk up enclosing scopes starting from the given scope node and
|
|
131
|
-
* accumulate all story/requirement pairs, limited by maxScopeDepth.
|
|
132
|
-
*
|
|
133
|
-
* This keeps REQ-SCOPE-INHERITANCE and REQ-CONFIGURABLE-STRICTNESS
|
|
134
|
-
* aligned with the story's configuration model while delegating the
|
|
135
|
-
* actual comment parsing to getScopePairs.
|
|
136
|
-
*
|
|
137
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE REQ-CONFIGURABLE-STRICTNESS
|
|
138
|
-
*/
|
|
139
|
-
function collectScopePairs(context, startingScopeNode, maxScopeDepth) {
|
|
140
|
-
const result = new Set();
|
|
141
|
-
if (!startingScopeNode || maxScopeDepth <= 0) {
|
|
142
|
-
return result;
|
|
143
|
-
}
|
|
144
|
-
let current = startingScopeNode;
|
|
145
|
-
let depth = 0;
|
|
146
|
-
while (current && depth < maxScopeDepth) {
|
|
147
|
-
const parent = current.parent;
|
|
148
|
-
const pairs = getScopePairs(context, current, parent);
|
|
149
|
-
for (const key of pairs) {
|
|
150
|
-
result.add(key);
|
|
151
|
-
}
|
|
152
|
-
current = parent;
|
|
153
|
-
depth += 1;
|
|
154
|
-
}
|
|
155
|
-
return result;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Extract statement-level comments and story/requirement pairs that are
|
|
159
|
-
* relevant for redundancy analysis within a given scope.
|
|
160
|
-
*
|
|
161
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-STATEMENT-SIGNIFICANCE REQ-SCOPE-ANALYSIS
|
|
162
|
-
*/
|
|
163
|
-
function getStatementPairsForRedundancy(context, stmt, scopePairs, options) {
|
|
164
|
-
if (scopePairs.size === 0) {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
if (!(0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)(stmt, options, branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES)) {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
const stmtComments = getStatementComments(context, stmt);
|
|
171
|
-
if (stmtComments.length === 0) {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
const stmtPairs = (0, annotation_scope_analyzer_1.extractStoryReqPairsFromComments)(stmtComments);
|
|
175
|
-
if (process.env.TRACEABILITY_DEBUG === "1") {
|
|
176
|
-
console.log("[no-redundant-annotation] Statement type=%s eligible=%s commentCount=%d pairs=%o", stmt && stmt.type, (0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)(stmt, options, branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES), stmtComments.length, Array.from(stmtPairs));
|
|
177
|
-
}
|
|
178
|
-
if (stmtPairs.size === 0) {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
return { comments: stmtComments, pairs: stmtPairs };
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Decide whether the provided statement-level pairs should be considered
|
|
185
|
-
* redundant within the given scope, respecting configuration options.
|
|
186
|
-
*
|
|
187
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-CONFIGURABLE-STRICTNESS
|
|
188
|
-
*/
|
|
189
|
-
function isStatementRedundantWithinScope(stmtPairs, scopePairs, options) {
|
|
190
|
-
if (options.allowEmphasisDuplication &&
|
|
191
|
-
stmtPairs.size === 1 &&
|
|
192
|
-
(0, annotation_scope_analyzer_1.arePairsFullyCovered)(stmtPairs, scopePairs)) {
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
if (!(0, annotation_scope_analyzer_1.arePairsFullyCovered)(stmtPairs, scopePairs)) {
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Filter a list of comments down to those that contain traceability
|
|
202
|
-
* annotations relevant for redundancy detection.
|
|
203
|
-
*
|
|
204
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SAFE-REMOVAL REQ-REDUNDANCY-PATTERNS
|
|
205
|
-
*/
|
|
206
|
-
function getAnnotationCommentsFromStatement(comments) {
|
|
207
|
-
return comments.filter((comment) => {
|
|
208
|
-
const commentText = typeof comment.value === "string" ? comment.value : "";
|
|
209
|
-
return /@story\b|@req\b|@supports\b/.test(commentText);
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Determine whether a statement is redundant relative to the provided
|
|
214
|
-
* scopePairs and options, using helper functions to gather statement
|
|
215
|
-
* pairs, apply redundancy rules, and collect the associated annotation
|
|
216
|
-
* comments. Returns null when the statement should not be treated as
|
|
217
|
-
* redundant.
|
|
218
|
-
*
|
|
219
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-STATEMENT-SIGNIFICANCE REQ-CONFIGURABLE-STRICTNESS
|
|
220
|
-
*/
|
|
221
|
-
function getRedundantStatementContext(context, stmt, scopePairs, options) {
|
|
222
|
-
const stmtInfo = getStatementPairsForRedundancy(context, stmt, scopePairs, options);
|
|
223
|
-
if (!stmtInfo) {
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
const { comments, pairs } = stmtInfo;
|
|
227
|
-
if (!isStatementRedundantWithinScope(pairs, scopePairs, options)) {
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
const annotationComments = getAnnotationCommentsFromStatement(comments);
|
|
231
|
-
if (annotationComments.length === 0) {
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
return { comments: annotationComments };
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Compute unique removal ranges for the given annotation comments.
|
|
238
|
-
*
|
|
239
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SAFE-REMOVAL
|
|
240
|
-
*/
|
|
241
|
-
function getRemovalRangesForAnnotationComments(comments, sourceCode) {
|
|
242
|
-
const rangeMap = new Map();
|
|
243
|
-
for (const comment of comments) {
|
|
244
|
-
const [removalStart, removalEnd] = (0, annotation_scope_analyzer_1.getCommentRemovalRange)(comment, sourceCode);
|
|
245
|
-
const key = `${removalStart}:${removalEnd}`;
|
|
246
|
-
if (!rangeMap.has(key)) {
|
|
247
|
-
rangeMap.set(key, [removalStart, removalEnd]);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return Array.from(rangeMap.values()).sort((a, b) => b[0] - a[0]);
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Analyze a block's statements and report redundant traceability annotations.
|
|
254
|
-
*
|
|
255
|
-
* This helper encapsulates the iteration and reporting logic so that the
|
|
256
|
-
* BlockStatement visitor remains small and focused on scope setup.
|
|
257
|
-
*
|
|
258
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-STATEMENT-SIGNIFICANCE
|
|
259
|
-
*/
|
|
260
|
-
function reportRedundantAnnotationsInBlock(context, blockNode, scopePairs, options) {
|
|
261
|
-
const statements = Array.isArray(blockNode.body) ? blockNode.body : [];
|
|
262
|
-
if (statements.length === 0 || scopePairs.size === 0)
|
|
263
|
-
return;
|
|
264
|
-
const sourceCode = context.getSourceCode();
|
|
265
|
-
for (const stmt of statements) {
|
|
266
|
-
const info = getRedundantStatementContext(context, stmt, scopePairs, options);
|
|
267
|
-
if (!info) {
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
const ranges = getRemovalRangesForAnnotationComments(info.comments, sourceCode);
|
|
271
|
-
if (ranges.length === 0) {
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
context.report({
|
|
275
|
-
node: stmt,
|
|
276
|
-
messageId: "redundantAnnotation",
|
|
277
|
-
fix(fixer) {
|
|
278
|
-
return ranges.map(([start, end]) => fixer.removeRange([start, end]));
|
|
279
|
-
},
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
49
|
const rule = {
|
|
284
50
|
meta: {
|
|
285
51
|
type: "suggestion",
|
|
@@ -337,11 +103,11 @@ const rule = {
|
|
|
337
103
|
if (parent && parent.type === "CatchClause") {
|
|
338
104
|
return;
|
|
339
105
|
}
|
|
340
|
-
const scopePairs = collectScopePairs(context, parent, options.maxScopeDepth);
|
|
341
|
-
debugScopePairs(parent, scopePairs);
|
|
106
|
+
const scopePairs = (0, redundancy_detector_1.collectScopePairs)(context, parent, options.maxScopeDepth);
|
|
107
|
+
(0, redundancy_detector_1.debugScopePairs)(parent, scopePairs);
|
|
342
108
|
if (scopePairs.size === 0)
|
|
343
109
|
return;
|
|
344
|
-
reportRedundantAnnotationsInBlock(context, node, scopePairs, options);
|
|
110
|
+
(0, redundancy_detector_1.reportRedundantAnnotationsInBlock)(context, node, scopePairs, options);
|
|
345
111
|
},
|
|
346
112
|
};
|
|
347
113
|
},
|
|
@@ -23,6 +23,18 @@
|
|
|
23
23
|
* @req REQ-AUTO-FIX - Provide safe, opt-in auto-fix for simple legacy patterns
|
|
24
24
|
*/
|
|
25
25
|
import type { Rule } from "eslint";
|
|
26
|
+
/**
|
|
27
|
+
* ESLint rule: prefer-implements-annotation
|
|
28
|
+
*
|
|
29
|
+
* Recommend migrating from legacy `@story` + `@req` annotations to the
|
|
30
|
+
* newer `@supports` format. This rule is **disabled by default** and
|
|
31
|
+
* is intended as an optional, opt-in migration aid.
|
|
32
|
+
*
|
|
33
|
+
* @story docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
|
|
34
|
+
* @req REQ-OPTIONAL-WARNING - Emit configurable recommendation diagnostics for legacy @story/@req usage
|
|
35
|
+
* @req REQ-MULTI-STORY-DETECT - Detect multi-story patterns that cannot be auto-fixed
|
|
36
|
+
* @req REQ-BACKWARD-COMP-VALIDATION - Keep legacy @story/@req annotations valid when the rule is disabled
|
|
37
|
+
*/
|
|
26
38
|
/**
|
|
27
39
|
* ESLint rule: prefer-implements-annotation
|
|
28
40
|
*
|