eslint-plugin-traceability 1.9.0 → 1.10.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/CHANGELOG.md +3 -3
- package/lib/src/rules/helpers/require-test-traceability-helpers.d.ts +34 -0
- package/lib/src/rules/helpers/require-test-traceability-helpers.js +258 -0
- package/lib/src/rules/helpers/valid-annotation-format-internal.d.ts +11 -0
- package/lib/src/rules/helpers/valid-annotation-format-internal.js +21 -0
- package/lib/src/rules/helpers/valid-annotation-format-validators.d.ts +125 -0
- package/lib/src/rules/helpers/valid-annotation-format-validators.js +270 -0
- package/lib/src/rules/require-test-traceability.d.ts +3 -0
- package/lib/src/rules/require-test-traceability.js +33 -90
- package/lib/src/rules/valid-annotation-format.js +10 -243
- package/lib/tests/rules/require-test-traceability.test.js +43 -6
- package/lib/tests/rules/valid-annotation-format.test.js +71 -0
- package/package.json +1 -1
- package/user-docs/api-reference.md +6 -2
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validators and helper functions for the valid-annotation-format rule.
|
|
4
|
+
*
|
|
5
|
+
* This module contains the core validation logic that was originally
|
|
6
|
+
* embedded in src/rules/valid-annotation-format.ts. It is extracted
|
|
7
|
+
* here to keep the main rule module smaller and easier to read while
|
|
8
|
+
* preserving existing behavior.
|
|
9
|
+
*
|
|
10
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
11
|
+
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
12
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
13
|
+
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
14
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
15
|
+
* @req REQ-FORMAT-SPECIFICATION
|
|
16
|
+
* @req REQ-SYNTAX-VALIDATION
|
|
17
|
+
* @req REQ-PATH-FORMAT
|
|
18
|
+
* @req REQ-REQ-FORMAT
|
|
19
|
+
* @req REQ-MULTILINE-SUPPORT
|
|
20
|
+
* @req REQ-AUTOFIX-FORMAT
|
|
21
|
+
* @req REQ-ERROR-SPECIFICITY
|
|
22
|
+
* @req REQ-REGEX-VALIDATION
|
|
23
|
+
* @req REQ-BACKWARD-COMP
|
|
24
|
+
* @req REQ-SUPPORTS-PARSE
|
|
25
|
+
* @req REQ-FORMAT-VALIDATION
|
|
26
|
+
* @req REQ-MIXED-SUPPORT
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.reportInvalidStoryFormat = reportInvalidStoryFormat;
|
|
30
|
+
exports.createStoryFix = createStoryFix;
|
|
31
|
+
exports.reportInvalidStoryFormatWithFix = reportInvalidStoryFormatWithFix;
|
|
32
|
+
exports.validateStoryAnnotation = validateStoryAnnotation;
|
|
33
|
+
exports.validateReqAnnotation = validateReqAnnotation;
|
|
34
|
+
exports.validateImplementsAnnotation = validateImplementsAnnotation;
|
|
35
|
+
exports.finalizePendingAnnotation = finalizePendingAnnotation;
|
|
36
|
+
const valid_annotation_utils_1 = require("./valid-annotation-utils");
|
|
37
|
+
const valid_implements_utils_1 = require("./valid-implements-utils");
|
|
38
|
+
const valid_annotation_options_1 = require("./valid-annotation-options");
|
|
39
|
+
/**
|
|
40
|
+
* Report an invalid @story annotation without applying a fix.
|
|
41
|
+
*
|
|
42
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
43
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
44
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
45
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
46
|
+
*/
|
|
47
|
+
function reportInvalidStoryFormat(context, comment, collapsed, options) {
|
|
48
|
+
context.report({
|
|
49
|
+
node: comment,
|
|
50
|
+
messageId: "invalidStoryFormat",
|
|
51
|
+
data: { details: (0, valid_annotation_utils_1.buildStoryErrorMessage)("invalid", collapsed, options) },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Compute the text replacement for an invalid @story annotation within a comment.
|
|
56
|
+
*
|
|
57
|
+
* This helper:
|
|
58
|
+
* - finds the @story tag in the raw comment text,
|
|
59
|
+
* - computes the character range of its value,
|
|
60
|
+
* - and returns an ESLint fix that replaces only that range.
|
|
61
|
+
*
|
|
62
|
+
* Returns null when the tag or value cannot be safely located.
|
|
63
|
+
*
|
|
64
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
65
|
+
* @req REQ-AUTOFIX-SAFE
|
|
66
|
+
* @req REQ-AUTOFIX-PRESERVE
|
|
67
|
+
*/
|
|
68
|
+
function createStoryFix(context, comment, fixed) {
|
|
69
|
+
const sourceCode = context.getSourceCode();
|
|
70
|
+
const commentText = sourceCode.getText(comment);
|
|
71
|
+
const search = "@story";
|
|
72
|
+
const tagIndex = commentText.indexOf(search);
|
|
73
|
+
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
74
|
+
// @req REQ-AUTOFIX-SAFE - Skip auto-fix when @story tag cannot be reliably located
|
|
75
|
+
if (tagIndex === valid_annotation_utils_1.TAG_NOT_FOUND_INDEX) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const afterTagIndex = tagIndex + search.length;
|
|
79
|
+
const rest = commentText.slice(afterTagIndex);
|
|
80
|
+
const valueMatch = rest.match(/[^\S\r\n]*([^\r\n*]+)/);
|
|
81
|
+
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
82
|
+
// @req REQ-AUTOFIX-SAFE - Abort auto-fix when story value range cannot be safely determined
|
|
83
|
+
if (!valueMatch || valueMatch.index === undefined) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const valueStartInComment = afterTagIndex +
|
|
87
|
+
valueMatch.index +
|
|
88
|
+
(valueMatch[0].length - valueMatch[1].length);
|
|
89
|
+
const valueEndInComment = valueStartInComment + valueMatch[1].length;
|
|
90
|
+
const start = comment.range[0];
|
|
91
|
+
const fixRange = [
|
|
92
|
+
start + valueStartInComment,
|
|
93
|
+
start + valueEndInComment,
|
|
94
|
+
];
|
|
95
|
+
return () => (fixer) => fixer.replaceTextRange(fixRange, fixed);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Report an invalid @story annotation and attempt a minimal, safe auto-fix
|
|
99
|
+
* for common path suffix issues by locating and replacing the path text
|
|
100
|
+
* within the original comment.
|
|
101
|
+
*
|
|
102
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
103
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
104
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
105
|
+
* @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
|
|
106
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
107
|
+
* @req REQ-AUTOFIX-SAFE - Auto-fix must be conservative and avoid changing semantics
|
|
108
|
+
* @req REQ-AUTOFIX-PRESERVE - Auto-fix must preserve surrounding formatting and comments
|
|
109
|
+
*/
|
|
110
|
+
function reportInvalidStoryFormatWithFix(context, comment, collapsed, fixed) {
|
|
111
|
+
const fixFactory = createStoryFix(context, comment, fixed);
|
|
112
|
+
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
113
|
+
// @req REQ-AUTOFIX-SAFE - Fall back to reporting without fix when safe fix cannot be created
|
|
114
|
+
if (!fixFactory) {
|
|
115
|
+
reportInvalidStoryFormat(context, comment, collapsed, (0, valid_annotation_options_1.getResolvedDefaults)());
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
context.report({
|
|
119
|
+
node: comment,
|
|
120
|
+
messageId: "invalidStoryFormat",
|
|
121
|
+
data: {
|
|
122
|
+
details: (0, valid_annotation_utils_1.buildStoryErrorMessage)("invalid", collapsed, (0, valid_annotation_options_1.getResolvedDefaults)()),
|
|
123
|
+
},
|
|
124
|
+
fix: fixFactory(),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validate a @story annotation value and report detailed errors when needed.
|
|
129
|
+
* Where safe and unambiguous, apply an automatic fix for missing suffixes.
|
|
130
|
+
*
|
|
131
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
132
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
133
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
134
|
+
* @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
|
|
135
|
+
* @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
|
|
136
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
137
|
+
* @req REQ-REGEX-VALIDATION - Validate configurable story regex patterns and fall back safely
|
|
138
|
+
* @req REQ-BACKWARD-COMP - Preserve behavior when invalid regex config is supplied
|
|
139
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
140
|
+
*/
|
|
141
|
+
function validateStoryAnnotation(context, comment, rawValue, options) {
|
|
142
|
+
const trimmed = rawValue.trim();
|
|
143
|
+
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
144
|
+
// @req REQ-PATH-FORMAT - Treat missing @story value as a specific validation error
|
|
145
|
+
if (!trimmed) {
|
|
146
|
+
context.report({
|
|
147
|
+
node: comment,
|
|
148
|
+
messageId: "invalidStoryFormat",
|
|
149
|
+
data: { details: (0, valid_annotation_utils_1.buildStoryErrorMessage)("missing", null, options) },
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const collapsed = (0, valid_annotation_utils_1.collapseAnnotationValue)(trimmed);
|
|
154
|
+
const pathPattern = options.storyPattern;
|
|
155
|
+
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
156
|
+
// @req REQ-PATH-FORMAT - Accept @story value when it matches configured storyPattern
|
|
157
|
+
if (pathPattern.test(collapsed)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
161
|
+
// @req REQ-PATH-FORMAT - Reject @story values containing internal whitespace as invalid
|
|
162
|
+
if (/\s/.test(trimmed)) {
|
|
163
|
+
reportInvalidStoryFormat(context, comment, collapsed, options);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const fixed = (0, valid_annotation_utils_1.getFixedStoryPath)(collapsed);
|
|
167
|
+
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
168
|
+
// @req REQ-AUTOFIX-FORMAT - Apply suffix-only auto-fix when it yields a pattern-compliant path
|
|
169
|
+
if (fixed && pathPattern.test(fixed)) {
|
|
170
|
+
reportInvalidStoryFormatWithFix(context, comment, collapsed, fixed);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
reportInvalidStoryFormat(context, comment, collapsed, options);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Validate a @req annotation value and report detailed errors when needed.
|
|
177
|
+
*
|
|
178
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
179
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
180
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
181
|
+
* @req REQ-REQ-FORMAT - Validate @req identifiers follow expected patterns
|
|
182
|
+
* @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
|
|
183
|
+
* @req REQ-REGEX-VALIDATION - Validate configurable requirement regex patterns and fall back safely
|
|
184
|
+
* @req REQ-BACKWARD-COMP - Preserve behavior when invalid regex config is supplied
|
|
185
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
186
|
+
*/
|
|
187
|
+
function validateReqAnnotation(context, comment, rawValue, options) {
|
|
188
|
+
const trimmed = rawValue.trim();
|
|
189
|
+
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
190
|
+
// @req REQ-REQ-FORMAT - Treat missing @req value as a specific validation error
|
|
191
|
+
if (!trimmed) {
|
|
192
|
+
context.report({
|
|
193
|
+
node: comment,
|
|
194
|
+
messageId: "invalidReqFormat",
|
|
195
|
+
data: { details: (0, valid_annotation_utils_1.buildReqErrorMessage)("missing", null, options) },
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const collapsed = (0, valid_annotation_utils_1.collapseAnnotationValue)(trimmed);
|
|
200
|
+
const reqPattern = options.reqPattern;
|
|
201
|
+
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
202
|
+
// @req REQ-REQ-FORMAT - Flag @req identifiers that do not match the configured pattern
|
|
203
|
+
if (!reqPattern.test(collapsed)) {
|
|
204
|
+
context.report({
|
|
205
|
+
node: comment,
|
|
206
|
+
messageId: "invalidReqFormat",
|
|
207
|
+
data: { details: (0, valid_annotation_utils_1.buildReqErrorMessage)("invalid", collapsed, options) },
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Validate an @supports annotation value and report detailed errors when needed.
|
|
213
|
+
*
|
|
214
|
+
* Expected format:
|
|
215
|
+
* @supports <storyPath> <REQ-ID> [<REQ-ID> ...]
|
|
216
|
+
*
|
|
217
|
+
* Validation rules:
|
|
218
|
+
* - Value must include at least a story path and one requirement ID.
|
|
219
|
+
* - Story path must match the same storyPattern used for @story (no auto-fix).
|
|
220
|
+
* - Each subsequent token must match reqPattern and is validated individually.
|
|
221
|
+
*
|
|
222
|
+
* Story path issues are reported with "invalidImplementsFormat" and
|
|
223
|
+
* requirement ID issues reuse the existing "invalidReqFormat" message.
|
|
224
|
+
*
|
|
225
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
226
|
+
* @req REQ-SUPPORTS-PARSE - Parse @supports annotations without affecting @story/@req
|
|
227
|
+
* @req REQ-FORMAT-VALIDATION - Validate @implements story path and requirement IDs
|
|
228
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
229
|
+
*/
|
|
230
|
+
function validateImplementsAnnotation(context, comment, rawValue, options) {
|
|
231
|
+
const deps = {
|
|
232
|
+
MIN_IMPLEMENTS_TOKENS: valid_implements_utils_1.MIN_IMPLEMENTS_TOKENS,
|
|
233
|
+
reportMissingImplementsReqIds: valid_implements_utils_1.reportMissingImplementsReqIds,
|
|
234
|
+
reportMissingImplementsValue: valid_implements_utils_1.reportMissingImplementsValue,
|
|
235
|
+
reportInvalidImplementsReqId: valid_implements_utils_1.reportInvalidImplementsReqId,
|
|
236
|
+
reportInvalidImplementsStoryPath: valid_implements_utils_1.reportInvalidImplementsStoryPath,
|
|
237
|
+
};
|
|
238
|
+
(0, valid_implements_utils_1.validateImplementsAnnotationHelper)(deps, context, comment, {
|
|
239
|
+
rawValue,
|
|
240
|
+
options,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Finalize and validate the currently pending annotation, if any.
|
|
245
|
+
*
|
|
246
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
247
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
248
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
249
|
+
* @req REQ-SYNTAX-VALIDATION - Validate annotation syntax matches specification
|
|
250
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
251
|
+
* @req REQ-MIXED-SUPPORT - Support mixed @story/@req/@implements usage in comments
|
|
252
|
+
*/
|
|
253
|
+
function finalizePendingAnnotation(context, comment, options, pending) {
|
|
254
|
+
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
255
|
+
// @req REQ-MULTILINE-SUPPORT - Do nothing when there is no pending multi-line annotation to finalize
|
|
256
|
+
if (!pending) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
260
|
+
// @req REQ-SYNTAX-VALIDATION - Dispatch to @story or @req validator based on pending annotation type
|
|
261
|
+
// @req REQ-AUTOFIX-FORMAT - Route to story validator which may apply safe auto-fixes
|
|
262
|
+
// @req REQ-MIXED-SUPPORT - Ensure @story and @req annotations are handled independently
|
|
263
|
+
if (pending.type === "story") {
|
|
264
|
+
validateStoryAnnotation(context, comment, pending.value, options);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
validateReqAnnotation(context, comment, pending.value, options);
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
@@ -6,6 +6,8 @@ import type { Rule } from "eslint";
|
|
|
6
6
|
* - Test files have a file-level @supports annotation listing tested requirements.
|
|
7
7
|
* - describe()/it()/test()/context() blocks include story and requirement references
|
|
8
8
|
* following project conventions.
|
|
9
|
+
* - When ESLint runs with --fix, safe, non-semantic auto-fixes are applied for
|
|
10
|
+
* missing file-level @supports and malformed [REQ-XXX] prefixes in test names.
|
|
9
11
|
*
|
|
10
12
|
* @story docs/stories/020.0-DEV-TEST-ANNOTATION-VALIDATION.story.md
|
|
11
13
|
* @req REQ-TEST-FILE-SUPPORTS
|
|
@@ -16,6 +18,7 @@ import type { Rule } from "eslint";
|
|
|
16
18
|
* @req REQ-TEST-FRAMEWORK-COMPAT
|
|
17
19
|
* @req REQ-TEST-NESTED-DESCRIBE
|
|
18
20
|
* @req REQ-TEST-ERROR-CONTEXT
|
|
21
|
+
* @supports docs/stories/021.0-DEV-TEST-ANNOTATION-AUTO-FIX.story.md REQ-TEST-FIX-TEMPLATE REQ-TEST-FIX-PREFIX-FORMAT REQ-TEST-FIX-SAFE REQ-TEST-FIX-PRESERVE REQ-TEST-FIX-PLACEHOLDER REQ-TEST-FIX-NO-INFERENCE
|
|
19
22
|
*/
|
|
20
23
|
declare const rule: Rule.RuleModule;
|
|
21
24
|
export default rule;
|
|
@@ -1,62 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
4
|
-
* Determine if a file should be treated as a test file based on patterns.
|
|
5
|
-
*
|
|
6
|
-
* @supports docs/stories/020.0-DEV-TEST-ANNOTATION-VALIDATION.story.md REQ-TEST-PATTERN-DETECT
|
|
7
|
-
*/
|
|
8
|
-
function determineIsTestFile(filename, rawPatterns = [
|
|
9
|
-
"/tests/",
|
|
10
|
-
"/test/",
|
|
11
|
-
"/__tests__/",
|
|
12
|
-
".test.",
|
|
13
|
-
".spec.",
|
|
14
|
-
]) {
|
|
15
|
-
return rawPatterns.some((pattern) => filename.includes(pattern.replace("**", "")));
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Ensure the file has a @supports annotation listing tested requirements.
|
|
19
|
-
*
|
|
20
|
-
* @supports docs/stories/020.0-DEV-TEST-ANNOTATION-VALIDATION.story.md REQ-TEST-FILE-SUPPORTS REQ-TEST-SUPPORTS-VALID
|
|
21
|
-
*/
|
|
22
|
-
function ensureFileSupportsAnnotation(context, sourceCode) {
|
|
23
|
-
const fileComments = sourceCode.getAllComments() || [];
|
|
24
|
-
const fileHasSupports = fileComments.some((comment) => /@supports\b/.test(comment.value || ""));
|
|
25
|
-
if (!fileHasSupports) {
|
|
26
|
-
const node = fileComments[0] || (sourceCode.ast && sourceCode.ast);
|
|
27
|
-
context.report({
|
|
28
|
-
node: node,
|
|
29
|
-
messageId: "missingFileSupports",
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Check if a callee name corresponds to a test framework function.
|
|
35
|
-
*
|
|
36
|
-
* @supports docs/stories/020.0-DEV-TEST-ANNOTATION-VALIDATION.story.md REQ-TEST-FRAMEWORK-COMPAT
|
|
37
|
-
*/
|
|
38
|
-
function isTestCallName(name) {
|
|
39
|
-
return ["describe", "it", "test", "context"].includes(name);
|
|
40
|
-
}
|
|
41
|
-
function getCalleeName(node) {
|
|
42
|
-
if (node.callee.type === "Identifier") {
|
|
43
|
-
return node.callee.name;
|
|
44
|
-
}
|
|
45
|
-
if (node.callee.type === "MemberExpression" &&
|
|
46
|
-
node.callee.object.type === "Identifier") {
|
|
47
|
-
return node.callee.object.name;
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
function getFirstArgumentLiteral(node) {
|
|
52
|
-
const arg = node.arguments && node.arguments[0];
|
|
53
|
-
if (!arg)
|
|
54
|
-
return null;
|
|
55
|
-
if (arg.type === "Literal" && typeof arg.value === "string") {
|
|
56
|
-
return arg.value;
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
3
|
+
const require_test_traceability_helpers_1 = require("./helpers/require-test-traceability-helpers");
|
|
60
4
|
/**
|
|
61
5
|
* Enforce traceability conventions in test files.
|
|
62
6
|
*
|
|
@@ -64,6 +8,8 @@ function getFirstArgumentLiteral(node) {
|
|
|
64
8
|
* - Test files have a file-level @supports annotation listing tested requirements.
|
|
65
9
|
* - describe()/it()/test()/context() blocks include story and requirement references
|
|
66
10
|
* following project conventions.
|
|
11
|
+
* - When ESLint runs with --fix, safe, non-semantic auto-fixes are applied for
|
|
12
|
+
* missing file-level @supports and malformed [REQ-XXX] prefixes in test names.
|
|
67
13
|
*
|
|
68
14
|
* @story docs/stories/020.0-DEV-TEST-ANNOTATION-VALIDATION.story.md
|
|
69
15
|
* @req REQ-TEST-FILE-SUPPORTS
|
|
@@ -74,6 +20,7 @@ function getFirstArgumentLiteral(node) {
|
|
|
74
20
|
* @req REQ-TEST-FRAMEWORK-COMPAT
|
|
75
21
|
* @req REQ-TEST-NESTED-DESCRIBE
|
|
76
22
|
* @req REQ-TEST-ERROR-CONTEXT
|
|
23
|
+
* @supports docs/stories/021.0-DEV-TEST-ANNOTATION-AUTO-FIX.story.md REQ-TEST-FIX-TEMPLATE REQ-TEST-FIX-PREFIX-FORMAT REQ-TEST-FIX-SAFE REQ-TEST-FIX-PRESERVE REQ-TEST-FIX-PLACEHOLDER REQ-TEST-FIX-NO-INFERENCE
|
|
77
24
|
*/
|
|
78
25
|
const rule = {
|
|
79
26
|
meta: {
|
|
@@ -82,6 +29,7 @@ const rule = {
|
|
|
82
29
|
description: "Enforce traceability annotations and naming conventions in test files",
|
|
83
30
|
recommended: "error",
|
|
84
31
|
},
|
|
32
|
+
fixable: "code",
|
|
85
33
|
schema: [
|
|
86
34
|
{
|
|
87
35
|
type: "object",
|
|
@@ -108,6 +56,17 @@ const rule = {
|
|
|
108
56
|
type: "string",
|
|
109
57
|
default: "Story [0-9]+\\.[0-9]+-",
|
|
110
58
|
},
|
|
59
|
+
autoFixTestTemplate: {
|
|
60
|
+
type: "boolean",
|
|
61
|
+
default: true,
|
|
62
|
+
},
|
|
63
|
+
autoFixTestPrefixFormat: {
|
|
64
|
+
type: "boolean",
|
|
65
|
+
default: true,
|
|
66
|
+
},
|
|
67
|
+
testSupportsTemplate: {
|
|
68
|
+
type: "string",
|
|
69
|
+
},
|
|
111
70
|
},
|
|
112
71
|
additionalProperties: false,
|
|
113
72
|
},
|
|
@@ -120,49 +79,33 @@ const rule = {
|
|
|
120
79
|
},
|
|
121
80
|
create(context) {
|
|
122
81
|
const filename = context.getFilename();
|
|
123
|
-
const
|
|
82
|
+
const rawOptions = (context.options && context.options[0]) || {};
|
|
124
83
|
const { testFilePatterns = [
|
|
125
84
|
"/tests/",
|
|
126
85
|
"/test/",
|
|
127
|
-
"/__tests__
|
|
86
|
+
"/__tests__",
|
|
128
87
|
".test.",
|
|
129
88
|
".spec.",
|
|
130
|
-
], requireDescribeStory = true, requireTestReqPrefix = true, describePattern = "Story [0-9]+\\.[0-9]+-", } =
|
|
131
|
-
const isTestFile = determineIsTestFile(filename, testFilePatterns);
|
|
132
|
-
if (!isTestFile)
|
|
89
|
+
], requireDescribeStory = true, requireTestReqPrefix = true, describePattern = "Story [0-9]+\\.[0-9]+-", autoFixTestTemplate = true, autoFixTestPrefixFormat = true, testSupportsTemplate, } = rawOptions;
|
|
90
|
+
const isTestFile = (0, require_test_traceability_helpers_1.determineIsTestFile)(filename, testFilePatterns);
|
|
91
|
+
if (!isTestFile)
|
|
133
92
|
return {};
|
|
134
|
-
}
|
|
135
93
|
const sourceCode = context.getSourceCode();
|
|
136
|
-
ensureFileSupportsAnnotation(context, sourceCode
|
|
94
|
+
(0, require_test_traceability_helpers_1.ensureFileSupportsAnnotation)(context, sourceCode, {
|
|
95
|
+
autoFixTestTemplate,
|
|
96
|
+
testSupportsTemplate,
|
|
97
|
+
});
|
|
137
98
|
const describeRegex = new RegExp(describePattern);
|
|
138
99
|
return {
|
|
139
100
|
// @supports docs/stories/020.0-DEV-TEST-ANNOTATION-VALIDATION.story.md REQ-TEST-DESCRIBE-STORY REQ-TEST-IT-REQ-PREFIX REQ-TEST-NESTED-DESCRIBE REQ-TEST-ERROR-CONTEXT
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (requireDescribeStory && calleeName === "describe") {
|
|
149
|
-
if (!describeRegex.test(description)) {
|
|
150
|
-
context.report({
|
|
151
|
-
node: node,
|
|
152
|
-
messageId: "missingDescribeStory",
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (requireTestReqPrefix &&
|
|
157
|
-
(calleeName === "it" || calleeName === "test")) {
|
|
158
|
-
if (!/^\[REQ-[^\]]+]/.test(description)) {
|
|
159
|
-
context.report({
|
|
160
|
-
node: node,
|
|
161
|
-
messageId: "missingReqPrefix",
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
},
|
|
101
|
+
// @supports docs/stories/021.0-DEV-TEST-ANNOTATION-AUTO-FIX.story.md REQ-TEST-FIX-PREFIX-FORMAT REQ-TEST-FIX-SAFE REQ-TEST-FIX-PRESERVE REQ-TEST-FIX-NO-INFERENCE
|
|
102
|
+
CallExpression: (0, require_test_traceability_helpers_1.handleCallExpression)(context, {
|
|
103
|
+
sourceCode,
|
|
104
|
+
describeRegex,
|
|
105
|
+
requireDescribeStory,
|
|
106
|
+
requireTestReqPrefix,
|
|
107
|
+
autoFixTestPrefixFormat,
|
|
108
|
+
}),
|
|
166
109
|
};
|
|
167
110
|
},
|
|
168
111
|
};
|