eslint-plugin-traceability 1.19.4 → 1.21.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 +3 -3
- package/README.md +3 -1
- package/lib/src/rules/helpers/require-story-core.d.ts +2 -0
- package/lib/src/rules/helpers/require-story-core.js +12 -1
- package/lib/src/rules/helpers/require-story-helpers.d.ts +20 -1
- package/lib/src/rules/helpers/require-story-helpers.js +47 -0
- package/lib/src/rules/helpers/require-story-visitors.js +6 -0
- package/lib/src/rules/helpers/test-callback-exclusion.d.ts +1 -0
- package/lib/src/rules/require-req-annotation.d.ts +3 -0
- package/lib/src/rules/require-req-annotation.js +14 -2
- package/lib/src/rules/require-story-annotation.d.ts +4 -0
- package/lib/src/rules/require-story-annotation.js +50 -20
- package/lib/src/utils/annotation-checker.d.ts +1 -0
- package/lib/src/utils/annotation-checker.js +13 -0
- package/lib/src/utils/function-annotation-helpers.d.ts +23 -0
- package/lib/src/utils/function-annotation-helpers.js +101 -0
- package/lib/tests/config/require-story-annotation-config.test.js +1 -0
- package/lib/tests/integration/require-traceability-aliases.integration.test.js +26 -0
- package/lib/tests/rules/require-req-annotation.test.js +20 -0
- package/lib/tests/rules/require-story-annotation.test.js +26 -0
- package/package.json +1 -1
- package/user-docs/api-reference.md +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
# [1.21.0](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.20.0...v1.21.0) (2025-12-19)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
###
|
|
4
|
+
### Features
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* support inside-brace placement for function-level rules ([064b1a4](https://github.com/voder-ai/eslint-plugin-traceability/commit/064b1a4e7627b5e35afd074752724ab5958e9d8b))
|
|
7
7
|
|
|
8
8
|
# Changelog
|
|
9
9
|
|
package/README.md
CHANGED
|
@@ -117,13 +117,15 @@ Traceability annotations are typically placed immediately adjacent to the code t
|
|
|
117
117
|
}
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
+
The `annotationPlacement` option is also supported by the function-level rules (`traceability/require-story-annotation` and `traceability/require-req-annotation`) when you configure them directly. In `"inside"` mode, these rules treat only the first comment-only lines inside function and method bodies as satisfying the annotation requirement; JSDoc and before-function comments are ignored for block-bodied functions and methods, while TypeScript declarations and signature-only nodes continue to use before-node annotations.
|
|
121
|
+
|
|
120
122
|
- **Function-level (`traceability/require-story-annotation`, `traceability/require-req-annotation`)**
|
|
121
123
|
|
|
122
124
|
Function-level rules continue to accept annotations:
|
|
123
125
|
- As JSDoc blocks immediately preceding the function, or
|
|
124
126
|
- As line comments placed directly before the function declaration or expression.
|
|
125
127
|
|
|
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.
|
|
128
|
+
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, but that behaviour is not yet implemented in the current release.
|
|
127
129
|
|
|
128
130
|
For full configuration details and migration guidance between placement styles, see:
|
|
129
131
|
|
|
@@ -38,6 +38,7 @@ import type { Rule } from "eslint";
|
|
|
38
38
|
type CoreReportOptions = {
|
|
39
39
|
annotationTemplateOverride?: string;
|
|
40
40
|
autoFixToggle?: boolean;
|
|
41
|
+
annotationPlacement?: "before" | "inside";
|
|
41
42
|
};
|
|
42
43
|
type ReportDeps = {
|
|
43
44
|
hasStoryAnnotation: (_sourceCode: any, _node: any) => boolean;
|
|
@@ -53,6 +54,7 @@ type ReportDeps = {
|
|
|
53
54
|
shouldApplyAutoFix: (_autoFix: boolean | undefined) => boolean;
|
|
54
55
|
createAddStoryFix: (_target: any, _annotationTemplate: string) => any;
|
|
55
56
|
createMethodFix: (_node: any, _annotationTemplate: string) => any;
|
|
57
|
+
hasStoryAnnotationWithPlacement?: (_sourceCode: any, _node: any, _placement: "before" | "inside") => boolean;
|
|
56
58
|
};
|
|
57
59
|
/**
|
|
58
60
|
* Core helper to report a missing @story annotation for a function-like node.
|
|
@@ -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,7 +157,14 @@ function createMissingStoryReportDescriptor(config) {
|
|
|
153
157
|
function coreReportMissing(deps, context, sourceCode, config) {
|
|
154
158
|
const { node, target: passedTarget, options = {} } = config;
|
|
155
159
|
withSafeReporting("coreReportMissing", () => {
|
|
156
|
-
|
|
160
|
+
const annotationPlacement = resolveAnnotationPlacement(options);
|
|
161
|
+
const hasWithPlacement = deps.hasStoryAnnotationWithPlacement;
|
|
162
|
+
if (typeof hasWithPlacement === "function") {
|
|
163
|
+
if (hasWithPlacement(sourceCode, node, annotationPlacement)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (deps.hasStoryAnnotation(sourceCode, node)) {
|
|
157
168
|
return;
|
|
158
169
|
}
|
|
159
170
|
const functionName = deps.getReportedFunctionName(node);
|
|
@@ -18,6 +18,7 @@ import { type CallbackExclusionOptions } from "./test-callback-exclusion";
|
|
|
18
18
|
interface ReportOptions extends CallbackExclusionOptions {
|
|
19
19
|
annotationTemplateOverride?: string;
|
|
20
20
|
autoFixToggle?: boolean;
|
|
21
|
+
annotationPlacement?: "before" | "inside";
|
|
21
22
|
}
|
|
22
23
|
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
23
24
|
declare function getAnnotationTemplate(override?: string, _options?: CallbackExclusionOptions): string;
|
|
@@ -53,6 +54,24 @@ declare function leadingCommentsHasStory(node: any): boolean;
|
|
|
53
54
|
* @req REQ-ANNOTATION-REQUIRED - Detect existing story annotations in JSDoc or comments
|
|
54
55
|
*/
|
|
55
56
|
declare function hasStoryAnnotation(sourceCode: any, node: any): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Placement-aware story detection helper used by core reporting.
|
|
59
|
+
*
|
|
60
|
+
* When annotationPlacement is "inside" and the node supports inside-brace
|
|
61
|
+
* semantics, this helper only treats annotations found on the first
|
|
62
|
+
* comment-only lines inside the function or method body as satisfying the
|
|
63
|
+
* requirement. JSDoc and before-function comments are intentionally ignored so
|
|
64
|
+
* that misplaced annotations are reported as violations under the inside
|
|
65
|
+
* standard.
|
|
66
|
+
*
|
|
67
|
+
* For nodes that do not support inside placement (such as TS declarations,
|
|
68
|
+
* signature-only nodes, or functions without block bodies), this helper
|
|
69
|
+
* delegates to the existing hasStoryAnnotation heuristics so that they
|
|
70
|
+
* continue to rely on before-function placement.
|
|
71
|
+
*
|
|
72
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-ALL-BLOCK-TYPES REQ-INSIDE-BRACE-PLACEMENT REQ-PLACEMENT-CONFIG
|
|
73
|
+
*/
|
|
74
|
+
declare function hasStoryAnnotationWithPlacement(sourceCode: any, node: any, annotationPlacement: "before" | "inside"): boolean;
|
|
56
75
|
/**
|
|
57
76
|
* Determine AST node where annotation should be inserted
|
|
58
77
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
@@ -84,4 +103,4 @@ declare function reportMethod(context: Rule.RuleContext, sourceCode: any, config
|
|
|
84
103
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
85
104
|
* @req REQ-ANNOTATION-REQUIRED
|
|
86
105
|
*/
|
|
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, };
|
|
106
|
+
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, };
|
|
@@ -8,6 +8,7 @@ exports.jsdocHasStory = jsdocHasStory;
|
|
|
8
8
|
exports.commentsBeforeHasStory = commentsBeforeHasStory;
|
|
9
9
|
exports.leadingCommentsHasStory = leadingCommentsHasStory;
|
|
10
10
|
exports.hasStoryAnnotation = hasStoryAnnotation;
|
|
11
|
+
exports.hasStoryAnnotationWithPlacement = hasStoryAnnotationWithPlacement;
|
|
11
12
|
exports.extractName = extractName;
|
|
12
13
|
exports.resolveTargetNode = resolveTargetNode;
|
|
13
14
|
exports.shouldProcessNode = shouldProcessNode;
|
|
@@ -19,6 +20,7 @@ Object.defineProperty(exports, "parentChainHasStory", { enumerable: true, get: f
|
|
|
19
20
|
Object.defineProperty(exports, "fallbackTextBeforeHasStory", { enumerable: true, get: function () { return require_story_io_1.fallbackTextBeforeHasStory; } });
|
|
20
21
|
const require_story_utils_1 = require("./require-story-utils");
|
|
21
22
|
Object.defineProperty(exports, "getNodeName", { enumerable: true, get: function () { return require_story_utils_1.getNodeName; } });
|
|
23
|
+
const function_annotation_helpers_1 = require("../../utils/function-annotation-helpers");
|
|
22
24
|
const require_story_core_1 = require("./require-story-core");
|
|
23
25
|
Object.defineProperty(exports, "DEFAULT_SCOPE", { enumerable: true, get: function () { return require_story_core_1.DEFAULT_SCOPE; } });
|
|
24
26
|
Object.defineProperty(exports, "EXPORT_PRIORITY_VALUES", { enumerable: true, get: function () { return require_story_core_1.EXPORT_PRIORITY_VALUES; } });
|
|
@@ -220,6 +222,49 @@ function hasStoryAnnotation(sourceCode, node) {
|
|
|
220
222
|
}
|
|
221
223
|
return false;
|
|
222
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Placement-aware story detection helper used by core reporting.
|
|
227
|
+
*
|
|
228
|
+
* When annotationPlacement is "inside" and the node supports inside-brace
|
|
229
|
+
* semantics, this helper only treats annotations found on the first
|
|
230
|
+
* comment-only lines inside the function or method body as satisfying the
|
|
231
|
+
* requirement. JSDoc and before-function comments are intentionally ignored so
|
|
232
|
+
* that misplaced annotations are reported as violations under the inside
|
|
233
|
+
* standard.
|
|
234
|
+
*
|
|
235
|
+
* For nodes that do not support inside placement (such as TS declarations,
|
|
236
|
+
* signature-only nodes, or functions without block bodies), this helper
|
|
237
|
+
* delegates to the existing hasStoryAnnotation heuristics so that they
|
|
238
|
+
* continue to rely on before-function placement.
|
|
239
|
+
*
|
|
240
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-ALL-BLOCK-TYPES REQ-INSIDE-BRACE-PLACEMENT REQ-PLACEMENT-CONFIG
|
|
241
|
+
*/
|
|
242
|
+
function hasStoryAnnotationWithPlacement(sourceCode, node, annotationPlacement) {
|
|
243
|
+
// Backward-compatible default: use existing heuristics when placement is
|
|
244
|
+
// "before" or when the function does not support inside-brace semantics.
|
|
245
|
+
if (annotationPlacement !== "inside" ||
|
|
246
|
+
!(0, function_annotation_helpers_1.supportsInsidePlacementForFunction)(node)) {
|
|
247
|
+
return hasStoryAnnotation(sourceCode, node);
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const insideText = (0, function_annotation_helpers_1.getFunctionInsideBodyCommentText)(sourceCode, node);
|
|
251
|
+
if (typeof insideText === "string" &&
|
|
252
|
+
(insideText.includes("@story") || insideText.includes("@supports"))) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (process.env.TRACEABILITY_DEBUG === "1") {
|
|
258
|
+
// Debug logging only when explicitly enabled for troubleshooting helper failures.
|
|
259
|
+
console.error("[traceability] hasStoryAnnotationWithPlacement failed for node", error?.message ?? error);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// In inside-placement mode for block-bodied functions and methods we
|
|
263
|
+
// intentionally do not fall back to before-function heuristics; callers
|
|
264
|
+
// should treat this as a missing annotation so that misplaced comments are
|
|
265
|
+
// reported as violations.
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
223
268
|
/**
|
|
224
269
|
* Determine AST node where annotation should be inserted
|
|
225
270
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
@@ -372,6 +417,7 @@ function resolveAnnotationTargetNode(sourceCode, node, passedTarget) {
|
|
|
372
417
|
function reportMissing(context, sourceCode, config) {
|
|
373
418
|
(0, require_story_core_1.coreReportMissing)({
|
|
374
419
|
hasStoryAnnotation,
|
|
420
|
+
hasStoryAnnotationWithPlacement,
|
|
375
421
|
getReportedFunctionName,
|
|
376
422
|
resolveAnnotationTargetNode,
|
|
377
423
|
getNameNodeForReport,
|
|
@@ -387,6 +433,7 @@ function reportMissing(context, sourceCode, config) {
|
|
|
387
433
|
function reportMethod(context, sourceCode, config) {
|
|
388
434
|
(0, require_story_core_1.coreReportMethod)({
|
|
389
435
|
hasStoryAnnotation,
|
|
436
|
+
hasStoryAnnotationWithPlacement,
|
|
390
437
|
getReportedFunctionName,
|
|
391
438
|
resolveAnnotationTargetNode,
|
|
392
439
|
getNameNodeForReport,
|
|
@@ -33,6 +33,7 @@ function buildFunctionDeclarationVisitor(context, sourceCode, options) {
|
|
|
33
33
|
options: {
|
|
34
34
|
annotationTemplateOverride: options.annotationTemplate,
|
|
35
35
|
autoFixToggle: options.autoFix,
|
|
36
|
+
annotationPlacement: options.annotationPlacement,
|
|
36
37
|
},
|
|
37
38
|
});
|
|
38
39
|
}
|
|
@@ -68,6 +69,7 @@ function buildFunctionExpressionVisitor(context, sourceCode, options) {
|
|
|
68
69
|
options: {
|
|
69
70
|
annotationTemplateOverride: options.annotationTemplate,
|
|
70
71
|
autoFixToggle: options.autoFix,
|
|
72
|
+
annotationPlacement: options.annotationPlacement,
|
|
71
73
|
},
|
|
72
74
|
});
|
|
73
75
|
}
|
|
@@ -96,6 +98,7 @@ function buildArrowFunctionVisitor(context, sourceCode, options) {
|
|
|
96
98
|
options: {
|
|
97
99
|
annotationTemplateOverride: options.annotationTemplate,
|
|
98
100
|
autoFixToggle: options.autoFix,
|
|
101
|
+
annotationPlacement: options.annotationPlacement,
|
|
99
102
|
},
|
|
100
103
|
});
|
|
101
104
|
}
|
|
@@ -123,6 +126,7 @@ function buildTSDeclareFunctionVisitor(context, sourceCode, options) {
|
|
|
123
126
|
options: {
|
|
124
127
|
annotationTemplateOverride: options.annotationTemplate,
|
|
125
128
|
autoFixToggle: options.autoFix,
|
|
129
|
+
annotationPlacement: options.annotationPlacement,
|
|
126
130
|
},
|
|
127
131
|
});
|
|
128
132
|
}
|
|
@@ -151,6 +155,7 @@ function buildTSMethodSignatureVisitor(context, sourceCode, options) {
|
|
|
151
155
|
options: {
|
|
152
156
|
annotationTemplateOverride: options.methodAnnotationTemplate ?? options.annotationTemplate,
|
|
153
157
|
autoFixToggle: options.autoFix,
|
|
158
|
+
annotationPlacement: options.annotationPlacement,
|
|
154
159
|
},
|
|
155
160
|
});
|
|
156
161
|
}
|
|
@@ -177,6 +182,7 @@ function buildMethodDefinitionVisitor(context, sourceCode, options) {
|
|
|
177
182
|
options: {
|
|
178
183
|
annotationTemplateOverride: options.methodAnnotationTemplate ?? options.annotationTemplate,
|
|
179
184
|
autoFixToggle: options.autoFix,
|
|
185
|
+
annotationPlacement: options.annotationPlacement,
|
|
180
186
|
},
|
|
181
187
|
});
|
|
182
188
|
}
|
|
@@ -21,6 +21,7 @@ import type { TSESTree } from "@typescript-eslint/utils";
|
|
|
21
21
|
interface CallbackExclusionOptions {
|
|
22
22
|
excludeTestCallbacks?: boolean;
|
|
23
23
|
additionalTestHelperNames?: string[];
|
|
24
|
+
annotationPlacement?: "before" | "inside";
|
|
24
25
|
}
|
|
25
26
|
type TraceabilityNodeWithParent = TSESTree.Node & {
|
|
26
27
|
parent?: TraceabilityNodeWithParent | null;
|
|
@@ -6,6 +6,9 @@
|
|
|
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
|
+
* This rule honors the same `annotationPlacement` semantics as
|
|
11
|
+
* `require-story-annotation` for block-bodied functions and methods.
|
|
9
12
|
*/
|
|
10
13
|
import type { Rule } from "eslint";
|
|
11
14
|
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
|
},
|
|
@@ -56,7 +59,13 @@ const rule = {
|
|
|
56
59
|
const rawScope = options?.scope;
|
|
57
60
|
const scope = Array.isArray(rawScope) && rawScope.length > 0 ? rawScope : require_story_helpers_1.DEFAULT_SCOPE;
|
|
58
61
|
const exportPriority = options?.exportPriority ?? "all";
|
|
59
|
-
const
|
|
62
|
+
const rawAnnotationPlacement = options?.annotationPlacement;
|
|
63
|
+
const annotationPlacement = rawAnnotationPlacement === "inside" || rawAnnotationPlacement === "before"
|
|
64
|
+
? rawAnnotationPlacement
|
|
65
|
+
: "before";
|
|
66
|
+
const shouldCheck = (node) => (0, require_story_helpers_1.shouldProcessNode)(node, scope, exportPriority, {
|
|
67
|
+
annotationPlacement,
|
|
68
|
+
});
|
|
60
69
|
/**
|
|
61
70
|
* Helper to conditionally run the annotation check only when the node
|
|
62
71
|
* should be processed according to scope/exportPriority.
|
|
@@ -64,7 +73,10 @@ const rule = {
|
|
|
64
73
|
const runCheck = (node) => {
|
|
65
74
|
if (!shouldCheck(node))
|
|
66
75
|
return;
|
|
67
|
-
(0, annotation_checker_1.checkReqAnnotation)(context, node, {
|
|
76
|
+
(0, annotation_checker_1.checkReqAnnotation)(context, node, {
|
|
77
|
+
enableFix: false,
|
|
78
|
+
annotationPlacement,
|
|
79
|
+
});
|
|
68
80
|
};
|
|
69
81
|
return {
|
|
70
82
|
/**
|
|
@@ -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
|
|
@@ -2,9 +2,50 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const require_story_visitors_1 = require("./helpers/require-story-visitors");
|
|
4
4
|
const require_story_helpers_1 = require("./helpers/require-story-helpers");
|
|
5
|
+
function getNormalizedOptions(context) {
|
|
6
|
+
const sourceCode = context.getSourceCode();
|
|
7
|
+
const opts = (context.options && context.options[0]) || {};
|
|
8
|
+
const scope = opts.scope || require_story_helpers_1.DEFAULT_SCOPE;
|
|
9
|
+
const exportPriority = opts.exportPriority || "all";
|
|
10
|
+
const annotationTemplate = typeof opts.annotationTemplate === "string" &&
|
|
11
|
+
opts.annotationTemplate.trim().length > 0
|
|
12
|
+
? opts.annotationTemplate.trim()
|
|
13
|
+
: undefined;
|
|
14
|
+
const methodAnnotationTemplate = typeof opts.methodAnnotationTemplate === "string" &&
|
|
15
|
+
opts.methodAnnotationTemplate.trim().length > 0
|
|
16
|
+
? opts.methodAnnotationTemplate.trim()
|
|
17
|
+
: undefined;
|
|
18
|
+
const autoFix = typeof opts.autoFix === "boolean" ? opts.autoFix : true;
|
|
19
|
+
const excludeTestCallbacks = typeof opts.excludeTestCallbacks === "boolean"
|
|
20
|
+
? opts.excludeTestCallbacks
|
|
21
|
+
: true;
|
|
22
|
+
const additionalTestHelperNames = Array.isArray(opts.additionalTestHelperNames) &&
|
|
23
|
+
opts.additionalTestHelperNames.every((name) => typeof name === "string")
|
|
24
|
+
? opts.additionalTestHelperNames
|
|
25
|
+
: undefined;
|
|
26
|
+
const rawAnnotationPlacement = opts.annotationPlacement;
|
|
27
|
+
const annotationPlacement = rawAnnotationPlacement === "inside" || rawAnnotationPlacement === "before"
|
|
28
|
+
? rawAnnotationPlacement
|
|
29
|
+
: "before";
|
|
30
|
+
return {
|
|
31
|
+
sourceCode,
|
|
32
|
+
scope,
|
|
33
|
+
exportPriority,
|
|
34
|
+
annotationTemplate,
|
|
35
|
+
methodAnnotationTemplate,
|
|
36
|
+
autoFix,
|
|
37
|
+
excludeTestCallbacks,
|
|
38
|
+
additionalTestHelperNames,
|
|
39
|
+
annotationPlacement,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
5
42
|
/**
|
|
6
43
|
* ESLint rule to require @story annotations on functions/methods.
|
|
7
44
|
*
|
|
45
|
+
* This rule participates in Story 028.0 placement standardization by supporting
|
|
46
|
+
* configurable annotation placement, including inside-brace function annotations
|
|
47
|
+
* when configured.
|
|
48
|
+
*
|
|
8
49
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
9
50
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
10
51
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
@@ -54,6 +95,12 @@ const rule = {
|
|
|
54
95
|
items: { type: "string" },
|
|
55
96
|
uniqueItems: true,
|
|
56
97
|
},
|
|
98
|
+
/**
|
|
99
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG REQ-DEFAULT-BACKWARD-COMPAT REQ-ALL-BLOCK-TYPES
|
|
100
|
+
*/
|
|
101
|
+
annotationPlacement: {
|
|
102
|
+
enum: ["before", "inside"],
|
|
103
|
+
},
|
|
57
104
|
},
|
|
58
105
|
additionalProperties: false,
|
|
59
106
|
},
|
|
@@ -68,26 +115,7 @@ const rule = {
|
|
|
68
115
|
* @req REQ-AUTOFIX-MISSING - The create hook wires in visitors that are capable of providing auto-fix suggestions for missing @story annotations.
|
|
69
116
|
*/
|
|
70
117
|
create(context) {
|
|
71
|
-
const sourceCode = context
|
|
72
|
-
const opts = (context.options && context.options[0]) || {};
|
|
73
|
-
const scope = opts.scope || require_story_helpers_1.DEFAULT_SCOPE;
|
|
74
|
-
const exportPriority = opts.exportPriority || "all";
|
|
75
|
-
const annotationTemplate = typeof opts.annotationTemplate === "string" &&
|
|
76
|
-
opts.annotationTemplate.trim().length > 0
|
|
77
|
-
? opts.annotationTemplate.trim()
|
|
78
|
-
: undefined;
|
|
79
|
-
const methodAnnotationTemplate = typeof opts.methodAnnotationTemplate === "string" &&
|
|
80
|
-
opts.methodAnnotationTemplate.trim().length > 0
|
|
81
|
-
? opts.methodAnnotationTemplate.trim()
|
|
82
|
-
: undefined;
|
|
83
|
-
const autoFix = typeof opts.autoFix === "boolean" ? opts.autoFix : true;
|
|
84
|
-
const excludeTestCallbacks = typeof opts.excludeTestCallbacks === "boolean"
|
|
85
|
-
? opts.excludeTestCallbacks
|
|
86
|
-
: true;
|
|
87
|
-
const additionalTestHelperNames = Array.isArray(opts.additionalTestHelperNames) &&
|
|
88
|
-
opts.additionalTestHelperNames.every((name) => typeof name === "string")
|
|
89
|
-
? opts.additionalTestHelperNames
|
|
90
|
-
: undefined;
|
|
118
|
+
const { sourceCode, scope, exportPriority, annotationTemplate, methodAnnotationTemplate, autoFix, excludeTestCallbacks, additionalTestHelperNames, annotationPlacement, } = getNormalizedOptions(context);
|
|
91
119
|
/**
|
|
92
120
|
* Optional debug logging for troubleshooting this rule.
|
|
93
121
|
* Developers can temporarily uncomment the block below to log when the rule
|
|
@@ -106,6 +134,7 @@ const rule = {
|
|
|
106
134
|
const should = (node) => (0, require_story_helpers_1.shouldProcessNode)(node, scope, exportPriority, {
|
|
107
135
|
excludeTestCallbacks,
|
|
108
136
|
additionalTestHelperNames,
|
|
137
|
+
annotationPlacement,
|
|
109
138
|
});
|
|
110
139
|
// Delegate visitor construction to helper to keep this file concise.
|
|
111
140
|
return (0, require_story_visitors_1.buildVisitors)(context, sourceCode, {
|
|
@@ -117,6 +146,7 @@ const rule = {
|
|
|
117
146
|
autoFix,
|
|
118
147
|
excludeTestCallbacks,
|
|
119
148
|
additionalTestHelperNames,
|
|
149
|
+
annotationPlacement,
|
|
120
150
|
});
|
|
121
151
|
},
|
|
122
152
|
};
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.checkReqAnnotation = checkReqAnnotation;
|
|
4
4
|
const require_story_utils_1 = require("../rules/helpers/require-story-utils");
|
|
5
5
|
const reqAnnotationDetection_1 = require("./reqAnnotationDetection");
|
|
6
|
+
const function_annotation_helpers_1 = require("./function-annotation-helpers");
|
|
6
7
|
/**
|
|
7
8
|
* Helper to retrieve the JSDoc comment for a node.
|
|
8
9
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
@@ -180,7 +181,19 @@ function reportMissing(context, node, enableFix = true) {
|
|
|
180
181
|
*/
|
|
181
182
|
function checkReqAnnotation(context, node, options) {
|
|
182
183
|
const { enableFix = true } = options ?? {};
|
|
184
|
+
const annotationPlacement = options?.annotationPlacement === "inside" ||
|
|
185
|
+
options?.annotationPlacement === "before"
|
|
186
|
+
? options.annotationPlacement
|
|
187
|
+
: "before";
|
|
183
188
|
const sourceCode = context.getSourceCode();
|
|
189
|
+
if (annotationPlacement === "inside" &&
|
|
190
|
+
(0, function_annotation_helpers_1.supportsInsidePlacementForFunction)(node)) {
|
|
191
|
+
const insideText = (0, function_annotation_helpers_1.getFunctionInsideBodyCommentText)(sourceCode, node);
|
|
192
|
+
if (typeof insideText === "string" &&
|
|
193
|
+
(insideText.includes("@req") || insideText.includes("@supports"))) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
184
197
|
const jsdoc = getJsdocComment(sourceCode, node);
|
|
185
198
|
const leading = getLeadingComments(node);
|
|
186
199
|
const comments = getCommentsBefore(sourceCode, node);
|
|
@@ -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
|
+
}
|
|
@@ -19,6 +19,7 @@ describe("ESLint Configuration Rule Options (Story 002.0-DEV-ESLINT-CONFIG)", ()
|
|
|
19
19
|
const schema = require_story_annotation_1.default.meta.schema[0];
|
|
20
20
|
expect(schema.properties).toHaveProperty("scope");
|
|
21
21
|
expect(schema.properties).toHaveProperty("exportPriority");
|
|
22
|
+
expect(schema.properties).toHaveProperty("annotationPlacement");
|
|
22
23
|
expect(schema.additionalProperties).toBe(false);
|
|
23
24
|
});
|
|
24
25
|
});
|
|
@@ -109,6 +109,32 @@ describe("Unified require-traceability and aliases integration (Story 010.4-DEV-
|
|
|
109
109
|
expect(messages).toHaveLength(0);
|
|
110
110
|
});
|
|
111
111
|
});
|
|
112
|
+
it("[REQ-INSIDE-BRACE-PLACEMENT][REQ-ALL-BLOCK-TYPES] unified rule and aliases accept inside-brace annotations when annotationPlacement is 'inside'", async () => {
|
|
113
|
+
const codeWithInsideAnnotations = `function foo() {\n // @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-FN-INSIDE\n return 1;\n}`;
|
|
114
|
+
const config = [
|
|
115
|
+
{
|
|
116
|
+
rules: {
|
|
117
|
+
"traceability/require-traceability": "error",
|
|
118
|
+
"traceability/require-story-annotation": [
|
|
119
|
+
"error",
|
|
120
|
+
{
|
|
121
|
+
annotationPlacement: "inside",
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
"traceability/require-req-annotation": [
|
|
125
|
+
"error",
|
|
126
|
+
{
|
|
127
|
+
annotationPlacement: "inside",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
const result = await lintTextWithConfig(codeWithInsideAnnotations, "example.js", config);
|
|
134
|
+
const ruleIds = result.messages.map((m) => m.ruleId);
|
|
135
|
+
expect(ruleIds).not.toContain("traceability/require-story-annotation");
|
|
136
|
+
expect(ruleIds).not.toContain("traceability/require-req-annotation");
|
|
137
|
+
});
|
|
112
138
|
it("[REQ-PRESETS-CANONICAL-RULE] recommended preset surfaces unified and legacy diagnostics together for missing annotations", async () => {
|
|
113
139
|
const result = await lintTextWithConfig(codeMissingAll, "example.js", index_1.configs.recommended);
|
|
114
140
|
const ruleIds = result.messages.map((m) => m.ruleId).sort();
|
|
@@ -137,6 +137,26 @@ describe("Require Req Annotation Rule (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", (
|
|
|
137
137
|
code: `class C {\n /** @req REQ-EXAMPLE */\n m() {}\n}`,
|
|
138
138
|
options: [{ exportPriority: "non-exported" }],
|
|
139
139
|
},
|
|
140
|
+
{
|
|
141
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-ALL-BLOCK-TYPES] function-level @supports requirement inside body is valid when annotationPlacement is 'inside'",
|
|
142
|
+
code: `function withInsideReq() {\n // @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-FN-INSIDE\n return 1;\n}`,
|
|
143
|
+
options: [{ annotationPlacement: "inside" }],
|
|
144
|
+
},
|
|
145
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
146
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-ALL-BLOCK-TYPES] method-level @req inside body is valid when annotationPlacement is 'inside'",
|
|
147
|
+
code: `class C {\n method() {\n // @req REQ-METHOD-INSIDE\n return 1;\n }\n}`,
|
|
148
|
+
options: [{ annotationPlacement: "inside" }],
|
|
149
|
+
}),
|
|
150
|
+
{
|
|
151
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT] before-function @req remains valid when annotationPlacement is 'inside' (REQ-REQ-PLACEMENT-BC)",
|
|
152
|
+
code: `/**\n * @req REQ-BEFORE-FN\n */\nfunction beforeReqOnly() {\n return 1;\n}`,
|
|
153
|
+
options: [{ annotationPlacement: "inside" }],
|
|
154
|
+
},
|
|
155
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
156
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT] before-method @req remains valid when annotationPlacement is 'inside' (REQ-REQ-PLACEMENT-BC)",
|
|
157
|
+
code: `class C {\n /**\n * @req REQ-BEFORE-METHOD\n */\n method() {\n return 1;\n }\n}`,
|
|
158
|
+
options: [{ annotationPlacement: "inside" }],
|
|
159
|
+
}),
|
|
140
160
|
],
|
|
141
161
|
invalid: [
|
|
142
162
|
{
|
|
@@ -105,6 +105,21 @@ describe('Vitest suite', () => {
|
|
|
105
105
|
code: `withTestCase("does something", () => {});`,
|
|
106
106
|
options: [{ additionalTestHelperNames: ["withTestCase"] }],
|
|
107
107
|
},
|
|
108
|
+
{
|
|
109
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-ALL-BLOCK-TYPES] function-level @supports annotation inside body is valid when annotationPlacement is 'inside'",
|
|
110
|
+
code: `function insideAnnotated() {\n // @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-INSIDE-FN\n return 1;\n}`,
|
|
111
|
+
options: [{ annotationPlacement: "inside" }],
|
|
112
|
+
},
|
|
113
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
114
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-ALL-BLOCK-TYPES] function-level @story annotation inside body is valid when annotationPlacement is 'inside' (TS)",
|
|
115
|
+
code: `function insideAnnotatedTs() {\n // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md\n return 1;\n}`,
|
|
116
|
+
options: [{ annotationPlacement: "inside" }],
|
|
117
|
+
}),
|
|
118
|
+
(0, ts_language_options_1.withTsLanguageOptions)({
|
|
119
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT] before-method @story remains valid when annotationPlacement is 'inside' (placement BC)",
|
|
120
|
+
code: `class C {\n /** @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md */\n method() {\n return 1;\n }\n}`,
|
|
121
|
+
options: [{ annotationPlacement: "inside" }],
|
|
122
|
+
}),
|
|
108
123
|
],
|
|
109
124
|
invalid: [
|
|
110
125
|
{
|
|
@@ -237,6 +252,17 @@ describe('Vitest suite', () => {
|
|
|
237
252
|
},
|
|
238
253
|
],
|
|
239
254
|
},
|
|
255
|
+
{
|
|
256
|
+
name: "[REQ-BEFORE-BRACE-ERROR][REQ-INSIDE-BRACE-PLACEMENT] before-function annotation is ignored when annotationPlacement is 'inside'",
|
|
257
|
+
code: `// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md\nfunction beforeOnly() {\n return 1;\n}`,
|
|
258
|
+
options: [{ annotationPlacement: "inside", autoFix: false }],
|
|
259
|
+
errors: [
|
|
260
|
+
{
|
|
261
|
+
messageId: "missingStory",
|
|
262
|
+
suggestions: 1, // satisfy TypeScript's SuggestionOutput[] typing while asserting suggestion count
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
},
|
|
240
266
|
],
|
|
241
267
|
});
|
|
242
268
|
ruleTester.run("require-story-annotation with exportPriority option", require_story_annotation_1.default, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.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. Current releases support `annotationPlacement` on both the branch-level rule and the function-level rules, so you can standardize on inside-brace placement for if/else/try/catch/switch/function/loop blocks. When you leave `annotationPlacement` unspecified, all rules default to the historical before-function/before-branch behavior for backward compatibility.
|
|
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 treats before-function comments as the source of annotations (`"before"`, the default and backward-compatible behavior) or instead requires annotations as the first comment-only lines inside function and method bodies (`"inside"`). In `"inside"` mode, JSDoc and before-function comments are ignored for block-bodied functions and methods, and only inside-body comments are considered; declaration-only nodes (e.g., TS signatures) continue to use before-node annotations.
|
|
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) Controls whether the rule treats before-function comments as the source of annotations (`"before"`, the default and backward-compatible behavior) or instead requires annotations as the first comment-only lines inside function and method bodies (`"inside"`). In `"inside"` mode, JSDoc and before-function comments are ignored for block-bodied functions and methods, and only inside-body comments are considered; declaration-only nodes (e.g., TS signatures) continue to use before-node annotations.
|
|
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-ANNOTATION-PLACEMENT-STANDARDIZATION` standardizes inside-brace placement for supported branch constructs; function-level rules continue to use before-function annotations until a future story extends the same standard to 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
|
|