eslint-plugin-traceability 1.18.0 → 1.19.1
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 +4 -3
- package/lib/src/rules/no-redundant-annotation.js +8 -1
- package/lib/src/utils/branch-annotation-helpers.d.ts +1 -1
- package/lib/src/utils/branch-annotation-helpers.js +101 -38
- package/lib/src/utils/branch-annotation-loop-helpers.d.ts +2 -1
- package/lib/src/utils/branch-annotation-loop-helpers.js +27 -13
- package/lib/src/utils/branch-annotation-report-helpers.js +49 -13
- package/lib/tests/rules/require-branch-annotation.test.js +85 -3
- package/lib/tests/utils/branch-annotation-helpers.test.js +133 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
## [1.19.1](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.19.0...v1.19.1) (2025-12-18)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
###
|
|
4
|
+
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* apply inside placement semantics to loop branches ([e0ba06f](https://github.com/voder-ai/eslint-plugin-traceability/commit/e0ba06fb9aba6947c6ac675f65cc1fb4639dc785))
|
|
7
|
+
* honor inside placement for catch clauses in branch annotation rule ([0e7a8e0](https://github.com/voder-ai/eslint-plugin-traceability/commit/0e7a8e01505bfa1e37e013dfda00e866f975ad33))
|
|
7
8
|
|
|
8
9
|
# Changelog
|
|
9
10
|
|
|
@@ -86,7 +86,14 @@ function getScopePairs(context, scopeNode, parent) {
|
|
|
86
86
|
const sourceCode = context.getSourceCode();
|
|
87
87
|
// Branch-style scope: use the branch helpers to collect comment text.
|
|
88
88
|
if (branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES.includes(scopeNode.type)) {
|
|
89
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Inside-brace annotations used as branch-level indicators (inside placement
|
|
91
|
+
* mode) should not be folded into scopePairs for redundancy purposes; only
|
|
92
|
+
* before-brace annotations define the covering scope here.
|
|
93
|
+
*
|
|
94
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-NON-REDUNDANT-INSIDE REQ-PLACEMENT-CONFIG
|
|
95
|
+
*/
|
|
96
|
+
const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, scopeNode, parent, "before");
|
|
90
97
|
return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromText)(text);
|
|
91
98
|
}
|
|
92
99
|
const comments = getScopeCommentsFromJSDocAndLeading(sourceCode, scopeNode);
|
|
@@ -41,7 +41,7 @@ export declare function scanCommentLinesInRange(lines: string[], startIndex: num
|
|
|
41
41
|
* @supports REQ-DUAL-POSITION-DETECTION
|
|
42
42
|
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG
|
|
43
43
|
*/
|
|
44
|
-
export declare function gatherBranchCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any, parent?: any,
|
|
44
|
+
export declare function gatherBranchCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any, parent?: any, annotationPlacement?: AnnotationPlacement): string;
|
|
45
45
|
/**
|
|
46
46
|
* Report missing @story annotation tag on a branch node when that branch lacks a corresponding @story reference in its comments.
|
|
47
47
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
@@ -161,20 +161,81 @@ function isElseIfBranch(node, parent) {
|
|
|
161
161
|
parent.type === "IfStatement" &&
|
|
162
162
|
parent.alternate === node);
|
|
163
163
|
}
|
|
164
|
+
function getInsideCatchCommentText(sourceCode, node) {
|
|
165
|
+
const getCommentsInside = sourceCode.getCommentsInside;
|
|
166
|
+
if (node.body && typeof getCommentsInside === "function") {
|
|
167
|
+
try {
|
|
168
|
+
const insideComments = getCommentsInside(node.body) || [];
|
|
169
|
+
const insideText = insideComments.map(extractCommentValue).join(" ");
|
|
170
|
+
if (insideText) {
|
|
171
|
+
return insideText;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// fall through to line-based fallback
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (node.body && node.body.loc && node.body.loc.start && node.body.loc.end) {
|
|
179
|
+
const lines = sourceCode.lines;
|
|
180
|
+
const startIndex = node.body.loc.start.line - 1;
|
|
181
|
+
const endIndex = node.body.loc.end.line - 1;
|
|
182
|
+
const insideText = scanCommentLinesInRange(lines, startIndex + 1, endIndex);
|
|
183
|
+
if (insideText) {
|
|
184
|
+
return insideText;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return "";
|
|
188
|
+
}
|
|
164
189
|
/**
|
|
165
190
|
* Gather annotation text for CatchClause nodes, supporting both before-catch and inside-catch positions.
|
|
166
191
|
* @story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md
|
|
167
192
|
* @req REQ-DUAL-POSITION-DETECTION
|
|
168
193
|
* @req REQ-FALLBACK-LOGIC
|
|
169
194
|
*/
|
|
170
|
-
function gatherCatchClauseCommentText(sourceCode, node, beforeText) {
|
|
171
|
-
if (
|
|
195
|
+
function gatherCatchClauseCommentText(sourceCode, node, annotationPlacement, beforeText) {
|
|
196
|
+
if (annotationPlacement === "inside") {
|
|
197
|
+
const insideText = getInsideCatchCommentText(sourceCode, node);
|
|
198
|
+
if (insideText) {
|
|
199
|
+
return insideText;
|
|
200
|
+
}
|
|
201
|
+
return "";
|
|
202
|
+
}
|
|
203
|
+
if (/@story\b/.test(beforeText) ||
|
|
204
|
+
/@req\b/.test(beforeText) ||
|
|
205
|
+
/@supports\b/.test(beforeText)) {
|
|
206
|
+
return beforeText;
|
|
207
|
+
}
|
|
208
|
+
const insideText = getInsideCatchCommentText(sourceCode, node);
|
|
209
|
+
if (insideText) {
|
|
210
|
+
return insideText;
|
|
211
|
+
}
|
|
212
|
+
return beforeText;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Gather annotation text for simple IfStatement branches, honoring the configured placement.
|
|
216
|
+
* When placement is "before", this helper preserves the existing behavior by returning the
|
|
217
|
+
* leading comment text unchanged. When placement is "inside", it switches to inside-brace
|
|
218
|
+
* semantics and scans for comments at the top of the consequent block.
|
|
219
|
+
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
220
|
+
* @supports REQ-INSIDE-BRACE-PLACEMENT
|
|
221
|
+
* @supports REQ-PLACEMENT-CONFIG
|
|
222
|
+
* @supports REQ-DEFAULT-BACKWARD-COMPAT
|
|
223
|
+
*/
|
|
224
|
+
function gatherSimpleIfCommentText(sourceCode, node, annotationPlacement, beforeText) {
|
|
225
|
+
if (annotationPlacement === "before") {
|
|
226
|
+
return beforeText;
|
|
227
|
+
}
|
|
228
|
+
if (annotationPlacement !== "inside") {
|
|
172
229
|
return beforeText;
|
|
173
230
|
}
|
|
231
|
+
if (!node.consequent || node.consequent.type !== "BlockStatement") {
|
|
232
|
+
return "";
|
|
233
|
+
}
|
|
234
|
+
const consequent = node.consequent;
|
|
174
235
|
const getCommentsInside = sourceCode.getCommentsInside;
|
|
175
|
-
if (
|
|
236
|
+
if (typeof getCommentsInside === "function") {
|
|
176
237
|
try {
|
|
177
|
-
const insideComments = getCommentsInside(
|
|
238
|
+
const insideComments = getCommentsInside(consequent) || [];
|
|
178
239
|
const insideText = insideComments.map(extractCommentValue).join(" ");
|
|
179
240
|
if (insideText) {
|
|
180
241
|
return insideText;
|
|
@@ -184,16 +245,20 @@ function gatherCatchClauseCommentText(sourceCode, node, beforeText) {
|
|
|
184
245
|
// fall through to line-based fallback
|
|
185
246
|
}
|
|
186
247
|
}
|
|
187
|
-
if (
|
|
248
|
+
if (consequent.loc &&
|
|
249
|
+
consequent.loc.start &&
|
|
250
|
+
consequent.loc.end &&
|
|
251
|
+
typeof consequent.loc.start.line === "number" &&
|
|
252
|
+
typeof consequent.loc.end.line === "number") {
|
|
188
253
|
const lines = sourceCode.lines;
|
|
189
|
-
const startIndex =
|
|
190
|
-
const endIndex =
|
|
254
|
+
const startIndex = consequent.loc.start.line - 1;
|
|
255
|
+
const endIndex = consequent.loc.end.line - 1;
|
|
191
256
|
const insideText = scanCommentLinesInRange(lines, startIndex + 1, endIndex);
|
|
192
257
|
if (insideText) {
|
|
193
258
|
return insideText;
|
|
194
259
|
}
|
|
195
260
|
}
|
|
196
|
-
return
|
|
261
|
+
return "";
|
|
197
262
|
}
|
|
198
263
|
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
199
264
|
function scanElseIfPrecedingComments(sourceCode, node) {
|
|
@@ -310,48 +375,46 @@ function gatherSwitchCaseCommentText(sourceCode, node) {
|
|
|
310
375
|
}
|
|
311
376
|
return comments.join(" ");
|
|
312
377
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
316
|
-
* @req REQ-COMMENT-ASSOCIATION - Associate inline comments with their corresponding code branches
|
|
317
|
-
* @story docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
|
|
318
|
-
* @supports REQ-DUAL-POSITION-DETECTION
|
|
319
|
-
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG
|
|
320
|
-
*/
|
|
321
|
-
function gatherBranchCommentText(sourceCode, node, parent, _annotationPlacement = "before") {
|
|
322
|
-
/**
|
|
323
|
-
* Conditional branch for SwitchCase nodes that may include inline comments.
|
|
324
|
-
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
325
|
-
* @req REQ-TRACEABILITY-SWITCHCASE-COMMENTS - Trace collection of preceding comments for SwitchCase
|
|
326
|
-
*/
|
|
378
|
+
function gatherBranchCommentTextByType(sourceCode, node, parent, context) {
|
|
379
|
+
const { annotationPlacement, beforeText } = context;
|
|
327
380
|
if (node.type === "SwitchCase") {
|
|
328
381
|
return gatherSwitchCaseCommentText(sourceCode, node);
|
|
329
382
|
}
|
|
330
|
-
const beforeComments = sourceCode.getCommentsBefore(node) || [];
|
|
331
|
-
const beforeText = beforeComments.map(extractCommentValue).join(" ");
|
|
332
383
|
if (node.type === "CatchClause") {
|
|
333
|
-
return gatherCatchClauseCommentText(sourceCode, node, beforeText);
|
|
384
|
+
return gatherCatchClauseCommentText(sourceCode, node, annotationPlacement, beforeText);
|
|
334
385
|
}
|
|
335
|
-
/**
|
|
336
|
-
* Conditional branch for IfStatement else-if nodes that may include inline comments
|
|
337
|
-
* after the else-if condition but before the consequent body.
|
|
338
|
-
* @story docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
|
|
339
|
-
* @supports REQ-DUAL-POSITION-DETECTION
|
|
340
|
-
*/
|
|
341
386
|
if (node.type === "IfStatement") {
|
|
342
|
-
|
|
387
|
+
if (isElseIfBranch(node, parent)) {
|
|
388
|
+
return gatherElseIfCommentText(sourceCode, node, parent, beforeText);
|
|
389
|
+
}
|
|
390
|
+
return gatherSimpleIfCommentText(sourceCode, node, annotationPlacement, beforeText);
|
|
343
391
|
}
|
|
344
|
-
/**
|
|
345
|
-
* Conditional branch for loop nodes that may include annotations either on the loop
|
|
346
|
-
* statement itself or at the top of the loop body, allowing flexible placement.
|
|
347
|
-
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-LOOP-ANNOTATION REQ-LOOP-PLACEMENT-FLEXIBLE
|
|
348
|
-
*/
|
|
349
392
|
if (node.type === "ForStatement" ||
|
|
350
393
|
node.type === "ForInStatement" ||
|
|
351
394
|
node.type === "ForOfStatement" ||
|
|
352
395
|
node.type === "WhileStatement" ||
|
|
353
396
|
node.type === "DoWhileStatement") {
|
|
354
|
-
return (0, branch_annotation_loop_helpers_1.gatherLoopCommentText)(sourceCode, node, beforeText);
|
|
397
|
+
return (0, branch_annotation_loop_helpers_1.gatherLoopCommentText)(sourceCode, node, annotationPlacement, beforeText);
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Gather leading comment text for a branch node.
|
|
403
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
404
|
+
* @req REQ-COMMENT-ASSOCIATION - Associate inline comments with their corresponding code branches
|
|
405
|
+
* @story docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
|
|
406
|
+
* @supports REQ-DUAL-POSITION-DETECTION
|
|
407
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG
|
|
408
|
+
*/
|
|
409
|
+
function gatherBranchCommentText(sourceCode, node, parent, annotationPlacement = "before") {
|
|
410
|
+
const beforeComments = sourceCode.getCommentsBefore(node) || [];
|
|
411
|
+
const beforeText = beforeComments.map(extractCommentValue).join(" ");
|
|
412
|
+
const handled = gatherBranchCommentTextByType(sourceCode, node, parent, {
|
|
413
|
+
annotationPlacement,
|
|
414
|
+
beforeText,
|
|
415
|
+
});
|
|
416
|
+
if (handled != null) {
|
|
417
|
+
return handled;
|
|
355
418
|
}
|
|
356
419
|
return beforeText;
|
|
357
420
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Rule } from "eslint";
|
|
2
|
+
import { type AnnotationPlacement } from "./branch-annotation-helpers";
|
|
2
3
|
/**
|
|
3
4
|
* Gather annotation text for loop branches, supporting annotations either on the
|
|
4
5
|
* loop statement itself or on the first comment lines inside the loop body.
|
|
@@ -6,4 +7,4 @@ import type { Rule } from "eslint";
|
|
|
6
7
|
* @req REQ-LOOP-ANNOTATION
|
|
7
8
|
* @req REQ-LOOP-PLACEMENT-FLEXIBLE
|
|
8
9
|
*/
|
|
9
|
-
export declare function gatherLoopCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any, beforeText: string): string;
|
|
10
|
+
export declare function gatherLoopCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any, annotationPlacement: AnnotationPlacement, beforeText: string): string;
|
|
@@ -2,19 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.gatherLoopCommentText = gatherLoopCommentText;
|
|
4
4
|
const branch_annotation_helpers_1 = require("./branch-annotation-helpers");
|
|
5
|
-
|
|
6
|
-
* Gather annotation text for loop branches, supporting annotations either on the
|
|
7
|
-
* loop statement itself or on the first comment lines inside the loop body.
|
|
8
|
-
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
9
|
-
* @req REQ-LOOP-ANNOTATION
|
|
10
|
-
* @req REQ-LOOP-PLACEMENT-FLEXIBLE
|
|
11
|
-
*/
|
|
12
|
-
function gatherLoopCommentText(sourceCode, node, beforeText) {
|
|
13
|
-
if (/@story\b/.test(beforeText) ||
|
|
14
|
-
/@req\b/.test(beforeText) ||
|
|
15
|
-
/@supports\b/.test(beforeText)) {
|
|
16
|
-
return beforeText;
|
|
17
|
-
}
|
|
5
|
+
function getInsideLoopCommentText(sourceCode, node) {
|
|
18
6
|
const body = node.body;
|
|
19
7
|
if (body &&
|
|
20
8
|
body.type === "BlockStatement" &&
|
|
@@ -32,5 +20,31 @@ function gatherLoopCommentText(sourceCode, node, beforeText) {
|
|
|
32
20
|
return insideText;
|
|
33
21
|
}
|
|
34
22
|
}
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gather annotation text for loop branches, supporting annotations either on the
|
|
27
|
+
* loop statement itself or on the first comment lines inside the loop body.
|
|
28
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
29
|
+
* @req REQ-LOOP-ANNOTATION
|
|
30
|
+
* @req REQ-LOOP-PLACEMENT-FLEXIBLE
|
|
31
|
+
*/
|
|
32
|
+
function gatherLoopCommentText(sourceCode, node, annotationPlacement, beforeText) {
|
|
33
|
+
if (annotationPlacement === "inside") {
|
|
34
|
+
const insideText = getInsideLoopCommentText(sourceCode, node);
|
|
35
|
+
if (insideText) {
|
|
36
|
+
return insideText;
|
|
37
|
+
}
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
if (/@story\b/.test(beforeText) ||
|
|
41
|
+
/@req\b/.test(beforeText) ||
|
|
42
|
+
/@supports\b/.test(beforeText)) {
|
|
43
|
+
return beforeText;
|
|
44
|
+
}
|
|
45
|
+
const insideText = getInsideLoopCommentText(sourceCode, node);
|
|
46
|
+
if (insideText) {
|
|
47
|
+
return insideText;
|
|
48
|
+
}
|
|
35
49
|
return beforeText;
|
|
36
50
|
}
|
|
@@ -52,6 +52,47 @@ function getBaseBranchIndentAndInsertPos(sourceCode, node, _annotationPlacement)
|
|
|
52
52
|
}
|
|
53
53
|
return { indent, insertPos };
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Determine whether a node represents an else-if branch that should be used for
|
|
57
|
+
* determining comment insertion position.
|
|
58
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
59
|
+
*/
|
|
60
|
+
function isElseIfBranchForInsert(node, parent) {
|
|
61
|
+
return (node &&
|
|
62
|
+
node.type === "IfStatement" &&
|
|
63
|
+
parent &&
|
|
64
|
+
parent.type === "IfStatement" &&
|
|
65
|
+
parent.alternate === node);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Compute indentation and insert position for IfStatement branches, handling
|
|
69
|
+
* both simple if and else-if cases, respecting the configured annotation
|
|
70
|
+
* placement and indentation rules.
|
|
71
|
+
* @story docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
|
|
72
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-INSIDE-BRACE-PLACEMENT REQ-PLACEMENT-CONFIG REQ-INDENTATION-CORRECT
|
|
73
|
+
*/
|
|
74
|
+
function getIfStatementIndentAndInsertPos(sourceCode, node, options, context) {
|
|
75
|
+
const { parent, annotationPlacement } = options;
|
|
76
|
+
let { indent, insertPos } = context;
|
|
77
|
+
const hasBlockConsequent = node.consequent &&
|
|
78
|
+
node.consequent.type === "BlockStatement" &&
|
|
79
|
+
node.consequent.loc &&
|
|
80
|
+
node.consequent.loc.start;
|
|
81
|
+
if (!hasBlockConsequent) {
|
|
82
|
+
return context;
|
|
83
|
+
}
|
|
84
|
+
const isElseIf = isElseIfBranchForInsert(node, parent);
|
|
85
|
+
const isSimpleIfInsidePlacement = annotationPlacement === "inside" && !isElseIf;
|
|
86
|
+
if (isSimpleIfInsidePlacement || isElseIf) {
|
|
87
|
+
const commentLine = node.consequent.loc.start.line + 1;
|
|
88
|
+
const commentLineInfo = getIndentAndInsertPosForLine(sourceCode, commentLine, indent);
|
|
89
|
+
indent = commentLineInfo.indent;
|
|
90
|
+
insertPos = commentLineInfo.insertPos;
|
|
91
|
+
context.indent = indent;
|
|
92
|
+
context.insertPos = insertPos;
|
|
93
|
+
}
|
|
94
|
+
return context;
|
|
95
|
+
}
|
|
55
96
|
/**
|
|
56
97
|
* Compute which annotations are missing for a branch based on its gathered comment text.
|
|
57
98
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
@@ -70,19 +111,14 @@ function getBranchMissingFlags(sourceCode, node, parent, annotationPlacement) {
|
|
|
70
111
|
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG
|
|
71
112
|
*/
|
|
72
113
|
function getBranchIndentAndInsertPos(sourceCode, node, parent, annotationPlacement) {
|
|
73
|
-
|
|
74
|
-
if (node.type === "IfStatement"
|
|
75
|
-
|
|
76
|
-
parent
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
node.consequent.loc.start) {
|
|
82
|
-
const commentLine = node.consequent.loc.start.line + 1;
|
|
83
|
-
const commentLineInfo = getIndentAndInsertPosForLine(sourceCode, commentLine, indent);
|
|
84
|
-
indent = commentLineInfo.indent;
|
|
85
|
-
insertPos = commentLineInfo.insertPos;
|
|
114
|
+
const { indent, insertPos } = getBaseBranchIndentAndInsertPos(sourceCode, node, annotationPlacement);
|
|
115
|
+
if (node.type === "IfStatement") {
|
|
116
|
+
const context = { indent, insertPos };
|
|
117
|
+
const updatedContext = getIfStatementIndentAndInsertPos(sourceCode, node, { parent, annotationPlacement }, context);
|
|
118
|
+
return {
|
|
119
|
+
indent: updatedContext.indent,
|
|
120
|
+
insertPos: updatedContext.insertPos,
|
|
121
|
+
};
|
|
86
122
|
}
|
|
87
123
|
return { indent, insertPos };
|
|
88
124
|
}
|
|
@@ -196,10 +196,34 @@ if (condition) {}`,
|
|
|
196
196
|
options: [{ annotationPlacement: "before" }],
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
|
-
name: "[REQ-PLACEMENT
|
|
199
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-PLACEMENT-CONFIG] if-statement annotated inside block under annotationPlacement: 'inside' (Story 028.0)",
|
|
200
|
+
code: `if (condition) {
|
|
201
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
202
|
+
// @req REQ-INSIDE-BRACE-PLACEMENT
|
|
203
|
+
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)",
|
|
200
209
|
code: `// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
201
|
-
// @req REQ-
|
|
202
|
-
|
|
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
|
+
}`,
|
|
218
|
+
options: [{ annotationPlacement: "inside" }],
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-PLACEMENT-CONFIG] for-of loop annotated inside block under annotationPlacement: 'inside' (Story 028.0)",
|
|
222
|
+
code: `for (const item of items) {
|
|
223
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
224
|
+
// @req REQ-LOOP-INSIDE
|
|
225
|
+
process(item);
|
|
226
|
+
}`,
|
|
203
227
|
options: [{ annotationPlacement: "inside" }],
|
|
204
228
|
},
|
|
205
229
|
{
|
|
@@ -422,6 +446,64 @@ if (a) {
|
|
|
422
446
|
}`,
|
|
423
447
|
errors: makeMissingAnnotationErrors("@story", "@req", "@story", "@req"),
|
|
424
448
|
},
|
|
449
|
+
{
|
|
450
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-BEFORE-BRACE-ERROR][REQ-PLACEMENT-CONFIG] before-brace annotations ignored when annotationPlacement: 'inside'",
|
|
451
|
+
code: `// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
452
|
+
// @req REQ-BEFORE-BRACE-ERROR
|
|
453
|
+
if (condition) {
|
|
454
|
+
doSomething();
|
|
455
|
+
}`,
|
|
456
|
+
options: [{ annotationPlacement: "inside" }],
|
|
457
|
+
output: `// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
458
|
+
// @req REQ-BEFORE-BRACE-ERROR
|
|
459
|
+
if (condition) {
|
|
460
|
+
// @story <story-file>.story.md
|
|
461
|
+
doSomething();
|
|
462
|
+
}`,
|
|
463
|
+
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-BEFORE-BRACE-ERROR][REQ-PLACEMENT-CONFIG] before-loop annotations ignored when annotationPlacement: 'inside' for loops",
|
|
467
|
+
code: `// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
468
|
+
// @req REQ-LOOP-BEFORE
|
|
469
|
+
for (const item of items) {
|
|
470
|
+
process(item);
|
|
471
|
+
}`,
|
|
472
|
+
options: [{ annotationPlacement: "inside" }],
|
|
473
|
+
output: `// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
474
|
+
// @req REQ-LOOP-BEFORE
|
|
475
|
+
// @story <story-file>.story.md
|
|
476
|
+
for (const item of items) {
|
|
477
|
+
process(item);
|
|
478
|
+
}`,
|
|
479
|
+
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-BEFORE-BRACE-ERROR][REQ-PLACEMENT-CONFIG] before-catch annotations ignored when annotationPlacement: 'inside' for CatchClause",
|
|
483
|
+
code: `// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
484
|
+
// @req REQ-BRANCH-TRY
|
|
485
|
+
try {
|
|
486
|
+
doSomething();
|
|
487
|
+
}
|
|
488
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
489
|
+
// @req REQ-CATCH-BEFORE
|
|
490
|
+
catch (error) {
|
|
491
|
+
handleError(error);
|
|
492
|
+
}`,
|
|
493
|
+
options: [{ annotationPlacement: "inside" }],
|
|
494
|
+
output: `// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
495
|
+
// @req REQ-BRANCH-TRY
|
|
496
|
+
try {
|
|
497
|
+
doSomething();
|
|
498
|
+
}
|
|
499
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
500
|
+
// @req REQ-CATCH-BEFORE
|
|
501
|
+
catch (error) {
|
|
502
|
+
// @story <story-file>.story.md
|
|
503
|
+
handleError(error);
|
|
504
|
+
}`,
|
|
505
|
+
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
506
|
+
},
|
|
425
507
|
],
|
|
426
508
|
});
|
|
427
509
|
runRule({
|
|
@@ -110,4 +110,137 @@ describe("validateBranchTypes helper (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () =
|
|
|
110
110
|
expect(sourceCodeLoop.getCommentsBefore).toHaveBeenCalledWith(forNode);
|
|
111
111
|
expect(loopText).toBe("@story loop branch story loop details");
|
|
112
112
|
});
|
|
113
|
+
it("[REQ-INSIDE-BRACE-PLACEMENT][REQ-PLACEMENT-CONFIG] uses inside-loop comments when annotationPlacement is 'inside' and ignores before-loop annotations", () => {
|
|
114
|
+
const sourceCode = {
|
|
115
|
+
lines: [
|
|
116
|
+
"// @story before-loop should be ignored in inside mode",
|
|
117
|
+
"for (const item of items) {",
|
|
118
|
+
" // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md",
|
|
119
|
+
" // @req REQ-LOOP-INSIDE",
|
|
120
|
+
" process(item);",
|
|
121
|
+
"}",
|
|
122
|
+
],
|
|
123
|
+
getCommentsBefore: jest
|
|
124
|
+
.fn()
|
|
125
|
+
.mockReturnValue([
|
|
126
|
+
{ value: "@story before-loop should be ignored in inside mode" },
|
|
127
|
+
]),
|
|
128
|
+
};
|
|
129
|
+
const loopNode = {
|
|
130
|
+
type: "ForOfStatement",
|
|
131
|
+
loc: {
|
|
132
|
+
start: { line: 2, column: 0 },
|
|
133
|
+
end: { line: 5, column: 1 },
|
|
134
|
+
},
|
|
135
|
+
body: {
|
|
136
|
+
type: "BlockStatement",
|
|
137
|
+
loc: {
|
|
138
|
+
start: { line: 2, column: 27 },
|
|
139
|
+
end: { line: 5, column: 1 },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
const parent = {
|
|
144
|
+
type: "BlockStatement",
|
|
145
|
+
body: [loopNode],
|
|
146
|
+
};
|
|
147
|
+
const insideText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, loopNode, parent, "inside");
|
|
148
|
+
expect(insideText).toContain("@story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md");
|
|
149
|
+
expect(insideText).toContain("@req REQ-LOOP-INSIDE");
|
|
150
|
+
expect(insideText).not.toContain("before-loop should be ignored");
|
|
151
|
+
});
|
|
152
|
+
it("[REQ-INSIDE-BRACE-PLACEMENT][REQ-PLACEMENT-CONFIG] uses inside-catch comments when annotationPlacement is 'inside' and ignores before-catch annotations", () => {
|
|
153
|
+
const sourceCode = {
|
|
154
|
+
lines: [
|
|
155
|
+
"// @story before-catch should be ignored in inside mode",
|
|
156
|
+
"try {",
|
|
157
|
+
" doSomething();",
|
|
158
|
+
"}",
|
|
159
|
+
"catch (error) {",
|
|
160
|
+
" // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md",
|
|
161
|
+
" // @req REQ-CATCH-INSIDE",
|
|
162
|
+
" handleError(error);",
|
|
163
|
+
"}",
|
|
164
|
+
],
|
|
165
|
+
getCommentsBefore: jest
|
|
166
|
+
.fn()
|
|
167
|
+
.mockReturnValue([
|
|
168
|
+
{ value: "@story before-catch should be ignored in inside mode" },
|
|
169
|
+
]),
|
|
170
|
+
};
|
|
171
|
+
const catchNode = {
|
|
172
|
+
type: "CatchClause",
|
|
173
|
+
loc: {
|
|
174
|
+
start: { line: 5, column: 0 },
|
|
175
|
+
end: { line: 8, column: 1 },
|
|
176
|
+
},
|
|
177
|
+
body: {
|
|
178
|
+
type: "BlockStatement",
|
|
179
|
+
loc: {
|
|
180
|
+
start: { line: 5, column: 14 },
|
|
181
|
+
end: { line: 8, column: 1 },
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
const parent = {
|
|
186
|
+
type: "TryStatement",
|
|
187
|
+
handler: catchNode,
|
|
188
|
+
};
|
|
189
|
+
const insideText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, catchNode, parent, "inside");
|
|
190
|
+
expect(insideText).toContain("@story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md");
|
|
191
|
+
expect(insideText).toContain("@req REQ-CATCH-INSIDE");
|
|
192
|
+
expect(insideText).not.toContain("before-catch should be ignored");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
/**
|
|
196
|
+
* Tests for annotationPlacement wiring at helper level
|
|
197
|
+
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
198
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG
|
|
199
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-DEFAULT-BACKWARD-COMPAT
|
|
200
|
+
*/
|
|
201
|
+
describe("gatherBranchCommentText annotationPlacement wiring (Story 028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION)", () => {
|
|
202
|
+
it("[REQ-PLACEMENT-CONFIG][REQ-DEFAULT-BACKWARD-COMPAT] honors configured placement for simple if-statements", () => {
|
|
203
|
+
const sourceCode = {
|
|
204
|
+
lines: [
|
|
205
|
+
"function demo() {",
|
|
206
|
+
" if (condition) {",
|
|
207
|
+
" // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md",
|
|
208
|
+
" // @req REQ-INSIDE",
|
|
209
|
+
" doSomething();",
|
|
210
|
+
" }",
|
|
211
|
+
"}",
|
|
212
|
+
],
|
|
213
|
+
getCommentsBefore: jest
|
|
214
|
+
.fn()
|
|
215
|
+
.mockReturnValue([
|
|
216
|
+
{ value: "@story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md" },
|
|
217
|
+
{ value: "@req REQ-BEFORE" },
|
|
218
|
+
]),
|
|
219
|
+
};
|
|
220
|
+
const ifNode = {
|
|
221
|
+
type: "IfStatement",
|
|
222
|
+
loc: {
|
|
223
|
+
start: { line: 2, column: 2 },
|
|
224
|
+
end: { line: 5, column: 3 },
|
|
225
|
+
},
|
|
226
|
+
consequent: {
|
|
227
|
+
type: "BlockStatement",
|
|
228
|
+
loc: {
|
|
229
|
+
start: { line: 2, column: 18 },
|
|
230
|
+
end: { line: 5, column: 3 },
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
const parent = {
|
|
235
|
+
type: "BlockStatement",
|
|
236
|
+
body: [ifNode],
|
|
237
|
+
};
|
|
238
|
+
const beforeText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, ifNode, parent, "before");
|
|
239
|
+
expect(beforeText).toContain("@story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md");
|
|
240
|
+
expect(beforeText).toContain("@req REQ-BEFORE");
|
|
241
|
+
const insideText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, ifNode, parent, "inside");
|
|
242
|
+
expect(insideText).toContain("@story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md");
|
|
243
|
+
expect(insideText).toContain("@req REQ-INSIDE");
|
|
244
|
+
expect(insideText).not.toContain("@req REQ-BEFORE");
|
|
245
|
+
});
|
|
113
246
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.1",
|
|
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",
|