eslint-plugin-traceability 1.14.1 → 1.16.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 +3 -3
- package/README.md +61 -13
- package/lib/src/index.js +61 -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 +66 -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.d.ts +19 -0
- package/lib/src/rules/require-traceability.js +72 -0
- package/lib/src/utils/annotation-checker.js +23 -4
- package/lib/tests/cli-error-handling.test.js +10 -1
- package/lib/tests/config/flat-config-presets-integration.test.js +2 -0
- 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 +26 -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 +49 -10
- package/lib/tests/rules/require-story-helpers.test.js +32 -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 +80 -21
- 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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Composite ESLint rule that enforces both story and requirement traceability
|
|
4
|
+
* annotations on functions and methods.
|
|
5
|
+
*
|
|
6
|
+
* Implements Story 003.0-DEV-FUNCTION-ANNOTATIONS with:
|
|
7
|
+
* - REQ-ANNOTATION-REQUIRED
|
|
8
|
+
* - REQ-FUNCTION-DETECTION
|
|
9
|
+
* - REQ-CONFIGURABLE-SCOPE
|
|
10
|
+
* - REQ-EXPORT-PRIORITY
|
|
11
|
+
* - REQ-ERROR-LOCATION
|
|
12
|
+
* - REQ-TYPESCRIPT-SUPPORT
|
|
13
|
+
*
|
|
14
|
+
* via composition of:
|
|
15
|
+
* - ./require-story-annotation
|
|
16
|
+
* - ./require-req-annotation
|
|
17
|
+
*/
|
|
18
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
19
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
const require_story_annotation_1 = __importDefault(require("./require-story-annotation"));
|
|
23
|
+
const require_req_annotation_1 = __importDefault(require("./require-req-annotation"));
|
|
24
|
+
const storyRule = require_story_annotation_1.default;
|
|
25
|
+
const reqRule = require_req_annotation_1.default;
|
|
26
|
+
const rule = {
|
|
27
|
+
meta: {
|
|
28
|
+
type: "problem",
|
|
29
|
+
docs: {
|
|
30
|
+
description: "Require both story and requirement traceability annotations on functions and methods via the unified alias rule",
|
|
31
|
+
recommended: "error",
|
|
32
|
+
},
|
|
33
|
+
hasSuggestions: true,
|
|
34
|
+
fixable: undefined,
|
|
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.
|
|
40
|
+
...(storyRule.meta?.messages ?? {}),
|
|
41
|
+
...(reqRule.meta?.messages ?? {}),
|
|
42
|
+
},
|
|
43
|
+
schema: [],
|
|
44
|
+
},
|
|
45
|
+
create(context) {
|
|
46
|
+
const storyListeners = storyRule.create(context) || {};
|
|
47
|
+
const reqListeners = reqRule.create(context) || {};
|
|
48
|
+
const mergedListener = {};
|
|
49
|
+
const allEventNames = new Set([
|
|
50
|
+
...Object.keys(storyListeners),
|
|
51
|
+
...Object.keys(reqListeners),
|
|
52
|
+
]);
|
|
53
|
+
for (const eventName of allEventNames) {
|
|
54
|
+
const storyHandler = storyListeners[eventName];
|
|
55
|
+
const reqHandler = reqListeners[eventName];
|
|
56
|
+
if (storyHandler && reqHandler) {
|
|
57
|
+
mergedListener[eventName] = function mergedHandler(...args) {
|
|
58
|
+
storyHandler.apply(this, args);
|
|
59
|
+
reqHandler.apply(this, args);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
else if (storyHandler) {
|
|
63
|
+
mergedListener[eventName] = storyHandler;
|
|
64
|
+
}
|
|
65
|
+
else if (reqHandler) {
|
|
66
|
+
mergedListener[eventName] = reqHandler;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return mergedListener;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
exports.default = rule;
|
|
@@ -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
|
});
|
|
@@ -61,6 +61,7 @@ describe("Flat config presets integration (Story 002.0-DEV-ESLINT-CONFIG)", () =
|
|
|
61
61
|
const code = "function foo() {}";
|
|
62
62
|
const result = await lintTextWithConfig(code, config);
|
|
63
63
|
const ruleIds = result.messages.map((m) => m.ruleId).sort();
|
|
64
|
+
expect(ruleIds).toContain("traceability/require-traceability");
|
|
64
65
|
expect(ruleIds).toContain("traceability/require-story-annotation");
|
|
65
66
|
});
|
|
66
67
|
it("[REQ-CONFIG-PRESETS] strict preset also enables traceability rules via documented usage", async () => {
|
|
@@ -68,6 +69,7 @@ describe("Flat config presets integration (Story 002.0-DEV-ESLINT-CONFIG)", () =
|
|
|
68
69
|
const code = "function bar() {}";
|
|
69
70
|
const result = await lintTextWithConfig(code, config);
|
|
70
71
|
const ruleIds = result.messages.map((m) => m.ruleId).sort();
|
|
72
|
+
expect(ruleIds).toContain("traceability/require-traceability");
|
|
71
73
|
expect(ruleIds).toContain("traceability/require-story-annotation");
|
|
72
74
|
});
|
|
73
75
|
});
|
|
@@ -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
|
+
});
|
|
@@ -51,6 +51,7 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
|
|
|
51
51
|
it("[REQ-PLUGIN-STRUCTURE] rules object has correct rule names", () => {
|
|
52
52
|
// Arrange: expected rule names in insertion order
|
|
53
53
|
const expected = [
|
|
54
|
+
"require-traceability",
|
|
54
55
|
"require-story-annotation",
|
|
55
56
|
"require-req-annotation",
|
|
56
57
|
"require-branch-annotation",
|
|
@@ -69,6 +70,7 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
|
|
|
69
70
|
});
|
|
70
71
|
it("[REQ-RULE-REGISTRY] configs.recommended contains correct rule configuration", () => {
|
|
71
72
|
const recommendedRules = index_1.configs.recommended[0].rules;
|
|
73
|
+
expect(recommendedRules).toHaveProperty("traceability/require-traceability", "error");
|
|
72
74
|
expect(recommendedRules).toHaveProperty("traceability/require-story-annotation", "error");
|
|
73
75
|
expect(recommendedRules).toHaveProperty("traceability/require-req-annotation", "error");
|
|
74
76
|
expect(recommendedRules).toHaveProperty("traceability/require-branch-annotation", "error");
|
|
@@ -80,6 +82,7 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
|
|
|
80
82
|
it("[REQ-ERROR-SEVERITY] configs.recommended maps valid-annotation-format to warn and others to error", () => {
|
|
81
83
|
const recommendedRules = index_1.configs.recommended[0].rules;
|
|
82
84
|
expect(recommendedRules).toHaveProperty("traceability/valid-annotation-format", "warn");
|
|
85
|
+
expect(recommendedRules).toHaveProperty("traceability/require-traceability", "error");
|
|
83
86
|
expect(recommendedRules).toHaveProperty("traceability/require-story-annotation", "error");
|
|
84
87
|
expect(recommendedRules).toHaveProperty("traceability/require-req-annotation", "error");
|
|
85
88
|
expect(recommendedRules).toHaveProperty("traceability/require-branch-annotation", "error");
|
|
@@ -92,4 +95,27 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
|
|
|
92
95
|
const recommendedRules = index_1.configs.recommended[0].rules;
|
|
93
96
|
expect(strictRules).toEqual(recommendedRules);
|
|
94
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
|
+
});
|
|
95
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,15 @@ 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
|
+
},
|
|
68
77
|
],
|
|
69
78
|
invalid: [
|
|
70
79
|
{
|
|
@@ -77,7 +86,7 @@ declare function tsDecl(): void;`,
|
|
|
77
86
|
messageId: "missingStory",
|
|
78
87
|
suggestions: [
|
|
79
88
|
{
|
|
80
|
-
desc: `Add
|
|
89
|
+
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
90
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}`,
|
|
82
91
|
},
|
|
83
92
|
],
|
|
@@ -93,7 +102,7 @@ declare function tsDecl(): void;`,
|
|
|
93
102
|
messageId: "missingStory",
|
|
94
103
|
suggestions: [
|
|
95
104
|
{
|
|
96
|
-
desc: `Add
|
|
105
|
+
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
106
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst fnExpr = function() {};`,
|
|
98
107
|
},
|
|
99
108
|
],
|
|
@@ -110,7 +119,7 @@ declare function tsDecl(): void;`,
|
|
|
110
119
|
data: { name: "method", functionName: "method" },
|
|
111
120
|
suggestions: [
|
|
112
121
|
{
|
|
113
|
-
desc: `Add
|
|
122
|
+
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
123
|
output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
115
124
|
},
|
|
116
125
|
],
|
|
@@ -126,7 +135,7 @@ declare function tsDecl(): void;`,
|
|
|
126
135
|
messageId: "missingStory",
|
|
127
136
|
suggestions: [
|
|
128
137
|
{
|
|
129
|
-
desc: `Add
|
|
138
|
+
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
139
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
|
|
131
140
|
},
|
|
132
141
|
],
|
|
@@ -142,7 +151,7 @@ declare function tsDecl(): void;`,
|
|
|
142
151
|
messageId: "missingStory",
|
|
143
152
|
suggestions: [
|
|
144
153
|
{
|
|
145
|
-
desc: `Add
|
|
154
|
+
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
155
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
|
|
147
156
|
},
|
|
148
157
|
],
|
|
@@ -158,7 +167,7 @@ declare function tsDecl(): void;`,
|
|
|
158
167
|
messageId: "missingStory",
|
|
159
168
|
suggestions: [
|
|
160
169
|
{
|
|
161
|
-
desc: `Add
|
|
170
|
+
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
171
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst handler = () => {};`,
|
|
163
172
|
},
|
|
164
173
|
],
|
|
@@ -174,7 +183,7 @@ declare function tsDecl(): void;`,
|
|
|
174
183
|
messageId: "missingStory",
|
|
175
184
|
suggestions: [
|
|
176
185
|
{
|
|
177
|
-
desc: `Add
|
|
186
|
+
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
187
|
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
188
|
},
|
|
180
189
|
],
|
|
@@ -207,7 +216,7 @@ declare function tsDecl(): void;`,
|
|
|
207
216
|
messageId: "missingStory",
|
|
208
217
|
suggestions: [
|
|
209
218
|
{
|
|
210
|
-
desc: `Add
|
|
219
|
+
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
220
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport function exportedMissing() {}`,
|
|
212
221
|
},
|
|
213
222
|
],
|
|
@@ -224,7 +233,7 @@ declare function tsDecl(): void;`,
|
|
|
224
233
|
messageId: "missingStory",
|
|
225
234
|
suggestions: [
|
|
226
235
|
{
|
|
227
|
-
desc: `Add
|
|
236
|
+
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
237
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport const arrowExported = () => {};`,
|
|
229
238
|
},
|
|
230
239
|
],
|
|
@@ -252,7 +261,7 @@ declare function tsDecl(): void;`,
|
|
|
252
261
|
messageId: "missingStory",
|
|
253
262
|
suggestions: [
|
|
254
263
|
{
|
|
255
|
-
desc: `Add
|
|
264
|
+
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
265
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction onlyDecl() {}`,
|
|
257
266
|
},
|
|
258
267
|
],
|
|
@@ -261,4 +270,34 @@ declare function tsDecl(): void;`,
|
|
|
261
270
|
},
|
|
262
271
|
],
|
|
263
272
|
});
|
|
273
|
+
ruleTester.run("require-story-annotation with excludeTestCallbacks option", require_story_annotation_1.default, {
|
|
274
|
+
valid: [
|
|
275
|
+
{
|
|
276
|
+
name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] non-test arrow function annotated when excludeTestCallbacks=false",
|
|
277
|
+
code: `/**
|
|
278
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
279
|
+
*/
|
|
280
|
+
const handler = () => {};`,
|
|
281
|
+
options: [{ excludeTestCallbacks: false }],
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
invalid: [
|
|
285
|
+
{
|
|
286
|
+
name: "[REQ-TEST-CALLBACK-EXCLUSION][Story 003.0] Jest-style it() callback requires annotation when excludeTestCallbacks=false",
|
|
287
|
+
code: `it('does something', () => {});`,
|
|
288
|
+
options: [{ excludeTestCallbacks: false, autoFix: false }],
|
|
289
|
+
errors: [
|
|
290
|
+
{
|
|
291
|
+
messageId: "missingStory",
|
|
292
|
+
suggestions: [
|
|
293
|
+
{
|
|
294
|
+
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 */`,
|
|
295
|
+
output: `it('does something', /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n() => {});`,
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
});
|
|
264
303
|
});
|