eslint-plugin-traceability 1.15.0 → 1.16.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/README.md +61 -13
- package/lib/src/index.js +59 -0
- package/lib/src/rules/helpers/require-story-core.js +1 -1
- package/lib/src/rules/helpers/require-story-helpers.d.ts +10 -3
- package/lib/src/rules/helpers/require-story-helpers.js +84 -7
- package/lib/src/rules/no-redundant-annotation.js +73 -55
- package/lib/src/rules/require-branch-annotation.js +2 -2
- package/lib/src/rules/require-req-annotation.js +2 -2
- package/lib/src/rules/require-story-annotation.js +8 -3
- package/lib/src/rules/require-traceability.js +8 -9
- package/lib/src/utils/annotation-checker.js +23 -4
- package/lib/tests/cli-error-handling.test.js +10 -1
- package/lib/tests/integration/cli-integration.test.js +5 -0
- package/lib/tests/integration/require-traceability-aliases.integration.test.js +126 -0
- package/lib/tests/plugin-default-export-and-configs.test.js +23 -0
- package/lib/tests/rules/auto-fix-behavior-008.test.js +7 -7
- package/lib/tests/rules/error-reporting.test.js +1 -1
- package/lib/tests/rules/no-redundant-annotation.test.js +20 -0
- package/lib/tests/rules/require-story-annotation.test.js +75 -10
- package/lib/tests/rules/require-story-helpers.test.js +224 -0
- package/lib/tests/rules/require-story-utils.test.d.ts +7 -0
- package/lib/tests/rules/require-story-utils.test.js +158 -0
- package/lib/tests/utils/annotation-checker-branches.test.d.ts +5 -0
- package/lib/tests/utils/annotation-checker-branches.test.js +103 -0
- package/lib/tests/utils/annotation-scope-analyzer.test.js +134 -0
- package/lib/tests/utils/branch-annotation-helpers.test.js +66 -0
- package/package.json +2 -2
- package/user-docs/api-reference.md +71 -15
- package/user-docs/examples.md +24 -13
- package/user-docs/migration-guide.md +127 -4
- package/user-docs/traceability-overview.md +116 -0
- package/lib/tests/integration/dogfooding-validation.test.js +0 -129
- /package/lib/tests/integration/{dogfooding-validation.test.d.ts → require-traceability-aliases.integration.test.d.ts} +0 -0
|
@@ -27,21 +27,20 @@ const rule = {
|
|
|
27
27
|
meta: {
|
|
28
28
|
type: "problem",
|
|
29
29
|
docs: {
|
|
30
|
-
description: "Require
|
|
30
|
+
description: "Require both story and requirement traceability annotations on functions and methods via the unified alias rule",
|
|
31
31
|
recommended: "error",
|
|
32
32
|
},
|
|
33
|
-
hasSuggestions:
|
|
34
|
-
|
|
35
|
-
fixable: (storyRule.meta && storyRule.meta.fixable) ||
|
|
36
|
-
(reqRule.meta && reqRule.meta.fixable) ||
|
|
37
|
-
undefined,
|
|
33
|
+
hasSuggestions: true,
|
|
34
|
+
fixable: undefined,
|
|
38
35
|
messages: {
|
|
36
|
+
// Unified messageId for potential future direct use by this rule.
|
|
37
|
+
missingTraceability: "Function '{{name}}' must declare both story and requirement traceability annotations.",
|
|
38
|
+
// Preserve underlying rule messageIds so that composed listeners can
|
|
39
|
+
// continue to report using their original IDs.
|
|
39
40
|
...(storyRule.meta?.messages ?? {}),
|
|
40
41
|
...(reqRule.meta?.messages ?? {}),
|
|
41
42
|
},
|
|
42
|
-
schema:
|
|
43
|
-
(reqRule.meta && reqRule.meta.schema) ??
|
|
44
|
-
[],
|
|
43
|
+
schema: [],
|
|
45
44
|
},
|
|
46
45
|
create(context) {
|
|
47
46
|
const storyListeners = storyRule.create(context) || {};
|
|
@@ -119,17 +119,19 @@ function getNameNodeForReqReport(node) {
|
|
|
119
119
|
return node;
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
|
-
* Helper to
|
|
123
|
-
* Uses getNodeName to provide a readable name for the node.
|
|
122
|
+
* Helper to build the report options object for missing traceability annotations.
|
|
123
|
+
* Uses getNodeName to provide a readable name for the node. @supports is the
|
|
124
|
+
* preferred format for expressing traceability to one or more requirements and
|
|
125
|
+
* stories, while @req is treated as a legacy shorthand for single-story usage.
|
|
124
126
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
125
127
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
126
|
-
* @req REQ-ANNOTATION-REPORTING - Report missing
|
|
128
|
+
* @req REQ-ANNOTATION-REPORTING - Report missing traceability annotations to context
|
|
127
129
|
* @req REQ-ERROR-SPECIFIC - Provide specific error details including node name
|
|
128
130
|
* @req REQ-ERROR-LOCATION - Include contextual location information in errors
|
|
129
131
|
* @req REQ-ERROR-SUGGESTION - Provide actionable suggestions or fixes where possible
|
|
130
132
|
* @req REQ-ERROR-CONTEXT - Include contextual hints to help understand the error
|
|
131
133
|
*/
|
|
132
|
-
function
|
|
134
|
+
function buildMissingReqReportOptions(node, enableFix) {
|
|
133
135
|
const parentNode = node?.parent;
|
|
134
136
|
const name = getReportedName(node, parentNode);
|
|
135
137
|
const nameNode = getNameNodeForReqReport(node);
|
|
@@ -144,6 +146,23 @@ function reportMissing(context, node, enableFix = true) {
|
|
|
144
146
|
if (enableFix) {
|
|
145
147
|
reportOptions.fix = createMissingReqFix(node);
|
|
146
148
|
}
|
|
149
|
+
return reportOptions;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Helper to report missing traceability annotations via the ESLint context API.
|
|
153
|
+
* Uses getNodeName to provide a readable name for the node. @supports is the
|
|
154
|
+
* preferred format for expressing traceability to one or more requirements and
|
|
155
|
+
* stories, while @req is treated as a legacy shorthand for single-story usage.
|
|
156
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
157
|
+
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
158
|
+
* @req REQ-ANNOTATION-REPORTING - Report missing traceability annotations to context
|
|
159
|
+
* @req REQ-ERROR-SPECIFIC - Provide specific error details including node name
|
|
160
|
+
* @req REQ-ERROR-LOCATION - Include contextual location information in errors
|
|
161
|
+
* @req REQ-ERROR-SUGGESTION - Provide actionable suggestions or fixes where possible
|
|
162
|
+
* @req REQ-ERROR-CONTEXT - Include contextual hints to help understand the error
|
|
163
|
+
*/
|
|
164
|
+
function reportMissing(context, node, enableFix = true) {
|
|
165
|
+
const reportOptions = buildMissingReqReportOptions(node, enableFix);
|
|
147
166
|
context.report(reportOptions);
|
|
148
167
|
}
|
|
149
168
|
/**
|
|
@@ -11,12 +11,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
11
11
|
*/
|
|
12
12
|
const child_process_1 = require("child_process");
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const originalNodePath = process.env.NODE_PATH;
|
|
14
15
|
describe("CLI Error Handling for Traceability Plugin (Story 001.0-DEV-PLUGIN-SETUP)", () => {
|
|
15
16
|
beforeAll(() => {
|
|
16
17
|
// Simulate missing plugin build by deleting lib directory (if exist)
|
|
17
18
|
// In tests, assume plugin built to lib/src/index.js; point plugin import to src/index.ts via env
|
|
18
19
|
process.env.NODE_PATH = path_1.default.resolve(__dirname, "../src");
|
|
19
20
|
});
|
|
21
|
+
afterAll(() => {
|
|
22
|
+
if (originalNodePath === undefined) {
|
|
23
|
+
delete process.env.NODE_PATH;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
process.env.NODE_PATH = originalNodePath;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
20
29
|
it("[REQ-ERROR-HANDLING] should exit with error when rule module missing", () => {
|
|
21
30
|
const eslintPkgDir = path_1.default.dirname(require.resolve("eslint/package.json"));
|
|
22
31
|
const eslintCliPath = path_1.default.join(eslintPkgDir, "bin", "eslint.js");
|
|
@@ -40,6 +49,6 @@ describe("CLI Error Handling for Traceability Plugin (Story 001.0-DEV-PLUGIN-SET
|
|
|
40
49
|
});
|
|
41
50
|
// Expect non-zero exit and missing annotation message on stdout
|
|
42
51
|
expect(result.status).not.toBe(0);
|
|
43
|
-
expect(result.stdout).toContain("Function 'foo' must
|
|
52
|
+
expect(result.stdout).toContain("Function 'foo' must declare a traceability annotation. Prefer adding an @supports line that links this function to at least one story (for example, '@supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED'), or, when you only need a single-story reference, add a legacy @story annotation that points to the implementing story file, such as docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md");
|
|
44
53
|
});
|
|
45
54
|
});
|
|
@@ -33,6 +33,7 @@ describe("CLI Integration (Story 001.0-DEV-PLUGIN-SETUP)", () => {
|
|
|
33
33
|
name: "does not report error when @story annotation is present",
|
|
34
34
|
code: `/**
|
|
35
35
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
36
|
+
* @req REQ-ANNOTATION-REQUIRED
|
|
36
37
|
*/
|
|
37
38
|
function foo() {}`,
|
|
38
39
|
rule: "traceability/require-story-annotation:error",
|
|
@@ -65,6 +66,10 @@ function baz() {}`,
|
|
|
65
66
|
expectedStatus: 1,
|
|
66
67
|
},
|
|
67
68
|
];
|
|
69
|
+
/**
|
|
70
|
+
* Helper to run ESLint CLI with a single rule for integration tests
|
|
71
|
+
* @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-PLUGIN-STRUCTURE
|
|
72
|
+
*/
|
|
68
73
|
function runEslint(code, rule) {
|
|
69
74
|
const args = [
|
|
70
75
|
"--no-config-lookup",
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
/**
|
|
37
|
+
* Integration tests for unified require-traceability rule and its legacy aliases.
|
|
38
|
+
*
|
|
39
|
+
* @supports docs/stories/010.4-DEV-UNIFIED-FUNCTION-RULE-AND-ALIASES.story.md REQ-UNIFIED-ALIAS-ENGINE REQ-SUPPORTS-FIRST-MODEL REQ-PRESETS-CANONICAL-RULE
|
|
40
|
+
*/
|
|
41
|
+
const use_at_your_own_risk_1 = require("eslint/use-at-your-own-risk");
|
|
42
|
+
const index_1 = __importStar(require("../../src/index"));
|
|
43
|
+
async function lintTextWithConfig(text, filename, extraConfig) {
|
|
44
|
+
const baseConfig = {
|
|
45
|
+
plugins: {
|
|
46
|
+
traceability: index_1.default,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const eslint = new use_at_your_own_risk_1.FlatESLint({
|
|
50
|
+
overrideConfig: [baseConfig, ...extraConfig],
|
|
51
|
+
overrideConfigFile: true,
|
|
52
|
+
ignore: false,
|
|
53
|
+
});
|
|
54
|
+
const [result] = await eslint.lintText(text, { filePath: filename });
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
describe("Unified require-traceability and aliases integration (Story 010.4-DEV-UNIFIED-FUNCTION-RULE-AND-ALIASES)", () => {
|
|
58
|
+
const codeMissingAll = "function foo() {}";
|
|
59
|
+
const codeWithSupportsOnly = `/**\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction foo() {}`;
|
|
60
|
+
const codeWithStoryAndReq = `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED\n */\nfunction foo() {}`;
|
|
61
|
+
async function getDiagnosticsForRule(ruleKey, code) {
|
|
62
|
+
const config = [
|
|
63
|
+
{
|
|
64
|
+
rules: {
|
|
65
|
+
[ruleKey]: "error",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
const result = await lintTextWithConfig(code, "example.js", config);
|
|
70
|
+
return result.messages.map((m) => ({
|
|
71
|
+
ruleId: m.ruleId,
|
|
72
|
+
messageId: m.messageId,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
it("[REQ-UNIFIED-ALIAS-ENGINE] canonical and alias keys all report missing traceability on unannotated function", async () => {
|
|
76
|
+
const ruleKeys = [
|
|
77
|
+
"traceability/require-traceability",
|
|
78
|
+
"traceability/require-story-annotation",
|
|
79
|
+
"traceability/require-req-annotation",
|
|
80
|
+
];
|
|
81
|
+
const results = await Promise.all(ruleKeys.map((ruleKey) => getDiagnosticsForRule(ruleKey, codeMissingAll)));
|
|
82
|
+
results.forEach((messages, index) => {
|
|
83
|
+
const ruleKey = ruleKeys[index];
|
|
84
|
+
expect(messages.length).toBeGreaterThan(0);
|
|
85
|
+
messages.forEach((msg) => {
|
|
86
|
+
expect(msg.ruleId).toBe(ruleKey);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
it("[REQ-SUPPORTS-FIRST-MODEL] @supports-only annotation satisfies all three rule keys", async () => {
|
|
91
|
+
const ruleKeys = [
|
|
92
|
+
"traceability/require-traceability",
|
|
93
|
+
"traceability/require-story-annotation",
|
|
94
|
+
"traceability/require-req-annotation",
|
|
95
|
+
];
|
|
96
|
+
const results = await Promise.all(ruleKeys.map((ruleKey) => getDiagnosticsForRule(ruleKey, codeWithSupportsOnly)));
|
|
97
|
+
results.forEach((messages) => {
|
|
98
|
+
expect(messages).toHaveLength(0);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
it("[REQ-SUPPORTS-FIRST-MODEL] @story + @req annotation satisfies all three rule keys", async () => {
|
|
102
|
+
const ruleKeys = [
|
|
103
|
+
"traceability/require-traceability",
|
|
104
|
+
"traceability/require-story-annotation",
|
|
105
|
+
"traceability/require-req-annotation",
|
|
106
|
+
];
|
|
107
|
+
const results = await Promise.all(ruleKeys.map((ruleKey) => getDiagnosticsForRule(ruleKey, codeWithStoryAndReq)));
|
|
108
|
+
results.forEach((messages) => {
|
|
109
|
+
expect(messages).toHaveLength(0);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
it("[REQ-PRESETS-CANONICAL-RULE] recommended preset surfaces unified and legacy diagnostics together for missing annotations", async () => {
|
|
113
|
+
const result = await lintTextWithConfig(codeMissingAll, "example.js", index_1.configs.recommended);
|
|
114
|
+
const ruleIds = result.messages.map((m) => m.ruleId).sort();
|
|
115
|
+
expect(ruleIds).toContain("traceability/require-traceability");
|
|
116
|
+
expect(ruleIds).toContain("traceability/require-story-annotation");
|
|
117
|
+
expect(ruleIds).toContain("traceability/require-req-annotation");
|
|
118
|
+
});
|
|
119
|
+
it("[REQ-PRESETS-CANONICAL-RULE] strict preset surfaces unified and legacy diagnostics together for missing annotations", async () => {
|
|
120
|
+
const result = await lintTextWithConfig(codeMissingAll, "example.js", index_1.configs.strict);
|
|
121
|
+
const ruleIds = result.messages.map((m) => m.ruleId).sort();
|
|
122
|
+
expect(ruleIds).toContain("traceability/require-traceability");
|
|
123
|
+
expect(ruleIds).toContain("traceability/require-story-annotation");
|
|
124
|
+
expect(ruleIds).toContain("traceability/require-req-annotation");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -95,4 +95,27 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
|
|
|
95
95
|
const recommendedRules = index_1.configs.recommended[0].rules;
|
|
96
96
|
expect(strictRules).toEqual(recommendedRules);
|
|
97
97
|
});
|
|
98
|
+
describe("Unified function-annotation rule aliases (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", () => {
|
|
99
|
+
it("[REQ-ANNOTATION-REQUIRED] legacy rule names share the unified require-traceability implementation", () => {
|
|
100
|
+
const unified = index_1.rules["require-traceability"];
|
|
101
|
+
const storyAlias = index_1.rules["require-story-annotation"];
|
|
102
|
+
const reqAlias = index_1.rules["require-req-annotation"];
|
|
103
|
+
expect(typeof unified.create).toBe("function");
|
|
104
|
+
expect(storyAlias.create).toBe(unified.create);
|
|
105
|
+
expect(reqAlias.create).toBe(unified.create);
|
|
106
|
+
});
|
|
107
|
+
it("[REQ-CONFIGURABLE-SCOPE] alias rules preserve metadata needed for configuration and diagnostics", () => {
|
|
108
|
+
const unified = index_1.rules["require-traceability"];
|
|
109
|
+
const storyAlias = index_1.rules["require-story-annotation"];
|
|
110
|
+
const reqAlias = index_1.rules["require-req-annotation"];
|
|
111
|
+
// All variants should expose a schema and messages map so that options
|
|
112
|
+
// like scope/exportPriority and the core diagnostics remain available.
|
|
113
|
+
expect(unified.meta?.schema).toBeDefined();
|
|
114
|
+
expect(storyAlias.meta?.schema).toBeDefined();
|
|
115
|
+
expect(reqAlias.meta?.schema).toBeDefined();
|
|
116
|
+
expect(unified.meta?.messages).toBeDefined();
|
|
117
|
+
expect(storyAlias.meta?.messages).toBeDefined();
|
|
118
|
+
expect(reqAlias.meta?.messages).toBeDefined();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
98
121
|
});
|
|
@@ -46,7 +46,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
|
46
46
|
messageId: "missingStory",
|
|
47
47
|
suggestions: [
|
|
48
48
|
{
|
|
49
|
-
desc: "Add
|
|
49
|
+
desc: "Add traceability annotation for function 'autoFixMe' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
50
50
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction autoFixMe() {}`,
|
|
51
51
|
},
|
|
52
52
|
],
|
|
@@ -62,7 +62,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
|
62
62
|
messageId: "missingStory",
|
|
63
63
|
suggestions: [
|
|
64
64
|
{
|
|
65
|
-
desc: "Add
|
|
65
|
+
desc: "Add traceability annotation for function 'fnExpr' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
66
66
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
|
|
67
67
|
},
|
|
68
68
|
],
|
|
@@ -78,7 +78,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
|
78
78
|
messageId: "missingStory",
|
|
79
79
|
suggestions: [
|
|
80
80
|
{
|
|
81
|
-
desc: "Add
|
|
81
|
+
desc: "Add traceability annotation for function 'method' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
82
82
|
output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
83
83
|
},
|
|
84
84
|
],
|
|
@@ -98,7 +98,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
|
98
98
|
messageId: "missingStory",
|
|
99
99
|
suggestions: [
|
|
100
100
|
{
|
|
101
|
-
desc: "Add
|
|
101
|
+
desc: "Add traceability annotation for function 'tsDecl' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
102
102
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
|
|
103
103
|
},
|
|
104
104
|
],
|
|
@@ -118,7 +118,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
|
118
118
|
messageId: "missingStory",
|
|
119
119
|
suggestions: [
|
|
120
120
|
{
|
|
121
|
-
desc: "Add
|
|
121
|
+
desc: "Add traceability annotation for function 'method' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
122
122
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
|
|
123
123
|
},
|
|
124
124
|
],
|
|
@@ -220,7 +220,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
|
220
220
|
messageId: "missingStory",
|
|
221
221
|
suggestions: [
|
|
222
222
|
{
|
|
223
|
-
desc: "Add
|
|
223
|
+
desc: "Add traceability annotation for function 'needsFixOnce' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
224
224
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction needsFixOnce() {}`,
|
|
225
225
|
},
|
|
226
226
|
],
|
|
@@ -236,7 +236,7 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
|
236
236
|
messageId: "missingStory",
|
|
237
237
|
suggestions: [
|
|
238
238
|
{
|
|
239
|
-
desc: "Add
|
|
239
|
+
desc: "Add traceability annotation for function 'method' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */",
|
|
240
240
|
output: `class F {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
241
241
|
},
|
|
242
242
|
],
|
|
@@ -91,7 +91,7 @@ describe("Error Reporting Enhancements for require-story-annotation (Story 007.0
|
|
|
91
91
|
expect(Array.isArray(error.suggest)).toBe(true);
|
|
92
92
|
expect(error.suggest.length).toBeGreaterThanOrEqual(1);
|
|
93
93
|
const suggestion = error.suggest[0];
|
|
94
|
-
expect(suggestion.desc).toBe("Add
|
|
94
|
+
expect(suggestion.desc).toBe("Add traceability annotation for function 'bar' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */");
|
|
95
95
|
expect(suggestion.fix).toBeDefined();
|
|
96
96
|
});
|
|
97
97
|
});
|
|
@@ -29,6 +29,14 @@ describe("no-redundant-annotation rule (Story 027.0-DEV-REDUNDANT-ANNOTATION-DET
|
|
|
29
29
|
name: "[REQ-STATEMENT-SIGNIFICANCE] preserves annotation on complex nested branch",
|
|
30
30
|
code: `function example() {\n // @story docs/stories/006.0-EXAMPLE.story.md\n // @req REQ-OUTER-CHECK\n if (enabled) {\n // @story docs/stories/006.0-EXAMPLE.story.md\n // @req REQ-INNER-VALIDATION\n if (validate) {\n validate(data);\n }\n }\n}`,
|
|
31
31
|
},
|
|
32
|
+
{
|
|
33
|
+
name: "[REQ-SUPPORTS-COVERAGE] preserves non-redundant mixed @supports/@req pairs when only partially covered by scope",
|
|
34
|
+
code: `function example() {\n /**\n * @story docs/stories/010.0-EXAMPLE.story.md\n * @req REQ-FN-LEVEL\n * @supports REQ-SHARED\n */\n if (flag) {\n // @story docs/stories/010.0-EXAMPLE.story.md\n // @req REQ-BRANCH-SPECIFIC\n // @supports REQ-SHARED\n doThing();\n }\n}`,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "[REQ-SCOPE-ANALYSIS] preserves annotations on both branch and statement when they intentionally duplicate each other",
|
|
38
|
+
code: `function example() {\n if (condition) { // @story docs/stories/007.0-EXAMPLE.story.md @req REQ-BRANCH\n // @story docs/stories/007.0-EXAMPLE.story.md\n // @req REQ-BRANCH\n doBranchWork();\n }\n}`,
|
|
39
|
+
},
|
|
32
40
|
],
|
|
33
41
|
invalid: [
|
|
34
42
|
{
|
|
@@ -67,6 +75,18 @@ describe("no-redundant-annotation rule (Story 027.0-DEV-REDUNDANT-ANNOTATION-DET
|
|
|
67
75
|
output: `function example() {\n const keep = 1;\n // @story docs/stories/003.0-EXAMPLE.story.md\n // @req REQ-INIT\n if (flag) {\n const value = 1;\n }\n}`,
|
|
68
76
|
errors: [{ messageId: "redundantAnnotation" }],
|
|
69
77
|
},
|
|
78
|
+
{
|
|
79
|
+
name: "[REQ-SCOPE-INHERITANCE] flags redundant statement annotation when scopePairs come from parent function JSDoc",
|
|
80
|
+
code: `/**\n * @story docs/stories/008.0-EXAMPLE.story.md\n * @req REQ-FUNC\n */\nfunction example() {\n // @story docs/stories/008.0-EXAMPLE.story.md\n // @req REQ-FUNC\n const result = compute();\n}`,
|
|
81
|
+
output: `/**\n * @story docs/stories/008.0-EXAMPLE.story.md\n * @req REQ-FUNC\n */\nfunction example() {\n const result = compute();\n}`,
|
|
82
|
+
errors: [{ messageId: "redundantAnnotation" }],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "[REQ-SUPPORTS-COVERAGE][REQ-DUPLICATION-DETECTION] flags redundant statement with multiple fully-covered @supports pairs",
|
|
86
|
+
code: `/**\n * @story docs/stories/009.0-EXAMPLE.story.md\n * @supports REQ-SUP-A, REQ-SUP-B\n */\nfunction example() {\n // @story docs/stories/009.0-EXAMPLE.story.md\n // @supports REQ-SUP-A, REQ-SUP-B\n const supported = checkSupport();\n}`,
|
|
87
|
+
output: `/**\n * @story docs/stories/009.0-EXAMPLE.story.md\n * @supports REQ-SUP-A, REQ-SUP-B\n */\nfunction example() {\n const supported = checkSupport();\n}`,
|
|
88
|
+
errors: [{ messageId: "redundantAnnotation" }],
|
|
89
|
+
},
|
|
70
90
|
// TODO: rule implementation exists; full invalid-case behavior tests pending refinement
|
|
71
91
|
// {
|
|
72
92
|
// name: "[REQ-SCOPE-ANALYSIS][REQ-STATEMENT-SIGNIFICANCE] flags redundant annotation on simple return inside annotated if",
|
|
@@ -65,6 +65,41 @@ declare function tsDecl(): void;`,
|
|
|
65
65
|
name: "[REQ-NESTED-FUNCTION-INHERITANCE] anonymous inner function inherits outer annotation",
|
|
66
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}`,
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] default exclusion of Jest-style anonymous test callbacks",
|
|
70
|
+
code: `/**
|
|
71
|
+
* @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-TEST-CALLBACK-EXCLUSION
|
|
72
|
+
*/
|
|
73
|
+
describe('Feature X', () => {
|
|
74
|
+
it('does something', () => {});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Mocha-style suite/context/specify examples
|
|
78
|
+
suite('Mocha suite', () => {
|
|
79
|
+
beforeEach(() => {});
|
|
80
|
+
afterEach(() => {});
|
|
81
|
+
before(() => {});
|
|
82
|
+
after(() => {});
|
|
83
|
+
|
|
84
|
+
test('Mocha test', () => {});
|
|
85
|
+
specify('Mocha specify', () => {});
|
|
86
|
+
context('Mocha context', () => {
|
|
87
|
+
it('nested it', () => {});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Vitest-style APIs including hooks and bench
|
|
92
|
+
describe('Vitest suite', () => {
|
|
93
|
+
beforeEach(() => {});
|
|
94
|
+
afterEach(() => {});
|
|
95
|
+
beforeAll(() => {});
|
|
96
|
+
afterAll(() => {});
|
|
97
|
+
|
|
98
|
+
it('Vitest it', () => {});
|
|
99
|
+
test('Vitest test', () => {});
|
|
100
|
+
bench('Vitest bench', () => {});
|
|
101
|
+
});`,
|
|
102
|
+
},
|
|
68
103
|
],
|
|
69
104
|
invalid: [
|
|
70
105
|
{
|
|
@@ -77,7 +112,7 @@ declare function tsDecl(): void;`,
|
|
|
77
112
|
messageId: "missingStory",
|
|
78
113
|
suggestions: [
|
|
79
114
|
{
|
|
80
|
-
desc: `Add
|
|
115
|
+
desc: `Add traceability annotation for function 'bar' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
81
116
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}`,
|
|
82
117
|
},
|
|
83
118
|
],
|
|
@@ -93,7 +128,7 @@ declare function tsDecl(): void;`,
|
|
|
93
128
|
messageId: "missingStory",
|
|
94
129
|
suggestions: [
|
|
95
130
|
{
|
|
96
|
-
desc: `Add
|
|
131
|
+
desc: `Add traceability annotation for function 'fnExpr' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
97
132
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
|
|
98
133
|
},
|
|
99
134
|
],
|
|
@@ -110,7 +145,7 @@ declare function tsDecl(): void;`,
|
|
|
110
145
|
data: { name: "method", functionName: "method" },
|
|
111
146
|
suggestions: [
|
|
112
147
|
{
|
|
113
|
-
desc: `Add
|
|
148
|
+
desc: `Add traceability annotation for function 'method' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
114
149
|
output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
115
150
|
},
|
|
116
151
|
],
|
|
@@ -126,7 +161,7 @@ declare function tsDecl(): void;`,
|
|
|
126
161
|
messageId: "missingStory",
|
|
127
162
|
suggestions: [
|
|
128
163
|
{
|
|
129
|
-
desc: `Add
|
|
164
|
+
desc: `Add traceability annotation for function 'tsDecl' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
130
165
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
|
|
131
166
|
},
|
|
132
167
|
],
|
|
@@ -142,7 +177,7 @@ declare function tsDecl(): void;`,
|
|
|
142
177
|
messageId: "missingStory",
|
|
143
178
|
suggestions: [
|
|
144
179
|
{
|
|
145
|
-
desc: `Add
|
|
180
|
+
desc: `Add traceability annotation for function 'method' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
146
181
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
|
|
147
182
|
},
|
|
148
183
|
],
|
|
@@ -158,7 +193,7 @@ declare function tsDecl(): void;`,
|
|
|
158
193
|
messageId: "missingStory",
|
|
159
194
|
suggestions: [
|
|
160
195
|
{
|
|
161
|
-
desc: `Add
|
|
196
|
+
desc: `Add traceability annotation for function 'handler' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
162
197
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst handler = () => {};`,
|
|
163
198
|
},
|
|
164
199
|
],
|
|
@@ -174,7 +209,7 @@ declare function tsDecl(): void;`,
|
|
|
174
209
|
messageId: "missingStory",
|
|
175
210
|
suggestions: [
|
|
176
211
|
{
|
|
177
|
-
desc: `Add
|
|
212
|
+
desc: `Add traceability annotation for function 'innerNamed' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
178
213
|
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
214
|
},
|
|
180
215
|
],
|
|
@@ -207,7 +242,7 @@ declare function tsDecl(): void;`,
|
|
|
207
242
|
messageId: "missingStory",
|
|
208
243
|
suggestions: [
|
|
209
244
|
{
|
|
210
|
-
desc: `Add
|
|
245
|
+
desc: `Add traceability annotation for function 'exportedMissing' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
211
246
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport function exportedMissing() {}`,
|
|
212
247
|
},
|
|
213
248
|
],
|
|
@@ -224,7 +259,7 @@ declare function tsDecl(): void;`,
|
|
|
224
259
|
messageId: "missingStory",
|
|
225
260
|
suggestions: [
|
|
226
261
|
{
|
|
227
|
-
desc: `Add
|
|
262
|
+
desc: `Add traceability annotation for function 'arrowExported' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
228
263
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport const arrowExported = () => {};`,
|
|
229
264
|
},
|
|
230
265
|
],
|
|
@@ -252,7 +287,7 @@ declare function tsDecl(): void;`,
|
|
|
252
287
|
messageId: "missingStory",
|
|
253
288
|
suggestions: [
|
|
254
289
|
{
|
|
255
|
-
desc: `Add
|
|
290
|
+
desc: `Add traceability annotation for function 'onlyDecl' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
256
291
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction onlyDecl() {}`,
|
|
257
292
|
},
|
|
258
293
|
],
|
|
@@ -261,4 +296,34 @@ declare function tsDecl(): void;`,
|
|
|
261
296
|
},
|
|
262
297
|
],
|
|
263
298
|
});
|
|
299
|
+
ruleTester.run("require-story-annotation with excludeTestCallbacks option", require_story_annotation_1.default, {
|
|
300
|
+
valid: [
|
|
301
|
+
{
|
|
302
|
+
name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] non-test arrow function annotated when excludeTestCallbacks=false",
|
|
303
|
+
code: `/**
|
|
304
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
305
|
+
*/
|
|
306
|
+
const handler = () => {};`,
|
|
307
|
+
options: [{ excludeTestCallbacks: false }],
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
invalid: [
|
|
311
|
+
{
|
|
312
|
+
name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] Jest-style it() callback requires annotation when excludeTestCallbacks=false",
|
|
313
|
+
code: `it('does something', () => {});`,
|
|
314
|
+
options: [{ excludeTestCallbacks: false, autoFix: false }],
|
|
315
|
+
errors: [
|
|
316
|
+
{
|
|
317
|
+
messageId: "missingStory",
|
|
318
|
+
suggestions: [
|
|
319
|
+
{
|
|
320
|
+
desc: `Add traceability annotation for function '(anonymous)' using @supports (preferred) or @story (legacy), for example: /** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
321
|
+
output: `it('does something', /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n() => {});`,
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
});
|
|
264
329
|
});
|