eslint-plugin-traceability 1.19.2 → 1.19.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.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,9 @@
1
- ## [1.19.2](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.19.1...v1.19.2) (2025-12-18)
1
+ ## [1.19.3](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.19.2...v1.19.3) (2025-12-18)
2
2
 
3
3
 
4
4
  ### Bug Fixes
5
5
 
6
- * honor annotationPlacement for else-if branches and refactor helpers ([8f747dc](https://github.com/voder-ai/eslint-plugin-traceability/commit/8f747dc8546ae8e5033cfe4aa9c394ca668a45d5))
6
+ * support inside placement for switch cases in branch helpers ([3fd08d1](https://github.com/voder-ai/eslint-plugin-traceability/commit/3fd08d1314085a7aa9894f6248dbb7f42a07bda0))
7
7
 
8
8
  # Changelog
9
9
 
@@ -10,7 +10,7 @@ const branch_annotation_report_helpers_1 = require("./branch-annotation-report-h
10
10
  Object.defineProperty(exports, "reportMissingAnnotations", { enumerable: true, get: function () { return branch_annotation_report_helpers_1.reportMissingAnnotations; } });
11
11
  const branch_annotation_loop_helpers_1 = require("./branch-annotation-loop-helpers");
12
12
  const branch_annotation_if_helpers_1 = require("./branch-annotation-if-helpers");
13
- const PRE_COMMENT_OFFSET = 2; // number of lines above branch to inspect for comments
13
+ const branch_annotation_switch_helpers_1 = require("./branch-annotation-switch-helpers");
14
14
  /**
15
15
  * Valid branch types for require-branch-annotation rule.
16
16
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
@@ -179,6 +179,30 @@ function getInsideCatchCommentText(sourceCode, node) {
179
179
  }
180
180
  return "";
181
181
  }
182
+ /**
183
+ * Gather comment text from the first contiguous comment lines inside a TryStatement block body.
184
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-INSIDE-BRACE-PLACEMENT REQ-PLACEMENT-CONFIG
185
+ */
186
+ function getInsideTryBlockCommentText(sourceCode, node) {
187
+ const block = node && node.block;
188
+ if (!block ||
189
+ block.type !== "BlockStatement" ||
190
+ !block.loc ||
191
+ !block.loc.start ||
192
+ !block.loc.end ||
193
+ typeof block.loc.start.line !== "number" ||
194
+ typeof block.loc.end.line !== "number") {
195
+ return "";
196
+ }
197
+ const lines = sourceCode.lines;
198
+ const startIndex = block.loc.start.line - 1;
199
+ const endIndex = block.loc.end.line - 1;
200
+ const insideText = scanCommentLinesInRange(lines, startIndex + 1, endIndex);
201
+ if (insideText) {
202
+ return insideText;
203
+ }
204
+ return "";
205
+ }
182
206
  /**
183
207
  * Gather annotation text for CatchClause nodes, supporting both before-catch and inside-catch positions.
184
208
  * @story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md
@@ -253,17 +277,33 @@ function gatherSimpleIfCommentText(sourceCode, node, annotationPlacement, before
253
277
  }
254
278
  return "";
255
279
  }
256
- /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
257
- function gatherSwitchCaseCommentText(sourceCode, node) {
258
- const lines = sourceCode.lines;
259
- const startLine = node.loc.start.line;
260
- let i = startLine - PRE_COMMENT_OFFSET;
261
- const comments = [];
262
- while (i >= 0 && /^\s*(\/\/|\/\*)/.test(lines[i])) {
263
- comments.unshift(lines[i].trim());
264
- i--;
280
+ function handleTryCatchBranch(sourceCode, node, context) {
281
+ const { annotationPlacement, beforeText } = context;
282
+ if (node.type === "TryStatement") {
283
+ if (annotationPlacement === "inside") {
284
+ const insideText = getInsideTryBlockCommentText(sourceCode, node);
285
+ if (insideText) {
286
+ return insideText;
287
+ }
288
+ return "";
289
+ }
290
+ return beforeText;
265
291
  }
266
- return comments.join(" ");
292
+ if (node.type === "CatchClause") {
293
+ return gatherCatchClauseCommentText(sourceCode, node, annotationPlacement, beforeText);
294
+ }
295
+ return null;
296
+ }
297
+ function handleLoopBranch(sourceCode, node, context) {
298
+ const { annotationPlacement, beforeText } = context;
299
+ if (node.type === "ForStatement" ||
300
+ node.type === "ForInStatement" ||
301
+ node.type === "ForOfStatement" ||
302
+ node.type === "WhileStatement" ||
303
+ node.type === "DoWhileStatement") {
304
+ return (0, branch_annotation_loop_helpers_1.gatherLoopCommentText)(sourceCode, node, annotationPlacement, beforeText);
305
+ }
306
+ return null;
267
307
  }
268
308
  /**
269
309
  * Helper that gathers comment text for non-IfStatement branch types using
@@ -274,19 +314,17 @@ function gatherSwitchCaseCommentText(sourceCode, node) {
274
314
  * @supports REQ-PLACEMENT-CONFIG
275
315
  */
276
316
  function gatherNonIfBranchCommentText(sourceCode, node, context) {
277
- const { annotationPlacement, beforeText } = context;
278
317
  if (node.type === "SwitchCase") {
279
- return gatherSwitchCaseCommentText(sourceCode, node);
318
+ const { annotationPlacement, beforeText } = context;
319
+ return (0, branch_annotation_switch_helpers_1.gatherSwitchCaseCommentText)(sourceCode, node, annotationPlacement, beforeText);
280
320
  }
281
- if (node.type === "CatchClause") {
282
- return gatherCatchClauseCommentText(sourceCode, node, annotationPlacement, beforeText);
321
+ const tryCatchResult = handleTryCatchBranch(sourceCode, node, context);
322
+ if (tryCatchResult != null) {
323
+ return tryCatchResult;
283
324
  }
284
- if (node.type === "ForStatement" ||
285
- node.type === "ForInStatement" ||
286
- node.type === "ForOfStatement" ||
287
- node.type === "WhileStatement" ||
288
- node.type === "DoWhileStatement") {
289
- return (0, branch_annotation_loop_helpers_1.gatherLoopCommentText)(sourceCode, node, annotationPlacement, beforeText);
325
+ const loopResult = handleLoopBranch(sourceCode, node, context);
326
+ if (loopResult != null) {
327
+ return loopResult;
290
328
  }
291
329
  return null;
292
330
  }
@@ -0,0 +1,11 @@
1
+ import type { Rule } from "eslint";
2
+ import { type AnnotationPlacement } from "./branch-annotation-helpers";
3
+ /**
4
+ * Gather annotation text for SwitchCase branches, honoring the configured placement
5
+ * while preserving legacy before-branch behavior in the default mode.
6
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
7
+ * @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
8
+ * @supports REQ-PLACEMENT-CONFIG
9
+ * @supports REQ-INSIDE-BRACE-PLACEMENT
10
+ */
11
+ export declare function gatherSwitchCaseCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any, annotationPlacement: AnnotationPlacement, beforeText: string): string;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.gatherSwitchCaseCommentText = gatherSwitchCaseCommentText;
4
+ const branch_annotation_helpers_1 = require("./branch-annotation-helpers");
5
+ /**
6
+ * Gather comment text from the first contiguous comment lines "inside" a SwitchCase body.
7
+ * Prefers a BlockStatement consequent when present, with a fallback to the entire case range.
8
+ * @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
9
+ * @supports REQ-INSIDE-BRACE-PLACEMENT
10
+ * @supports REQ-PLACEMENT-CONFIG
11
+ */
12
+ function getInsideSwitchCaseCommentText(sourceCode, node) {
13
+ const lines = sourceCode.lines;
14
+ const firstConsequent = node.consequent && node.consequent[0];
15
+ if (firstConsequent &&
16
+ firstConsequent.type === "BlockStatement" &&
17
+ firstConsequent.loc &&
18
+ firstConsequent.loc.start &&
19
+ firstConsequent.loc.end &&
20
+ typeof firstConsequent.loc.start.line === "number" &&
21
+ typeof firstConsequent.loc.end.line === "number") {
22
+ const startIndex = firstConsequent.loc.start.line - 1;
23
+ const endIndex = firstConsequent.loc.end.line - 1;
24
+ const insideText = (0, branch_annotation_helpers_1.scanCommentLinesInRange)(lines, startIndex + 1, endIndex);
25
+ if (insideText) {
26
+ return insideText;
27
+ }
28
+ return "";
29
+ }
30
+ if (node.loc &&
31
+ node.loc.start &&
32
+ node.loc.end &&
33
+ typeof node.loc.start.line === "number" &&
34
+ typeof node.loc.end.line === "number") {
35
+ const startIndex = node.loc.start.line - 1;
36
+ const endIndex = node.loc.end.line - 1;
37
+ const insideText = (0, branch_annotation_helpers_1.scanCommentLinesInRange)(lines, startIndex + 1, endIndex);
38
+ if (insideText) {
39
+ return insideText;
40
+ }
41
+ }
42
+ return "";
43
+ }
44
+ /**
45
+ * Gather annotation text for SwitchCase branches, honoring the configured placement
46
+ * while preserving legacy before-branch behavior in the default mode.
47
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
48
+ * @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
49
+ * @supports REQ-PLACEMENT-CONFIG
50
+ * @supports REQ-INSIDE-BRACE-PLACEMENT
51
+ */
52
+ function gatherSwitchCaseCommentText(sourceCode, node, annotationPlacement, beforeText) {
53
+ if (annotationPlacement === "inside") {
54
+ const insideText = getInsideSwitchCaseCommentText(sourceCode, node);
55
+ if (insideText) {
56
+ return insideText;
57
+ }
58
+ return "";
59
+ }
60
+ if (/@story\b/.test(beforeText) ||
61
+ /@req\b/.test(beforeText) ||
62
+ /@supports\b/.test(beforeText)) {
63
+ return beforeText;
64
+ }
65
+ // In before-placement mode, rely on the caller's beforeText and any
66
+ // configured PRE_COMMENT_OFFSET logic in the main helpers module.
67
+ return beforeText;
68
+ }
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ /**
7
+ * Prettier integration tests for annotationPlacement: "inside" across multiple branch types.
8
+ * @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
9
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PRETTIER-STABLE REQ-INSIDE-BRACE-PLACEMENT REQ-PLACEMENT-CONFIG
10
+ */
11
+ const path_1 = __importDefault(require("path"));
12
+ const child_process_1 = require("child_process");
13
+ const prettier_test_helpers_1 = require("./prettier-test-helpers");
14
+ describe("annotationPlacement: 'inside' with Prettier (Story 028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION)", () => {
15
+ const eslintPkgDir = path_1.default.dirname(require.resolve("eslint/package.json"));
16
+ const eslintCliPath = path_1.default.join(eslintPkgDir, "bin", "eslint.js");
17
+ const configPath = path_1.default.resolve(__dirname, "../../eslint.config.js");
18
+ function buildInsidePlacementArgs(stdinFilename) {
19
+ return [
20
+ "--no-config-lookup",
21
+ "--config",
22
+ configPath,
23
+ "--stdin",
24
+ "--stdin-filename",
25
+ stdinFilename,
26
+ "--rule",
27
+ "no-unused-vars:off",
28
+ "--rule",
29
+ "no-magic-numbers:off",
30
+ "--rule",
31
+ "no-undef:off",
32
+ "--rule",
33
+ "no-console:off",
34
+ "--rule",
35
+ 'traceability/require-branch-annotation:["error",{"annotationPlacement":"inside"}]',
36
+ ];
37
+ }
38
+ function runEslintWithInsidePlacement(code, _filename) {
39
+ // Pin stdin filename to a tsconfig-included path to satisfy @typescript-eslint/parser's project lookup in these integration tests.
40
+ const args = buildInsidePlacementArgs("src/annotation-placement-inside.ts");
41
+ return (0, child_process_1.spawnSync)(process.execPath, [eslintCliPath, ...args], {
42
+ encoding: "utf-8",
43
+ input: code,
44
+ });
45
+ }
46
+ function formatWithPrettier(source) {
47
+ return (0, prettier_test_helpers_1.formatWithPrettier)(source, { parser: "typescript" });
48
+ }
49
+ it("[REQ-PRETTIER-STABLE][REQ-INSIDE-BRACE-PLACEMENT] accepts formatted code with inside-brace annotations for if/else and loops", () => {
50
+ const original = `
51
+ function demo(value: number) {
52
+ if (value > 0) {
53
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
54
+ // @req REQ-IF-INSIDE
55
+ console.log('positive');
56
+ } else if (value < 0) {
57
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
58
+ // @req REQ-ELSE-IF-INSIDE
59
+ console.log('negative');
60
+ } else {
61
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
62
+ // @req REQ-ELSE-INSIDE
63
+ console.log('zero');
64
+ }
65
+
66
+ for (const item of [1, 2, 3]) {
67
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
68
+ // @req REQ-LOOP-INSIDE
69
+ console.log(item);
70
+ }
71
+ }
72
+ `;
73
+ const formatted = formatWithPrettier(original);
74
+ const result = runEslintWithInsidePlacement(formatted, "annotation-placement-inside-if-loop.ts");
75
+ expect(result.stdout).not.toContain("traceability/require-branch-annotation");
76
+ expect([0, 1]).toContain(result.status);
77
+ });
78
+ it("[REQ-PRETTIER-STABLE][REQ-INSIDE-BRACE-PLACEMENT] accepts formatted code with inside-brace annotations for try/finally and catch", () => {
79
+ const original = `
80
+ function demoTry(flag: boolean) {
81
+ try {
82
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
83
+ // @req REQ-TRY-INSIDE
84
+ if (flag) {
85
+ throw new Error('boom');
86
+ }
87
+ } catch (error) {
88
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
89
+ // @req REQ-CATCH-INSIDE
90
+ console.error(error);
91
+ } finally {
92
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
93
+ // @req REQ-FINALLY-INSIDE
94
+ console.log('cleanup');
95
+ }
96
+ }
97
+ `;
98
+ const formatted = formatWithPrettier(original);
99
+ const result = runEslintWithInsidePlacement(formatted, "annotation-placement-inside-try.ts");
100
+ expect(result.stdout).not.toContain("traceability/require-branch-annotation");
101
+ expect([0, 1]).toContain(result.status);
102
+ });
103
+ it("[REQ-PRETTIER-STABLE][REQ-INSIDE-BRACE-PLACEMENT] accepts formatted code with inside-brace annotations for switch cases", () => {
104
+ const original = `
105
+ function demoSwitch(status: 'pending' | 'done' | 'other') {
106
+ switch (status) {
107
+ case 'pending': {
108
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
109
+ // @req REQ-SWITCH-PENDING-INSIDE
110
+ console.log('pending');
111
+ break;
112
+ }
113
+ case 'done': {
114
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
115
+ // @req REQ-SWITCH-DONE-INSIDE
116
+ console.log('done');
117
+ break;
118
+ }
119
+ default: {
120
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
121
+ // @req REQ-SWITCH-DEFAULT-INSIDE
122
+ console.log('other');
123
+ }
124
+ }
125
+ }
126
+ `;
127
+ const formatted = formatWithPrettier(original);
128
+ const result = runEslintWithInsidePlacement(formatted, "annotation-placement-inside-switch.ts");
129
+ expect(result.stdout).not.toContain("traceability/require-branch-annotation");
130
+ expect([0, 1]).toContain(result.status);
131
+ });
132
+ });
@@ -10,12 +10,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  */
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const child_process_1 = require("child_process");
13
+ const prettier_test_helpers_1 = require("./prettier-test-helpers");
13
14
  describe("CatchClause annotations with Prettier (Story 025.0-DEV-CATCH-ANNOTATION-POSITION)", () => {
14
15
  const eslintPkgDir = path_1.default.dirname(require.resolve("eslint/package.json"));
15
16
  const eslintCliPath = path_1.default.join(eslintPkgDir, "bin", "eslint.js");
16
17
  const configPath = path_1.default.resolve(__dirname, "../../eslint.config.js");
17
- const prettierPackageJson = require.resolve("prettier/package.json");
18
- const prettierCliPath = path_1.default.join(path_1.default.dirname(prettierPackageJson), "bin", "prettier.cjs");
19
18
  function runEslintWithRequireBranchAnnotation(code) {
20
19
  const args = [
21
20
  "--no-config-lookup",
@@ -40,16 +39,6 @@ describe("CatchClause annotations with Prettier (Story 025.0-DEV-CATCH-ANNOTATIO
40
39
  input: code,
41
40
  });
42
41
  }
43
- function formatWithPrettier(source) {
44
- const result = (0, child_process_1.spawnSync)(process.execPath, [prettierCliPath, "--parser", "typescript"], {
45
- encoding: "utf-8",
46
- input: source,
47
- });
48
- if (result.status !== 0) {
49
- throw new Error(`Prettier formatting failed: ${result.stderr || result.stdout}`);
50
- }
51
- return result.stdout;
52
- }
53
42
  it("[REQ-PRETTIER-COMPATIBILITY-BEFORE] accepts code where annotations start before catch but are moved inside by Prettier", () => {
54
43
  const original = `
55
44
  function doSomething() {
@@ -71,7 +60,7 @@ catch (error) {
71
60
  handleError(error);
72
61
  }
73
62
  `;
74
- const formatted = formatWithPrettier(original);
63
+ const formatted = (0, prettier_test_helpers_1.formatWithPrettier)(original);
75
64
  // Sanity check: Prettier should move the branch annotations inside the catch body.
76
65
  expect(formatted).toContain("catch (error) {");
77
66
  const catchIndex = formatted.indexOf("catch (error) {");
@@ -100,7 +89,7 @@ try {
100
89
  handleError(error);
101
90
  }
102
91
  `;
103
- const formatted = formatWithPrettier(original);
92
+ const formatted = (0, prettier_test_helpers_1.formatWithPrettier)(original);
104
93
  // Sanity: annotations should still be associated with the catch body after formatting.
105
94
  expect(formatted).toContain("catch (error) {");
106
95
  const catchIndex = formatted.indexOf("catch (error) {");
@@ -124,7 +113,7 @@ try {
124
113
  // @req REQ-CATCH-EMPTY
125
114
  }
126
115
  `;
127
- const formatted = formatWithPrettier(original);
116
+ const formatted = (0, prettier_test_helpers_1.formatWithPrettier)(original);
128
117
  const result = runEslintWithRequireBranchAnnotation(formatted);
129
118
  expect(result.status).toBe(0);
130
119
  });
@@ -10,12 +10,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  */
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const child_process_1 = require("child_process");
13
+ const prettier_test_helpers_1 = require("./prettier-test-helpers");
13
14
  describe("Else-if annotations with Prettier (Story 026.0-DEV-ELSE-IF-ANNOTATION-POSITION)", () => {
14
15
  const eslintPkgDir = path_1.default.dirname(require.resolve("eslint/package.json"));
15
16
  const eslintCliPath = path_1.default.join(eslintPkgDir, "bin", "eslint.js");
16
17
  const configPath = path_1.default.resolve(__dirname, "../../eslint.config.js");
17
- const prettierPackageJson = require.resolve("prettier/package.json");
18
- const prettierCliPath = path_1.default.join(path_1.default.dirname(prettierPackageJson), "bin", "prettier.cjs");
19
18
  function runEslintWithRequireBranchAnnotation(code) {
20
19
  const args = [
21
20
  "--no-config-lookup",
@@ -40,16 +39,6 @@ describe("Else-if annotations with Prettier (Story 026.0-DEV-ELSE-IF-ANNOTATION-
40
39
  input: code,
41
40
  });
42
41
  }
43
- function formatWithPrettier(source) {
44
- const result = (0, child_process_1.spawnSync)(process.execPath, [prettierCliPath, "--parser", "typescript"], {
45
- encoding: "utf-8",
46
- input: source,
47
- });
48
- if (result.status !== 0) {
49
- throw new Error(`Prettier formatting failed: ${result.stderr || result.stdout}`);
50
- }
51
- return result.stdout;
52
- }
53
42
  it("[REQ-PRETTIER-COMPATIBILITY-ELSE-IF-BEFORE] accepts code where annotations start before else-if but are moved between condition and body by Prettier", () => {
54
43
  const original = `
55
44
  function doA() {
@@ -71,7 +60,7 @@ else if (anotherVeryLongConditionThatForcesWrapping && someOtherCondition) {
71
60
  doB();
72
61
  }
73
62
  `;
74
- const formatted = formatWithPrettier(original);
63
+ const formatted = (0, prettier_test_helpers_1.formatWithPrettier)(original);
75
64
  // Sanity checks: Prettier should keep both the else-if branch and the associated story annotation,
76
65
  // but the exact layout and comment movement may vary between versions.
77
66
  expect(formatted).toContain("else if");
@@ -101,7 +90,7 @@ if (aVeryLongConditionThatForcesPrettierToWrapTheElseIfBranch && anotherConditio
101
90
  doB();
102
91
  }
103
92
  `;
104
- const formatted = formatWithPrettier(original);
93
+ const formatted = (0, prettier_test_helpers_1.formatWithPrettier)(original);
105
94
  // Note: Prettier's exact layout of the else-if and its comments may differ between versions;
106
95
  // the rule should accept any of the supported annotation positions regardless of formatting.
107
96
  const result = runEslintWithRequireBranchAnnotation(formatted);
@@ -0,0 +1,9 @@
1
+ interface FormatOptions {
2
+ parser?: "babel" | "typescript" | "babel-ts" | "espree" | string;
3
+ }
4
+ /**
5
+ * Format arbitrary source with Prettier using the installed CLI binary.
6
+ * Defaults to the TypeScript parser when none is provided.
7
+ */
8
+ export declare function formatWithPrettier(source: string, options?: FormatOptions): string;
9
+ export {};
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.formatWithPrettier = formatWithPrettier;
7
+ /**
8
+ * Shared helpers for Prettier-based integration tests.
9
+ * @story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md
10
+ * @story docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
11
+ * @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
12
+ * @supports docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md REQ-PRETTIER-COMPATIBILITY
13
+ * @supports docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md REQ-PRETTIER-AUTOFIX-ELSE-IF
14
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PRETTIER-STABLE
15
+ */
16
+ const path_1 = __importDefault(require("path"));
17
+ const child_process_1 = require("child_process");
18
+ /**
19
+ * Format arbitrary source with Prettier using the installed CLI binary.
20
+ * Defaults to the TypeScript parser when none is provided.
21
+ */
22
+ function formatWithPrettier(source, options = {}) {
23
+ const prettierPackageJson = require.resolve("prettier/package.json");
24
+ const prettierCliPath = path_1.default.join(path_1.default.dirname(prettierPackageJson), "bin", "prettier.cjs");
25
+ const parser = options.parser || "typescript";
26
+ const result = (0, child_process_1.spawnSync)(process.execPath, [prettierCliPath, "--parser", parser], {
27
+ encoding: "utf-8",
28
+ input: source,
29
+ });
30
+ if (result.status !== 0) {
31
+ throw new Error(`Prettier formatting failed: ${result.stderr || result.stdout}`);
32
+ }
33
+ return result.stdout;
34
+ }
@@ -107,6 +107,17 @@ catch (error) {
107
107
  handleError(error);
108
108
  }`,
109
109
  },
110
+ {
111
+ name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-PLACEMENT-CONFIG] try block annotated inside body under annotationPlacement: 'inside' (Story 028.0)",
112
+ code: `try {
113
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
114
+ // @req REQ-TRY-INSIDE-BRANCH
115
+ doWork();
116
+ } finally {
117
+ cleanup();
118
+ }`,
119
+ options: [{ annotationPlacement: "inside" }],
120
+ },
110
121
  {
111
122
  name: "[REQ-BRANCH-DETECTION] valid do-while loop with annotations",
112
123
  code: `/* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md */
@@ -201,19 +212,6 @@ if (condition) {}`,
201
212
  // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
202
213
  // @req REQ-INSIDE-BRACE-PLACEMENT
203
214
  doSomething();
204
- }`,
205
- options: [{ annotationPlacement: "inside" }],
206
- },
207
- {
208
- name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-PLACEMENT-CONFIG] catch clause annotated inside block under annotationPlacement: 'inside' (Story 028.0)",
209
- code: `// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
210
- // @req REQ-BRANCH-TRY
211
- try {
212
- doSomething();
213
- } catch (error) {
214
- // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
215
- // @req REQ-INSIDE-CATCH
216
- handleError(error);
217
215
  }`,
218
216
  options: [{ annotationPlacement: "inside" }],
219
217
  },
@@ -223,6 +221,22 @@ try {
223
221
  // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
224
222
  // @req REQ-LOOP-INSIDE
225
223
  process(item);
224
+ }`,
225
+ options: [{ annotationPlacement: "inside" }],
226
+ },
227
+ {
228
+ name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-PLACEMENT-CONFIG] switch cases annotated inside block under annotationPlacement: 'inside' (Story 028.0)",
229
+ code: `switch (value) {
230
+ case 'a': {
231
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
232
+ // @req REQ-SWITCH-CASE-INSIDE
233
+ doSomething();
234
+ }
235
+ default: {
236
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
237
+ // @req REQ-SWITCH-DEFAULT-INSIDE
238
+ doDefault();
239
+ }
226
240
  }`,
227
241
  options: [{ annotationPlacement: "inside" }],
228
242
  },
@@ -446,6 +460,31 @@ if (a) {
446
460
  }`,
447
461
  errors: makeMissingAnnotationErrors("@story", "@req", "@story", "@req"),
448
462
  },
463
+ {
464
+ // Current behavior: inside-only catch annotations do NOT satisfy try branch in inside-placement mode.
465
+ name: "TODO-FUTURE-BEHAVIOR: [REQ-INSIDE-BRACE-PLACEMENT][REQ-PLACEMENT-CONFIG] catch clause annotated inside block under annotationPlacement: 'inside' (Story 028.0)",
466
+ code: `// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
467
+ // @req REQ-BRANCH-TRY
468
+ try {
469
+ doSomething();
470
+ } catch (error) {
471
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
472
+ // @req REQ-INSIDE-CATCH
473
+ handleError(error);
474
+ }`,
475
+ options: [{ annotationPlacement: "inside" }],
476
+ output: `// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
477
+ // @req REQ-BRANCH-TRY
478
+ // @story <story-file>.story.md
479
+ try {
480
+ doSomething();
481
+ } catch (error) {
482
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
483
+ // @req REQ-INSIDE-CATCH
484
+ handleError(error);
485
+ }`,
486
+ errors: makeMissingAnnotationErrors("@story", "@req"),
487
+ },
449
488
  {
450
489
  name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-BEFORE-BRACE-ERROR][REQ-PLACEMENT-CONFIG] before-brace annotations ignored when annotationPlacement: 'inside'",
451
490
  code: `// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
@@ -493,14 +532,34 @@ catch (error) {
493
532
  options: [{ annotationPlacement: "inside" }],
494
533
  output: `// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
495
534
  // @req REQ-BRANCH-TRY
535
+ // @story <story-file>.story.md
496
536
  try {
497
537
  doSomething();
498
538
  }
499
539
  // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
500
540
  // @req REQ-CATCH-BEFORE
501
541
  catch (error) {
502
- // @story <story-file>.story.md
503
542
  handleError(error);
543
+ }`,
544
+ errors: makeMissingAnnotationErrors("@story", "@req", "@story", "@req"),
545
+ },
546
+ {
547
+ name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-BEFORE-BRACE-ERROR][REQ-PLACEMENT-CONFIG] before-try annotations ignored when annotationPlacement: 'inside' for TryStatement (Story 028.0)",
548
+ code: `// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
549
+ // @req REQ-TRY-BEFORE
550
+ try {
551
+ doWork();
552
+ } finally {
553
+ cleanup();
554
+ }`,
555
+ options: [{ annotationPlacement: "inside" }],
556
+ output: `// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
557
+ // @req REQ-TRY-BEFORE
558
+ // @story <story-file>.story.md
559
+ try {
560
+ doWork();
561
+ } finally {
562
+ cleanup();
504
563
  }`,
505
564
  errors: makeMissingAnnotationErrors("@story", "@req"),
506
565
  },
@@ -555,6 +614,26 @@ if (a) {
555
614
  doB();
556
615
  } else {
557
616
  doC();
617
+ }`,
618
+ errors: makeMissingAnnotationErrors("@story", "@req"),
619
+ },
620
+ {
621
+ name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-BEFORE-BRACE-ERROR][REQ-PLACEMENT-CONFIG] before-case annotations ignored when annotationPlacement: 'inside' for SwitchCase",
622
+ code: `switch (value) {
623
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
624
+ // @req REQ-SWITCH-BEFORE
625
+ case 'a': {
626
+ doSomething();
627
+ }
628
+ }`,
629
+ options: [{ annotationPlacement: "inside" }],
630
+ output: `switch (value) {
631
+ // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
632
+ // @req REQ-SWITCH-BEFORE
633
+ // @story <story-file>.story.md
634
+ case 'a': {
635
+ doSomething();
636
+ }
558
637
  }`,
559
638
  errors: makeMissingAnnotationErrors("@story", "@req"),
560
639
  },
@@ -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.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",
@@ -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.
@@ -805,5 +811,4 @@ If `--from` or `--to` is missing, the CLI prints an error, shows the help text,
805
811
  In CI:
806
812
 
807
813
  ```bash
808
- npm run traceability:verify
809
- ```
814
+ npm run traceability:verify