eslint-plugin-traceability 1.15.0 → 1.16.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 (34) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/README.md +61 -13
  3. package/lib/src/index.js +59 -0
  4. package/lib/src/rules/helpers/require-story-core.js +1 -1
  5. package/lib/src/rules/helpers/require-story-helpers.d.ts +10 -3
  6. package/lib/src/rules/helpers/require-story-helpers.js +84 -7
  7. package/lib/src/rules/no-redundant-annotation.js +73 -55
  8. package/lib/src/rules/require-branch-annotation.js +2 -2
  9. package/lib/src/rules/require-req-annotation.js +2 -2
  10. package/lib/src/rules/require-story-annotation.js +8 -3
  11. package/lib/src/rules/require-traceability.js +8 -9
  12. package/lib/src/utils/annotation-checker.js +23 -4
  13. package/lib/tests/cli-error-handling.test.js +10 -1
  14. package/lib/tests/integration/cli-integration.test.js +5 -0
  15. package/lib/tests/integration/require-traceability-aliases.integration.test.js +126 -0
  16. package/lib/tests/plugin-default-export-and-configs.test.js +23 -0
  17. package/lib/tests/rules/auto-fix-behavior-008.test.js +7 -7
  18. package/lib/tests/rules/error-reporting.test.js +1 -1
  19. package/lib/tests/rules/no-redundant-annotation.test.js +20 -0
  20. package/lib/tests/rules/require-story-annotation.test.js +75 -10
  21. package/lib/tests/rules/require-story-helpers.test.js +224 -0
  22. package/lib/tests/rules/require-story-utils.test.d.ts +7 -0
  23. package/lib/tests/rules/require-story-utils.test.js +158 -0
  24. package/lib/tests/utils/annotation-checker-branches.test.d.ts +5 -0
  25. package/lib/tests/utils/annotation-checker-branches.test.js +103 -0
  26. package/lib/tests/utils/annotation-scope-analyzer.test.js +134 -0
  27. package/lib/tests/utils/branch-annotation-helpers.test.js +66 -0
  28. package/package.json +2 -2
  29. package/user-docs/api-reference.md +71 -15
  30. package/user-docs/examples.md +24 -13
  31. package/user-docs/migration-guide.md +127 -4
  32. package/user-docs/traceability-overview.md +116 -0
  33. package/lib/tests/integration/dogfooding-validation.test.js +0 -129
  34. /package/lib/tests/integration/{dogfooding-validation.test.d.ts → require-traceability-aliases.integration.test.d.ts} +0 -0
@@ -46,7 +46,7 @@ The following diff shows a typical migration in **your own project**, where `doc
46
46
 
47
47
  ### 3.1 Multi-story `@supports` annotations
48
48
 
49
- Starting in v1.x, `eslint-plugin-traceability` supports an additional annotation form for integration code that implements requirements from multiple stories in a consuming project. The following snippet shows one example of how you might structure such an annotation in **your** codebase:
49
+ Starting in v1.x, `eslint-plugin-traceability` introduces and prefers the `@supports` annotation for integration code that implements requirements from multiple stories in a consuming project. The following snippet shows one example of how you might structure such an annotation in **your** codebase:
50
50
 
51
51
  ```js
52
52
  /**
@@ -102,10 +102,20 @@ Aligned with the internal rule behavior, the key cases are:
102
102
  The following are **ignored** by this rule and remain valid:
103
103
  - Comments that contain only `@story` lines,
104
104
  - Comments that contain only `@req` lines,
105
- - Comments that contain only `@supports` lines, and
106
- - Line comments such as `// @story ...`.
105
+ - Comments that contain only `@supports` lines.
107
106
 
108
- These forms are still supported by the plugin and are not modified by `traceability/prefer-supports-annotation`.
107
+ Line comments are treated more selectively:
108
+ - Simple, consecutive line comments such as:
109
+
110
+ ```js
111
+ // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
112
+ // @req REQ-ANNOTATION-REQUIRED
113
+ function initAuth() {}
114
+ ```
115
+
116
+ that are directly attached to a function, method, or branch and describe exactly one story path plus one or more requirement IDs can now be migrated automatically. When the rule is enabled and you run ESLint with `--fix`, these are consolidated into a single `// @supports ...` line that preserves the same story path and requirement IDs.
117
+
118
+ - More complex inline patterns — for example, mixed traceability and non-traceability content, multiple distinct `@story` paths, or interleaved unrelated comments between `@story` and `@req` lines — are still reported but are **not** auto-fixed. In these cases, the rule continues to treat unsupported inline shapes conservatively, emitting diagnostics and leaving the comments unchanged so you can adjust them manually.
109
119
 
110
120
  A typical migration path is:
111
121
 
@@ -133,6 +143,8 @@ export function initAuth() {
133
143
  }
134
144
  ```
135
145
 
146
+ These `@story` and `@req` forms are treated as a legacy single-story style that remains valid for simple cases, while new multi-story integrations should prefer `@supports` as the primary format.
147
+
136
148
  #### When to introduce `@supports`
137
149
 
138
150
  Adopt `@supports` for **multi-story integration** code, especially when:
@@ -183,6 +195,7 @@ You can introduce `@supports` gradually without breaking existing code:
183
195
 
184
196
  1. Leave existing `@story` and `@req` annotations in place.
185
197
  2. Add `@supports` lines that group requirements by story file.
198
+ Over time, teams are encouraged to converge on `@supports` as the canonical format for multi-story integrations, keeping `@story`/`@req` primarily for simple, single-story cases.
186
199
  3. Run ESLint with `traceability/valid-annotation-format` and `traceability/valid-req-reference` enabled to confirm everything passes.
187
200
  4. Optionally, once you are comfortable, standardize on using `@supports` for multi-story integration functions while keeping `@story` + `@req` for simple, single-story code.
188
201
 
@@ -197,6 +210,116 @@ Versions 1.x of `eslint-plugin-traceability` extend the `traceability/require-br
197
210
 
198
211
  If you previously added suppressions or workaround comments around `else if` branches due to formatter conflicts, you can usually remove those workarounds after upgrading to 1.x as long as your annotations live in one of the supported locations. For new code, you can place annotations either directly above the `else if` or, when you know a formatter will wrap a long condition, on the first comment-only line inside the consequent block body, which is where the rule places auto-fix placeholders by default.
199
212
 
213
+ ### 3.3 Redundant traceability annotation cleanup
214
+
215
+ As you move toward an **`@supports`-first** style while still supporting legacy `@story`/`@req`, v1.x adds `traceability/no-redundant-annotation` to help clean up redundant **statement-level** annotations that often accumulate during migration and refactoring.
216
+
217
+ This rule is enabled as `"warn"` in the built-in recommended presets, so you automatically get guidance without breaking existing builds.
218
+
219
+ At a high level, the rule targets common duplication patterns where multiple adjacent statements or branches repeat the **same story/requirement coverage**, for example:
220
+
221
+ - **Branch + statement duplication** — the `if`/`else` branch is annotated, and the first statement inside the block repeats the exact same coverage.
222
+ - **Sequential simple statements** — back‑to‑back statements each carry identical traceability, even though they form a single logical step.
223
+ - **Trivial returns** — a branch and an immediately returning statement both repeat the same story/requirement pair.
224
+
225
+ In all cases, the rule is conservative:
226
+
227
+ - It **never removes the last annotation** that provides coverage for a given `(story path, requirement ID)` pair.
228
+ - It prefers to keep annotations in the positions that are easiest to reason about (for example, on the controlling branch or on the first statement in a short sequence), and trims only clearly redundant copies.
229
+
230
+ The rule operates over both `@supports` and legacy `@story`/`@req` style annotations, so it continues to work even in mixed codebases during a long-running migration.
231
+
232
+ A simplified example, using an illustrative story path that represents a file in **your** documentation tree:
233
+
234
+ Before (redundant duplication inside a branch):
235
+
236
+ ```js
237
+ if (!user) {
238
+ // @supports docs/stories/010.0-AUTH-SESSION-MANAGEMENT.story.md REQ-UNAUTH-REDIRECT
239
+ // @supports docs/stories/010.0-AUTH-SESSION-MANAGEMENT.story.md REQ-UNAUTH-REDIRECT
240
+ return redirectToLogin();
241
+ }
242
+ ```
243
+
244
+ After (`no-redundant-annotation` auto-fix, coverage preserved but duplication removed):
245
+
246
+ ```js
247
+ if (!user) {
248
+ // @supports docs/stories/010.0-AUTH-SESSION-MANAGEMENT.story.md REQ-UNAUTH-REDIRECT
249
+ return redirectToLogin();
250
+ }
251
+ ```
252
+
253
+ Another example where the branch and first statement are both annotated with the same coverage:
254
+
255
+ Before:
256
+
257
+ ```js
258
+ // @supports docs/stories/020.0-ORDERS-CHECKOUT.story.md REQ-CALCULATE-TOTAL
259
+ if (cart.items.length === 0) {
260
+ // @supports docs/stories/020.0-ORDERS-CHECKOUT.story.md REQ-CALCULATE-TOTAL
261
+ return 0;
262
+ }
263
+ ```
264
+
265
+ After (keep coverage at the branch, drop the redundant inner statement annotation):
266
+
267
+ ```js
268
+ // @supports docs/stories/020.0-ORDERS-CHECKOUT.story.md REQ-CALCULATE-TOTAL
269
+ if (cart.items.length === 0) {
270
+ return 0;
271
+ }
272
+ ```
273
+
274
+ #### Safe migration workflow
275
+
276
+ To use `traceability/no-redundant-annotation` safely during your v1.x migration:
277
+
278
+ 1. **Start from the recommended preset**
279
+
280
+ Make sure your ESLint flat config includes the plugin’s recommended config (which enables this rule as `"warn"`):
281
+
282
+ ```js
283
+ import traceability from "eslint-plugin-traceability";
284
+
285
+ export default [traceability.configs.recommended];
286
+ ```
287
+
288
+ 2. **Run ESLint without auto-fix**
289
+
290
+ Run ESLint without `--fix` to review the warnings:
291
+
292
+ ```bash
293
+ npm run lint
294
+ ```
295
+
296
+ Look for diagnostics from `traceability/no-redundant-annotation` and confirm that the suggested removals match your expectations.
297
+
298
+ 3. **Run with `--fix` once comfortable**
299
+
300
+ When you are satisfied with the behavior, re-run ESLint with auto-fix enabled to apply safe cleanups in bulk:
301
+
302
+ ```bash
303
+ npm run lint -- --fix
304
+ ```
305
+
306
+ 4. **Optionally tighten over time**
307
+
308
+ As your team gets comfortable:
309
+ - You can raise the rule severity to `"error"` for new code, and/or
310
+ - Increase strictness via configuration (see below) to catch more subtle forms of duplication.
311
+
312
+ #### Key configuration knobs
313
+
314
+ The full API reference documents all options, but the most important knobs for migration are:
315
+
316
+ - **`strictness`** — controls how aggressively the rule interprets “redundant.” Lower settings only remove obvious duplication; higher levels consider more complex patterns across nearby statements and branches.
317
+ - **`allowEmphasisDuplication`** — when `true`, allows an explicit “emphasis” annotation to repeat coverage (for example, keeping a second comment in a particularly critical spot) without being reported as redundant.
318
+ - **`maxScopeDepth`** — limits how far into nested blocks the rule looks when deciding what is redundant, which can be useful if you want very local cleanups only.
319
+ - **`alwaysCovered`** — a safety valve that forces the rule to preserve at least one annotation for each `(story, requirement)` pair within the relevant scope, even under stricter modes.
320
+
321
+ For most teams, the defaults in the recommended preset are a good starting point; you can then tune these options incrementally as your traceability style and `@supports` usage stabilize.
322
+
200
323
  ## 4. Test and Validate
201
324
 
202
325
  Run your test suite to confirm everything passes:
@@ -0,0 +1,116 @@
1
+ # Traceability Overview and FAQ
2
+
3
+ Created autonomously by [voder.ai](https://voder.ai).
4
+
5
+ This page gives a high-level overview of how to use `eslint-plugin-traceability` in day-to-day development and answers common questions about annotations, rules, and their relationship to legacy aliases.
6
+
7
+ For detailed rule and option descriptions, see the [API Reference](api-reference.md). For concrete code samples, see the [Examples](examples.md) document.
8
+
9
+ ## Which annotations should I use?
10
+
11
+ The plugin understands three annotation forms:
12
+
13
+ - `@supports` – **Preferred for new code and multi-story integrations.**
14
+ Use this when a function, module, or branch implements requirements from one or more stories. A single `@supports` tag can express both the story path and the requirement IDs it implements.
15
+ - `@story` – Legacy story-level tag.
16
+ Still valid and useful when a function is tied to a single story file and you have not yet migrated to `@supports`.
17
+ - `@req` – Legacy requirement-level tag.
18
+ Pairs naturally with `@story` for simple, single-story scenarios.
19
+
20
+ Recommended usage:
21
+
22
+ - For **new or refactored code**, prefer `@supports` as your primary annotation.
23
+ - For **simple, single-story functions** that already use `@story` + `@req`, you can keep that style; there is no forced cut-over.
24
+ - During migration, you can temporarily have both `@story`/`@req` and `@supports` in the same block; the core rules and the optional `traceability/prefer-supports-annotation` rule are designed to support this.
25
+
26
+ The [Migration Guide](migration-guide.md) explains when and how to introduce `@supports` in more detail, including conservative auto-fix behavior.
27
+
28
+ ## Which ESLint rule should I enable for functions?
29
+
30
+ For function-level checks, think in terms of a single canonical rule plus a small set of supporting rules.
31
+
32
+ - `traceability/require-traceability` – **Canonical function-level rule for new configurations.**
33
+ Ensures that in-scope functions and methods have both story and requirement coverage. It understands both `@supports` (preferred) and legacy `@story` / `@req` annotations.
34
+
35
+ Most users can choose one of these options:
36
+
37
+ 1. **Use the recommended preset** (simplest):
38
+
39
+ ```js
40
+ // eslint.config.js
41
+ import js from "@eslint/js";
42
+ import traceability from "eslint-plugin-traceability";
43
+
44
+ export default [js.configs.recommended, traceability.configs.recommended];
45
+ ```
46
+
47
+ This enables `traceability/require-traceability` and the other core rules with sensible defaults.
48
+
49
+ 2. **Manually enable the unified rule and common helpers** (when you need custom tuning):
50
+
51
+ ```js
52
+ // eslint.config.js
53
+ import traceability from "eslint-plugin-traceability";
54
+
55
+ export default [
56
+ {
57
+ plugins: { traceability },
58
+ rules: {
59
+ // Canonical function-level rule
60
+ "traceability/require-traceability": "error",
61
+
62
+ // Common supporting rules
63
+ "traceability/require-branch-annotation": "warn",
64
+ "traceability/valid-annotation-format": "error",
65
+ "traceability/valid-story-reference": "error",
66
+ "traceability/valid-req-reference": "error",
67
+
68
+ // Optional: enforce test traceability conventions
69
+ "traceability/require-test-traceability": "warn",
70
+ },
71
+ },
72
+ ];
73
+ ```
74
+
75
+ The same guidance is summarized in the README under **"Canonical function-level rule and legacy aliases"**.
76
+
77
+ ## What about the legacy alias rules?
78
+
79
+ Two additional rule keys exist for backward compatibility:
80
+
81
+ - `traceability/require-story-annotation`
82
+ - `traceability/require-req-annotation`
83
+
84
+ Key points:
85
+
86
+ - They are **legacy aliases** that share the same underlying engine as `traceability/require-traceability`.
87
+ - They are kept so that older configurations continue to work without change.
88
+ - New configurations should **not** rely on these keys directly unless you have a specific reason to tune their severities independently.
89
+
90
+ If you are starting from scratch, you can safely ignore the legacy keys and use only `traceability/require-traceability` together with the supporting rules listed above.
91
+
92
+ ## Do I have to migrate existing `@story` / `@req` annotations to `@supports`?
93
+
94
+ No. Existing `@story` + `@req` annotations remain valid and fully supported.
95
+
96
+ Typical migration path:
97
+
98
+ 1. Keep your current `@story` + `@req` annotations for simple, single-story functions.
99
+ 2. Introduce `@supports` gradually for integration code that naturally spans multiple stories.
100
+ 3. Optionally enable `traceability/prefer-supports-annotation` at `"warn"` to get gentle guidance and conservative auto-fixes for straightforward single-story blocks.
101
+ 4. Once you are comfortable, you can tighten enforcement or standardize on `@supports` for new multi-story work.
102
+
103
+ See the [Migration Guide](migration-guide.md#31-multi-story-supports-annotations) for concrete before/after examples.
104
+
105
+ ## Where can I see concrete examples?
106
+
107
+ - **Quick start and minimal config:** See the main [README](../README.md#quick-start).
108
+ - **Full rule list and options:** See the [API Reference](api-reference.md#rules).
109
+ - **End-to-end examples:** See the [Examples](examples.md) document, including:
110
+ - Flat-config snippets using the recommended and strict presets.
111
+ - CLI usage with the unified rule and clearly labeled legacy-alias examples.
112
+ - Test traceability examples using `traceability/require-test-traceability`.
113
+ - Branch annotation patterns that work well with formatters such as Prettier.
114
+ - **Migration guidance:** See the [Migration Guide](migration-guide.md).
115
+
116
+ These resources are designed to be complementary: start with this overview to choose the right annotations and rules, then refer to the API reference and examples when you need exact configuration shapes or runnable code samples.
@@ -1,129 +0,0 @@
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
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- /**
37
- * Dogfooding validation integration tests
38
- * @supports docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md REQ-DOGFOODING-TEST REQ-DOGFOODING-CI REQ-DOGFOODING-VERIFY REQ-DOGFOODING-PRESET
39
- */
40
- const path = __importStar(require("path"));
41
- const child_process_1 = require("child_process");
42
- const use_at_your_own_risk_1 = require("eslint/use-at-your-own-risk");
43
- const index_1 = __importStar(require("../../src/index"));
44
- /**
45
- * @supports docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md REQ-DOGFOODING-TEST
46
- */
47
- function getTsConfigFromEslintConfig(eslintConfig) {
48
- const configs = Array.isArray(eslintConfig) ? eslintConfig : [eslintConfig];
49
- return configs.find((config) => {
50
- if (!config || !config.files)
51
- return false;
52
- const files = config.files;
53
- return files.includes("**/*.ts") && files.includes("**/*.tsx");
54
- });
55
- }
56
- describe("Dogfooding Validation (Story 023.0-MAINT-DOGFOODING-VALIDATION)", () => {
57
- // TEMPORARILY SKIPPED - dogfooding rules disabled pending systematic annotation format review
58
- // @story docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md
59
- // @req REQ-DOGFOODING - Plugin should dogfood its own rules
60
- it.skip("[REQ-DOGFOODING-TEST] should have traceability/require-story-annotation enabled for TS sources", () => {
61
- /**
62
- * @supports docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md REQ-DOGFOODING-TEST
63
- */
64
- // Require the project's eslint.config.js and find the TS-specific config
65
- // that applies to *.ts and *.tsx files.
66
- const eslintConfig = require("../../eslint.config.js");
67
- const tsConfig = getTsConfigFromEslintConfig(eslintConfig);
68
- expect(tsConfig).toBeDefined();
69
- const rules = tsConfig.rules || {};
70
- const ruleEntry = rules["traceability/require-story-annotation"];
71
- expect(ruleEntry).toBeDefined();
72
- const severity = Array.isArray(ruleEntry) && ruleEntry.length > 0
73
- ? ruleEntry[0]
74
- : ruleEntry;
75
- expect(severity).toBe("error");
76
- });
77
- it("[REQ-DOGFOODING-CI] should run traceability/require-story-annotation via ESLint CLI on TS sources", () => {
78
- /**
79
- * @supports docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md REQ-DOGFOODING-CI
80
- */
81
- const eslintBin = path.resolve(__dirname, "../../node_modules/.bin/eslint");
82
- const configPath = path.resolve(__dirname, "../../eslint.config.js");
83
- const tsSnippet = `
84
- const x: number = 42;
85
- export function foo() {
86
- return x;
87
- }
88
- `;
89
- const result = (0, child_process_1.spawnSync)(process.platform === "win32" ? `${eslintBin}.cmd` : eslintBin, ["--config", configPath, "--stdin", "--stdin-filename", "src/dogfood.ts"], {
90
- encoding: "utf8",
91
- input: tsSnippet,
92
- });
93
- // The snippet intentionally lacks @story annotations, so the rule should
94
- // report an error for the generated `src/dogfood.ts` virtual file.
95
- expect(result.status).not.toBe(0);
96
- expect(result.stdout).toContain("error");
97
- expect(result.stdout).toContain("src/dogfood.ts");
98
- });
99
- it.skip("[REQ-DOGFOODING-VERIFY] should report at least one traceability rule active for TS sources", () => {
100
- /**
101
- * @supports docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md REQ-DOGFOODING-VERIFY
102
- */
103
- const eslintConfig = require("../../eslint.config.js");
104
- const tsConfig = getTsConfigFromEslintConfig(eslintConfig);
105
- expect(tsConfig).toBeDefined();
106
- const rules = tsConfig.rules || {};
107
- const hasTraceabilityRule = Object.keys(rules).some((key) => key.startsWith("traceability/"));
108
- expect(hasTraceabilityRule).toBe(true);
109
- });
110
- it("[REQ-DOGFOODING-PRESET] should be compatible with recommended preset usage without throwing", async () => {
111
- /**
112
- * @supports docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md REQ-DOGFOODING-PRESET
113
- */
114
- const config = [
115
- { plugins: { traceability: index_1.default }, rules: {} },
116
- ...index_1.configs.recommended,
117
- ];
118
- const eslint = new use_at_your_own_risk_1.FlatESLint({
119
- overrideConfig: config,
120
- overrideConfigFile: true,
121
- ignore: false,
122
- });
123
- const results = await eslint.lintText("function foo() {}", {
124
- filePath: "example.ts",
125
- });
126
- expect(results.length).toBeGreaterThanOrEqual(1);
127
- expect(Array.isArray(results[0].messages)).toBe(true);
128
- });
129
- });