eslint-plugin-traceability 1.6.5 → 1.7.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/README.md +38 -1
- package/lib/src/index.d.ts +28 -25
- package/lib/src/index.js +49 -31
- package/lib/src/maintenance/cli.d.ts +12 -0
- package/lib/src/maintenance/cli.js +279 -0
- package/lib/src/maintenance/detect.js +27 -12
- package/lib/src/maintenance/update.js +42 -34
- package/lib/src/maintenance/utils.js +30 -30
- package/lib/src/rules/helpers/require-story-io.js +51 -15
- package/lib/src/rules/helpers/valid-annotation-options.d.ts +118 -0
- package/lib/src/rules/helpers/valid-annotation-options.js +167 -0
- package/lib/src/rules/helpers/valid-annotation-utils.d.ts +68 -0
- package/lib/src/rules/helpers/valid-annotation-utils.js +103 -0
- package/lib/src/rules/helpers/valid-story-reference-helpers.d.ts +67 -0
- package/lib/src/rules/helpers/valid-story-reference-helpers.js +92 -0
- package/lib/src/rules/valid-annotation-format.js +168 -180
- package/lib/src/rules/valid-req-reference.js +139 -29
- package/lib/src/rules/valid-story-reference.d.ts +7 -0
- package/lib/src/rules/valid-story-reference.js +38 -80
- package/lib/src/utils/annotation-checker.js +2 -145
- package/lib/src/utils/branch-annotation-helpers.js +12 -3
- package/lib/src/utils/reqAnnotationDetection.d.ts +6 -0
- package/lib/src/utils/reqAnnotationDetection.js +152 -0
- package/lib/tests/maintenance/cli.test.d.ts +1 -0
- package/lib/tests/maintenance/cli.test.js +172 -0
- package/lib/tests/rules/require-branch-annotation.test.js +3 -2
- package/lib/tests/rules/require-req-annotation.test.js +57 -68
- package/lib/tests/rules/require-story-annotation.test.js +13 -28
- package/lib/tests/rules/require-story-core-edgecases.test.js +3 -58
- package/lib/tests/rules/require-story-core.autofix.test.js +5 -41
- package/lib/tests/rules/valid-annotation-format.test.js +328 -51
- package/lib/tests/utils/annotation-checker.test.d.ts +23 -0
- package/lib/tests/utils/annotation-checker.test.js +24 -17
- package/lib/tests/utils/require-story-core-test-helpers.d.ts +10 -0
- package/lib/tests/utils/require-story-core-test-helpers.js +75 -0
- package/lib/tests/utils/ts-language-options.d.ts +22 -0
- package/lib/tests/utils/ts-language-options.js +27 -0
- package/package.json +12 -3
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasReqAnnotation = hasReqAnnotation;
|
|
4
|
+
/**
|
|
5
|
+
* Shared @req detection helpers used by annotation-checker utilities.
|
|
6
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
7
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req markers around function-like nodes
|
|
8
|
+
*/
|
|
9
|
+
const require_story_io_1 = require("../rules/helpers/require-story-io");
|
|
10
|
+
/**
|
|
11
|
+
* Predicate helper to check whether a comment contains a @req annotation.
|
|
12
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
13
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req tag inside a comment
|
|
14
|
+
*/
|
|
15
|
+
function commentContainsReq(c) {
|
|
16
|
+
return c && typeof c.value === "string" && c.value.includes("@req");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Line-based helper adapted from linesBeforeHasStory to detect @req.
|
|
20
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
21
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req in preceding source lines
|
|
22
|
+
*/
|
|
23
|
+
function linesBeforeHasReq(sourceCode, node) {
|
|
24
|
+
const lines = sourceCode && sourceCode.lines;
|
|
25
|
+
const startLine = node && node.loc && typeof node.loc.start?.line === "number"
|
|
26
|
+
? node.loc.start.line
|
|
27
|
+
: null;
|
|
28
|
+
// Guard against missing or malformed source/loc information before scanning.
|
|
29
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
30
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Avoid false positives when sourceCode/loc is incomplete
|
|
31
|
+
if (!Array.isArray(lines) || typeof startLine !== "number") {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const from = Math.max(0, startLine - 1 - require_story_io_1.LOOKBACK_LINES);
|
|
35
|
+
const to = Math.max(0, startLine - 1);
|
|
36
|
+
// Scan each physical line in the configured lookback window for an @req marker.
|
|
37
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
38
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Search preceding lines for @req text
|
|
39
|
+
for (let i = from; i < to; i++) {
|
|
40
|
+
const text = lines[i];
|
|
41
|
+
// When a line contains @req we treat the function as already annotated.
|
|
42
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
43
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req marker in raw source lines
|
|
44
|
+
if (typeof text === "string" && text.includes("@req")) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parent-chain helper adapted from parentChainHasStory to detect @req.
|
|
52
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
53
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req in parent-chain comments
|
|
54
|
+
*/
|
|
55
|
+
function parentChainHasReq(sourceCode, node) {
|
|
56
|
+
let p = node && node.parent;
|
|
57
|
+
// Walk up the parent chain and inspect comments attached to each ancestor.
|
|
58
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
59
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Traverse parent nodes when local comments are absent
|
|
60
|
+
while (p) {
|
|
61
|
+
const pComments = typeof sourceCode?.getCommentsBefore === "function"
|
|
62
|
+
? sourceCode.getCommentsBefore(p) || []
|
|
63
|
+
: [];
|
|
64
|
+
// Look for @req in comments immediately preceding each parent node.
|
|
65
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
66
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req markers in parent comments
|
|
67
|
+
if (Array.isArray(pComments) && pComments.some(commentContainsReq)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
const pLeading = p.leadingComments || [];
|
|
71
|
+
// Also inspect leadingComments attached directly to the parent node.
|
|
72
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
73
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req markers in parent leadingComments
|
|
74
|
+
if (Array.isArray(pLeading) && pLeading.some(commentContainsReq)) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
p = p.parent;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Fallback text window helper adapted from fallbackTextBeforeHasStory to detect @req.
|
|
83
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
84
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req in fallback text window before node
|
|
85
|
+
*/
|
|
86
|
+
function fallbackTextBeforeHasReq(sourceCode, node) {
|
|
87
|
+
// Guard against unsupported sourceCode or nodes without a usable range.
|
|
88
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
89
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Ensure we only inspect text when range information is available
|
|
90
|
+
if (typeof sourceCode?.getText !== "function" ||
|
|
91
|
+
!Array.isArray((node && node.range) || [])) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const range = node.range;
|
|
95
|
+
// Guard when the node range cannot provide a numeric start index.
|
|
96
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
97
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Avoid scanning when range start is not a number
|
|
98
|
+
if (!Array.isArray(range) || typeof range[0] !== "number") {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const start = Math.max(0, range[0] - require_story_io_1.FALLBACK_WINDOW);
|
|
103
|
+
const textBefore = sourceCode.getText().slice(start, range[0]);
|
|
104
|
+
// Detect @req in the bounded text window immediately preceding the node.
|
|
105
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
106
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req marker in fallback text window
|
|
107
|
+
if (typeof textBefore === "string" && textBefore.includes("@req")) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Swallow detection errors to avoid breaking lint runs due to malformed source.
|
|
113
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
114
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Treat IO/detection failures as "no annotation" instead of throwing
|
|
115
|
+
/* noop */
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Helper to determine whether a JSDoc or any nearby comments contain a @req annotation.
|
|
121
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
122
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
123
|
+
*/
|
|
124
|
+
function hasReqAnnotation(jsdoc, comments, context, node) {
|
|
125
|
+
try {
|
|
126
|
+
const sourceCode = context && typeof context.getSourceCode === "function"
|
|
127
|
+
? context.getSourceCode()
|
|
128
|
+
: undefined;
|
|
129
|
+
// Prefer robust, location-based heuristics when sourceCode and node are available.
|
|
130
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
131
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Use multiple heuristics to detect @req markers around the node
|
|
132
|
+
if (sourceCode && node) {
|
|
133
|
+
if (linesBeforeHasReq(sourceCode, node) ||
|
|
134
|
+
parentChainHasReq(sourceCode, node) ||
|
|
135
|
+
fallbackTextBeforeHasReq(sourceCode, node)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Swallow detection errors and fall through to simple checks.
|
|
142
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
143
|
+
// @req REQ-ANNOTATION-REQ-DETECTION - Fail gracefully when advanced detection heuristics throw
|
|
144
|
+
}
|
|
145
|
+
// BRANCH @req detection on JSDoc or comments
|
|
146
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
147
|
+
// @req REQ-ANNOTATION-REQ-DETECTION
|
|
148
|
+
return ((jsdoc &&
|
|
149
|
+
typeof jsdoc.value === "string" &&
|
|
150
|
+
jsdoc.value.includes("@req")) ||
|
|
151
|
+
comments.some(commentContainsReq));
|
|
152
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
/**
|
|
7
|
+
* Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
8
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
9
|
+
* @req REQ-MAINT-DETECT - CLI detection of stale annotations
|
|
10
|
+
* @req REQ-MAINT-VERIFY - CLI verification of annotations
|
|
11
|
+
* @req REQ-MAINT-REPORT - CLI reporting of stale annotations
|
|
12
|
+
* @req REQ-MAINT-UPDATE - CLI updating of annotation references
|
|
13
|
+
* @req REQ-MAINT-SAFE - Clear exit codes and non-destructive dry-run
|
|
14
|
+
*/
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const os_1 = __importDefault(require("os"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const cli_1 = require("../../src/maintenance/cli");
|
|
19
|
+
function withTempDir() {
|
|
20
|
+
const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "maint-cli-"));
|
|
21
|
+
return tmpDir;
|
|
22
|
+
}
|
|
23
|
+
describe("Maintenance CLI (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
24
|
+
let originalCwd;
|
|
25
|
+
beforeAll(() => {
|
|
26
|
+
originalCwd = process.cwd();
|
|
27
|
+
});
|
|
28
|
+
afterAll(() => {
|
|
29
|
+
process.chdir(originalCwd);
|
|
30
|
+
});
|
|
31
|
+
it("[REQ-MAINT-DETECT] detect exits with code 0 and message when no stale annotations", () => {
|
|
32
|
+
const dir = withTempDir();
|
|
33
|
+
process.chdir(dir);
|
|
34
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
35
|
+
const code = (0, cli_1.runMaintenanceCli)(["node", "traceability-maint", "detect"]);
|
|
36
|
+
try {
|
|
37
|
+
expect(code).toBe(0);
|
|
38
|
+
expect(logSpy).toHaveBeenCalledWith("No stale @story annotations found.");
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
logSpy.mockRestore();
|
|
42
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
it("[REQ-MAINT-VERIFY] verify exits with code 0 when annotations valid", () => {
|
|
46
|
+
const dir = withTempDir();
|
|
47
|
+
process.chdir(dir);
|
|
48
|
+
const tsContent = `/**\n * @story my-story.story.md\n */`;
|
|
49
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, "file.ts"), tsContent, "utf8");
|
|
50
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, "my-story.story.md"), "# Dummy Story", "utf8");
|
|
51
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
52
|
+
const code = (0, cli_1.runMaintenanceCli)(["node", "traceability-maint", "verify"]);
|
|
53
|
+
try {
|
|
54
|
+
expect(code).toBe(0);
|
|
55
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
logSpy.mockRestore();
|
|
59
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
it("[REQ-MAINT-REPORT] report prints human-readable summary and exits 0", () => {
|
|
63
|
+
const dir = withTempDir();
|
|
64
|
+
process.chdir(dir);
|
|
65
|
+
const tsContent = `/**\n * @story missing.story.md\n */`;
|
|
66
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, "file.ts"), tsContent, "utf8");
|
|
67
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
68
|
+
const code = (0, cli_1.runMaintenanceCli)(["node", "traceability-maint", "report"]);
|
|
69
|
+
try {
|
|
70
|
+
expect(code).toBe(0);
|
|
71
|
+
const allMessages = logSpy.mock.calls.flat().join("\n");
|
|
72
|
+
expect(allMessages).toContain("Traceability Maintenance Report");
|
|
73
|
+
expect(allMessages).toContain("missing.story.md");
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
logSpy.mockRestore();
|
|
77
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
it("[REQ-MAINT-UPDATE] update performs replacements and exits 0", () => {
|
|
81
|
+
const dir = withTempDir();
|
|
82
|
+
process.chdir(dir);
|
|
83
|
+
const tsContent = `/**\n * @story old.path.md\n */`;
|
|
84
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, "file.ts"), tsContent, "utf8");
|
|
85
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
86
|
+
const code = (0, cli_1.runMaintenanceCli)([
|
|
87
|
+
"node",
|
|
88
|
+
"traceability-maint",
|
|
89
|
+
"update",
|
|
90
|
+
"--from",
|
|
91
|
+
"old.path.md",
|
|
92
|
+
"--to",
|
|
93
|
+
"new.path.md",
|
|
94
|
+
]);
|
|
95
|
+
try {
|
|
96
|
+
expect(code).toBe(0);
|
|
97
|
+
const updated = fs_1.default.readFileSync(path_1.default.join(dir, "file.ts"), "utf8");
|
|
98
|
+
expect(updated).toContain("@story new.path.md");
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
logSpy.mockRestore();
|
|
102
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
it("[REQ-MAINT-SAFE] update requires --from and --to and exits 2 when missing", () => {
|
|
106
|
+
const dir = withTempDir();
|
|
107
|
+
process.chdir(dir);
|
|
108
|
+
const errorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
|
|
109
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
110
|
+
const code = (0, cli_1.runMaintenanceCli)(["node", "traceability-maint", "update"]);
|
|
111
|
+
try {
|
|
112
|
+
expect(code).toBe(2);
|
|
113
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
114
|
+
expect(logSpy).toHaveBeenCalled();
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
errorSpy.mockRestore();
|
|
118
|
+
logSpy.mockRestore();
|
|
119
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
it("[REQ-MAINT-SAFE] dry-run does not modify files and exits 0", () => {
|
|
123
|
+
const dir = withTempDir();
|
|
124
|
+
process.chdir(dir);
|
|
125
|
+
const tsContent = `/**\n * @story old.path.md\n */`;
|
|
126
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, "file.ts"), tsContent, "utf8");
|
|
127
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
128
|
+
const code = (0, cli_1.runMaintenanceCli)([
|
|
129
|
+
"node",
|
|
130
|
+
"traceability-maint",
|
|
131
|
+
"update",
|
|
132
|
+
"--from",
|
|
133
|
+
"old.path.md",
|
|
134
|
+
"--to",
|
|
135
|
+
"new.path.md",
|
|
136
|
+
"--dry-run",
|
|
137
|
+
]);
|
|
138
|
+
try {
|
|
139
|
+
expect(code).toBe(0);
|
|
140
|
+
const contentAfter = fs_1.default.readFileSync(path_1.default.join(dir, "file.ts"), "utf8");
|
|
141
|
+
expect(contentAfter).toBe(tsContent);
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
logSpy.mockRestore();
|
|
145
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
it("[REQ-MAINT-DETECT] detect supports --json output", () => {
|
|
149
|
+
const dir = withTempDir();
|
|
150
|
+
process.chdir(dir);
|
|
151
|
+
const tsContent = `/**\n * @story stale.story.md\n */`;
|
|
152
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, "file.ts"), tsContent, "utf8");
|
|
153
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
154
|
+
const code = (0, cli_1.runMaintenanceCli)([
|
|
155
|
+
"node",
|
|
156
|
+
"traceability-maint",
|
|
157
|
+
"detect",
|
|
158
|
+
"--json",
|
|
159
|
+
]);
|
|
160
|
+
try {
|
|
161
|
+
expect(code).toBe(1);
|
|
162
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
163
|
+
const payload = JSON.parse(String(logSpy.mock.calls[0][0]));
|
|
164
|
+
expect(Array.isArray(payload.stale)).toBe(true);
|
|
165
|
+
expect(payload.stale).toContain("stale.story.md");
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
logSpy.mockRestore();
|
|
169
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -17,8 +17,9 @@ const require_branch_annotation_1 = __importDefault(require("../../src/rules/req
|
|
|
17
17
|
const ruleTester = new eslint_1.RuleTester({
|
|
18
18
|
languageOptions: { parserOptions: { ecmaVersion: 2020 } },
|
|
19
19
|
});
|
|
20
|
+
const runRule = (tests) => ruleTester.run("require-branch-annotation", require_branch_annotation_1.default, tests);
|
|
20
21
|
describe("Require Branch Annotation Rule (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () => {
|
|
21
|
-
|
|
22
|
+
runRule({
|
|
22
23
|
valid: [
|
|
23
24
|
{
|
|
24
25
|
name: "[REQ-BRANCH-DETECTION] valid fallback scanning comment detection",
|
|
@@ -277,7 +278,7 @@ for (let i = 0; i < 3; i++) {}`,
|
|
|
277
278
|
},
|
|
278
279
|
],
|
|
279
280
|
});
|
|
280
|
-
|
|
281
|
+
runRule({
|
|
281
282
|
valid: [],
|
|
282
283
|
invalid: [
|
|
283
284
|
{
|
|
@@ -9,14 +9,63 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
* @req REQ-ANNOTATION-REQUIRED - Verify require-req-annotation rule enforces @req on functions
|
|
10
10
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
11
11
|
* @req REQ-ERROR-SPECIFIC - Verify enhanced, specific error messaging behavior
|
|
12
|
+
*
|
|
13
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
14
|
+
* @req REQ-TYPESCRIPT-SUPPORT - Verify TypeScript declarations are checked via shared annotation checker helper
|
|
12
15
|
*/
|
|
13
16
|
const eslint_1 = require("eslint");
|
|
14
17
|
const require_req_annotation_1 = __importDefault(require("../../src/rules/require-req-annotation"));
|
|
18
|
+
const ts_language_options_1 = require("../utils/ts-language-options");
|
|
19
|
+
const annotation_checker_test_1 = require("../utils/annotation-checker.test");
|
|
15
20
|
const ruleTester = new eslint_1.RuleTester({
|
|
16
21
|
languageOptions: {
|
|
17
22
|
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
18
23
|
},
|
|
19
24
|
});
|
|
25
|
+
/**
|
|
26
|
+
* @trace Story 003.0-DEV-FUNCTION-ANNOTATIONS / REQ-TYPESCRIPT-SUPPORT
|
|
27
|
+
* Exercise the require-req-annotation rule's behavior on TSDeclareFunction and
|
|
28
|
+
* TSMethodSignature via the shared runAnnotationCheckerTests helper.
|
|
29
|
+
*
|
|
30
|
+
* The helper delegates to the real rule's TypeScript-specific visitors
|
|
31
|
+
* without changing its behavior; it provides the common TS parser
|
|
32
|
+
* configuration and assertion plumbing.
|
|
33
|
+
*/
|
|
34
|
+
(0, annotation_checker_test_1.runAnnotationCheckerTests)("require-req-annotation", {
|
|
35
|
+
rule: require_req_annotation_1.default,
|
|
36
|
+
valid: [
|
|
37
|
+
{
|
|
38
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] valid with @req annotation on TSDeclareFunction",
|
|
39
|
+
code: `/**\n * @req REQ-EXAMPLE\n */\ndeclare function foo(): void;`,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] valid with @req annotation on TSMethodSignature",
|
|
43
|
+
code: `interface I {\n /**\n * @req REQ-EXAMPLE\n */\n method(): void;\n}`,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
invalid: [
|
|
47
|
+
{
|
|
48
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSDeclareFunction",
|
|
49
|
+
code: `declare function baz(): void;`,
|
|
50
|
+
errors: [
|
|
51
|
+
{
|
|
52
|
+
messageId: "missingReq",
|
|
53
|
+
data: { name: "baz", functionName: "baz" },
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSMethodSignature",
|
|
59
|
+
code: `interface I { method(): void; }`,
|
|
60
|
+
errors: [
|
|
61
|
+
{
|
|
62
|
+
messageId: "missingReq",
|
|
63
|
+
data: { name: "method", functionName: "method" },
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
20
69
|
describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", () => {
|
|
21
70
|
ruleTester.run("require-req-annotation", require_req_annotation_1.default, {
|
|
22
71
|
valid: [
|
|
@@ -28,22 +77,6 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
28
77
|
name: "[REQ-ANNOTATION-REQUIRED] valid with @story and @req annotations",
|
|
29
78
|
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-EXAMPLE\n */\nfunction bar() {}`,
|
|
30
79
|
},
|
|
31
|
-
{
|
|
32
|
-
name: "[REQ-TYPESCRIPT-SUPPORT] valid with @req annotation on TSDeclareFunction",
|
|
33
|
-
code: `/**\n * @req REQ-EXAMPLE\n */\ndeclare function foo(): void;`,
|
|
34
|
-
languageOptions: {
|
|
35
|
-
parser: require("@typescript-eslint/parser"),
|
|
36
|
-
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: "[REQ-TYPESCRIPT-SUPPORT] valid with @req annotation on TSMethodSignature",
|
|
41
|
-
code: `interface I {\n /**\n * @req REQ-EXAMPLE\n */\n method(): void;\n}`,
|
|
42
|
-
languageOptions: {
|
|
43
|
-
parser: require("@typescript-eslint/parser"),
|
|
44
|
-
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
80
|
{
|
|
48
81
|
name: "[REQ-FUNCTION-DETECTION][Story 003.0] valid FunctionExpression with @req annotation",
|
|
49
82
|
code: `const fn = /**\n * @req REQ-EXAMPLE\n */\nfunction() {};`,
|
|
@@ -52,22 +85,14 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
52
85
|
name: "[REQ-FUNCTION-DETECTION][Story 003.0] valid MethodDefinition with @req annotation",
|
|
53
86
|
code: `class C {\n /**\n * @req REQ-EXAMPLE\n */\n m() {}\n}`,
|
|
54
87
|
},
|
|
55
|
-
{
|
|
88
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
56
89
|
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] valid TS FunctionExpression in variable declarator with @req",
|
|
57
90
|
code: `const fn = /**\n * @req REQ-EXAMPLE\n */\nfunction () {};`,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
{
|
|
91
|
+
}),
|
|
92
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
64
93
|
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] valid exported TS FunctionExpression in variable declarator with @req",
|
|
65
94
|
code: `export const fn = /**\n * @req REQ-EXAMPLE\n */\nfunction () {};`,
|
|
66
|
-
|
|
67
|
-
parser: require("@typescript-eslint/parser"),
|
|
68
|
-
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
69
|
-
},
|
|
70
|
-
},
|
|
95
|
+
}),
|
|
71
96
|
{
|
|
72
97
|
name: "[REQ-CONFIGURABLE-SCOPE][Story 003.0] FunctionExpression ignored when scope only includes FunctionDeclaration",
|
|
73
98
|
code: `const fn = function () {};`,
|
|
@@ -125,34 +150,6 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
125
150
|
},
|
|
126
151
|
],
|
|
127
152
|
},
|
|
128
|
-
{
|
|
129
|
-
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSDeclareFunction",
|
|
130
|
-
code: `declare function baz(): void;`,
|
|
131
|
-
errors: [
|
|
132
|
-
{
|
|
133
|
-
messageId: "missingReq",
|
|
134
|
-
data: { name: "baz", functionName: "baz" },
|
|
135
|
-
},
|
|
136
|
-
],
|
|
137
|
-
languageOptions: {
|
|
138
|
-
parser: require("@typescript-eslint/parser"),
|
|
139
|
-
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSMethodSignature",
|
|
144
|
-
code: `interface I { method(): void; }`,
|
|
145
|
-
errors: [
|
|
146
|
-
{
|
|
147
|
-
messageId: "missingReq",
|
|
148
|
-
data: { name: "method", functionName: "method" },
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
languageOptions: {
|
|
152
|
-
parser: require("@typescript-eslint/parser"),
|
|
153
|
-
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
153
|
{
|
|
157
154
|
name: "[REQ-FUNCTION-DETECTION][Story 003.0] missing @req on FunctionExpression assigned to variable",
|
|
158
155
|
code: `const fn = function () {};`,
|
|
@@ -193,7 +190,7 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
193
190
|
},
|
|
194
191
|
],
|
|
195
192
|
},
|
|
196
|
-
{
|
|
193
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
197
194
|
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] missing @req on TS FunctionExpression in variable declarator",
|
|
198
195
|
code: `const fn = function () {};`,
|
|
199
196
|
errors: [
|
|
@@ -202,12 +199,8 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
202
199
|
data: { name: "fn", functionName: "fn" },
|
|
203
200
|
},
|
|
204
201
|
],
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
{
|
|
202
|
+
}),
|
|
203
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
211
204
|
name: "[REQ-TYPESCRIPT-SUPPORT][REQ-FUNCTION-DETECTION][Story 003.0] missing @req on exported TS FunctionExpression in variable declarator",
|
|
212
205
|
code: `export const fn = function () {};`,
|
|
213
206
|
errors: [
|
|
@@ -216,11 +209,7 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
216
209
|
data: { name: "fn", functionName: "fn" },
|
|
217
210
|
},
|
|
218
211
|
],
|
|
219
|
-
|
|
220
|
-
parser: require("@typescript-eslint/parser"),
|
|
221
|
-
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
|
|
222
|
-
},
|
|
223
|
-
},
|
|
212
|
+
}),
|
|
224
213
|
{
|
|
225
214
|
name: "[REQ-CONFIGURABLE-SCOPE][Story 003.0] FunctionDeclaration still reported when scope only includes FunctionDeclaration",
|
|
226
215
|
code: `function scoped() {}`,
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
10
|
*/
|
|
11
11
|
const eslint_1 = require("eslint");
|
|
12
12
|
const require_story_annotation_1 = __importDefault(require("../../src/rules/require-story-annotation"));
|
|
13
|
+
const ts_language_options_1 = require("../utils/ts-language-options");
|
|
13
14
|
const ruleTester = new eslint_1.RuleTester({
|
|
14
15
|
languageOptions: {
|
|
15
16
|
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
@@ -36,30 +37,22 @@ function foo() {}`,
|
|
|
36
37
|
code: `// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
37
38
|
const arrowFn = () => {};`,
|
|
38
39
|
},
|
|
39
|
-
{
|
|
40
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
40
41
|
name: "[REQ-ANNOTATION-REQUIRED] valid on class method with annotation",
|
|
41
42
|
code: `class A {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
42
|
-
},
|
|
43
|
-
{
|
|
43
|
+
}),
|
|
44
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
44
45
|
name: "[REQ-FUNCTION-DETECTION] valid with annotation on TS declare function",
|
|
45
46
|
code: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
46
47
|
declare function tsDecl(): void;`,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
{
|
|
48
|
+
}),
|
|
49
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
53
50
|
name: "[REQ-FUNCTION-DETECTION] valid with annotation on TS method signature",
|
|
54
51
|
code: `interface C {
|
|
55
52
|
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
56
53
|
method(): void;
|
|
57
54
|
}`,
|
|
58
|
-
|
|
59
|
-
parser: require("@typescript-eslint/parser"),
|
|
60
|
-
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
61
|
-
},
|
|
62
|
-
},
|
|
55
|
+
}),
|
|
63
56
|
{
|
|
64
57
|
name: "[REQ-ANNOTATION-REQUIRED] unannotated arrow function allowed by default",
|
|
65
58
|
code: `const arrowFn = () => {};`,
|
|
@@ -98,7 +91,7 @@ declare function tsDecl(): void;`,
|
|
|
98
91
|
},
|
|
99
92
|
],
|
|
100
93
|
},
|
|
101
|
-
{
|
|
94
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
102
95
|
name: "[REQ-ANNOTATION-REQUIRED] missing @story on class method",
|
|
103
96
|
code: `class C {\n method() {}\n}`,
|
|
104
97
|
output: `class C {\n /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\n method() {}\n}`,
|
|
@@ -114,15 +107,11 @@ declare function tsDecl(): void;`,
|
|
|
114
107
|
],
|
|
115
108
|
},
|
|
116
109
|
],
|
|
117
|
-
},
|
|
118
|
-
{
|
|
110
|
+
}),
|
|
111
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
119
112
|
name: "[REQ-ANNOTATION-REQUIRED] missing @story on TS declare function",
|
|
120
113
|
code: `declare function tsDecl(): void;`,
|
|
121
114
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ndeclare function tsDecl(): void;`,
|
|
122
|
-
languageOptions: {
|
|
123
|
-
parser: require("@typescript-eslint/parser"),
|
|
124
|
-
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
125
|
-
},
|
|
126
115
|
errors: [
|
|
127
116
|
{
|
|
128
117
|
messageId: "missingStory",
|
|
@@ -134,15 +123,11 @@ declare function tsDecl(): void;`,
|
|
|
134
123
|
],
|
|
135
124
|
},
|
|
136
125
|
],
|
|
137
|
-
},
|
|
138
|
-
{
|
|
126
|
+
}),
|
|
127
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
139
128
|
name: "[REQ-ANNOTATION-REQUIRED] missing @story on TS method signature",
|
|
140
129
|
code: `interface D {\n method(): void;\n}`,
|
|
141
130
|
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\ninterface D {\n method(): void;\n}`,
|
|
142
|
-
languageOptions: {
|
|
143
|
-
parser: require("@typescript-eslint/parser"),
|
|
144
|
-
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
145
|
-
},
|
|
146
131
|
errors: [
|
|
147
132
|
{
|
|
148
133
|
messageId: "missingStory",
|
|
@@ -154,7 +139,7 @@ declare function tsDecl(): void;`,
|
|
|
154
139
|
],
|
|
155
140
|
},
|
|
156
141
|
],
|
|
157
|
-
},
|
|
142
|
+
}),
|
|
158
143
|
],
|
|
159
144
|
});
|
|
160
145
|
ruleTester.run("require-story-annotation with exportPriority option", require_story_annotation_1.default, {
|