eslint-plugin-traceability 1.11.2 → 1.11.4

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.
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const reqAnnotationDetection_1 = require("../../src/utils/reqAnnotationDetection");
4
+ // Small helper to construct a minimal SourceCode-like object for the detection helpers.
5
+ function createMockSourceCode(options = {}) {
6
+ const { lines = null, text = "", commentsBefore = [] } = options;
7
+ return {
8
+ lines: lines ?? undefined,
9
+ getText() {
10
+ return text;
11
+ },
12
+ getCommentsBefore() {
13
+ return commentsBefore;
14
+ },
15
+ };
16
+ }
17
+ describe("reqAnnotationDetection advanced heuristics (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", () => {
18
+ it("[REQ-ANNOTATION-REQ-DETECTION] returns false when sourceCode is missing", () => {
19
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], undefined, {
20
+ loc: null,
21
+ });
22
+ expect(has).toBe(false);
23
+ });
24
+ it("[REQ-ANNOTATION-REQ-DETECTION] returns false when node is missing", () => {
25
+ const context = {
26
+ getSourceCode() {
27
+ return createMockSourceCode({ lines: ["/** @req REQ-TEST */"] });
28
+ },
29
+ };
30
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], context, undefined);
31
+ expect(has).toBe(false);
32
+ });
33
+ it("[REQ-ANNOTATION-REQ-DETECTION] inspects jsdoc and comments when advanced heuristics throw", () => {
34
+ const context = {
35
+ getSourceCode() {
36
+ // This object intentionally causes hasReqInAdvancedHeuristics to throw by
37
+ // providing a getCommentsBefore implementation that throws on access.
38
+ return {
39
+ getCommentsBefore() {
40
+ throw new Error("boom");
41
+ },
42
+ };
43
+ },
44
+ };
45
+ const jsdoc = { value: "/** @req REQ-FROM-JSDOC */" };
46
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(jsdoc, [], context, {
47
+ // Minimal shape – the helper will call into the mock sourceCode and trigger the throw
48
+ parent: {},
49
+ });
50
+ expect(has).toBe(true);
51
+ });
52
+ it("[REQ-ANNOTATION-REQ-DETECTION] treats @supports in comments as satisfying requirement", () => {
53
+ const context = {
54
+ getSourceCode() {
55
+ return createMockSourceCode();
56
+ },
57
+ };
58
+ const comments = [{ value: "// @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-X" }];
59
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, comments, context, {
60
+ parent: {},
61
+ });
62
+ expect(has).toBe(true);
63
+ });
64
+ it("[REQ-ANNOTATION-REQ-DETECTION] linesBeforeHasReq returns false when lines is not an array", () => {
65
+ const context = {
66
+ getSourceCode() {
67
+ // lines is null here, causing the helper to see a non-array and return false
68
+ return createMockSourceCode({ lines: null });
69
+ },
70
+ };
71
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], context, {
72
+ // Provide a minimal location so advanced heuristics try to use line info
73
+ loc: { start: { line: 5 } },
74
+ parent: {},
75
+ });
76
+ expect(has).toBe(false);
77
+ });
78
+ it("[REQ-ANNOTATION-REQ-DETECTION] linesBeforeHasReq returns false when startLine is not a number", () => {
79
+ const sourceCode = createMockSourceCode({ lines: ["// @req REQ-SHOULD-NOT-BE-SEEN"] });
80
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], { getSourceCode: () => sourceCode }, {
81
+ // loc is missing/undefined; startLine will not be a valid number
82
+ loc: undefined,
83
+ parent: {},
84
+ });
85
+ expect(has).toBe(false);
86
+ });
87
+ it("[REQ-ANNOTATION-REQ-DETECTION] parentChainHasReq returns false when getCommentsBefore is not a function and no leadingComments/parents have req", () => {
88
+ const context = {
89
+ getSourceCode() {
90
+ return {
91
+ // getCommentsBefore is not a function here
92
+ getCommentsBefore: 123,
93
+ };
94
+ },
95
+ };
96
+ const node = {
97
+ parent: {
98
+ leadingComments: [{ value: "no req here" }],
99
+ parent: {
100
+ leadingComments: [{ value: "still nothing" }],
101
+ },
102
+ },
103
+ };
104
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], context, node);
105
+ expect(has).toBe(false);
106
+ });
107
+ it("[REQ-ANNOTATION-REQ-DETECTION] parentChainHasReq returns true when getCommentsBefore returns comments containing @req", () => {
108
+ const sourceCode = {
109
+ getCommentsBefore(n) {
110
+ if (n && n.isTargetParent) {
111
+ return [{ value: "/* @req REQ-FROM-PARENT */" }];
112
+ }
113
+ return [];
114
+ },
115
+ };
116
+ const context = {
117
+ getSourceCode() {
118
+ return sourceCode;
119
+ },
120
+ };
121
+ const node = {
122
+ parent: {
123
+ isTargetParent: true,
124
+ parent: {},
125
+ },
126
+ };
127
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], context, node);
128
+ expect(has).toBe(true);
129
+ });
130
+ it("[REQ-ANNOTATION-REQ-DETECTION] fallbackTextBeforeHasReq returns false when getText is not a function", () => {
131
+ const context = {
132
+ getSourceCode() {
133
+ return {
134
+ // getText is not a function
135
+ getText: "not-a-function",
136
+ };
137
+ },
138
+ };
139
+ const node = {
140
+ range: [0, 10],
141
+ parent: {},
142
+ };
143
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], context, node);
144
+ expect(has).toBe(false);
145
+ });
146
+ it("[REQ-ANNOTATION-REQ-DETECTION] fallbackTextBeforeHasReq returns false when node.range is not an array", () => {
147
+ const context = {
148
+ getSourceCode() {
149
+ return createMockSourceCode({ text: "/* @req REQ-IN-TEXT */" });
150
+ },
151
+ };
152
+ const node = {
153
+ // range is missing; helper should see non-array range and return false
154
+ range: null,
155
+ parent: {},
156
+ };
157
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], context, node);
158
+ expect(has).toBe(false);
159
+ });
160
+ it("[REQ-ANNOTATION-REQ-DETECTION] fallbackTextBeforeHasReq returns true when text window contains @req", () => {
161
+ const fullText = `
162
+ // some header
163
+ /** @req REQ-IN-TEXT-WINDOW */
164
+ function foo() {}
165
+ `;
166
+ const context = {
167
+ getSourceCode() {
168
+ return createMockSourceCode({ text: fullText });
169
+ },
170
+ };
171
+ // Choose a range that starts after the @req comment so the "text before"
172
+ // window that the helper inspects includes the annotation.
173
+ const startIndex = fullText.indexOf("function foo");
174
+ const node = {
175
+ range: [startIndex, startIndex + 5],
176
+ parent: {},
177
+ };
178
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], context, node);
179
+ expect(has).toBe(true);
180
+ });
181
+ it("[REQ-ANNOTATION-REQ-DETECTION] fallbackTextBeforeHasReq returns false when getText throws", () => {
182
+ const context = {
183
+ getSourceCode() {
184
+ return {
185
+ getText() {
186
+ throw new Error("boom from getText");
187
+ },
188
+ };
189
+ },
190
+ };
191
+ const node = {
192
+ range: [0, 10],
193
+ parent: {},
194
+ };
195
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], context, node);
196
+ expect(has).toBe(false);
197
+ });
198
+ it("[REQ-ANNOTATION-REQ-DETECTION] hasReqInAdvancedHeuristics short-circuits and returns false when sourceCode is missing", () => {
199
+ const context = {
200
+ // No getSourceCode method at all – internal advanced heuristics
201
+ // should immediately return false and not throw.
202
+ };
203
+ const node = {
204
+ loc: { start: { line: 3 } },
205
+ range: [0, 10],
206
+ parent: {},
207
+ };
208
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], context, node);
209
+ expect(has).toBe(false);
210
+ });
211
+ it("[REQ-ANNOTATION-REQ-DETECTION] hasReqInAdvancedHeuristics short-circuits and returns false when node is missing", () => {
212
+ const context = {
213
+ getSourceCode() {
214
+ return createMockSourceCode({ text: "@req REQ-SHOULD-NOT-BE-SEEN" });
215
+ },
216
+ };
217
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(null, [], context, undefined);
218
+ expect(has).toBe(false);
219
+ });
220
+ it("[REQ-ANNOTATION-REQ-DETECTION] hasReqAnnotation returns true when jsdoc contains @supports and advanced heuristics are false", () => {
221
+ const context = {
222
+ getSourceCode() {
223
+ // Returning a sourceCode that will not satisfy any advanced heuristic
224
+ // (no lines, no comments, empty text).
225
+ return createMockSourceCode({ lines: [], text: "" });
226
+ },
227
+ };
228
+ const jsdoc = {
229
+ value: "/** @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-JSDOC-SUPPORTS */",
230
+ };
231
+ const node = {
232
+ parent: {},
233
+ };
234
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(jsdoc, [], context, node);
235
+ expect(has).toBe(true);
236
+ });
237
+ it("[REQ-ANNOTATION-REQ-DETECTION] falls back to jsdoc/comments when context.getSourceCode throws", () => {
238
+ const context = {
239
+ getSourceCode() {
240
+ throw new Error("boom from getSourceCode");
241
+ },
242
+ };
243
+ const jsdoc = { value: "/** @req REQ-FROM-GETSOURCECODE */" };
244
+ const has = (0, reqAnnotationDetection_1.hasReqAnnotation)(jsdoc, [], context, { parent: {} });
245
+ expect(has).toBe(true);
246
+ });
247
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.11.2",
3
+ "version": "1.11.4",
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",
@@ -41,7 +41,7 @@
41
41
  "safety:deps": "node scripts/ci-safety-deps.js",
42
42
  "audit:ci": "node scripts/ci-audit.js",
43
43
  "check:ci-artifacts": "node scripts/check-no-tracked-ci-artifacts.js",
44
- "security:secrets": "secretlint \"**/*\" --no-color",
44
+ "security:secrets": "secretlint \"**/*\"",
45
45
  "smoke-test": "./scripts/smoke-test.sh",
46
46
  "debug:cli": "node scripts/cli-debug.js",
47
47
  "debug:require-story": "node scripts/debug-require-story.js",
@@ -99,7 +99,7 @@
99
99
  "eslint": "^9.0.0"
100
100
  },
101
101
  "engines": {
102
- "node": ">=18.18.0"
102
+ "node": "^18.18.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
103
103
  },
104
104
  "overrides": {
105
105
  "glob": "12.0.0",
@@ -15,7 +15,7 @@ In addition to the core `@story` and `@req` annotations, the plugin also underst
15
15
  `@supports docs/stories/010.0-PAYMENTS.story.md#REQ-PAYMENTS-REFUND`
16
16
  to indicate that a given function supports a particular requirement from a payments story document within that project’s own `docs/stories` tree. For a detailed explanation of `@supports` behavior and validation, see [Migration Guide](migration-guide.md) (section **3.1 Multi-story @supports annotations**). Additional background on multi-story semantics is available in the project’s internal rule documentation, which is intended for maintainers rather than end users.
17
17
 
18
- The `prefer-implements-annotation` rule is an **opt-in migration helper** that is disabled by default and **not** part of any built-in preset. It can be enabled and given a severity like `"warn"` or `"error"` using normal ESLint rule configuration when you want to gradually encourage multi-story `@supports` usage. Detailed behavior and migration guidance are documented in the project’s internal rule documentation, which is targeted at maintainers; typical end users can rely on the high-level guidance in this API reference and the [Migration Guide](migration-guide.md).
18
+ The `prefer-supports-annotation` rule is an **opt-in migration helper** that is disabled by default and **not** part of any built-in preset. It can be enabled and given a severity like `"warn"` or `"error"` using normal ESLint rule configuration when you want to gradually encourage multi-story `@supports` usage. The legacy rule key `traceability/prefer-implements-annotation` remains available as a **deprecated alias** for backward compatibility, but new configurations should prefer `traceability/prefer-supports-annotation`. Detailed behavior and migration guidance are documented in the project’s internal rule documentation, which is targeted at maintainers; typical end users can rely on the high-level guidance in this API reference and the [Migration Guide](migration-guide.md).
19
19
 
20
20
  ### traceability/require-story-annotation
21
21
 
@@ -68,11 +68,17 @@ function initAuth() {
68
68
 
69
69
  ### traceability/require-branch-annotation
70
70
 
71
- Description: Ensures significant code branches (if/else, loops, switch cases, try/catch) have both `@story` and `@req` annotations in preceding comments.
71
+ Description: Ensures significant code branches (if/else, loops, switch cases, try/catch) have both `@story` and `@req` annotations in preceding comments. For `catch` clauses specifically, the rule accepts annotations either immediately before the `catch` keyword or as the first comment-only lines inside the catch block; for `else if` branches, the rule accepts annotations either immediately before the `else if` keyword or on comment-only lines between the `else if (condition)` and the first statement of the consequent body, matching Prettier’s wrapped style.
72
+
72
73
  Options:
73
74
 
74
75
  - `branchTypes` (string[], optional) – AST node types that are treated as significant branches for annotation enforcement. Allowed values: "IfStatement", "SwitchCase", "TryStatement", "CatchClause", "ForStatement", "ForOfStatement", "ForInStatement", "WhileStatement", "DoWhileStatement". Default: ["IfStatement", "SwitchCase", "TryStatement", "CatchClause", "ForStatement", "ForOfStatement", "ForInStatement", "WhileStatement", "DoWhileStatement"]. If an invalid branch type is provided, the rule reports a configuration error for each invalid value.
75
76
 
77
+ Behavior notes:
78
+
79
+ - When both before-`catch` and inside-block annotations are present for the same catch clause, the comments immediately before `catch` take precedence for validation and reporting.
80
+ - When auto-fixing missing annotations on a catch clause, the rule inserts placeholder comments inside the catch body so that formatters like Prettier preserve them and do not move them to unexpected locations.
81
+
76
82
  Default Severity: `error`
77
83
  Example:
78
84
 
@@ -246,9 +252,9 @@ describe("Refunds flow docs/stories/010.0-PAYMENTS.story.md", () => {
246
252
  });
247
253
  ```
248
254
 
249
- ### traceability/prefer-implements-annotation
255
+ ### traceability/prefer-supports-annotation
250
256
 
251
- Description: An optional, opt-in migration helper that encourages converting legacy single‑story `@story` + `@req` JSDoc blocks into the newer multi‑story `@supports` format. The rule is **disabled by default** and is **not included in any built‑in preset**; you enable it explicitly and control its behavior entirely via ESLint severity (`"off" | "warn" | "error"`). It does not change what the core rules consider valid—it only adds migration recommendations and safe auto‑fixes on top of existing validation.
257
+ Description: An optional, opt-in migration helper that encourages converting legacy single‑story `@story` + `@req` JSDoc blocks into the newer multi‑story `@supports` format. The rule is **disabled by default** and is **not included in any built‑in preset**; you enable it explicitly and control its behavior entirely via ESLint severity (`"off" | "warn" | "error"`). It does not change what the core rules consider valid—it only adds migration recommendations and safe auto‑fixes on top of existing validation. The legacy rule key `traceability/prefer-implements-annotation` is still recognized as a **deprecated alias** for `traceability/prefer-supports-annotation` so that existing configurations continue to work unchanged.
252
258
 
253
259
  Options: None – this rule does not accept a configuration object. All tuning is done via the ESLint rule level (`"off"`, `"warn"`, `"error"`).
254
260
 
@@ -318,7 +324,7 @@ function issueRefund() {
318
324
  }
319
325
  ```
320
326
 
321
- With `traceability/prefer-implements-annotation` enabled and ESLint run with `--fix`, the rule rewrites it to:
327
+ With `traceability/prefer-supports-annotation` enabled (or its deprecated alias `traceability/prefer-implements-annotation`) and ESLint run with `--fix`, the rule rewrites it to:
322
328
 
323
329
  ```js
324
330
  /**
@@ -343,7 +349,9 @@ export default [
343
349
  traceability.configs.recommended,
344
350
  {
345
351
  rules: {
346
- "traceability/prefer-implements-annotation": "warn",
352
+ "traceability/prefer-supports-annotation": "warn",
353
+ // The deprecated alias is still honored if you prefer:
354
+ // "traceability/prefer-implements-annotation": "warn",
347
355
  },
348
356
  },
349
357
  ];
@@ -358,7 +366,7 @@ The plugin provides two built-in presets for easy configuration:
358
366
  Enables the **six core traceability rules** with severities tuned for common usage (most at `error`, with
359
367
  `traceability/valid-annotation-format` at `warn` to reduce noise). This `warn` level for `traceability/valid-annotation-format` is intentional to keep early adoption noise low, but you can safely raise it to `error` in projects that want strict enforcement of annotation formatting.
360
368
 
361
- The `prefer-implements-annotation` migration rule is **not included** in this (or any) preset and remains disabled by default. If you want to encourage or enforce multi-story `@supports` annotations, you must enable `traceability/prefer-implements-annotation` explicitly in your ESLint configuration and choose an appropriate severity (for example, `"warn"` during migration or `"error"` once fully adopted).
369
+ The `prefer-supports-annotation` migration rule (and its deprecated alias key `traceability/prefer-implements-annotation`) is **not included** in this (or any) preset and remains disabled by default. If you want to encourage or enforce multi-story `@supports` annotations, you must enable `traceability/prefer-supports-annotation` explicitly in your ESLint configuration and choose an appropriate severity (for example, `"warn"` during migration or `"error"` once fully adopted).
362
370
 
363
371
  Core rules enabled by the `recommended` preset:
364
372
 
@@ -381,7 +389,7 @@ export default [js.configs.recommended, traceability.configs.recommended];
381
389
 
382
390
  ### strict
383
391
 
384
- Currently mirrors the **recommended** preset, reserved for future stricter policies. As with the `recommended` preset, the `traceability/prefer-implements-annotation` rule is **not** enabled here by default and must be configured manually if desired.
392
+ Currently mirrors the **recommended** preset, reserved for future stricter policies. As with the `recommended` preset, the `traceability/prefer-supports-annotation` rule is **not** enabled here by default, and the deprecated alias key `traceability/prefer-implements-annotation` continues to be honored only when you opt into it manually in your own configuration.
385
393
 
386
394
  Usage:
387
395
 
@@ -681,5 +689,4 @@ If `--from` or `--to` is missing, the CLI prints an error, shows the help text,
681
689
  In CI:
682
690
 
683
691
  ```bash
684
- npm run traceability:verify
685
- ```
692
+ npm run traceability:verify
@@ -57,18 +57,22 @@ function integrate() {}
57
57
 
58
58
  You **do not** need to change existing, single-story annotations that already use `@story` and `@req`. Migration to `@supports` is only recommended when a function or module genuinely implements requirements from more than one story file.
59
59
 
60
- #### Optional `prefer-implements-annotation` migration rule
60
+ #### Optional `prefer-supports-annotation` migration rule
61
61
 
62
- For teams that want to gradually migrate from `@story` + `@req` to `@supports`, the plugin provides an optional rule: `traceability/prefer-implements-annotation`.
62
+ For teams that want to gradually migrate from `@story` + `@req` to `@supports`, the plugin provides an optional rule: `traceability/prefer-supports-annotation`.
63
63
 
64
- - This rule is **disabled by default** and is **not** included in any built-in presets.
64
+ - This is the canonical rule name starting in v1.x.
65
+ - The legacy key `traceability/prefer-implements-annotation` remains supported as a **deprecated alias** for backward compatibility, but should not be used in new configurations.
66
+
67
+ - This rule is **disabled by default** and is **not** included in any built-in presets (the deprecated alias is also not enabled by any presets).
65
68
  - You can enable it with any standard ESLint severity (`"off"`, `"warn"`, or `"error"`) in your config, for example:
66
69
 
67
70
  ```js
68
71
  // excerpt from eslint.config.js
69
72
  {
70
73
  rules: {
71
- "traceability/prefer-implements-annotation": "warn",
74
+ "traceability/prefer-supports-annotation": "warn",
75
+ // "traceability/prefer-implements-annotation": "warn", // deprecated alias
72
76
  },
73
77
  }
74
78
  ```
@@ -101,7 +105,7 @@ Aligned with the internal rule behavior, the key cases are:
101
105
  - Comments that contain only `@supports` lines, and
102
106
  - Line comments such as `// @story ...`.
103
107
 
104
- These forms are still supported by the plugin and are not modified by `traceability/prefer-implements-annotation`.
108
+ These forms are still supported by the plugin and are not modified by `traceability/prefer-supports-annotation`.
105
109
 
106
110
  A typical migration path is:
107
111