eslint-plugin-traceability 1.14.0 → 1.14.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-story-core.js +1 -0
- package/lib/src/rules/helpers/require-story-helpers.d.ts +4 -0
- package/lib/src/rules/helpers/require-story-helpers.js +92 -3
- package/lib/src/rules/helpers/require-test-traceability-helpers.js +24 -0
- package/lib/src/rules/helpers/valid-annotation-options.d.ts +32 -1
- package/lib/src/rules/helpers/valid-annotation-options.js +144 -1
- package/lib/src/rules/helpers/valid-implements-utils.js +13 -5
- package/lib/src/rules/no-redundant-annotation.js +12 -0
- package/lib/src/rules/prefer-implements-annotation.js +39 -0
- package/lib/src/rules/require-branch-annotation.js +73 -1
- package/lib/src/rules/require-test-traceability.js +8 -0
- package/lib/src/rules/valid-req-reference.js +4 -0
- package/lib/src/rules/valid-story-reference.d.ts +9 -0
- package/lib/src/rules/valid-story-reference.js +9 -0
- package/lib/src/utils/branch-annotation-helpers.d.ts +12 -10
- package/lib/src/utils/branch-annotation-helpers.js +31 -140
- package/lib/src/utils/branch-annotation-loop-helpers.d.ts +9 -0
- package/lib/src/utils/branch-annotation-loop-helpers.js +36 -0
- package/lib/src/utils/branch-annotation-report-helpers.d.ts +11 -0
- package/lib/src/utils/branch-annotation-report-helpers.js +111 -0
- package/lib/tests/integration/dogfooding-validation.test.js +5 -2
- package/lib/tests/rules/require-branch-annotation.test.js +88 -19
- package/lib/tests/rules/require-story-annotation.test.js +56 -8
- package/lib/tests/utils/temp-dir-helpers.d.ts +6 -1
- package/lib/tests/utils/temp-dir-helpers.js +2 -1
- package/package.json +1 -1
|
@@ -23,12 +23,14 @@ const require_branch_annotation_1 = __importDefault(require("../../src/rules/req
|
|
|
23
23
|
const ruleTester = new eslint_1.RuleTester({
|
|
24
24
|
languageOptions: { parserOptions: { ecmaVersion: 2020 } },
|
|
25
25
|
});
|
|
26
|
+
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
26
27
|
const makeMissingAnnotationErrors = (...missing) => missing.map((item) => ({
|
|
27
28
|
messageId: "missingAnnotation",
|
|
28
29
|
data: { missing: item },
|
|
29
30
|
}));
|
|
31
|
+
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
30
32
|
const runRule = (tests) => ruleTester.run("require-branch-annotation", require_branch_annotation_1.default, tests);
|
|
31
|
-
describe("Require Branch Annotation Rule (Story 004.0-DEV-BRANCH-ANNOTATIONS)"
|
|
33
|
+
describe("Require Branch Annotation Rule (Story 004.0-DEV-BRANCH-ANNOTATIONS)" /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */, () => {
|
|
32
34
|
runRule({
|
|
33
35
|
valid: [
|
|
34
36
|
{
|
|
@@ -60,8 +62,22 @@ for (let i = 0; i < 10; i++) {}`,
|
|
|
60
62
|
// @req REQ-BRANCH-DETECTION
|
|
61
63
|
case 'a':
|
|
62
64
|
break;
|
|
65
|
+
// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
66
|
+
// @req REQ-SWITCH-DEFAULT-REQUIRED
|
|
63
67
|
default:
|
|
64
68
|
break;
|
|
69
|
+
}`,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "[REQ-SWITCH-FALLTHROUGH] valid fall-through group only requires annotation on last case before body",
|
|
73
|
+
code: `switch (status) {
|
|
74
|
+
case "pending":
|
|
75
|
+
case "processing":
|
|
76
|
+
// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
77
|
+
// @req REQ-SWITCH-FALLTHROUGH
|
|
78
|
+
case "validating":
|
|
79
|
+
handleInProgress();
|
|
80
|
+
break;
|
|
65
81
|
}`,
|
|
66
82
|
},
|
|
67
83
|
{
|
|
@@ -101,6 +117,14 @@ do {
|
|
|
101
117
|
/* @req REQ-BRANCH-DETECTION */
|
|
102
118
|
for (const item of items) {
|
|
103
119
|
process(item);
|
|
120
|
+
}`,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "[REQ-LOOP-PLACEMENT-FLEXIBLE] for-of loop annotated via comment inside body",
|
|
124
|
+
code: `for (const item of items) {
|
|
125
|
+
// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
126
|
+
// @req REQ-LOOP-ANNOTATION
|
|
127
|
+
process(item);
|
|
104
128
|
}`,
|
|
105
129
|
},
|
|
106
130
|
{
|
|
@@ -117,6 +141,14 @@ for (const key in object) {
|
|
|
117
141
|
/* @req REQ-BRANCH-DETECTION */
|
|
118
142
|
while (condition) {
|
|
119
143
|
iterate();
|
|
144
|
+
}`,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "[REQ-LOOP-PLACEMENT-FLEXIBLE] while loop annotated via comment inside body",
|
|
148
|
+
code: `while (condition) {
|
|
149
|
+
// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
150
|
+
// @req REQ-LOOP-ANNOTATION
|
|
151
|
+
iterate();
|
|
120
152
|
}`,
|
|
121
153
|
},
|
|
122
154
|
{
|
|
@@ -138,13 +170,6 @@ if (outer) {
|
|
|
138
170
|
if (inner) {
|
|
139
171
|
doWork();
|
|
140
172
|
}
|
|
141
|
-
}`,
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
name: "[REQ-BRANCH-DETECTION] valid default case without annotations",
|
|
145
|
-
code: `switch (value) {
|
|
146
|
-
default:
|
|
147
|
-
doSomething();
|
|
148
173
|
}`,
|
|
149
174
|
},
|
|
150
175
|
{
|
|
@@ -214,6 +239,17 @@ while (true) {}`,
|
|
|
214
239
|
while (true) {}`,
|
|
215
240
|
errors: makeMissingAnnotationErrors("@story"),
|
|
216
241
|
},
|
|
242
|
+
{
|
|
243
|
+
name: "[REQ-LOOP-ANNOTATION] missing annotations when loop body contains only non-comment code",
|
|
244
|
+
code: `for (const item of items) {
|
|
245
|
+
process(item);
|
|
246
|
+
}`,
|
|
247
|
+
output: `// @story <story-file>.story.md
|
|
248
|
+
for (const item of items) {
|
|
249
|
+
process(item);
|
|
250
|
+
}`,
|
|
251
|
+
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
252
|
+
},
|
|
217
253
|
{
|
|
218
254
|
name: "[REQ-BRANCH-DETECTION] missing annotations on switch-case",
|
|
219
255
|
code: `switch (value) {
|
|
@@ -224,6 +260,50 @@ while (true) {}`,
|
|
|
224
260
|
// @story <story-file>.story.md
|
|
225
261
|
case 'a':
|
|
226
262
|
break;
|
|
263
|
+
}`,
|
|
264
|
+
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "[REQ-SWITCH-FALLTHROUGH] intermediate fall-through case should not be the only annotated case",
|
|
268
|
+
code: `switch (status) {
|
|
269
|
+
// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
270
|
+
// @req REQ-SWITCH-FALLTHROUGH
|
|
271
|
+
case "pending":
|
|
272
|
+
case "processing":
|
|
273
|
+
case "validating":
|
|
274
|
+
handleInProgress();
|
|
275
|
+
break;
|
|
276
|
+
}`,
|
|
277
|
+
output: `switch (status) {
|
|
278
|
+
// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
279
|
+
// @req REQ-SWITCH-FALLTHROUGH
|
|
280
|
+
case "pending":
|
|
281
|
+
case "processing":
|
|
282
|
+
// @story <story-file>.story.md
|
|
283
|
+
case "validating":
|
|
284
|
+
handleInProgress();
|
|
285
|
+
break;
|
|
286
|
+
}`,
|
|
287
|
+
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "[REQ-SWITCH-DEFAULT-REQUIRED] missing annotations on default case",
|
|
291
|
+
code: `switch (value) {
|
|
292
|
+
// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
293
|
+
// @req REQ-BRANCH-DETECTION
|
|
294
|
+
case 'a':
|
|
295
|
+
doSomething();
|
|
296
|
+
default:
|
|
297
|
+
doDefault();
|
|
298
|
+
}`,
|
|
299
|
+
output: `switch (value) {
|
|
300
|
+
// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
301
|
+
// @req REQ-BRANCH-DETECTION
|
|
302
|
+
case 'a':
|
|
303
|
+
doSomething();
|
|
304
|
+
// @story <story-file>.story.md
|
|
305
|
+
default:
|
|
306
|
+
doDefault();
|
|
227
307
|
}`,
|
|
228
308
|
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
229
309
|
},
|
|
@@ -238,17 +318,6 @@ do {
|
|
|
238
318
|
} while (condition);`,
|
|
239
319
|
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
240
320
|
},
|
|
241
|
-
{
|
|
242
|
-
name: "[REQ-BRANCH-DETECTION] missing annotations on for-of loop",
|
|
243
|
-
code: `for (const item of items) {
|
|
244
|
-
process(item);
|
|
245
|
-
}`,
|
|
246
|
-
output: `// @story <story-file>.story.md
|
|
247
|
-
for (const item of items) {
|
|
248
|
-
process(item);
|
|
249
|
-
}`,
|
|
250
|
-
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
251
|
-
},
|
|
252
321
|
{
|
|
253
322
|
name: "[REQ-BRANCH-DETECTION] missing annotations on for-in loop",
|
|
254
323
|
code: `for (const key in object) {
|
|
@@ -16,7 +16,7 @@ const ts_language_options_1 = require("../utils/ts-language-options");
|
|
|
16
16
|
const ruleTester = new eslint_1.RuleTester({
|
|
17
17
|
languageOptions: ts_language_options_1.tsRuleTesterLanguageOptions,
|
|
18
18
|
});
|
|
19
|
-
describe("Require Story Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)"
|
|
19
|
+
describe("Require Story Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)" /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */ /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */, () => {
|
|
20
20
|
ruleTester.run("require-story-annotation", require_story_annotation_1.default, {
|
|
21
21
|
valid: [
|
|
22
22
|
{
|
|
@@ -58,8 +58,12 @@ declare function tsDecl(): void;`,
|
|
|
58
58
|
}`,
|
|
59
59
|
}),
|
|
60
60
|
{
|
|
61
|
-
name: "[REQ-
|
|
62
|
-
code:
|
|
61
|
+
name: "[REQ-ARROW-FUNCTION-EXCLUDED] anonymous arrow callback in higher-order function is allowed without annotation",
|
|
62
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction mapValues(items) {\n return items.map(() => {\n return 1;\n });\n}`,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "[REQ-NESTED-FUNCTION-INHERITANCE] anonymous inner function inherits outer annotation",
|
|
66
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction outer() {\n const inner = function() {\n return 1;\n };\n return inner();\n}`,
|
|
63
67
|
},
|
|
64
68
|
],
|
|
65
69
|
invalid: [
|
|
@@ -145,6 +149,38 @@ declare function tsDecl(): void;`,
|
|
|
145
149
|
},
|
|
146
150
|
],
|
|
147
151
|
}),
|
|
152
|
+
{
|
|
153
|
+
name: "[REQ-ARROW-FUNCTION-EXCLUDED] named arrow function must be annotated",
|
|
154
|
+
code: `const handler = () => {};`,
|
|
155
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst handler = () => {};`,
|
|
156
|
+
errors: [
|
|
157
|
+
{
|
|
158
|
+
messageId: "missingStory",
|
|
159
|
+
suggestions: [
|
|
160
|
+
{
|
|
161
|
+
desc: `Add JSDoc @story annotation for function 'handler', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
162
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst handler = () => {};`,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "[REQ-NESTED-FUNCTION-INHERITANCE] named inner function inside annotated outer must still be annotated",
|
|
170
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction outer() {\n function innerNamed() {\n return 1;\n }\n return innerNamed();\n}`,
|
|
171
|
+
output: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction outer() {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction innerNamed() {\n return 1;\n }\n return innerNamed();\n}`,
|
|
172
|
+
errors: [
|
|
173
|
+
{
|
|
174
|
+
messageId: "missingStory",
|
|
175
|
+
suggestions: [
|
|
176
|
+
{
|
|
177
|
+
desc: `Add JSDoc @story annotation for function 'innerNamed', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
178
|
+
output: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction outer() {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction innerNamed() {\n return 1;\n }\n return innerNamed();\n}`,
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
},
|
|
148
184
|
],
|
|
149
185
|
});
|
|
150
186
|
ruleTester.run("require-story-annotation with exportPriority option", require_story_annotation_1.default, {
|
|
@@ -159,11 +195,6 @@ declare function tsDecl(): void;`,
|
|
|
159
195
|
code: `// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\nexport function exportedAnnotated() {}`,
|
|
160
196
|
options: [{ exportPriority: "exported" }],
|
|
161
197
|
},
|
|
162
|
-
{
|
|
163
|
-
name: "[exportPriority] exported arrow function missing @story annotation",
|
|
164
|
-
code: `export const arrowExported = () => {};`,
|
|
165
|
-
options: [{ exportPriority: "exported" }],
|
|
166
|
-
},
|
|
167
198
|
],
|
|
168
199
|
invalid: [
|
|
169
200
|
{
|
|
@@ -183,6 +214,23 @@ declare function tsDecl(): void;`,
|
|
|
183
214
|
},
|
|
184
215
|
],
|
|
185
216
|
},
|
|
217
|
+
{
|
|
218
|
+
name: "[exportPriority][REQ-ARROW-FUNCTION-EXCLUDED] exported named arrow function must be annotated",
|
|
219
|
+
code: `export const arrowExported = () => {};`,
|
|
220
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport const arrowExported = () => {};`,
|
|
221
|
+
options: [{ exportPriority: "exported" }],
|
|
222
|
+
errors: [
|
|
223
|
+
{
|
|
224
|
+
messageId: "missingStory",
|
|
225
|
+
suggestions: [
|
|
226
|
+
{
|
|
227
|
+
desc: `Add JSDoc @story annotation for function 'arrowExported', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
228
|
+
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport const arrowExported = () => {};`,
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
},
|
|
186
234
|
],
|
|
187
235
|
});
|
|
188
236
|
ruleTester.run("require-story-annotation with scope option", require_story_annotation_1.default, {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export interface TempDirHandle {
|
|
2
2
|
/** The absolute path to the created temporary directory. */
|
|
3
3
|
readonly dir: string;
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Remove the directory recursively; safe to call multiple times.
|
|
6
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-SAFE
|
|
7
|
+
*/
|
|
5
8
|
cleanup(): void;
|
|
6
9
|
}
|
|
7
10
|
/**
|
|
@@ -10,5 +13,7 @@ export interface TempDirHandle {
|
|
|
10
13
|
* This helper centralizes the mkdtemp + rmSync pattern that appears in
|
|
11
14
|
* multiple maintenance tests so those tests can focus on behavior instead
|
|
12
15
|
* of filesystem plumbing.
|
|
16
|
+
*
|
|
17
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-TEMP-HELPERS REQ-MAINT-SAFE
|
|
13
18
|
*/
|
|
14
19
|
export declare function createTempDir(prefix: string): TempDirHandle;
|
|
@@ -48,13 +48,14 @@ const path = __importStar(require("path"));
|
|
|
48
48
|
* This helper centralizes the mkdtemp + rmSync pattern that appears in
|
|
49
49
|
* multiple maintenance tests so those tests can focus on behavior instead
|
|
50
50
|
* of filesystem plumbing.
|
|
51
|
+
*
|
|
52
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-TEMP-HELPERS REQ-MAINT-SAFE
|
|
51
53
|
*/
|
|
52
54
|
function createTempDir(prefix) {
|
|
53
55
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
54
56
|
return {
|
|
55
57
|
dir,
|
|
56
58
|
cleanup() {
|
|
57
|
-
// @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-SAFE
|
|
58
59
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
59
60
|
},
|
|
60
61
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.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",
|