eslint-plugin-traceability 1.4.9 → 1.4.11
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/lib/src/rules/helpers/require-story-core.js +0 -1
- package/lib/src/rules/helpers/require-story-helpers.js +6 -2
- package/lib/src/rules/require-story-annotation.js +1 -1
- package/lib/src/rules/valid-story-reference.js +71 -13
- package/lib/src/utils/storyReferenceUtils.d.ts +67 -2
- package/lib/src/utils/storyReferenceUtils.js +102 -20
- package/lib/tests/rules/require-story-annotation.test.js +9 -31
- package/lib/tests/rules/valid-story-reference.test.js +89 -0
- package/package.json +1 -1
|
@@ -252,8 +252,11 @@ function reportMissing(context, sourceCode, node, passedTarget) {
|
|
|
252
252
|
}
|
|
253
253
|
const resolvedTarget = passedTarget ?? resolveTargetNode(sourceCode, node);
|
|
254
254
|
const name = functionName;
|
|
255
|
+
const nameNode = (node.id && node.id.type === "Identifier" && node.id) ||
|
|
256
|
+
(node.key && node.key.type === "Identifier" && node.key) ||
|
|
257
|
+
node;
|
|
255
258
|
context.report({
|
|
256
|
-
node,
|
|
259
|
+
node: nameNode,
|
|
257
260
|
messageId: "missingStory",
|
|
258
261
|
data: { name },
|
|
259
262
|
suggest: [
|
|
@@ -285,8 +288,9 @@ function reportMethod(context, sourceCode, node, passedTarget) {
|
|
|
285
288
|
}
|
|
286
289
|
const resolvedTarget = passedTarget ?? resolveTargetNode(sourceCode, node);
|
|
287
290
|
const name = extractName(node);
|
|
291
|
+
const nameNode = (node.key && node.key.type === "Identifier" && node.key) || node;
|
|
288
292
|
context.report({
|
|
289
|
-
node,
|
|
293
|
+
node: nameNode,
|
|
290
294
|
messageId: "missingStory",
|
|
291
295
|
data: { name },
|
|
292
296
|
suggest: [
|
|
@@ -35,16 +35,63 @@ function validateStoryPath(opts) {
|
|
|
35
35
|
requireExt,
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Report any problems related to the existence or accessibility of the
|
|
40
|
+
* referenced story file. Filesystem and I/O errors are surfaced with a
|
|
41
|
+
* dedicated diagnostic that differentiates them from missing files.
|
|
42
|
+
*
|
|
43
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
44
|
+
* @req REQ-FILE-EXISTENCE - Ensure referenced files exist
|
|
45
|
+
* @req REQ-ERROR-HANDLING - Differentiate missing files from filesystem errors
|
|
46
|
+
*/
|
|
47
|
+
function reportExistenceProblems(opts) {
|
|
48
|
+
const { storyPath, commentNode, context, cwd, storyDirs } = opts;
|
|
49
|
+
const result = (0, storyReferenceUtils_1.normalizeStoryPath)(storyPath, cwd, storyDirs);
|
|
50
|
+
const existenceResult = result.existence;
|
|
51
|
+
if (!existenceResult || existenceResult.status === "exists") {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (existenceResult.status === "missing") {
|
|
55
|
+
context.report({
|
|
56
|
+
node: commentNode,
|
|
57
|
+
messageId: "fileMissing",
|
|
58
|
+
data: { path: storyPath },
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (existenceResult.status === "fs-error") {
|
|
63
|
+
const rawError = existenceResult.error;
|
|
64
|
+
let errorMessage;
|
|
65
|
+
if (rawError == null) {
|
|
66
|
+
errorMessage = "Unknown filesystem error";
|
|
67
|
+
}
|
|
68
|
+
else if (rawError instanceof Error) {
|
|
69
|
+
errorMessage = rawError.message;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
errorMessage = String(rawError);
|
|
73
|
+
}
|
|
74
|
+
context.report({
|
|
75
|
+
node: commentNode,
|
|
76
|
+
messageId: "fileAccessError",
|
|
77
|
+
data: {
|
|
78
|
+
path: storyPath,
|
|
79
|
+
error: errorMessage,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
38
84
|
/**
|
|
39
85
|
* Process and validate the story path for security, extension, and existence.
|
|
40
86
|
* Filesystem and I/O errors are handled inside the underlying utilities
|
|
41
|
-
* (e.g. storyExists) and surfaced as missing-file
|
|
87
|
+
* (e.g. storyExists) and surfaced as missing-file or filesystem-error
|
|
88
|
+
* diagnostics where appropriate.
|
|
42
89
|
*
|
|
43
90
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
44
91
|
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
45
92
|
* @req REQ-PATH-RESOLUTION - Resolve relative paths correctly and enforce configuration
|
|
46
93
|
* @req REQ-SECURITY-VALIDATION - Prevent path traversal and absolute path usage
|
|
47
|
-
* @req REQ-ERROR-HANDLING - Delegate filesystem and I/O error handling to utilities
|
|
94
|
+
* @req REQ-ERROR-HANDLING - Delegate filesystem and I/O error handling to utilities and differentiate error types
|
|
48
95
|
*/
|
|
49
96
|
function processStoryPath(opts) {
|
|
50
97
|
const { storyPath, commentNode, context, cwd, storyDirs, allowAbsolute, requireExt, } = opts;
|
|
@@ -80,16 +127,22 @@ function processStoryPath(opts) {
|
|
|
80
127
|
});
|
|
81
128
|
return;
|
|
82
129
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Existence check:
|
|
132
|
+
* - Distinguish between missing files and filesystem errors.
|
|
133
|
+
* - Filesystem and I/O errors are surfaced with a dedicated diagnostic.
|
|
134
|
+
*
|
|
135
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
136
|
+
* @req REQ-FILE-EXISTENCE - Ensure referenced files exist
|
|
137
|
+
* @req REQ-ERROR-HANDLING - Differentiate missing files from filesystem errors
|
|
138
|
+
*/
|
|
139
|
+
reportExistenceProblems({
|
|
140
|
+
storyPath,
|
|
141
|
+
commentNode,
|
|
142
|
+
context,
|
|
143
|
+
cwd,
|
|
144
|
+
storyDirs,
|
|
145
|
+
});
|
|
93
146
|
}
|
|
94
147
|
/**
|
|
95
148
|
* Handle a single comment node by processing its lines.
|
|
@@ -130,6 +183,11 @@ exports.default = {
|
|
|
130
183
|
fileMissing: "Story file '{{path}}' not found",
|
|
131
184
|
invalidExtension: "Invalid story file extension for '{{path}}', expected '.story.md'",
|
|
132
185
|
invalidPath: "Invalid story path '{{path}}'",
|
|
186
|
+
/**
|
|
187
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
188
|
+
* @req REQ-ERROR-HANDLING - Provide clear diagnostics for filesystem errors
|
|
189
|
+
*/
|
|
190
|
+
fileAccessError: "Could not validate story file '{{path}}' due to a filesystem error: {{error}}. Please check file existence and permissions.",
|
|
133
191
|
},
|
|
134
192
|
schema: [
|
|
135
193
|
{
|
|
@@ -153,7 +211,7 @@ exports.default = {
|
|
|
153
211
|
/**
|
|
154
212
|
* Program-level handler: iterate comments and validate @story annotations.
|
|
155
213
|
* Filesystem and I/O errors are handled by underlying utilities and
|
|
156
|
-
* surfaced as missing-file diagnostics where appropriate.
|
|
214
|
+
* surfaced as missing-file or filesystem-error diagnostics where appropriate.
|
|
157
215
|
*
|
|
158
216
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
159
217
|
* @req REQ-ANNOTATION-VALIDATION - Discover and dispatch @story annotations for validation
|
|
@@ -1,22 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Describes the possible existence states for a checked path.
|
|
3
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
4
|
+
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
5
|
+
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
6
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
7
|
+
*/
|
|
8
|
+
export type StoryExistenceStatus = "exists" | "missing" | "fs-error";
|
|
9
|
+
/**
|
|
10
|
+
* Result of checking a single candidate path.
|
|
11
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
12
|
+
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
13
|
+
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
14
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
15
|
+
*/
|
|
16
|
+
export interface StoryPathCheckResult {
|
|
17
|
+
path: string;
|
|
18
|
+
status: StoryExistenceStatus;
|
|
19
|
+
error?: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Aggregated existence result across multiple candidate paths.
|
|
23
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
24
|
+
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
25
|
+
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
26
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
27
|
+
*/
|
|
28
|
+
export interface StoryExistenceResult {
|
|
29
|
+
candidates: string[];
|
|
30
|
+
status: StoryExistenceStatus;
|
|
31
|
+
matchedPath?: string;
|
|
32
|
+
error?: unknown;
|
|
33
|
+
}
|
|
1
34
|
/**
|
|
2
35
|
* Build candidate file paths for a given story path.
|
|
3
36
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
4
37
|
* @req REQ-PATH-RESOLUTION - Resolve relative paths correctly and enforce configuration
|
|
5
38
|
*/
|
|
6
39
|
export declare function buildStoryCandidates(storyPath: string, cwd: string, storyDirs: string[]): string[];
|
|
40
|
+
/**
|
|
41
|
+
* Aggregate existence status across multiple candidate paths.
|
|
42
|
+
* Returns the first successful match (`exists`), or, if none exist,
|
|
43
|
+
* the first filesystem error encountered. If there are only missing
|
|
44
|
+
* candidates, returns a missing status.
|
|
45
|
+
*
|
|
46
|
+
* This function never throws and is the preferred richer API for callers
|
|
47
|
+
* that need more than a boolean.
|
|
48
|
+
*
|
|
49
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
50
|
+
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
51
|
+
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
52
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
53
|
+
*/
|
|
54
|
+
export declare function getStoryExistence(candidates: string[]): StoryExistenceResult;
|
|
55
|
+
/**
|
|
56
|
+
* Check if any of the provided file paths exist.
|
|
57
|
+
* Handles filesystem errors (e.g., EACCES) gracefully by treating them as non-existent
|
|
58
|
+
* and never throwing.
|
|
59
|
+
*
|
|
60
|
+
* Internally delegates to the richer status-based helper while preserving the
|
|
61
|
+
* original boolean-only API for backwards compatibility.
|
|
62
|
+
*
|
|
63
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
64
|
+
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
65
|
+
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
66
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
67
|
+
*/
|
|
7
68
|
export declare function storyExists(paths: string[]): boolean;
|
|
8
69
|
/**
|
|
9
70
|
* Normalize a story path to candidate absolute paths and check existence.
|
|
10
|
-
* Filesystem errors are handled via
|
|
11
|
-
* and treats such cases as non-existent
|
|
71
|
+
* Filesystem errors are handled via the status-aware helper, which suppresses
|
|
72
|
+
* exceptions and treats such cases as non-existent for the boolean flag while
|
|
73
|
+
* still surfacing error details in the status field.
|
|
74
|
+
*
|
|
12
75
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
13
76
|
* @req REQ-PATH-RESOLUTION - Resolve relative paths correctly and enforce configuration
|
|
14
77
|
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
15
78
|
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
79
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
16
80
|
*/
|
|
17
81
|
export declare function normalizeStoryPath(storyPath: string, cwd: string, storyDirs: string[]): {
|
|
18
82
|
candidates: string[];
|
|
19
83
|
exists: boolean;
|
|
84
|
+
existence: StoryExistenceResult;
|
|
20
85
|
};
|
|
21
86
|
/**
|
|
22
87
|
* Check if the provided path is absolute.
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.buildStoryCandidates = buildStoryCandidates;
|
|
7
|
+
exports.getStoryExistence = getStoryExistence;
|
|
7
8
|
exports.storyExists = storyExists;
|
|
8
9
|
exports.normalizeStoryPath = normalizeStoryPath;
|
|
9
10
|
exports.isAbsolutePath = isAbsolutePath;
|
|
@@ -40,45 +41,126 @@ function buildStoryCandidates(storyPath, cwd, storyDirs) {
|
|
|
40
41
|
return candidates;
|
|
41
42
|
}
|
|
42
43
|
/**
|
|
43
|
-
*
|
|
44
|
-
* Handles filesystem errors (e.g., EACCES) gracefully by treating them as non-existent
|
|
45
|
-
* and never throwing.
|
|
44
|
+
* Cache of filesystem existence checks keyed by absolute path.
|
|
46
45
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
47
46
|
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
48
47
|
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
48
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
49
49
|
*/
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
const fileExistStatusCache = new Map();
|
|
51
|
+
/**
|
|
52
|
+
* Check a single candidate path, with caching and robust error handling.
|
|
53
|
+
* All filesystem interactions are wrapped in try/catch and never throw.
|
|
54
|
+
*
|
|
55
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
56
|
+
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
57
|
+
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
58
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
59
|
+
*/
|
|
60
|
+
function checkSingleCandidate(candidate) {
|
|
61
|
+
const cached = fileExistStatusCache.get(candidate);
|
|
62
|
+
if (cached) {
|
|
63
|
+
return cached;
|
|
64
|
+
}
|
|
65
|
+
let result;
|
|
66
|
+
try {
|
|
67
|
+
const exists = fs_1.default.existsSync(candidate);
|
|
68
|
+
if (!exists) {
|
|
69
|
+
result = { path: candidate, status: "missing" };
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const stat = fs_1.default.statSync(candidate);
|
|
73
|
+
if (stat.isFile()) {
|
|
74
|
+
result = { path: candidate, status: "exists" };
|
|
57
75
|
}
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
else {
|
|
77
|
+
// Path exists but is not a file; treat as missing for story purposes.
|
|
78
|
+
result = { path: candidate, status: "missing" };
|
|
60
79
|
}
|
|
61
|
-
fileExistCache.set(candidate, ok);
|
|
62
80
|
}
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
// Any filesystem error is captured and surfaced as fs-error.
|
|
84
|
+
result = { path: candidate, status: "fs-error", error };
|
|
85
|
+
}
|
|
86
|
+
fileExistStatusCache.set(candidate, result);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Aggregate existence status across multiple candidate paths.
|
|
91
|
+
* Returns the first successful match (`exists`), or, if none exist,
|
|
92
|
+
* the first filesystem error encountered. If there are only missing
|
|
93
|
+
* candidates, returns a missing status.
|
|
94
|
+
*
|
|
95
|
+
* This function never throws and is the preferred richer API for callers
|
|
96
|
+
* that need more than a boolean.
|
|
97
|
+
*
|
|
98
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
99
|
+
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
100
|
+
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
101
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
102
|
+
*/
|
|
103
|
+
function getStoryExistence(candidates) {
|
|
104
|
+
let firstFsError;
|
|
105
|
+
for (const candidate of candidates) {
|
|
106
|
+
const res = checkSingleCandidate(candidate);
|
|
107
|
+
if (res.status === "exists") {
|
|
108
|
+
return {
|
|
109
|
+
candidates,
|
|
110
|
+
status: "exists",
|
|
111
|
+
matchedPath: res.path,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (res.status === "fs-error" && !firstFsError) {
|
|
115
|
+
firstFsError = res;
|
|
65
116
|
}
|
|
66
117
|
}
|
|
67
|
-
|
|
118
|
+
if (firstFsError) {
|
|
119
|
+
return {
|
|
120
|
+
candidates,
|
|
121
|
+
status: "fs-error",
|
|
122
|
+
error: firstFsError.error,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
candidates,
|
|
127
|
+
status: "missing",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Check if any of the provided file paths exist.
|
|
132
|
+
* Handles filesystem errors (e.g., EACCES) gracefully by treating them as non-existent
|
|
133
|
+
* and never throwing.
|
|
134
|
+
*
|
|
135
|
+
* Internally delegates to the richer status-based helper while preserving the
|
|
136
|
+
* original boolean-only API for backwards compatibility.
|
|
137
|
+
*
|
|
138
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
139
|
+
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
140
|
+
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
141
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
142
|
+
*/
|
|
143
|
+
function storyExists(paths) {
|
|
144
|
+
const result = getStoryExistence(paths);
|
|
145
|
+
return result.status === "exists";
|
|
68
146
|
}
|
|
69
147
|
/**
|
|
70
148
|
* Normalize a story path to candidate absolute paths and check existence.
|
|
71
|
-
* Filesystem errors are handled via
|
|
72
|
-
* and treats such cases as non-existent
|
|
149
|
+
* Filesystem errors are handled via the status-aware helper, which suppresses
|
|
150
|
+
* exceptions and treats such cases as non-existent for the boolean flag while
|
|
151
|
+
* still surfacing error details in the status field.
|
|
152
|
+
*
|
|
73
153
|
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
74
154
|
* @req REQ-PATH-RESOLUTION - Resolve relative paths correctly and enforce configuration
|
|
75
155
|
* @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
|
|
76
156
|
* @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
|
|
157
|
+
* @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
|
|
77
158
|
*/
|
|
78
159
|
function normalizeStoryPath(storyPath, cwd, storyDirs) {
|
|
79
160
|
const candidates = buildStoryCandidates(storyPath, cwd, storyDirs);
|
|
80
|
-
const
|
|
81
|
-
|
|
161
|
+
const existence = getStoryExistence(candidates);
|
|
162
|
+
const exists = existence.status === "exists";
|
|
163
|
+
return { candidates, exists, existence };
|
|
82
164
|
}
|
|
83
165
|
/**
|
|
84
166
|
* Check if the provided path is absolute.
|
|
@@ -60,6 +60,10 @@ declare function tsDecl(): void;`,
|
|
|
60
60
|
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
61
61
|
},
|
|
62
62
|
},
|
|
63
|
+
{
|
|
64
|
+
name: "[REQ-ANNOTATION-REQUIRED] unannotated arrow function allowed by default",
|
|
65
|
+
code: `const arrowFn = () => {};`,
|
|
66
|
+
},
|
|
63
67
|
],
|
|
64
68
|
invalid: [
|
|
65
69
|
{
|
|
@@ -92,21 +96,6 @@ declare function tsDecl(): void;`,
|
|
|
92
96
|
},
|
|
93
97
|
],
|
|
94
98
|
},
|
|
95
|
-
{
|
|
96
|
-
name: "[REQ-ANNOTATION-REQUIRED] missing @story on arrow function",
|
|
97
|
-
code: `const arrowFn = () => {};`,
|
|
98
|
-
errors: [
|
|
99
|
-
{
|
|
100
|
-
messageId: "missingStory",
|
|
101
|
-
suggestions: [
|
|
102
|
-
{
|
|
103
|
-
desc: `Add JSDoc @story annotation for function 'arrowFn', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
104
|
-
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nconst arrowFn = () => {};`,
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
},
|
|
110
99
|
{
|
|
111
100
|
name: "[REQ-ANNOTATION-REQUIRED] missing @story on class method",
|
|
112
101
|
code: `class C {\n method() {}\n}`,
|
|
@@ -174,6 +163,11 @@ declare function tsDecl(): void;`,
|
|
|
174
163
|
code: `// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\nexport function exportedAnnotated() {}`,
|
|
175
164
|
options: [{ exportPriority: "exported" }],
|
|
176
165
|
},
|
|
166
|
+
{
|
|
167
|
+
name: "[exportPriority] exported arrow function missing @story annotation",
|
|
168
|
+
code: `export const arrowExported = () => {};`,
|
|
169
|
+
options: [{ exportPriority: "exported" }],
|
|
170
|
+
},
|
|
177
171
|
],
|
|
178
172
|
invalid: [
|
|
179
173
|
{
|
|
@@ -192,22 +186,6 @@ declare function tsDecl(): void;`,
|
|
|
192
186
|
},
|
|
193
187
|
],
|
|
194
188
|
},
|
|
195
|
-
{
|
|
196
|
-
name: "[exportPriority] exported arrow function missing @story annotation",
|
|
197
|
-
code: `export const arrowExported = () => {};`,
|
|
198
|
-
options: [{ exportPriority: "exported" }],
|
|
199
|
-
errors: [
|
|
200
|
-
{
|
|
201
|
-
messageId: "missingStory",
|
|
202
|
-
suggestions: [
|
|
203
|
-
{
|
|
204
|
-
desc: `Add JSDoc @story annotation for function 'arrowExported', e.g., /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */`,
|
|
205
|
-
output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nexport const arrowExported = () => {};`,
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
},
|
|
209
|
-
],
|
|
210
|
-
},
|
|
211
189
|
],
|
|
212
190
|
});
|
|
213
191
|
ruleTester.run("require-story-annotation with scope option", require_story_annotation_1.default, {
|
|
@@ -68,6 +68,37 @@ describe("Valid Story Reference Rule (Story 006.0-DEV-FILE-VALIDATION)", () => {
|
|
|
68
68
|
],
|
|
69
69
|
});
|
|
70
70
|
});
|
|
71
|
+
/**
|
|
72
|
+
* Helper to run the valid-story-reference rule against a single source string
|
|
73
|
+
* and collect reported diagnostics.
|
|
74
|
+
*
|
|
75
|
+
* @req REQ-ERROR-HANDLING - Used to verify fileAccessError reporting behavior
|
|
76
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
77
|
+
*/
|
|
78
|
+
function runRuleOnCode(code) {
|
|
79
|
+
const messages = [];
|
|
80
|
+
const context = {
|
|
81
|
+
report: (descriptor) => {
|
|
82
|
+
messages.push(descriptor);
|
|
83
|
+
},
|
|
84
|
+
getSourceCode: () => ({
|
|
85
|
+
text: code,
|
|
86
|
+
getAllComments: () => [
|
|
87
|
+
{
|
|
88
|
+
type: "Line",
|
|
89
|
+
value: code.replace(/^\/\//, "").trim(),
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
}),
|
|
93
|
+
options: [],
|
|
94
|
+
parserOptions: { ecmaVersion: 2020 },
|
|
95
|
+
};
|
|
96
|
+
const listeners = valid_story_reference_1.default.create(context);
|
|
97
|
+
if (typeof listeners.Program === "function") {
|
|
98
|
+
listeners.Program({});
|
|
99
|
+
}
|
|
100
|
+
return messages;
|
|
101
|
+
}
|
|
71
102
|
describe("Valid Story Reference Rule Error Handling (Story 006.0-DEV-FILE-VALIDATION)", () => {
|
|
72
103
|
/**
|
|
73
104
|
* @req REQ-ERROR-HANDLING - Verify storyExists swallows fs errors and returns false
|
|
@@ -92,4 +123,62 @@ describe("Valid Story Reference Rule Error Handling (Story 006.0-DEV-FILE-VALIDA
|
|
|
92
123
|
expect(() => (0, storyReferenceUtils_1.storyExists)(["docs/stories/permission-denied.story.md"])).not.toThrow();
|
|
93
124
|
expect((0, storyReferenceUtils_1.storyExists)(["docs/stories/permission-denied.story.md"])).toBe(false);
|
|
94
125
|
});
|
|
126
|
+
/**
|
|
127
|
+
* @req REQ-ERROR-HANDLING - Verify storyExists handles EIO from fs.statSync
|
|
128
|
+
* by returning false and not throwing when fs.existsSync returns true.
|
|
129
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
130
|
+
*/
|
|
131
|
+
it("[REQ-ERROR-HANDLING] storyExists returns false when fs.statSync throws EIO and existsSync is true", () => {
|
|
132
|
+
jest.spyOn(fs, "existsSync").mockImplementation(() => true);
|
|
133
|
+
jest.spyOn(fs, "statSync").mockImplementation(() => {
|
|
134
|
+
const err = new Error("EIO: i/o error while reading file");
|
|
135
|
+
err.code = "EIO";
|
|
136
|
+
throw err;
|
|
137
|
+
});
|
|
138
|
+
expect(() => (0, storyReferenceUtils_1.storyExists)(["docs/stories/io-error.story.md"])).not.toThrow();
|
|
139
|
+
expect((0, storyReferenceUtils_1.storyExists)(["docs/stories/io-error.story.md"])).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
/**
|
|
142
|
+
* @req REQ-ERROR-HANDLING - Verify rule reports fileAccessError when fs.statSync throws
|
|
143
|
+
* and fs.existsSync returns true, treating it as a filesystem access problem
|
|
144
|
+
* rather than a missing file.
|
|
145
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
146
|
+
*/
|
|
147
|
+
it("[REQ-ERROR-HANDLING] rule reports fileAccessError when fs.statSync throws and existsSync is true", () => {
|
|
148
|
+
const accessError = new Error("EIO: i/o error while reading file metadata");
|
|
149
|
+
accessError.code = "EIO";
|
|
150
|
+
jest.spyOn(fs, "existsSync").mockImplementation(() => true);
|
|
151
|
+
jest.spyOn(fs, "statSync").mockImplementation(() => {
|
|
152
|
+
throw accessError;
|
|
153
|
+
});
|
|
154
|
+
const diagnostics = runRuleOnCode(`// @story docs/stories/fs-stat-io-error.story.md`);
|
|
155
|
+
expect(diagnostics.length).toBeGreaterThan(0);
|
|
156
|
+
const fileAccessDiagnostics = diagnostics.filter((d) => d.messageId === "fileAccessError");
|
|
157
|
+
expect(fileAccessDiagnostics.length).toBeGreaterThan(0);
|
|
158
|
+
const errorData = fileAccessDiagnostics[0].data;
|
|
159
|
+
expect(errorData).toBeDefined();
|
|
160
|
+
expect(String(errorData.error)).toMatch(/EIO/i);
|
|
161
|
+
});
|
|
162
|
+
/**
|
|
163
|
+
* @req REQ-ERROR-HANDLING - Verify rule reports fileAccessError when filesystem operations fail
|
|
164
|
+
* instead of treating it as a missing file.
|
|
165
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
166
|
+
*/
|
|
167
|
+
it("[REQ-ERROR-HANDLING] rule reports fileAccessError when fs throws", () => {
|
|
168
|
+
const accessError = new Error("EACCES: permission denied while accessing");
|
|
169
|
+
accessError.code = "EACCES";
|
|
170
|
+
jest.spyOn(fs, "existsSync").mockImplementation(() => {
|
|
171
|
+
throw accessError;
|
|
172
|
+
});
|
|
173
|
+
jest.spyOn(fs, "statSync").mockImplementation(() => {
|
|
174
|
+
throw accessError;
|
|
175
|
+
});
|
|
176
|
+
const diagnostics = runRuleOnCode(`// @story docs/stories/fs-error.story.md`);
|
|
177
|
+
expect(diagnostics.length).toBeGreaterThan(0);
|
|
178
|
+
const fileAccessDiagnostics = diagnostics.filter((d) => d.messageId === "fileAccessError");
|
|
179
|
+
expect(fileAccessDiagnostics.length).toBeGreaterThan(0);
|
|
180
|
+
const errorData = fileAccessDiagnostics[0].data;
|
|
181
|
+
expect(errorData).toBeDefined();
|
|
182
|
+
expect(String(errorData.error)).toMatch(/EACCES/i);
|
|
183
|
+
});
|
|
95
184
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.11",
|
|
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",
|