eslint-plugin-traceability 1.1.6 → 1.1.8
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 +2 -2
- package/lib/src/rules/require-branch-annotation.js +80 -86
- package/lib/src/rules/valid-req-reference.js +77 -72
- package/lib/tests/maintenance/detect-isolated.test.js +17 -15
- package/lib/tests/maintenance/detect.test.js +8 -4
- package/lib/tests/maintenance/update-isolated.test.js +12 -12
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Prerequisites: Node.js v12+ and ESLint v9+.
|
|
|
15
15
|
2. Using Yarn
|
|
16
16
|
yarn add --dev eslint-plugin-traceability
|
|
17
17
|
|
|
18
|
-
For detailed setup with ESLint v9, see docs/eslint-9-setup-guide.md.
|
|
18
|
+
For detailed setup with ESLint v9, see user-docs/eslint-9-setup-guide.md.
|
|
19
19
|
|
|
20
20
|
## Usage
|
|
21
21
|
|
|
@@ -145,7 +145,7 @@ The CLI integration tests are also executed automatically in CI under the `integ
|
|
|
145
145
|
|
|
146
146
|
## Documentation Links
|
|
147
147
|
|
|
148
|
-
- ESLint v9 Setup Guide: docs/eslint-9-setup-guide.md
|
|
148
|
+
- ESLint v9 Setup Guide: user-docs/eslint-9-setup-guide.md
|
|
149
149
|
- Plugin Development Guide: docs/eslint-plugin-development-guide.md
|
|
150
150
|
- API Reference: user-docs/api-reference.md
|
|
151
151
|
- Examples: user-docs/examples.md
|
|
@@ -1,10 +1,80 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
2
|
/**
|
|
4
3
|
* Rule to enforce @story and @req annotations on significant code branches
|
|
5
4
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
6
5
|
* @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
|
|
7
6
|
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
/**
|
|
9
|
+
* Helper to check a branch AST node for traceability annotations.
|
|
10
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
11
|
+
* @req REQ-BRANCH-DETECTION - Helper for branch annotation detection
|
|
12
|
+
*/
|
|
13
|
+
function checkBranchNode(sourceCode, context, node) {
|
|
14
|
+
// Skip default switch cases during annotation checks
|
|
15
|
+
if (node.type === "SwitchCase" && node.test == null) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let comments = sourceCode.getCommentsBefore(node) || [];
|
|
19
|
+
// Fallback scanning for SwitchCase when no leading comment nodes
|
|
20
|
+
if (node.type === "SwitchCase" && comments.length === 0) {
|
|
21
|
+
const lines = sourceCode.lines;
|
|
22
|
+
const startLine = node.loc.start.line;
|
|
23
|
+
let i = startLine - 1;
|
|
24
|
+
const fallbackComments = [];
|
|
25
|
+
while (i > 0) {
|
|
26
|
+
const lineText = lines[i - 1];
|
|
27
|
+
if (/^\s*(\/\/|\/\*)/.test(lineText)) {
|
|
28
|
+
fallbackComments.unshift(lineText.trim());
|
|
29
|
+
i--;
|
|
30
|
+
}
|
|
31
|
+
else if (/^\s*$/.test(lineText)) {
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
comments = fallbackComments.map((text) => ({ value: text }));
|
|
39
|
+
}
|
|
40
|
+
const text = comments.map((c) => c.value).join(" ");
|
|
41
|
+
const missingStory = !/@story\b/.test(text);
|
|
42
|
+
const missingReq = !/@req\b/.test(text);
|
|
43
|
+
if (missingStory) {
|
|
44
|
+
const reportObj = {
|
|
45
|
+
node,
|
|
46
|
+
messageId: "missingAnnotation",
|
|
47
|
+
data: { missing: "@story" },
|
|
48
|
+
};
|
|
49
|
+
if (node.type !== "CatchClause") {
|
|
50
|
+
if (node.type === "SwitchCase") {
|
|
51
|
+
const indent = " ".repeat(node.loc.start.column);
|
|
52
|
+
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @story <story-file>.story.md\n${indent}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @story <story-file>.story.md\n`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
context.report(reportObj);
|
|
59
|
+
}
|
|
60
|
+
if (missingReq) {
|
|
61
|
+
const reportObj = {
|
|
62
|
+
node,
|
|
63
|
+
messageId: "missingAnnotation",
|
|
64
|
+
data: { missing: "@req" },
|
|
65
|
+
};
|
|
66
|
+
if (!missingStory && node.type !== "CatchClause") {
|
|
67
|
+
if (node.type === "SwitchCase") {
|
|
68
|
+
const indent = " ".repeat(node.loc.start.column);
|
|
69
|
+
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @req <REQ-ID>\n${indent}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @req <REQ-ID>\n`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
context.report(reportObj);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
8
78
|
exports.default = {
|
|
9
79
|
meta: {
|
|
10
80
|
type: "problem",
|
|
@@ -20,92 +90,16 @@ exports.default = {
|
|
|
20
90
|
},
|
|
21
91
|
create(context) {
|
|
22
92
|
const sourceCode = context.getSourceCode();
|
|
23
|
-
/**
|
|
24
|
-
* Helper to check a branch AST node for traceability annotations.
|
|
25
|
-
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
26
|
-
* @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
|
|
27
|
-
*/
|
|
28
|
-
function checkBranch(node) {
|
|
29
|
-
// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
30
|
-
// @req REQ-BRANCH-DETECTION - Skip default switch cases during annotation checks
|
|
31
|
-
// skip default cases in switch
|
|
32
|
-
if (node.type === "SwitchCase" && node.test == null) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
// collect comments before node
|
|
36
|
-
let comments = sourceCode.getCommentsBefore(node) || [];
|
|
37
|
-
// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
38
|
-
// @req REQ-BRANCH-DETECTION - Fallback scanning for SwitchCase when leading comments are absent
|
|
39
|
-
// fallback scanning for SwitchCase if no leading comment nodes
|
|
40
|
-
/* istanbul ignore if */
|
|
41
|
-
if (node.type === "SwitchCase" && comments.length === 0) {
|
|
42
|
-
const lines = sourceCode.lines;
|
|
43
|
-
const startLine = node.loc.start.line;
|
|
44
|
-
let i = startLine - 1;
|
|
45
|
-
const fallbackComments = [];
|
|
46
|
-
while (i > 0) {
|
|
47
|
-
const lineText = lines[i - 1];
|
|
48
|
-
if (/^\s*(\/\/|\/\*)/.test(lineText)) {
|
|
49
|
-
fallbackComments.unshift(lineText.trim());
|
|
50
|
-
i--;
|
|
51
|
-
}
|
|
52
|
-
else if (/^\s*$/.test(lineText)) {
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
comments = fallbackComments.map((text) => ({ value: text }));
|
|
60
|
-
}
|
|
61
|
-
const text = comments.map((c) => c.value).join(" ");
|
|
62
|
-
const missingStory = !/@story\b/.test(text);
|
|
63
|
-
const missingReq = !/@req\b/.test(text);
|
|
64
|
-
if (missingStory) {
|
|
65
|
-
const reportObj = {
|
|
66
|
-
node,
|
|
67
|
-
messageId: "missingAnnotation",
|
|
68
|
-
data: { missing: "@story" },
|
|
69
|
-
};
|
|
70
|
-
if (node.type !== "CatchClause") {
|
|
71
|
-
if (node.type === "SwitchCase") {
|
|
72
|
-
const indent = " ".repeat(node.loc.start.column);
|
|
73
|
-
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @story <story-file>.story.md\n${indent}`);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @story <story-file>.story.md\n`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
context.report(reportObj);
|
|
80
|
-
}
|
|
81
|
-
if (missingReq) {
|
|
82
|
-
const reportObj = {
|
|
83
|
-
node,
|
|
84
|
-
messageId: "missingAnnotation",
|
|
85
|
-
data: { missing: "@req" },
|
|
86
|
-
};
|
|
87
|
-
if (!missingStory && node.type !== "CatchClause") {
|
|
88
|
-
if (node.type === "SwitchCase") {
|
|
89
|
-
const indent = " ".repeat(node.loc.start.column);
|
|
90
|
-
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @req <REQ-ID>\n${indent}`);
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @req <REQ-ID>\n`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
context.report(reportObj);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
93
|
return {
|
|
100
|
-
IfStatement:
|
|
101
|
-
SwitchCase:
|
|
102
|
-
TryStatement:
|
|
103
|
-
CatchClause:
|
|
104
|
-
ForStatement:
|
|
105
|
-
ForOfStatement:
|
|
106
|
-
ForInStatement:
|
|
107
|
-
WhileStatement:
|
|
108
|
-
DoWhileStatement:
|
|
94
|
+
IfStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
95
|
+
SwitchCase: (node) => checkBranchNode(sourceCode, context, node),
|
|
96
|
+
TryStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
97
|
+
CatchClause: (node) => checkBranchNode(sourceCode, context, node),
|
|
98
|
+
ForStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
99
|
+
ForOfStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
100
|
+
ForInStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
101
|
+
WhileStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
102
|
+
DoWhileStatement: (node) => checkBranchNode(sourceCode, context, node),
|
|
109
103
|
};
|
|
110
104
|
},
|
|
111
105
|
};
|
|
@@ -14,6 +14,81 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
*/
|
|
15
15
|
const fs_1 = __importDefault(require("fs"));
|
|
16
16
|
const path_1 = __importDefault(require("path"));
|
|
17
|
+
/**
|
|
18
|
+
* Create the Program listener for validating @req annotations.
|
|
19
|
+
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
20
|
+
* @req REQ-DEEP-PARSE - Parse story files to extract requirement identifiers
|
|
21
|
+
* @req REQ-DEEP-MATCH - Validate @req references against story file content
|
|
22
|
+
* @req REQ-DEEP-CACHE - Cache parsed story content for performance
|
|
23
|
+
* @req REQ-DEEP-PATH - Protect against path traversal in story paths
|
|
24
|
+
*/
|
|
25
|
+
function createProgramListener(context) {
|
|
26
|
+
const sourceCode = context.getSourceCode();
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
const reqCache = new Map();
|
|
29
|
+
let rawStoryPath = null;
|
|
30
|
+
return function Program() {
|
|
31
|
+
const comments = sourceCode.getAllComments() || [];
|
|
32
|
+
comments.forEach((comment) => {
|
|
33
|
+
const rawLines = comment.value.split(/\r?\n/);
|
|
34
|
+
const lines = rawLines.map((rawLine) => rawLine.trim().replace(/^\*+\s*/, ""));
|
|
35
|
+
lines.forEach((line) => {
|
|
36
|
+
if (line.startsWith("@story")) {
|
|
37
|
+
const parts = line.split(/\s+/);
|
|
38
|
+
rawStoryPath = parts[1] || null;
|
|
39
|
+
}
|
|
40
|
+
if (line.startsWith("@req")) {
|
|
41
|
+
const parts = line.split(/\s+/);
|
|
42
|
+
const reqId = parts[1];
|
|
43
|
+
if (!reqId || !rawStoryPath) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (rawStoryPath.includes("..") || path_1.default.isAbsolute(rawStoryPath)) {
|
|
47
|
+
context.report({
|
|
48
|
+
node: comment,
|
|
49
|
+
messageId: "invalidPath",
|
|
50
|
+
data: { storyPath: rawStoryPath },
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const resolvedStoryPath = path_1.default.resolve(cwd, rawStoryPath);
|
|
55
|
+
if (!resolvedStoryPath.startsWith(cwd + path_1.default.sep) &&
|
|
56
|
+
resolvedStoryPath !== cwd) {
|
|
57
|
+
context.report({
|
|
58
|
+
node: comment,
|
|
59
|
+
messageId: "invalidPath",
|
|
60
|
+
data: { storyPath: rawStoryPath },
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!reqCache.has(resolvedStoryPath)) {
|
|
65
|
+
try {
|
|
66
|
+
const content = fs_1.default.readFileSync(resolvedStoryPath, "utf8");
|
|
67
|
+
const found = new Set();
|
|
68
|
+
const regex = /REQ-[A-Z0-9-]+/g;
|
|
69
|
+
let match;
|
|
70
|
+
while ((match = regex.exec(content)) !== null) {
|
|
71
|
+
found.add(match[0]);
|
|
72
|
+
}
|
|
73
|
+
reqCache.set(resolvedStoryPath, found);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
reqCache.set(resolvedStoryPath, new Set());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const reqSet = reqCache.get(resolvedStoryPath);
|
|
80
|
+
if (!reqSet.has(reqId)) {
|
|
81
|
+
context.report({
|
|
82
|
+
node: comment,
|
|
83
|
+
messageId: "reqMissing",
|
|
84
|
+
data: { reqId, storyPath: rawStoryPath },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
}
|
|
17
92
|
exports.default = {
|
|
18
93
|
meta: {
|
|
19
94
|
type: "problem",
|
|
@@ -28,77 +103,7 @@ exports.default = {
|
|
|
28
103
|
schema: [],
|
|
29
104
|
},
|
|
30
105
|
create(context) {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
// Cache for resolved story file paths to parsed set of requirement IDs
|
|
34
|
-
const reqCache = new Map();
|
|
35
|
-
let rawStoryPath = null;
|
|
36
|
-
return {
|
|
37
|
-
Program() {
|
|
38
|
-
const comments = sourceCode.getAllComments() || [];
|
|
39
|
-
comments.forEach((comment) => {
|
|
40
|
-
const rawLines = comment.value.split(/\r?\n/);
|
|
41
|
-
const lines = rawLines.map((rawLine) => rawLine.trim().replace(/^\*+\s*/, ""));
|
|
42
|
-
lines.forEach((line) => {
|
|
43
|
-
if (line.startsWith("@story")) {
|
|
44
|
-
const parts = line.split(/\s+/);
|
|
45
|
-
rawStoryPath = parts[1] || null;
|
|
46
|
-
}
|
|
47
|
-
if (line.startsWith("@req")) {
|
|
48
|
-
const parts = line.split(/\s+/);
|
|
49
|
-
const reqId = parts[1];
|
|
50
|
-
if (!reqId || !rawStoryPath) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
// Protect against path traversal and absolute paths
|
|
54
|
-
if (rawStoryPath.includes("..") ||
|
|
55
|
-
path_1.default.isAbsolute(rawStoryPath)) {
|
|
56
|
-
context.report({
|
|
57
|
-
node: comment,
|
|
58
|
-
messageId: "invalidPath",
|
|
59
|
-
data: { storyPath: rawStoryPath },
|
|
60
|
-
});
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const resolvedStoryPath = path_1.default.resolve(cwd, rawStoryPath);
|
|
64
|
-
if (!resolvedStoryPath.startsWith(cwd + path_1.default.sep) &&
|
|
65
|
-
resolvedStoryPath !== cwd) {
|
|
66
|
-
context.report({
|
|
67
|
-
node: comment,
|
|
68
|
-
messageId: "invalidPath",
|
|
69
|
-
data: { storyPath: rawStoryPath },
|
|
70
|
-
});
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
// Load and parse story file if not cached
|
|
74
|
-
if (!reqCache.has(resolvedStoryPath)) {
|
|
75
|
-
try {
|
|
76
|
-
const content = fs_1.default.readFileSync(resolvedStoryPath, "utf8");
|
|
77
|
-
const found = new Set();
|
|
78
|
-
const regex = /REQ-[A-Z0-9-]+/g;
|
|
79
|
-
let match;
|
|
80
|
-
while ((match = regex.exec(content)) !== null) {
|
|
81
|
-
found.add(match[0]);
|
|
82
|
-
}
|
|
83
|
-
reqCache.set(resolvedStoryPath, found);
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
// Unable to read file, treat as no requirements
|
|
87
|
-
reqCache.set(resolvedStoryPath, new Set());
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
const reqSet = reqCache.get(resolvedStoryPath);
|
|
91
|
-
if (!reqSet.has(reqId)) {
|
|
92
|
-
context.report({
|
|
93
|
-
node: comment,
|
|
94
|
-
messageId: "reqMissing",
|
|
95
|
-
data: { reqId, storyPath: rawStoryPath },
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
},
|
|
102
|
-
};
|
|
106
|
+
const program = createProgramListener(context);
|
|
107
|
+
return { Program: program };
|
|
103
108
|
},
|
|
104
109
|
};
|
|
@@ -48,30 +48,32 @@ describe("detectStaleAnnotations isolated (Story 009.0-DEV-MAINTENANCE-TOOLS)",
|
|
|
48
48
|
expect(result).toEqual([]);
|
|
49
49
|
});
|
|
50
50
|
it("[REQ-MAINT-DETECT] detects stale annotations in nested directories", () => {
|
|
51
|
-
// Arrange
|
|
52
51
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "tmp-nested-"));
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
try {
|
|
53
|
+
const nestedDir = path.join(tmpDir, "nested");
|
|
54
|
+
fs.mkdirSync(nestedDir);
|
|
55
|
+
const filePath1 = path.join(tmpDir, "file1.ts");
|
|
56
|
+
const filePath2 = path.join(nestedDir, "file2.ts");
|
|
57
|
+
const content1 = `
|
|
58
58
|
/**
|
|
59
59
|
* @story stale1.story.md
|
|
60
60
|
*/
|
|
61
61
|
`;
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
fs.writeFileSync(filePath1, content1, "utf8");
|
|
63
|
+
const content2 = `
|
|
64
64
|
/**
|
|
65
65
|
* @story stale2.story.md
|
|
66
66
|
*/
|
|
67
67
|
`;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
fs.writeFileSync(filePath2, content2, "utf8");
|
|
69
|
+
const result = (0, detect_1.detectStaleAnnotations)(tmpDir);
|
|
70
|
+
expect(result).toHaveLength(2);
|
|
71
|
+
expect(result).toContain("stale1.story.md");
|
|
72
|
+
expect(result).toContain("stale2.story.md");
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
76
|
+
}
|
|
75
77
|
});
|
|
76
78
|
it("[REQ-MAINT-DETECT] throws error on permission denied", () => {
|
|
77
79
|
const tmpDir2 = fs.mkdtempSync(path.join(os.tmpdir(), "tmp-perm-"));
|
|
@@ -15,9 +15,13 @@ const detect_1 = require("../../src/maintenance/detect");
|
|
|
15
15
|
describe("detectStaleAnnotations (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
16
16
|
it("[REQ-MAINT-DETECT] should return empty array when no stale annotations", () => {
|
|
17
17
|
const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "detect-test-"));
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
try {
|
|
19
|
+
// No annotation files are created in tmpDir to simulate no stale annotations
|
|
20
|
+
const result = (0, detect_1.detectStaleAnnotations)(tmpDir);
|
|
21
|
+
expect(result).toEqual([]);
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
25
|
+
}
|
|
22
26
|
});
|
|
23
27
|
});
|
|
@@ -44,23 +44,23 @@ const os = __importStar(require("os"));
|
|
|
44
44
|
const update_1 = require("../../src/maintenance/update");
|
|
45
45
|
describe("updateAnnotationReferences isolated (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
46
46
|
it("[REQ-MAINT-UPDATE] updates @story annotations in files", () => {
|
|
47
|
-
// Create a temporary directory for testing
|
|
48
47
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "tmp-"));
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
try {
|
|
49
|
+
const filePath = path.join(tmpDir, "file.ts");
|
|
50
|
+
const originalContent = `
|
|
51
51
|
/**
|
|
52
52
|
* @story old.path.md
|
|
53
53
|
*/
|
|
54
54
|
function foo() {}
|
|
55
55
|
`;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
fs.writeFileSync(filePath, originalContent, "utf8");
|
|
57
|
+
const count = (0, update_1.updateAnnotationReferences)(tmpDir, "old.path.md", "new.path.md");
|
|
58
|
+
expect(count).toBe(1);
|
|
59
|
+
const updatedContent = fs.readFileSync(filePath, "utf8");
|
|
60
|
+
expect(updatedContent).toContain("@story new.path.md");
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
65
|
});
|
|
66
66
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "A customizable ESLint plugin that enforces traceability annotations in your code, ensuring each implementation is linked to its requirement or test case.",
|
|
5
5
|
"main": "lib/src/index.js",
|
|
6
6
|
"types": "lib/src/index.d.ts",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"node": ">=14"
|
|
74
74
|
},
|
|
75
75
|
"overrides": {
|
|
76
|
-
"js-yaml": ">=4.1.1"
|
|
76
|
+
"js-yaml": ">=4.1.1",
|
|
77
|
+
"tar": ">=6.1.11"
|
|
77
78
|
}
|
|
78
79
|
}
|