eslint-plugin-traceability 1.7.1 → 1.8.1

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +76 -37
  3. package/SECURITY.md +132 -0
  4. package/lib/src/index.d.ts +6 -35
  5. package/lib/src/index.js +8 -5
  6. package/lib/src/maintenance/batch.d.ts +5 -0
  7. package/lib/src/maintenance/batch.js +5 -0
  8. package/lib/src/maintenance/cli.js +34 -212
  9. package/lib/src/maintenance/commands.d.ts +32 -0
  10. package/lib/src/maintenance/commands.js +139 -0
  11. package/lib/src/maintenance/detect.d.ts +2 -0
  12. package/lib/src/maintenance/detect.js +4 -0
  13. package/lib/src/maintenance/flags.d.ts +99 -0
  14. package/lib/src/maintenance/flags.js +121 -0
  15. package/lib/src/maintenance/report.d.ts +2 -0
  16. package/lib/src/maintenance/report.js +2 -0
  17. package/lib/src/maintenance/update.d.ts +4 -0
  18. package/lib/src/maintenance/update.js +4 -0
  19. package/lib/src/rules/helpers/require-story-io.d.ts +3 -0
  20. package/lib/src/rules/helpers/require-story-io.js +20 -6
  21. package/lib/src/rules/helpers/valid-annotation-options.js +15 -4
  22. package/lib/src/rules/helpers/valid-annotation-utils.js +5 -0
  23. package/lib/src/rules/helpers/valid-story-reference-helpers.d.ts +3 -4
  24. package/lib/src/utils/reqAnnotationDetection.d.ts +4 -1
  25. package/lib/src/utils/reqAnnotationDetection.js +43 -15
  26. package/lib/tests/config/flat-config-presets-integration.test.d.ts +1 -0
  27. package/lib/tests/config/flat-config-presets-integration.test.js +75 -0
  28. package/lib/tests/maintenance/cli.test.js +89 -0
  29. package/lib/tests/plugin-default-export-and-configs.test.js +0 -2
  30. package/lib/tests/rules/prefer-implements-annotation.test.js +28 -0
  31. package/lib/tests/rules/require-req-annotation.test.js +8 -1
  32. package/lib/tests/rules/require-story-annotation.test.js +9 -4
  33. package/lib/tests/utils/ts-language-options.d.ts +1 -7
  34. package/lib/tests/utils/ts-language-options.js +8 -5
  35. package/package.json +11 -7
  36. package/user-docs/api-reference.md +527 -0
  37. package/user-docs/eslint-9-setup-guide.md +722 -0
  38. package/user-docs/examples.md +74 -0
  39. package/user-docs/migration-guide.md +174 -0
@@ -145,6 +145,31 @@ describe("Maintenance CLI (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
145
145
  fs_1.default.rmSync(dir, { recursive: true, force: true });
146
146
  }
147
147
  });
148
+ it("[REQ-MAINT-SAFE] report exits 2 and prints error on invalid --format value", () => {
149
+ const dir = withTempDir();
150
+ process.chdir(dir);
151
+ const errorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
152
+ const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
153
+ const code = (0, cli_1.runMaintenanceCli)([
154
+ "node",
155
+ "traceability-maint",
156
+ "report",
157
+ "--format",
158
+ "yaml",
159
+ ]);
160
+ try {
161
+ expect(code).toBe(2);
162
+ expect(errorSpy).toHaveBeenCalledTimes(1);
163
+ const message = String(errorSpy.mock.calls[0][0]);
164
+ expect(message).toContain("Invalid format: yaml");
165
+ expect(message).toContain("Expected 'text' or 'json'");
166
+ }
167
+ finally {
168
+ errorSpy.mockRestore();
169
+ logSpy.mockRestore();
170
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
171
+ }
172
+ });
148
173
  it("[REQ-MAINT-DETECT] detect supports --json output", () => {
149
174
  const dir = withTempDir();
150
175
  process.chdir(dir);
@@ -169,4 +194,68 @@ describe("Maintenance CLI (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
169
194
  fs_1.default.rmSync(dir, { recursive: true, force: true });
170
195
  }
171
196
  });
197
+ it("[REQ-MAINT-DETECT] detect with non-existent --root exits 0 and reports no stale annotations", () => {
198
+ const dir = withTempDir();
199
+ process.chdir(dir);
200
+ const missingRoot = path_1.default.join(dir, "missing-root");
201
+ const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
202
+ const code = (0, cli_1.runMaintenanceCli)([
203
+ "node",
204
+ "traceability-maint",
205
+ "detect",
206
+ "--root",
207
+ missingRoot,
208
+ ]);
209
+ try {
210
+ expect(code).toBe(0);
211
+ expect(logSpy).toHaveBeenCalledWith("No stale @story annotations found.");
212
+ }
213
+ finally {
214
+ logSpy.mockRestore();
215
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
216
+ }
217
+ });
218
+ it("[REQ-MAINT-SAFE] prints help and exits 0 when no subcommand is provided", () => {
219
+ const dir = withTempDir();
220
+ process.chdir(dir);
221
+ const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
222
+ const errorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
223
+ const code = (0, cli_1.runMaintenanceCli)(["node", "traceability-maint"]);
224
+ try {
225
+ expect(code).toBe(0);
226
+ expect(logSpy).toHaveBeenCalled();
227
+ const allMessages = logSpy.mock.calls.flat().join("\n");
228
+ expect(allMessages).toContain("traceability-maint - Traceability annotation maintenance tools");
229
+ expect(errorSpy).not.toHaveBeenCalled();
230
+ }
231
+ finally {
232
+ logSpy.mockRestore();
233
+ errorSpy.mockRestore();
234
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
235
+ }
236
+ });
237
+ it("[REQ-MAINT-SAFE] detect catches filesystem permission errors and exits 2 with prefixed error message", () => {
238
+ const dir = withTempDir();
239
+ process.chdir(dir);
240
+ const errorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
241
+ const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
242
+ const statSpy = jest.spyOn(fs_1.default, "statSync").mockImplementation(() => {
243
+ const err = new Error("EACCES simulated");
244
+ err.code = "EACCES";
245
+ throw err;
246
+ });
247
+ const code = (0, cli_1.runMaintenanceCli)(["node", "traceability-maint", "detect"]);
248
+ try {
249
+ expect(code).toBe(2);
250
+ expect(errorSpy).toHaveBeenCalled();
251
+ const message = String(errorSpy.mock.calls[0][0]);
252
+ expect(message).toContain("traceability-maint failed:");
253
+ }
254
+ finally {
255
+ statSpy.mockRestore();
256
+ errorSpy.mockRestore();
257
+ logSpy.mockRestore();
258
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
259
+ }
260
+ });
172
261
  });
@@ -80,12 +80,10 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
80
80
  expect(recommendedRules).toHaveProperty("traceability/require-branch-annotation", "error");
81
81
  expect(recommendedRules).toHaveProperty("traceability/valid-story-reference", "error");
82
82
  expect(recommendedRules).toHaveProperty("traceability/valid-req-reference", "error");
83
- expect(recommendedRules).toHaveProperty("traceability/prefer-implements-annotation", "warn");
84
83
  });
85
84
  it("[REQ-ERROR-SEVERITY] configs.strict uses same severity mapping as recommended", () => {
86
85
  const strictRules = index_1.configs.strict[0].rules;
87
86
  const recommendedRules = index_1.configs.recommended[0].rules;
88
87
  expect(strictRules).toEqual(recommendedRules);
89
- expect(strictRules).toHaveProperty("traceability/prefer-implements-annotation", "warn");
90
88
  });
91
89
  });
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  */
13
13
  const eslint_1 = require("eslint");
14
14
  const prefer_implements_annotation_1 = __importDefault(require("../../src/rules/prefer-implements-annotation"));
15
+ const src_1 = require("../../src");
15
16
  const ruleTester = new eslint_1.RuleTester({
16
17
  languageOptions: {
17
18
  parserOptions: { ecmaVersion: 2020, sourceType: "module" },
@@ -82,3 +83,30 @@ describe("prefer-implements-annotation rule (Story 010.3-DEV-MIGRATE-TO-IMPLEMEN
82
83
  ],
83
84
  });
84
85
  });
86
+ describe("prefer-implements-annotation configuration severity (REQ-CONFIG-SEVERITY)", () => {
87
+ test("rule is disabled by default in recommended preset (not present in configs.recommended[0].rules)", () => {
88
+ const recommended = src_1.configs.recommended;
89
+ expect(Array.isArray(recommended)).toBe(true);
90
+ const firstConfig = recommended[0];
91
+ expect(firstConfig).toBeDefined();
92
+ const rules = firstConfig.rules || {};
93
+ expect(rules["@eslint-sweat/prefer-implements-annotation"]).toBeUndefined();
94
+ expect(rules["prefer-implements-annotation"]).toBeUndefined();
95
+ });
96
+ test("rule can be configured with severity 'warn' or 'error' in flat config", () => {
97
+ const flatWarnConfig = {
98
+ files: ["**/*.ts"],
99
+ rules: {
100
+ "prefer-implements-annotation": "warn",
101
+ },
102
+ };
103
+ expect(flatWarnConfig.rules["prefer-implements-annotation"]).toBe("warn");
104
+ const flatErrorConfig = {
105
+ files: ["**/*.ts"],
106
+ rules: {
107
+ "prefer-implements-annotation": "error",
108
+ },
109
+ };
110
+ expect(flatErrorConfig.rules["prefer-implements-annotation"]).toBe("error");
111
+ });
112
+ });
@@ -12,6 +12,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  *
13
13
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
14
14
  * @req REQ-TYPESCRIPT-SUPPORT - Verify TypeScript declarations are checked via shared annotation checker helper
15
+ *
16
+ * @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
17
+ * @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Verify @implements is accepted as satisfying requirement annotations
15
18
  */
16
19
  const eslint_1 = require("eslint");
17
20
  const require_req_annotation_1 = __importDefault(require("../../src/rules/require-req-annotation"));
@@ -73,6 +76,10 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
73
76
  name: "[REQ-ANNOTATION-REQUIRED] valid with only @req annotation",
74
77
  code: `/**\n * @req REQ-EXAMPLE\n */\nfunction foo() {}`,
75
78
  },
79
+ {
80
+ name: "[REQ-REQUIRE-ACCEPTS-IMPLEMENTS] valid with only @implements annotation",
81
+ code: `/**\n * @implements docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction implOnly() {}`,
82
+ },
76
83
  {
77
84
  name: "[REQ-ANNOTATION-REQUIRED] valid with @story and @req annotations",
78
85
  code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-EXAMPLE\n */\nfunction bar() {}`,
@@ -131,7 +138,7 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
131
138
  ],
132
139
  invalid: [
133
140
  {
134
- name: "[REQ-ANNOTATION-REQUIRED] missing @req on function without JSDoc",
141
+ name: "[REQ-ANNOTATION-REQUIRED][REQ-REQUIRE-ACCEPTS-IMPLEMENTS] missing @req on function without JSDoc remains invalid under multi-story support",
135
142
  code: `function baz() {}`,
136
143
  errors: [
137
144
  {
@@ -6,15 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  /**
7
7
  * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
8
8
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
9
+ * @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
9
10
  * @req REQ-ANNOTATION-REQUIRED - Verify require-story-annotation rule enforces @story annotation on functions
11
+ * @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Verify @implements annotation is accepted as satisfying story requirements
10
12
  */
11
13
  const eslint_1 = require("eslint");
12
14
  const require_story_annotation_1 = __importDefault(require("../../src/rules/require-story-annotation"));
13
15
  const ts_language_options_1 = require("../utils/ts-language-options");
14
16
  const ruleTester = new eslint_1.RuleTester({
15
- languageOptions: {
16
- parserOptions: { ecmaVersion: 2020, sourceType: "module" },
17
- },
17
+ languageOptions: ts_language_options_1.tsRuleTesterLanguageOptions,
18
18
  });
19
19
  describe("Require Story Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", () => {
20
20
  ruleTester.run("require-story-annotation", require_story_annotation_1.default, {
@@ -23,6 +23,10 @@ describe("Require Story Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)",
23
23
  name: "[REQ-ANNOTATION-REQUIRED] valid with JSDoc @story annotation",
24
24
  code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction foo() {}`,
25
25
  },
26
+ {
27
+ name: "[REQ-REQUIRE-ACCEPTS-IMPLEMENTS] valid with only @implements annotation",
28
+ code: `/**\n * @implements docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction implOnly() {}`,
29
+ },
26
30
  {
27
31
  name: "[REQ-ANNOTATION-REQUIRED] valid with line comment @story annotation",
28
32
  code: `// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
@@ -60,7 +64,8 @@ declare function tsDecl(): void;`,
60
64
  ],
61
65
  invalid: [
62
66
  {
63
- name: "[REQ-ANNOTATION-REQUIRED] missing @story annotation on function",
67
+ // Backward compatibility: plain unannotated functions remain invalid under multi-story support
68
+ name: "[REQ-ANNOTATION-REQUIRED][BACKCOMPAT] missing @story annotation on function with no @implements",
64
69
  code: `function bar() {}`,
65
70
  output: `/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */\nfunction bar() {}`,
66
71
  errors: [
@@ -3,13 +3,7 @@
3
3
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
4
  * @req REQ-TYPESCRIPT-SUPPORT - Provide reusable TypeScript parser setup for tests
5
5
  */
6
- export declare const tsRuleTesterLanguageOptions: {
7
- readonly parser: any;
8
- readonly parserOptions: {
9
- readonly ecmaVersion: 2022;
10
- readonly sourceType: "module";
11
- };
12
- };
6
+ export declare const tsRuleTesterLanguageOptions: any;
13
7
  /**
14
8
  * Attach shared TypeScript RuleTester language options to a test case definition.
15
9
  * This helper allows tests to avoid repeating the languageOptions assignment.
@@ -1,16 +1,19 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.tsRuleTesterLanguageOptions = void 0;
4
- exports.withTsLanguageOptions = withTsLanguageOptions;
5
2
  /**
6
3
  * Shared TypeScript RuleTester language options for traceability tests.
7
4
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
8
5
  * @req REQ-TYPESCRIPT-SUPPORT - Provide reusable TypeScript parser setup for tests
9
6
  */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.tsRuleTesterLanguageOptions = void 0;
9
+ exports.withTsLanguageOptions = withTsLanguageOptions;
10
+ const tsEcmaVersion = 2022;
10
11
  exports.tsRuleTesterLanguageOptions = {
11
12
  parser: require("@typescript-eslint/parser"),
12
- // eslint-disable-next-line no-magic-numbers -- ECMAScript version constant
13
- parserOptions: { ecmaVersion: 2022, sourceType: "module" },
13
+ parserOptions: {
14
+ ecmaVersion: tsEcmaVersion,
15
+ sourceType: "module",
16
+ },
14
17
  };
15
18
  /**
16
19
  * Attach shared TypeScript RuleTester language options to a test case definition.
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.7.1",
3
+ "version": "1.8.1",
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",
7
7
  "files": [
8
8
  "lib",
9
9
  "README.md",
10
- "LICENSE"
10
+ "LICENSE",
11
+ "SECURITY.md",
12
+ "user-docs",
13
+ "CHANGELOG.md"
11
14
  ],
12
15
  "directories": {
13
16
  "doc": "docs"
@@ -27,11 +30,12 @@
27
30
  "test": "jest --ci --bail",
28
31
  "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",
29
32
  "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 --omit=dev --audit-level=high && npm run audit:dev-high",
30
- "ci-verify:fast": "npm run type-check && npm run check:traceability && npm run duplication && jest --ci --bail --passWithNoTests --testPathPatterns 'tests/(unit|fast)'",
33
+ "ci-verify:fast": "npm run type-check && npm run check:traceability && npm run duplication && jest --ci --bail --passWithNoTests --testPathPatterns 'tests/(rules|maintenance)'",
31
34
  "format": "prettier --write .",
32
35
  "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
33
36
  "lint-staged": "lint-staged",
34
37
  "duplication": "jscpd src tests --reporters console --threshold 3 --ignore tests/utils/**",
38
+ "deps:maturity": "dry-aged-deps",
35
39
  "audit:dev-high": "node scripts/generate-dev-deps-audit.js",
36
40
  "safety:deps": "node scripts/ci-safety-deps.js",
37
41
  "audit:ci": "node scripts/ci-audit.js",
@@ -63,8 +67,8 @@
63
67
  "@eslint/js": "^9.39.1",
64
68
  "@semantic-release/changelog": "^6.0.3",
65
69
  "@semantic-release/git": "^10.0.1",
66
- "@semantic-release/github": "^10.3.5",
67
- "@semantic-release/npm": "^10.0.6",
70
+ "@semantic-release/github": "12.0.2",
71
+ "@semantic-release/npm": "13.1.2",
68
72
  "@types/eslint": "^9.6.1",
69
73
  "@types/jest": "^30.0.0",
70
74
  "@types/node": "^24.10.1",
@@ -76,9 +80,9 @@
76
80
  "husky": "^9.1.7",
77
81
  "jest": "^30.2.0",
78
82
  "jscpd": "^4.0.5",
79
- "lint-staged": "^16.2.6",
83
+ "lint-staged": "^16.2.7",
80
84
  "prettier": "^3.6.2",
81
- "semantic-release": "^21.1.2",
85
+ "semantic-release": "25.0.2",
82
86
  "ts-jest": "^29.4.5",
83
87
  "typescript": "^5.9.3",
84
88
  "secretlint": "11.2.5",