eslint-plugin-traceability 1.4.8 → 1.4.10

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.
@@ -35,12 +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.
86
+ * Filesystem and I/O errors are handled inside the underlying utilities
87
+ * (e.g. storyExists) and surfaced as missing-file or filesystem-error
88
+ * diagnostics where appropriate.
89
+ *
40
90
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
41
91
  * @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
42
92
  * @req REQ-PATH-RESOLUTION - Resolve relative paths correctly and enforce configuration
43
93
  * @req REQ-SECURITY-VALIDATION - Prevent path traversal and absolute path usage
94
+ * @req REQ-ERROR-HANDLING - Delegate filesystem and I/O error handling to utilities and differentiate error types
44
95
  */
45
96
  function processStoryPath(opts) {
46
97
  const { storyPath, commentNode, context, cwd, storyDirs, allowAbsolute, requireExt, } = opts;
@@ -76,14 +127,22 @@ function processStoryPath(opts) {
76
127
  });
77
128
  return;
78
129
  }
79
- // Existence check
80
- if (!(0, storyReferenceUtils_1.normalizeStoryPath)(storyPath, cwd, storyDirs).exists) {
81
- context.report({
82
- node: commentNode,
83
- messageId: "fileMissing",
84
- data: { path: storyPath },
85
- });
86
- }
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
+ });
87
146
  }
88
147
  /**
89
148
  * Handle a single comment node by processing its lines.
@@ -124,6 +183,11 @@ exports.default = {
124
183
  fileMissing: "Story file '{{path}}' not found",
125
184
  invalidExtension: "Invalid story file extension for '{{path}}', expected '.story.md'",
126
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.",
127
191
  },
128
192
  schema: [
129
193
  {
@@ -146,10 +210,14 @@ exports.default = {
146
210
  return {
147
211
  /**
148
212
  * Program-level handler: iterate comments and validate @story annotations.
213
+ * Filesystem and I/O errors are handled by underlying utilities and
214
+ * surfaced as missing-file or filesystem-error diagnostics where appropriate.
215
+ *
149
216
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
150
217
  * @req REQ-ANNOTATION-VALIDATION - Discover and dispatch @story annotations for validation
151
218
  * @req REQ-FILE-EXISTENCE - Ensure referenced files exist
152
219
  * @req REQ-PATH-RESOLUTION - Resolve using cwd and configured story directories
220
+ * @req REQ-ERROR-HANDLING - Delegate filesystem and I/O error handling to utilities
153
221
  */
154
222
  Program() {
155
223
  const comments = context.getSourceCode().getAllComments() || [];
@@ -1,19 +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.
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
+ *
10
75
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
11
76
  * @req REQ-PATH-RESOLUTION - Resolve relative paths correctly and enforce configuration
12
77
  * @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
78
+ * @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
79
+ * @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
13
80
  */
14
81
  export declare function normalizeStoryPath(storyPath: string, cwd: string, storyDirs: string[]): {
15
82
  candidates: string[];
16
83
  exists: boolean;
84
+ existence: StoryExistenceResult;
17
85
  };
18
86
  /**
19
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;
@@ -17,6 +18,7 @@ exports.isUnsafeStoryPath = isUnsafeStoryPath;
17
18
  * @req REQ-PATH-RESOLUTION - Resolve relative paths correctly and enforce configuration
18
19
  * @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
19
20
  * @req REQ-SECURITY-VALIDATION - Prevent path traversal and absolute path usage
21
+ * @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
20
22
  */
21
23
  const fs_1 = __importDefault(require("fs"));
22
24
  const path_1 = __importDefault(require("path"));
@@ -39,34 +41,126 @@ function buildStoryCandidates(storyPath, cwd, storyDirs) {
39
41
  return candidates;
40
42
  }
41
43
  /**
42
- * Check if any of the provided file paths exist.
44
+ * Cache of filesystem existence checks keyed by absolute path.
43
45
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
44
46
  * @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
47
+ * @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
48
+ * @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
45
49
  */
46
- const fileExistCache = new Map();
47
- function storyExists(paths) {
48
- for (const candidate of paths) {
49
- let ok = fileExistCache.get(candidate);
50
- if (ok === undefined) {
51
- ok = fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isFile();
52
- fileExistCache.set(candidate, ok);
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" };
75
+ }
76
+ else {
77
+ // Path exists but is not a file; treat as missing for story purposes.
78
+ result = { path: candidate, status: "missing" };
79
+ }
80
+ }
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
+ };
53
113
  }
54
- if (ok) {
55
- return true;
114
+ if (res.status === "fs-error" && !firstFsError) {
115
+ firstFsError = res;
56
116
  }
57
117
  }
58
- return false;
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";
59
146
  }
60
147
  /**
61
148
  * Normalize a story path to candidate absolute paths and check existence.
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
+ *
62
153
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
63
154
  * @req REQ-PATH-RESOLUTION - Resolve relative paths correctly and enforce configuration
64
155
  * @req REQ-FILE-EXISTENCE - Validate that story file paths reference existing files
156
+ * @req REQ-ERROR-HANDLING - Handle filesystem errors gracefully without throwing
157
+ * @req REQ-PERFORMANCE-OPTIMIZATION - Cache filesystem checks to avoid redundant work
65
158
  */
66
159
  function normalizeStoryPath(storyPath, cwd, storyDirs) {
67
160
  const candidates = buildStoryCandidates(storyPath, cwd, storyDirs);
68
- const exists = storyExists(candidates);
69
- return { candidates, exists };
161
+ const existence = getStoryExistence(candidates);
162
+ const exists = existence.status === "exists";
163
+ return { candidates, exists, existence };
70
164
  }
71
165
  /**
72
166
  * Check if the provided path is absolute.
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  /**
4
- * Branch tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
+ * Edge-case tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
5
5
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
6
6
  * @req REQ-AUTOFIX - Cover additional branch cases in require-story-core (addStoryFixer/reportMissing)
7
7
  */
8
8
  const require_story_core_1 = require("../../src/rules/helpers/require-story-core");
9
9
  const require_story_helpers_1 = require("../../src/rules/helpers/require-story-helpers");
10
- describe("Require Story Core (Story 003.0)", () => {
10
+ describe("Require Story Core - edge cases (Story 003.0)", () => {
11
11
  test("createAddStoryFix falls back to 0 when target is falsy", () => {
12
12
  const fixer = {
13
13
  insertTextBeforeRange: jest.fn((r, t) => ({ r, t })),
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
3
3
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
- * @req REQ-COVERAGE-IO - Additional tests to exercise uncovered branches in require-story-io.ts
4
+ * @req REQ-HELPERS-EDGE-CASES - Edge-case behavior tests for helpers in require-story-helpers.ts
5
5
  */
6
6
  export {};
@@ -2,11 +2,11 @@
2
2
  /**
3
3
  * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
4
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
5
- * @req REQ-COVERAGE-HELPERS - Additional tests to exercise edge branches in require-story-helpers.ts
5
+ * @req REQ-HELPERS-EDGE-CASES - Edge-case behavior tests for helpers in require-story-helpers.ts
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  const require_story_helpers_1 = require("../../src/rules/helpers/require-story-helpers");
9
- describe("Require Story Helpers - additional branch coverage (Story 003.0)", () => {
9
+ describe("Require Story Helpers - edge cases (Story 003.0)", () => {
10
10
  test("jsdocHasStory returns false when JSDoc exists but value is not a string", () => {
11
11
  const fakeSource = { getJSDocComment: () => ({ value: 123 }) };
12
12
  const res = (0, require_story_helpers_1.jsdocHasStory)(fakeSource, {});
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
3
3
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
- * @req REQ-COVERAGE-HELPERS - Additional tests to exercise edge branches in require-story-helpers.ts
4
+ * @req REQ-IO-BEHAVIOR-EDGE-CASES - Edge-case behavior tests for IO helpers in require-story-io.ts
5
5
  */
6
6
  export {};
@@ -2,11 +2,11 @@
2
2
  /**
3
3
  * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
4
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
5
- * @req REQ-COVERAGE-IO - Additional tests to exercise uncovered branches in require-story-io.ts
5
+ * @req REQ-IO-BEHAVIOR-EDGE-CASES - Edge-case behavior tests for IO helpers in require-story-io.ts
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  const require_story_io_1 = require("../../src/rules/helpers/require-story-io");
9
- describe("Require Story IO helpers - branch coverage (Story 003.0)", () => {
9
+ describe("Require Story IO helpers - additional behavior (Story 003.0)", () => {
10
10
  test("parentChainHasStory returns false when sourceCode.getCommentsBefore is not a function", () => {
11
11
  const fakeSource = {}; // no getCommentsBefore function
12
12
  const node = { parent: { parent: null } };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
3
3
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
- * @req REQ-COVERAGE-VISITORS - Tests to cover visitors branches in require-story-visitors.ts
4
+ * @req REQ-VISITORS-BEHAVIOR - Behavior tests for visitors in require-story-visitors.ts
5
5
  */
6
6
  export {};
@@ -2,11 +2,11 @@
2
2
  /**
3
3
  * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
4
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
5
- * @req REQ-COVERAGE-VISITORS - Tests to cover visitors branches in require-story-visitors.ts
5
+ * @req REQ-VISITORS-BEHAVIOR - Behavior tests for visitors in require-story-visitors.ts
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  const require_story_visitors_1 = require("../../src/rules/helpers/require-story-visitors");
9
- describe("Require Story Visitors - branch coverage (Story 003.0)", () => {
9
+ describe("Require Story Visitors - behavior (Story 003.0)", () => {
10
10
  test("build visitors returns handlers for FunctionDeclaration and ArrowFunctionExpression", () => {
11
11
  const fakeContext = { getFilename: () => "file.ts" };
12
12
  const fakeSource = { getText: () => "" };
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  */
11
11
  const eslint_1 = require("eslint");
12
12
  const valid_story_reference_1 = __importDefault(require("../../src/rules/valid-story-reference"));
13
+ const storyReferenceUtils_1 = require("../../src/utils/storyReferenceUtils");
13
14
  const ruleTester = new eslint_1.RuleTester({
14
15
  languageOptions: { parserOptions: { ecmaVersion: 2020 } },
15
16
  });
@@ -67,3 +68,81 @@ describe("Valid Story Reference Rule (Story 006.0-DEV-FILE-VALIDATION)", () => {
67
68
  ],
68
69
  });
69
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
+ }
102
+ describe("Valid Story Reference Rule Error Handling (Story 006.0-DEV-FILE-VALIDATION)", () => {
103
+ /**
104
+ * @req REQ-ERROR-HANDLING - Verify storyExists swallows fs errors and returns false
105
+ * instead of throwing when filesystem operations fail.
106
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
107
+ */
108
+ const fs = require("fs");
109
+ afterEach(() => {
110
+ jest.restoreAllMocks();
111
+ });
112
+ it("[REQ-ERROR-HANDLING] storyExists returns false when fs throws", () => {
113
+ jest.spyOn(fs, "existsSync").mockImplementation(() => {
114
+ const err = new Error("EACCES: permission denied");
115
+ err.code = "EACCES";
116
+ throw err;
117
+ });
118
+ jest.spyOn(fs, "statSync").mockImplementation(() => {
119
+ const err = new Error("EACCES: permission denied");
120
+ err.code = "EACCES";
121
+ throw err;
122
+ });
123
+ expect(() => (0, storyReferenceUtils_1.storyExists)(["docs/stories/permission-denied.story.md"])).not.toThrow();
124
+ expect((0, storyReferenceUtils_1.storyExists)(["docs/stories/permission-denied.story.md"])).toBe(false);
125
+ });
126
+ /**
127
+ * @req REQ-ERROR-HANDLING - Verify rule reports fileAccessError when filesystem operations fail
128
+ * instead of treating it as a missing file.
129
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
130
+ */
131
+ it("[REQ-ERROR-HANDLING] rule reports fileAccessError when fs throws", () => {
132
+ const accessError = new Error("EACCES: permission denied while accessing");
133
+ accessError.code = "EACCES";
134
+ jest.spyOn(fs, "existsSync").mockImplementation(() => {
135
+ throw accessError;
136
+ });
137
+ jest.spyOn(fs, "statSync").mockImplementation(() => {
138
+ throw accessError;
139
+ });
140
+ const diagnostics = runRuleOnCode(`// @story docs/stories/fs-error.story.md`);
141
+ expect(diagnostics.length).toBeGreaterThan(0);
142
+ const fileAccessDiagnostics = diagnostics.filter((d) => d.messageId === "fileAccessError");
143
+ expect(fileAccessDiagnostics.length).toBeGreaterThan(0);
144
+ const errorData = fileAccessDiagnostics[0].data;
145
+ expect(errorData).toBeDefined();
146
+ expect(String(errorData.error)).toMatch(/EACCES/i);
147
+ });
148
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.4.8",
3
+ "version": "1.4.10",
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",
@@ -22,6 +22,7 @@
22
22
  "lint": "eslint --config eslint.config.js \"src/**/*.{js,ts}\" \"tests/**/*.{js,ts}\" --max-warnings=0",
23
23
  "test": "jest --ci --bail",
24
24
  "ci-verify": "npm run type-check && npm run lint && npm run format:check && npm run duplication && npm run check:traceability && npm test && npm run audit:ci && npm run safety:deps",
25
+ "ci-verify:full": "npm run check:traceability && npm run safety:deps && npm run audit:ci && npm run build && npm run type-check && npm run lint-plugin-check && npm run lint -- --max-warnings=0 && npm run duplication && npm run test -- --coverage && npm run format:check && npm audit --production --audit-level=high && npm run audit:dev-high",
25
26
  "ci-verify:fast": "npm run type-check && npm run check:traceability && npm run duplication && jest --ci --bail --passWithNoTests --testPathPatterns 'tests/(unit|fast)'",
26
27
  "format": "prettier --write .",
27
28
  "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",