eslint-plugin-traceability 1.19.4 → 1.20.0

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.4](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.19.3...v1.19.4) (2025-12-18)
1
+ # [1.20.0](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.19.4...v1.20.0) (2025-12-18)
2
2
 
3
3
 
4
- ### Bug Fixes
4
+ ### Features
5
5
 
6
- * migrate before-brace annotations into inside-brace placement ([c8c381d](https://github.com/voder-ai/eslint-plugin-traceability/commit/c8c381d5f5fbd39fdb626e9dd6fe2dd837bffb53))
6
+ * extend branch placement standard docs and helpers ([ae05a52](https://github.com/voder-ai/eslint-plugin-traceability/commit/ae05a5232b724060f7b1baae9a6be6eedf466617))
7
7
 
8
8
  # Changelog
9
9
 
package/README.md CHANGED
@@ -123,7 +123,9 @@ Traceability annotations are typically placed immediately adjacent to the code t
123
123
  - As JSDoc blocks immediately preceding the function, or
124
124
  - As line comments placed directly before the function declaration or expression.
125
125
 
126
- This placement is stable and supported for all current versions. Future versions may introduce an **inside-brace** placement mode for function bodies (similar to branch blocks) to align function annotations with the branch-level `"inside"` standard.
126
+ Function-level rules now support the same placement configuration model as branches:
127
+ - By default, annotations are still placed immediately before the function (JSDoc or line comments).
128
+ - When you configure `annotationPlacement: "inside"` on `traceability/require-story-annotation`, the rule prefers annotations as the first comment-only lines inside the function or method body, mirroring the branch-level inside-brace standard from Story 028.0. Declaration-only shapes such as `TSDeclareFunction` and `TSMethodSignature` remain before-function only, since they have no executable body.
127
129
 
128
130
  For full configuration details and migration guidance between placement styles, see:
129
131
 
@@ -139,6 +139,10 @@ function createMissingStoryReportDescriptor(config) {
139
139
  ],
140
140
  };
141
141
  }
142
+ function resolveAnnotationPlacement(options) {
143
+ const raw = options?.annotationPlacement;
144
+ return raw === "inside" || raw === "before" ? raw : "before";
145
+ }
142
146
  /**
143
147
  * Core helper to report a missing @story annotation for a function-like node.
144
148
  * This reporting utility delegates behavior to injected dependencies so that
@@ -153,6 +157,11 @@ function createMissingStoryReportDescriptor(config) {
153
157
  function coreReportMissing(deps, context, sourceCode, config) {
154
158
  const { node, target: passedTarget, options = {} } = config;
155
159
  withSafeReporting("coreReportMissing", () => {
160
+ const annotationPlacement = resolveAnnotationPlacement(options);
161
+ if (typeof deps.hasStoryAnnotationWithPlacement === "function" &&
162
+ deps.hasStoryAnnotationWithPlacement(sourceCode, node, annotationPlacement)) {
163
+ return;
164
+ }
156
165
  if (deps.hasStoryAnnotation(sourceCode, node)) {
157
166
  return;
158
167
  }
@@ -53,6 +53,7 @@ declare function leadingCommentsHasStory(node: any): boolean;
53
53
  * @req REQ-ANNOTATION-REQUIRED - Detect existing story annotations in JSDoc or comments
54
54
  */
55
55
  declare function hasStoryAnnotation(sourceCode: any, node: any): boolean;
56
+ declare const hasStoryAnnotationWithPlacement: typeof hasStoryAnnotation;
56
57
  /**
57
58
  * Determine AST node where annotation should be inserted
58
59
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
@@ -84,4 +85,4 @@ declare function reportMethod(context: Rule.RuleContext, sourceCode: any, config
84
85
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
85
86
  * @req REQ-ANNOTATION-REQUIRED
86
87
  */
87
- export { STORY_PATH, getAnnotationTemplate, shouldApplyAutoFix, isExportedNode, jsdocHasStory, commentsBeforeHasStory, leadingCommentsHasStory, hasStoryAnnotation, getNodeName, extractName, resolveTargetNode, shouldProcessNode, DEFAULT_SCOPE, EXPORT_PRIORITY_VALUES, linesBeforeHasStory, parentChainHasStory, fallbackTextBeforeHasStory, reportMissing, reportMethod, };
88
+ export { STORY_PATH, getAnnotationTemplate, shouldApplyAutoFix, isExportedNode, jsdocHasStory, commentsBeforeHasStory, leadingCommentsHasStory, hasStoryAnnotation, hasStoryAnnotationWithPlacement, getNodeName, extractName, resolveTargetNode, shouldProcessNode, DEFAULT_SCOPE, EXPORT_PRIORITY_VALUES, linesBeforeHasStory, parentChainHasStory, fallbackTextBeforeHasStory, reportMissing, reportMethod, };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fallbackTextBeforeHasStory = exports.parentChainHasStory = exports.linesBeforeHasStory = exports.EXPORT_PRIORITY_VALUES = exports.DEFAULT_SCOPE = exports.getNodeName = exports.STORY_PATH = void 0;
3
+ exports.fallbackTextBeforeHasStory = exports.parentChainHasStory = exports.linesBeforeHasStory = exports.EXPORT_PRIORITY_VALUES = exports.DEFAULT_SCOPE = exports.getNodeName = exports.hasStoryAnnotationWithPlacement = exports.STORY_PATH = void 0;
4
4
  exports.getAnnotationTemplate = getAnnotationTemplate;
5
5
  exports.shouldApplyAutoFix = shouldApplyAutoFix;
6
6
  exports.isExportedNode = isExportedNode;
@@ -220,6 +220,9 @@ function hasStoryAnnotation(sourceCode, node) {
220
220
  }
221
221
  return false;
222
222
  }
223
+ // Placement-aware alias reserved for future inside-brace function placement.
224
+ const hasStoryAnnotationWithPlacement = hasStoryAnnotation;
225
+ exports.hasStoryAnnotationWithPlacement = hasStoryAnnotationWithPlacement;
223
226
  /**
224
227
  * Determine AST node where annotation should be inserted
225
228
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
@@ -6,6 +6,10 @@
6
6
  * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
7
7
  * @req REQ-CONFIGURABLE-SCOPE - Allow configuration of which exports are checked
8
8
  * @req REQ-EXPORT-PRIORITY - Allow configuration of export priority behavior
9
+ *
10
+ * Note: This rule accepts annotationPlacement for configuration parity with
11
+ * require-story-annotation, but currently still requires annotations before
12
+ * the function (no inside-function support yet).
9
13
  */
10
14
  import type { Rule } from "eslint";
11
15
  declare const rule: Rule.RuleModule;
@@ -39,6 +39,9 @@ const rule = {
39
39
  exportPriority: {
40
40
  enum: require_story_helpers_1.EXPORT_PRIORITY_VALUES,
41
41
  },
42
+ annotationPlacement: {
43
+ enum: ["before", "inside"],
44
+ },
42
45
  },
43
46
  additionalProperties: false,
44
47
  },
@@ -17,6 +17,10 @@ import type { Rule } from "eslint";
17
17
  /**
18
18
  * ESLint rule to require @story annotations on functions/methods.
19
19
  *
20
+ * This rule participates in Story 028.0 placement standardization by supporting
21
+ * configurable annotation placement, including inside-brace function annotations
22
+ * when configured.
23
+ *
20
24
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
21
25
  * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
22
26
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
@@ -5,6 +5,10 @@ const require_story_helpers_1 = require("./helpers/require-story-helpers");
5
5
  /**
6
6
  * ESLint rule to require @story annotations on functions/methods.
7
7
  *
8
+ * This rule participates in Story 028.0 placement standardization by supporting
9
+ * configurable annotation placement, including inside-brace function annotations
10
+ * when configured.
11
+ *
8
12
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
9
13
  * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
10
14
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
@@ -0,0 +1,23 @@
1
+ import type { Rule } from "eslint";
2
+ /**
3
+ * Determine whether a function-like node can support inside-brace
4
+ * placement semantics. Only nodes with a concrete BlockStatement body are
5
+ * eligible; TypeScript declarations and signature-only nodes are
6
+ * intentionally excluded.
7
+ *
8
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-ALL-BLOCK-TYPES
9
+ */
10
+ export declare function supportsInsidePlacementForFunction(node: any): boolean;
11
+ /**
12
+ * Gather the concatenated comment text from the first contiguous
13
+ * comment-only lines inside a function body. When no such comments are
14
+ * present or the node has no block body, an empty string is returned.
15
+ *
16
+ * This mirrors the branch helpers' behaviour for inside-brace placement
17
+ * (for example, simple if-statements and switch cases) so that
18
+ * function-level rules can share the same mental model: annotations live
19
+ * on the first comment-only line(s) inside the body braces.
20
+ *
21
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-INSIDE-BRACE-PLACEMENT REQ-ALL-BLOCK-TYPES
22
+ */
23
+ export declare function getFunctionInsideBodyCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any): string;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.supportsInsidePlacementForFunction = supportsInsidePlacementForFunction;
4
+ exports.getFunctionInsideBodyCommentText = getFunctionInsideBodyCommentText;
5
+ const branch_annotation_helpers_1 = require("./branch-annotation-helpers");
6
+ /**
7
+ * Helpers for determining function-body annotation placement.
8
+ *
9
+ * These utilities are shared between the function-level traceability rules
10
+ * (require-story-annotation, require-req-annotation) so they can honour the
11
+ * same "inside" placement semantics used by branch rules when
12
+ * `annotationPlacement: "inside"` is configured.
13
+ *
14
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-ALL-BLOCK-TYPES REQ-INSIDE-BRACE-PLACEMENT REQ-PLACEMENT-CONFIG
15
+ */
16
+ /**
17
+ * Locate the BlockStatement that represents the executable body of a
18
+ * function-like construct. Returns null when the node has no block body
19
+ * (for example, TypeScript declarations or arrow functions with
20
+ * expression bodies).
21
+ *
22
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-ALL-BLOCK-TYPES
23
+ */
24
+ function getFunctionBodyBlock(node) {
25
+ if (!node || typeof node.type !== "string") {
26
+ return null;
27
+ }
28
+ if (node.type === "FunctionDeclaration" ||
29
+ node.type === "FunctionExpression" ||
30
+ node.type === "ArrowFunctionExpression") {
31
+ const body = node.body;
32
+ return body && body.type === "BlockStatement" ? body : null;
33
+ }
34
+ if (node.type === "MethodDefinition") {
35
+ const value = node.value;
36
+ if (value && value.type === "FunctionExpression") {
37
+ const body = value.body;
38
+ return body && body.type === "BlockStatement" ? body : null;
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ /**
44
+ * Determine whether a function-like node can support inside-brace
45
+ * placement semantics. Only nodes with a concrete BlockStatement body are
46
+ * eligible; TypeScript declarations and signature-only nodes are
47
+ * intentionally excluded.
48
+ *
49
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-ALL-BLOCK-TYPES
50
+ */
51
+ function supportsInsidePlacementForFunction(node) {
52
+ return !!getFunctionBodyBlock(node);
53
+ }
54
+ /**
55
+ * Gather the concatenated comment text from the first contiguous
56
+ * comment-only lines inside a function body. When no such comments are
57
+ * present or the node has no block body, an empty string is returned.
58
+ *
59
+ * This mirrors the branch helpers' behaviour for inside-brace placement
60
+ * (for example, simple if-statements and switch cases) so that
61
+ * function-level rules can share the same mental model: annotations live
62
+ * on the first comment-only line(s) inside the body braces.
63
+ *
64
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-INSIDE-BRACE-PLACEMENT REQ-ALL-BLOCK-TYPES
65
+ */
66
+ function getFunctionInsideBodyCommentText(sourceCode, node) {
67
+ const block = getFunctionBodyBlock(node);
68
+ if (!block ||
69
+ !block.loc ||
70
+ !block.loc.start ||
71
+ !block.loc.end ||
72
+ typeof block.loc.start.line !== "number" ||
73
+ typeof block.loc.end.line !== "number") {
74
+ return "";
75
+ }
76
+ const getCommentsInside = sourceCode.getCommentsInside;
77
+ if (typeof getCommentsInside === "function") {
78
+ try {
79
+ const insideComments = getCommentsInside(block) || [];
80
+ const insideText = insideComments
81
+ .filter((c) => c && typeof c.value === "string")
82
+ .map((c) => c.value)
83
+ .join(" ");
84
+ if (insideText) {
85
+ return insideText;
86
+ }
87
+ }
88
+ catch {
89
+ // Fall through to the line-based fallback when structured comment
90
+ // retrieval is unavailable or fails.
91
+ }
92
+ }
93
+ const lines = sourceCode.lines;
94
+ if (!Array.isArray(lines)) {
95
+ return "";
96
+ }
97
+ const startIndex = block.loc.start.line - 1;
98
+ const endIndex = block.loc.end.line - 1;
99
+ const insideText = (0, branch_annotation_helpers_1.scanCommentLinesInRange)(lines, startIndex + 1, endIndex);
100
+ return insideText || "";
101
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.19.4",
3
+ "version": "1.20.0",
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",
@@ -26,7 +26,7 @@ For function-level traceability, the plugin exposes a unified rule and two legac
26
26
  - `traceability/require-traceability` is the **canonical function-level rule** for new configurations. It ensures functions and methods have both story coverage and requirement coverage, and it accepts either `@supports` (preferred) or legacy `@story` / `@req` annotations.
27
27
  - `traceability/require-story-annotation` and `traceability/require-req-annotation` are **backward-compatible aliases** that focus on the story and requirement aspects separately. They are retained for existing configurations and share the same underlying implementation model as the unified rule, but new ESLint configs should normally rely on `traceability/require-traceability` rather than enabling these legacy keys directly.
28
28
 
29
- All three rule keys can still be configured individually if you need fine-grained control (for example, to tune severities separately), but the recommended and strict presets enable `traceability/require-traceability` by default and keep the legacy keys primarily for projects that adopted them before the unified rule existed.
29
+ All three rule keys can still be configured individually if you need fine-grained control (for example, to tune severities separately), but the recommended and strict presets enable `traceability/require-traceability` by default and keep the legacy keys primarily for projects that adopted them before the unified rule existed. When the underlying function rules are configured with `annotationPlacement: "inside"`, the unified `require-traceability` rule honours inside-brace placement for function and method bodies in the same formatter-aware way that `require-branch-annotation` handles branches.
30
30
 
31
31
  ### traceability/require-traceability
32
32
 
@@ -44,6 +44,7 @@ Options:
44
44
  - `methodAnnotationTemplate` (string, optional) – Overrides the default placeholder JSDoc used when inserting missing `@story` annotations for class methods and TypeScript method signatures. When omitted or blank, falls back to `annotationTemplate` if provided, otherwise the built-in template.
45
45
  - `autoFix` (boolean, optional) – When set to `false`, disables all automatic fix behavior for this rule while retaining its suggestions and diagnostics. When omitted or `true`, the rule behaves as before, inserting placeholder annotations in `--fix` mode.
46
46
  - `excludeTestCallbacks` (boolean, optional) – When `true` (default), excludes anonymous arrow functions that are direct callbacks to common test framework functions (for example, Jest/Mocha/Vitest `describe`/`it`/`test`/`beforeEach`/`afterEach`/`beforeAll`/`afterAll`, plus focused/skipped/concurrent variants such as `fdescribe`, `xdescribe`, `fit`, `xit`, `test.concurrent`, `describe.concurrent`) from function-level annotation requirements. This assumes those test files are already covered by file-level `@supports` annotations and `traceability/require-test-traceability`. When set to `false`, these callbacks are treated like any other arrow function and must be annotated when in-scope.
47
+ - `annotationPlacement` ("before" | "inside", optional) – Controls whether the rule looks for annotations immediately before functions (`"before"`, the default and backward-compatible behaviour) or allows annotations as the first comment-only lines inside function and method bodies (`"inside"`). In inside mode, the rule continues to treat TypeScript declarations and signature-only nodes (such as `TSDeclareFunction` and `TSMethodSignature`) as before-function only, since they have no executable body.
47
48
 
48
49
  Default Severity: `error`
49
50
  Example:
@@ -70,6 +71,7 @@ Options:
70
71
 
71
72
  - `scope` (string[], optional) – Controls which function-like node types are required to have @req annotations. Allowed values: "FunctionDeclaration", "FunctionExpression", "MethodDefinition", "TSDeclareFunction", "TSMethodSignature". Default: ["FunctionDeclaration", "FunctionExpression", "MethodDefinition", "TSDeclareFunction", "TSMethodSignature"].
72
73
  - `exportPriority` ("all" | "exported" | "non-exported", optional) – Controls whether the rule checks all functions, only exported ones, or only non-exported ones. Default: "all".
74
+ - `annotationPlacement` ("before" | "inside", optional) – Accepted for configuration parity with `require-story-annotation`; requirement annotations are still evaluated using before-function comments and JSDoc today, so `"inside"` does not change behaviour yet.
73
75
 
74
76
  Default Severity: `error`
75
77
  Example (with both `@story` and `@req`, as typically used when both rules are enabled):
@@ -113,7 +115,7 @@ Behavior notes:
113
115
  Placement modes:
114
116
 
115
117
  - `"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.
118
+ - `"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. Story `028.0-DEV-FUNCTION-PLACEMENT` also extends this inside-brace standard to function and method bodies via `require-story-annotation` when configured, so projects can apply a single inside-brace rule consistently to both branches and functions.
117
119
 
118
120
  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.
119
121