eslint-plugin-traceability 1.6.1 → 1.6.3

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.
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -7,10 +40,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
40
  * Tests for: docs/stories/006.0-DEV-FILE-VALIDATION.story.md
8
41
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
9
42
  * @req REQ-FILE-EXISTENCE - Verify valid-story-reference rule enforces existing .story.md files
43
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
44
+ * @req REQ-ERROR-SPECIFIC - Verify file-related error messages are specific about failure causes
45
+ * @req REQ-ERROR-CONTEXT - Verify file-related error messages include contextual information (path, underlying error)
46
+ * @req REQ-ERROR-CONSISTENCY - Verify file-related error messages follow consistent formatting and identifiers
47
+ * @req REQ-ERROR-HANDLING - Verify file-related errors are reported via diagnostics instead of uncaught exceptions
10
48
  */
11
49
  const eslint_1 = require("eslint");
12
50
  const valid_story_reference_1 = __importDefault(require("../../src/rules/valid-story-reference"));
13
51
  const storyReferenceUtils_1 = require("../../src/utils/storyReferenceUtils");
52
+ const path = __importStar(require("path"));
14
53
  const ruleTester = new eslint_1.RuleTester({
15
54
  languageOptions: { parserOptions: { ecmaVersion: 2020 } },
16
55
  });
@@ -68,6 +107,233 @@ describe("Valid Story Reference Rule (Story 006.0-DEV-FILE-VALIDATION)", () => {
68
107
  ],
69
108
  });
70
109
  });
110
+ // @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
111
+ // @req REQ-CONFIGURABLE-PATHS - Verify custom storyDirectories behavior
112
+ const configurablePathsTester = new eslint_1.RuleTester({
113
+ languageOptions: { parserOptions: { ecmaVersion: 2020 } },
114
+ });
115
+ configurablePathsTester.run("valid-story-reference", valid_story_reference_1.default, {
116
+ valid: [
117
+ {
118
+ name: "[REQ-CONFIGURABLE-PATHS] honors custom storyDirectories using docs/stories",
119
+ code: `// @story 001.0-DEV-PLUGIN-SETUP.story.md\n// @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md`,
120
+ options: [{ storyDirectories: ["docs/stories"] }],
121
+ },
122
+ ],
123
+ invalid: [],
124
+ });
125
+ // @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
126
+ // @req REQ-CONFIGURABLE-PATHS - Verify allowAbsolutePaths behavior
127
+ const allowAbsolutePathsTester = new eslint_1.RuleTester({
128
+ languageOptions: { parserOptions: { ecmaVersion: 2020 } },
129
+ });
130
+ const absoluteStoryPath = path.resolve(process.cwd(), "docs/stories/001.0-DEV-PLUGIN-SETUP.story.md");
131
+ allowAbsolutePathsTester.run("valid-story-reference", valid_story_reference_1.default, {
132
+ valid: [
133
+ {
134
+ name: "[REQ-CONFIGURABLE-PATHS] allowAbsolutePaths accepts existing absolute .story.md inside project",
135
+ code: `// @story ${absoluteStoryPath}\n// @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md`,
136
+ options: [
137
+ {
138
+ allowAbsolutePaths: true,
139
+ storyDirectories: ["docs/stories"],
140
+ },
141
+ ],
142
+ },
143
+ ],
144
+ invalid: [
145
+ {
146
+ name: "[REQ-CONFIGURABLE-PATHS] disallows absolute paths when allowAbsolutePaths is false",
147
+ code: `// @story ${absoluteStoryPath}\n// @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md`,
148
+ options: [
149
+ {
150
+ allowAbsolutePaths: false,
151
+ storyDirectories: ["docs/stories"],
152
+ },
153
+ ],
154
+ errors: [
155
+ {
156
+ messageId: "invalidPath",
157
+ },
158
+ ],
159
+ },
160
+ ],
161
+ });
162
+ // @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
163
+ // @req REQ-CONFIGURABLE-PATHS - Verify requireStoryExtension behavior
164
+ const relaxedExtensionTester = new eslint_1.RuleTester({
165
+ languageOptions: { parserOptions: { ecmaVersion: 2020 } },
166
+ });
167
+ relaxedExtensionTester.run("valid-story-reference", valid_story_reference_1.default, {
168
+ valid: [
169
+ {
170
+ name: "[REQ-CONFIGURABLE-PATHS] accepts .story.md story path when requireStoryExtension is false (still valid and existing)",
171
+ code: `// @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md\n// @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md`,
172
+ options: [
173
+ {
174
+ storyDirectories: ["docs/stories"],
175
+ requireStoryExtension: false,
176
+ },
177
+ ],
178
+ },
179
+ ],
180
+ invalid: [],
181
+ });
182
+ // @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
183
+ // @req REQ-PROJECT-BOUNDARY - Verify project boundary handling
184
+ const projectBoundaryTester = new eslint_1.RuleTester({
185
+ languageOptions: { parserOptions: { ecmaVersion: 2020 } },
186
+ });
187
+ projectBoundaryTester.run("valid-story-reference", valid_story_reference_1.default, {
188
+ valid: [],
189
+ invalid: [
190
+ {
191
+ name: "[REQ-PROJECT-BOUNDARY] story reference outside project root is rejected when discovered via absolute path",
192
+ code: `// @story ${path.resolve(path.sep, "outside-project", "001.0-DEV-PLUGIN-SETUP.story.md")}\n// @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md`,
193
+ options: [
194
+ {
195
+ allowAbsolutePaths: true,
196
+ storyDirectories: [path.resolve(path.sep, "outside-project")],
197
+ },
198
+ ],
199
+ errors: [
200
+ {
201
+ messageId: "invalidPath",
202
+ },
203
+ ],
204
+ },
205
+ ],
206
+ });
207
+ describe("Valid Story Reference Rule Configuration and Boundaries (Story 006.0-DEV-FILE-VALIDATION)", () => {
208
+ const fs = require("fs");
209
+ const pathModule = require("path");
210
+ let tempDirs = [];
211
+ afterEach(() => {
212
+ for (const dir of tempDirs) {
213
+ try {
214
+ fs.rmSync(dir, { recursive: true, force: true });
215
+ }
216
+ catch {
217
+ // ignore cleanup errors
218
+ }
219
+ }
220
+ tempDirs = [];
221
+ (0, storyReferenceUtils_1.__resetStoryExistenceCacheForTests)();
222
+ jest.restoreAllMocks();
223
+ });
224
+ it("[REQ-CONFIGURABLE-PATHS] uses storyDirectories when resolving relative paths (Story 006.0-DEV-FILE-VALIDATION)", () => {
225
+ const storyPath = pathModule.join(process.cwd(), "docs/stories/001.0-DEV-PLUGIN-SETUP.story.md");
226
+ jest.spyOn(fs, "existsSync").mockImplementation((...args) => {
227
+ const p = args[0];
228
+ return p === storyPath;
229
+ });
230
+ jest.spyOn(fs, "statSync").mockImplementation((...args) => {
231
+ const p = args[0];
232
+ if (p === storyPath) {
233
+ return {
234
+ isFile: () => true,
235
+ };
236
+ }
237
+ throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
238
+ });
239
+ const diagnostics = runRuleOnCode(`// @story 001.0-DEV-PLUGIN-SETUP.story.md`, [{ storyDirectories: ["docs/stories"] }]);
240
+ // When storyDirectories is configured, the underlying resolution should
241
+ // treat the path as valid; absence of errors is asserted via RuleTester
242
+ // above. Here we just ensure no crash path via storyExists cache reset.
243
+ expect(Array.isArray(diagnostics)).toBe(true);
244
+ });
245
+ it("[REQ-CONFIGURABLE-PATHS] allowAbsolutePaths permits absolute paths inside project when enabled (Story 006.0-DEV-FILE-VALIDATION)", () => {
246
+ const absPath = pathModule.resolve(process.cwd(), "docs/stories/001.0-DEV-PLUGIN-SETUP.story.md");
247
+ const diagnostics = runRuleOnCode(`// @story ${absPath}`, [
248
+ {
249
+ allowAbsolutePaths: true,
250
+ storyDirectories: ["docs/stories"],
251
+ },
252
+ ]);
253
+ // Detailed behavior is verified by RuleTester above; this Jest test
254
+ // ensures helper path construction does not throw and diagnostics are collected.
255
+ expect(Array.isArray(diagnostics)).toBe(true);
256
+ });
257
+ it("[REQ-PROJECT-BOUNDARY] storyDirectories cannot escape project even when normalize resolves outside cwd (Story 006.0-DEV-FILE-VALIDATION)", () => {
258
+ const ruleModule = require("../../src/rules/valid-story-reference");
259
+ const originalCreate = ruleModule.default.create || ruleModule.create;
260
+ // Spy on create to intercept normalizeStoryPath behavior indirectly if needed
261
+ expect(typeof originalCreate).toBe("function");
262
+ const diagnostics = runRuleOnCode(`// @story ../outside-boundary.story.md\n// @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md`);
263
+ // Behavior of reporting invalidPath for outside project is ensured
264
+ // in RuleTester projectBoundaryTester above; here ensure diagnostics collected.
265
+ expect(Array.isArray(diagnostics)).toBe(true);
266
+ });
267
+ /**
268
+ * @req REQ-PROJECT-BOUNDARY - Verify misconfigured storyDirectories pointing outside
269
+ * the project cannot cause external files to be treated as valid, and invalidPath is reported.
270
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
271
+ */
272
+ it("[REQ-PROJECT-BOUNDARY] misconfigured storyDirectories outside project cannot validate external files", () => {
273
+ const fs = require("fs");
274
+ const pathModule = require("path");
275
+ const outsideDir = pathModule.resolve(pathModule.sep, "tmp", "outside");
276
+ const outsideFile = pathModule.join(outsideDir, "external-story.story.md");
277
+ jest.spyOn(fs, "existsSync").mockImplementation((...args) => {
278
+ const p = args[0];
279
+ return p === outsideFile;
280
+ });
281
+ jest.spyOn(fs, "statSync").mockImplementation((...args) => {
282
+ const p = args[0];
283
+ if (p === outsideFile) {
284
+ return {
285
+ isFile: () => true,
286
+ };
287
+ }
288
+ const err = new Error("ENOENT");
289
+ err.code = "ENOENT";
290
+ throw err;
291
+ });
292
+ const diagnostics = runRuleOnCode(`// @story ${outsideFile}\n// @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md`, [
293
+ {
294
+ allowAbsolutePaths: true,
295
+ storyDirectories: [outsideDir],
296
+ },
297
+ ]);
298
+ expect(Array.isArray(diagnostics)).toBe(true);
299
+ const invalidPathDiagnostics = diagnostics.filter((d) => d.messageId === "invalidPath");
300
+ expect(invalidPathDiagnostics.length).toBeGreaterThan(0);
301
+ });
302
+ /**
303
+ * @req REQ-CONFIGURABLE-PATHS - Verify requireStoryExtension: false allows .md story
304
+ * files that do not end with .story.md when they exist in storyDirectories.
305
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
306
+ */
307
+ it("[REQ-CONFIGURABLE-PATHS] requireStoryExtension=false accepts existing .md story file", () => {
308
+ const fs = require("fs");
309
+ const pathModule = require("path");
310
+ const storyPath = pathModule.join(process.cwd(), "docs/stories/developer-story.map.md");
311
+ jest.spyOn(fs, "existsSync").mockImplementation((...args) => {
312
+ const p = args[0];
313
+ return p === storyPath;
314
+ });
315
+ jest.spyOn(fs, "statSync").mockImplementation((...args) => {
316
+ const p = args[0];
317
+ if (p === storyPath) {
318
+ return {
319
+ isFile: () => true,
320
+ };
321
+ }
322
+ const err = new Error("ENOENT");
323
+ err.code = "ENOENT";
324
+ throw err;
325
+ });
326
+ const diagnostics = runRuleOnCode(`// @story docs/stories/developer-story.map.md\n// @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md`, [
327
+ {
328
+ storyDirectories: ["docs/stories"],
329
+ requireStoryExtension: false,
330
+ },
331
+ ]);
332
+ expect(Array.isArray(diagnostics)).toBe(true);
333
+ const invalidExtensionDiagnostics = diagnostics.filter((d) => d.messageId === "invalidExtension");
334
+ expect(invalidExtensionDiagnostics.length).toBe(0);
335
+ });
336
+ });
71
337
  /**
72
338
  * Helper to run the valid-story-reference rule against a single source string
73
339
  * and collect reported diagnostics.
@@ -75,7 +341,7 @@ describe("Valid Story Reference Rule (Story 006.0-DEV-FILE-VALIDATION)", () => {
75
341
  * @req REQ-ERROR-HANDLING - Used to verify fileAccessError reporting behavior
76
342
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
77
343
  */
78
- function runRuleOnCode(code) {
344
+ function runRuleOnCode(code, options = []) {
79
345
  const messages = [];
80
346
  const context = {
81
347
  report: (descriptor) => {
@@ -90,7 +356,7 @@ function runRuleOnCode(code) {
90
356
  },
91
357
  ],
92
358
  }),
93
- options: [],
359
+ options,
94
360
  parserOptions: { ecmaVersion: 2020 },
95
361
  };
96
362
  const listeners = valid_story_reference_1.default.create(context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
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",