eslint-plugin-traceability 1.19.2 → 1.19.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.
@@ -45,14 +45,12 @@ describe("validateBranchTypes helper (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () =
45
45
  });
46
46
  });
47
47
  it("should gather SwitchCase comment text via gatherBranchCommentText (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () => {
48
- // Fake SourceCode-like object with lines aligned to PRE_COMMENT_OFFSET logic
49
48
  const sourceCode = {
50
- lines: [
51
- "// @story first part",
52
- "// continuation second part",
53
- "case 1:",
54
- ],
55
- getCommentsBefore: () => [],
49
+ lines: [],
50
+ getCommentsBefore: jest.fn().mockReturnValue([
51
+ { value: "@story first part" },
52
+ { value: "@req REQ-FIRST" },
53
+ ]),
56
54
  getText: jest.fn(),
57
55
  };
58
56
  // SwitchCase-like node with loc.start.line corresponding to "case 1:" line (line 3)
@@ -64,8 +62,7 @@ describe("validateBranchTypes helper (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () =
64
62
  },
65
63
  };
66
64
  const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, switchCaseNode);
67
- // Expect combined text using space separator and preserving leading //
68
- expect(text).toBe("// @story first part // continuation second part");
65
+ expect(text).toBe("@story first part @req REQ-FIRST");
69
66
  });
70
67
  it("should gather comment text for CatchClause and loop nodes via gatherBranchCommentText (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () => {
71
68
  // CatchClause: comments come from getCommentsBefore when beforeText already contains @story
@@ -191,6 +188,50 @@ describe("validateBranchTypes helper (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () =
191
188
  expect(insideText).toContain("@req REQ-CATCH-INSIDE");
192
189
  expect(insideText).not.toContain("before-catch should be ignored");
193
190
  });
191
+ it("[REQ-INSIDE-BRACE-PLACEMENT][REQ-PLACEMENT-CONFIG] uses inside-switch comments when annotationPlacement is 'inside' and ignores before-case annotations", () => {
192
+ const sourceCode = {
193
+ lines: [
194
+ "// @story before-switch should be ignored in inside mode",
195
+ "switch (value) {",
196
+ " case 'a': {",
197
+ " // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md",
198
+ " // @req REQ-SWITCH-INSIDE",
199
+ " doSomething();",
200
+ " }",
201
+ "}",
202
+ ],
203
+ getCommentsBefore: jest
204
+ .fn()
205
+ .mockReturnValue([
206
+ { value: "@story before-switch should be ignored in inside mode" },
207
+ ]),
208
+ };
209
+ const switchCaseNode = {
210
+ type: "SwitchCase",
211
+ loc: {
212
+ start: { line: 3, column: 2 },
213
+ end: { line: 7, column: 4 },
214
+ },
215
+ consequent: [
216
+ {
217
+ type: "BlockStatement",
218
+ loc: {
219
+ start: { line: 3, column: 16 },
220
+ end: { line: 7, column: 4 },
221
+ },
222
+ },
223
+ ],
224
+ };
225
+ const parent = {
226
+ type: "SwitchStatement",
227
+ discriminant: { type: "Identifier", name: "value" },
228
+ cases: [switchCaseNode],
229
+ };
230
+ const insideText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, switchCaseNode, parent, "inside");
231
+ expect(insideText).toContain("@story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md");
232
+ expect(insideText).toContain("@req REQ-SWITCH-INSIDE");
233
+ expect(insideText).not.toContain("before-switch should be ignored");
234
+ });
194
235
  });
195
236
  /**
196
237
  * Tests for annotationPlacement wiring at helper level
@@ -311,4 +352,110 @@ describe("gatherBranchCommentText annotationPlacement wiring (Story 028.0-DEV-AN
311
352
  expect(insideText).not.toContain("REQ-BEFORE-ELSE");
312
353
  expect(insideText).not.toContain("docs/stories/026.0-DEV-BRANCH-ANNOTATIONS-ELSE-BRANCHES.story.md");
313
354
  });
355
+ it("[REQ-PLACEMENT-CONFIG][REQ-DEFAULT-BACKWARD-COMPAT] honors configured placement for TryStatement branches in try/finally patterns", () => {
356
+ const sourceCode = {
357
+ lines: [
358
+ "function demoTry() {", // 1
359
+ " // @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md", // 2 (before try)
360
+ " // @req REQ-BEFORE-TRY", // 3
361
+ " try {", // 4
362
+ " // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md", // 5 (inside try)
363
+ " // @req REQ-TRY-INSIDE", // 6
364
+ " doWork();", // 7
365
+ " } finally {", // 8
366
+ " cleanup();", // 9
367
+ " }", // 10
368
+ "}", // 11
369
+ ],
370
+ getCommentsBefore: jest.fn().mockImplementation((node) => {
371
+ if (node && node.loc && node.loc.start && node.loc.start.line === 4) {
372
+ return [
373
+ {
374
+ value: "@story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md",
375
+ },
376
+ { value: "@req REQ-BEFORE-TRY" },
377
+ ];
378
+ }
379
+ return [];
380
+ }),
381
+ };
382
+ const tryNode = {
383
+ type: "TryStatement",
384
+ loc: {
385
+ start: { line: 4, column: 2 },
386
+ end: { line: 9, column: 3 },
387
+ },
388
+ block: {
389
+ type: "BlockStatement",
390
+ loc: {
391
+ start: { line: 4, column: 8 },
392
+ end: { line: 7, column: 3 },
393
+ },
394
+ },
395
+ handler: null,
396
+ finalizer: {
397
+ type: "BlockStatement",
398
+ loc: {
399
+ start: { line: 8, column: 12 },
400
+ end: { line: 9, column: 3 },
401
+ },
402
+ },
403
+ };
404
+ const parent = {
405
+ type: "BlockStatement",
406
+ body: [tryNode],
407
+ };
408
+ const beforeText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, tryNode, parent, "before");
409
+ expect(beforeText).toContain("@story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md");
410
+ expect(beforeText).toContain("@req REQ-BEFORE-TRY");
411
+ expect(beforeText).not.toContain("REQ-TRY-INSIDE");
412
+ const insideText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, tryNode, parent, "inside");
413
+ expect(insideText).toContain("@story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md");
414
+ expect(insideText).toContain("@req REQ-TRY-INSIDE");
415
+ expect(insideText).not.toContain("REQ-BEFORE-TRY");
416
+ });
417
+ it("[REQ-PLACEMENT-CONFIG][REQ-DEFAULT-BACKWARD-COMPAT] honors before-case annotations for SwitchCase in default placement mode", () => {
418
+ const sourceCode = {
419
+ lines: [
420
+ "// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md",
421
+ "// @req REQ-SWITCH-BEFORE",
422
+ "switch (value) {",
423
+ " case 'a':",
424
+ " doSomething();",
425
+ "}",
426
+ ],
427
+ getCommentsBefore: jest
428
+ .fn()
429
+ .mockReturnValue([
430
+ {
431
+ value: "@story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md",
432
+ },
433
+ { value: "@req REQ-SWITCH-BEFORE" },
434
+ ]),
435
+ };
436
+ const switchCaseNode = {
437
+ type: "SwitchCase",
438
+ loc: {
439
+ start: { line: 4, column: 2 },
440
+ end: { line: 5, column: 18 },
441
+ },
442
+ consequent: [
443
+ {
444
+ type: "ExpressionStatement",
445
+ loc: {
446
+ start: { line: 5, column: 4 },
447
+ end: { line: 5, column: 18 },
448
+ },
449
+ },
450
+ ],
451
+ };
452
+ const parent = {
453
+ type: "SwitchStatement",
454
+ discriminant: { type: "Identifier", name: "value" },
455
+ cases: [switchCaseNode],
456
+ };
457
+ const beforeText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, switchCaseNode, parent, "before");
458
+ expect(beforeText).toContain("@story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md");
459
+ expect(beforeText).toContain("@req REQ-SWITCH-BEFORE");
460
+ });
314
461
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.19.2",
3
+ "version": "1.19.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",
@@ -86,13 +86,14 @@ function initAuth() {
86
86
 
87
87
  ### traceability/require-branch-annotation
88
88
 
89
- Description: Ensures significant code branches (if/else chains, loops, switch cases, try/catch) have traceability coverage, typically via a single `@supports` line, while still accepting legacy `@story` and `@req` annotations in nearby comments. When you adopt multi-story `@supports` annotations, a single `@supports <storyPath> <REQ-ID>...` line placed in any of the valid branch comment locations is treated as satisfying both the story and requirement presence checks for that branch, while detailed format validation of the `@supports` value (including story paths and requirement IDs) continues to be handled by `traceability/valid-annotation-format`, `traceability/valid-story-reference`, and `traceability/valid-req-reference`.
89
+ Description: Ensures significant code branches (if/else chains, loops, switch cases, try/catch) have traceability coverage, typically via a single `@supports` line, while still accepting legacy `@story` and `@req` annotations in nearby comments. When you adopt multi-story `@supports` annotations, a single `@supports <storyPath> <REQ-ID>...` line placed in any of the valid branch comment locations is treated as satisfying both the story and requirement presence checks for that branch, while detailed format validation of the `@supports` value (including story paths and requirement IDs) continues to be handled by `traceability/valid-annotation-format`, `traceability/valid-story-reference`, and `traceability/valid-req-reference`. The rule supports a configurable placement mode for branch annotations via `annotationPlacement: "before" | "inside"`, defaulting to `"before"` for backward compatibility.
90
90
 
91
91
  For most branches, the rule looks for annotations in comments immediately preceding the branch keyword (for example, the line above an `if` or `for` statement). For `catch` clauses and `else if` branches, the rule is formatter-aware and accepts annotations in additional positions that common formatters like Prettier use when they reflow code.
92
92
 
93
93
  Options:
94
94
 
95
95
  - `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.
96
+ - `annotationPlacement` ("before" | "inside", optional) – Controls whether the rule looks for annotations immediately before branches (`"before"`, the default and backward-compatible behavior) or requires annotations as the first comment lines inside branch bodies where supported (`"inside"`). When set to `"inside"`, the rule prefers comments at the top of the block bodies for simple `if`/`else if` branches, loops, `catch` clauses, and `try` blocks, while continuing to treat any existing before-branch comments as diagnostics in that mode.
96
97
 
97
98
  Behavior notes:
98
99
 
@@ -109,6 +110,11 @@ Behavior notes:
109
110
  - When annotations appear in more than one of these locations, the rule prefers the comments immediately before the `else if` line, then comments between the condition and the block, and finally comments inside the block body. This precedence is designed to closely mirror real-world formatter behavior and matches the formatter-aware scenarios described in stories 025.0 and 026.0.
110
111
  - When auto-fixing missing annotations on an `else if` branch, the rule inserts placeholder comments as the first comment-only line inside the consequent block body (just after the opening `{`), which is a stable location under Prettier and similar formatters. As with catch clauses, a single `@supports` annotation placed in any of these accepted locations is treated as equivalent to having both `@story` and `@req` comments for that branch, with deep format and existence checks delegated to the other validation rules.
111
112
 
113
+ Placement modes:
114
+
115
+ - `"before"` mode preserves the existing semantics described above, including the dual-position behavior for `catch` and `else if` branches where comments immediately before the branch and the first comment-only lines inside the block are both acceptable and validated according to their existing precedence rules.
116
+ - `"inside"` mode standardizes on the first comment-only lines inside supported branch blocks (`if`/`else if`, loops, `catch`, and `try`) for validation and auto-fix insertion. This placement is designed to work well with Prettier and other formatters, while the current implementation still treats many before-branch annotations as needing migration and may, in corner cases where it cannot confidently rewrite placement, insert new placeholder comments above the branch rather than moving existing ones.
117
+
112
118
  For a concrete illustration of how these rules interact with Prettier, see the formatter-aware if/else/else-if example in [user-docs/examples.md](examples.md) (section **6. Branch annotations with if/else/else-if and Prettier**), which shows both the hand-written and formatted code that the rule considers valid.
113
119
 
114
120
  These behaviors are intentionally limited to `catch` clauses and `else if` branches; other branch types (plain `if`, `else`, loops, and `switch` cases) continue to use the simpler "comments immediately before the branch" association model for both validation and auto-fix placement.
@@ -354,6 +354,46 @@ The full API reference documents all options, but the most important knobs for m
354
354
 
355
355
  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.
356
356
 
357
+ ### 3.4 Inside-brace branch annotation placement (optional)
358
+
359
+ Story 028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION introduces an **inside-brace** placement standard for branch annotations. Instead of placing annotations directly above a branch, you can configure `traceability/require-branch-annotation` to look for annotations as the first comment-only lines **inside** each block body.
360
+
361
+ The feature is controlled by the `annotationPlacement` option on `require-branch-annotation`:
362
+
363
+ ```js
364
+ // eslint.config.js (flat config example)
365
+ import traceability from "eslint-plugin-traceability";
366
+
367
+ export default [
368
+ traceability.configs.recommended,
369
+ {
370
+ rules: {
371
+ "traceability/require-branch-annotation": [
372
+ "error",
373
+ {
374
+ annotationPlacement: "inside", // "before" (default) or "inside"
375
+ },
376
+ ],
377
+ },
378
+ },
379
+ ];
380
+ ```
381
+
382
+ With `annotationPlacement: "inside"`, the rule expects annotations in these locations:
383
+
384
+ - `if` / `else if` / `else`: first comment-only lines inside the `{ ... }` block.
385
+ - Loops: first comment-only lines inside the loop body.
386
+ - `try` / `catch` / `finally`: first comment-only lines inside the corresponding block body.
387
+ - `switch` cases: first comment-only lines inside the `case` body when it is a block (`case 'a': { ... }`).
388
+
389
+ Before-brace annotations are still honored when you leave `annotationPlacement` at the default value (`"before"`), so you can migrate gradually:
390
+
391
+ 1. **Start in default mode** — keep `annotationPlacement` unspecified (or set to `"before"`) and continue using your existing `// @story` / `// @req` comments above branches.
392
+ 2. **Introduce inside-brace style for new code** — when adding or refactoring branches, place annotations on the first comment-only line inside the block body. This layout plays nicely with Prettier and is what the rule’s auto-fix uses for `if`/`else if` and similar branches.
393
+ 3. **Opt-in to `annotationPlacement: "inside"`** — once your codebase is mostly using inside-brace annotations, enable the option. Branches that still rely only on before-brace comments will be reported as missing annotations in inside mode, and the rule’s autofix can insert placeholders at the correct inside location to help you complete the migration.
394
+
395
+ The default configuration in 1.x keeps `annotationPlacement` at `"before"` for backward compatibility, so existing projects do not need to change anything unless they want the new inside-brace behavior.
396
+
357
397
  ## 4. Test and Validate
358
398
 
359
399
  Run your test suite to confirm everything passes: