eslint-plugin-traceability 1.19.0 → 1.19.2
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/lib/src/utils/branch-annotation-helpers.js +98 -152
- package/lib/src/utils/branch-annotation-if-helpers.d.ts +12 -0
- package/lib/src/utils/branch-annotation-if-helpers.js +137 -0
- 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 +2 -1
- package/lib/tests/rules/require-branch-annotation.test.js +118 -0
- package/lib/tests/utils/branch-annotation-else-if-insert-position.test.js +6 -5
- package/lib/tests/utils/branch-annotation-helpers.test.js +149 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
## [1.19.2](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.19.1...v1.19.2) (2025-12-18)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
###
|
|
4
|
+
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* honor annotationPlacement for else-if branches and refactor helpers ([8f747dc](https://github.com/voder-ai/eslint-plugin-traceability/commit/8f747dc8546ae8e5033cfe4aa9c394ca668a45d5))
|
|
7
7
|
|
|
8
8
|
# Changelog
|
|
9
9
|
|
|
@@ -9,6 +9,7 @@ exports.reportMissingReq = reportMissingReq;
|
|
|
9
9
|
const branch_annotation_report_helpers_1 = require("./branch-annotation-report-helpers");
|
|
10
10
|
Object.defineProperty(exports, "reportMissingAnnotations", { enumerable: true, get: function () { return branch_annotation_report_helpers_1.reportMissingAnnotations; } });
|
|
11
11
|
const branch_annotation_loop_helpers_1 = require("./branch-annotation-loop-helpers");
|
|
12
|
+
const branch_annotation_if_helpers_1 = require("./branch-annotation-if-helpers");
|
|
12
13
|
const PRE_COMMENT_OFFSET = 2; // number of lines above branch to inspect for comments
|
|
13
14
|
/**
|
|
14
15
|
* Valid branch types for require-branch-annotation rule.
|
|
@@ -153,24 +154,7 @@ function scanCommentLinesInRange(lines, startIndex, endIndexInclusive) {
|
|
|
153
154
|
}
|
|
154
155
|
return comments.join(" ");
|
|
155
156
|
}
|
|
156
|
-
|
|
157
|
-
function isElseIfBranch(node, parent) {
|
|
158
|
-
return (node &&
|
|
159
|
-
node.type === "IfStatement" &&
|
|
160
|
-
parent &&
|
|
161
|
-
parent.type === "IfStatement" &&
|
|
162
|
-
parent.alternate === node);
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Gather annotation text for CatchClause nodes, supporting both before-catch and inside-catch positions.
|
|
166
|
-
* @story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md
|
|
167
|
-
* @req REQ-DUAL-POSITION-DETECTION
|
|
168
|
-
* @req REQ-FALLBACK-LOGIC
|
|
169
|
-
*/
|
|
170
|
-
function gatherCatchClauseCommentText(sourceCode, node, beforeText) {
|
|
171
|
-
if (/@story\b/.test(beforeText) || /@req\b/.test(beforeText)) {
|
|
172
|
-
return beforeText;
|
|
173
|
-
}
|
|
157
|
+
function getInsideCatchCommentText(sourceCode, node) {
|
|
174
158
|
const getCommentsInside = sourceCode.getCommentsInside;
|
|
175
159
|
if (node.body && typeof getCommentsInside === "function") {
|
|
176
160
|
try {
|
|
@@ -193,6 +177,31 @@ function gatherCatchClauseCommentText(sourceCode, node, beforeText) {
|
|
|
193
177
|
return insideText;
|
|
194
178
|
}
|
|
195
179
|
}
|
|
180
|
+
return "";
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Gather annotation text for CatchClause nodes, supporting both before-catch and inside-catch positions.
|
|
184
|
+
* @story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md
|
|
185
|
+
* @req REQ-DUAL-POSITION-DETECTION
|
|
186
|
+
* @req REQ-FALLBACK-LOGIC
|
|
187
|
+
*/
|
|
188
|
+
function gatherCatchClauseCommentText(sourceCode, node, annotationPlacement, beforeText) {
|
|
189
|
+
if (annotationPlacement === "inside") {
|
|
190
|
+
const insideText = getInsideCatchCommentText(sourceCode, node);
|
|
191
|
+
if (insideText) {
|
|
192
|
+
return insideText;
|
|
193
|
+
}
|
|
194
|
+
return "";
|
|
195
|
+
}
|
|
196
|
+
if (/@story\b/.test(beforeText) ||
|
|
197
|
+
/@req\b/.test(beforeText) ||
|
|
198
|
+
/@supports\b/.test(beforeText)) {
|
|
199
|
+
return beforeText;
|
|
200
|
+
}
|
|
201
|
+
const insideText = getInsideCatchCommentText(sourceCode, node);
|
|
202
|
+
if (insideText) {
|
|
203
|
+
return insideText;
|
|
204
|
+
}
|
|
196
205
|
return beforeText;
|
|
197
206
|
}
|
|
198
207
|
/**
|
|
@@ -245,119 +254,89 @@ function gatherSimpleIfCommentText(sourceCode, node, annotationPlacement, before
|
|
|
245
254
|
return "";
|
|
246
255
|
}
|
|
247
256
|
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
248
|
-
function
|
|
257
|
+
function gatherSwitchCaseCommentText(sourceCode, node) {
|
|
249
258
|
const lines = sourceCode.lines;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
const startLine = node.loc.start.line - 1;
|
|
259
|
+
const startLine = node.loc.start.line;
|
|
260
|
+
let i = startLine - PRE_COMMENT_OFFSET;
|
|
254
261
|
const comments = [];
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
while (i >= 0 && scanned < PRE_COMMENT_OFFSET) {
|
|
258
|
-
const commentText = getCommentTextAtLine(lines, i);
|
|
259
|
-
if (!commentText) {
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
comments.unshift(commentText);
|
|
262
|
+
while (i >= 0 && /^\s*(\/\/|\/\*)/.test(lines[i])) {
|
|
263
|
+
comments.unshift(lines[i].trim());
|
|
263
264
|
i--;
|
|
264
|
-
scanned++;
|
|
265
265
|
}
|
|
266
266
|
return comments.join(" ");
|
|
267
267
|
}
|
|
268
|
-
/**
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
function scanElseIfBetweenConditionAndBody(sourceCode, node) {
|
|
281
|
-
const lines = sourceCode.lines;
|
|
282
|
-
const conditionEndLine = node.test.loc.end.line;
|
|
283
|
-
const consequentStartLine = node.consequent.loc.start.line;
|
|
284
|
-
// Lines in sourceCode are 0-based indexes, but loc.line values are 1-based.
|
|
285
|
-
// We want to scan comments strictly between the condition and the
|
|
286
|
-
// consequent body, so we start at the line after the condition's end and
|
|
287
|
-
// stop at the line immediately before the consequent's starting line.
|
|
288
|
-
const startIndex = conditionEndLine; // already the next logical line index when 0-based
|
|
289
|
-
const endIndexExclusive = consequentStartLine - 1;
|
|
290
|
-
if (endIndexExclusive <= startIndex) {
|
|
291
|
-
return "";
|
|
268
|
+
/**
|
|
269
|
+
* Helper that gathers comment text for non-IfStatement branch types using
|
|
270
|
+
* straightforward behavior (SwitchCase, CatchClause, and loop statements).
|
|
271
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
272
|
+
* @supports REQ-COMMENT-ASSOCIATION
|
|
273
|
+
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
274
|
+
* @supports REQ-PLACEMENT-CONFIG
|
|
275
|
+
*/
|
|
276
|
+
function gatherNonIfBranchCommentText(sourceCode, node, context) {
|
|
277
|
+
const { annotationPlacement, beforeText } = context;
|
|
278
|
+
if (node.type === "SwitchCase") {
|
|
279
|
+
return gatherSwitchCaseCommentText(sourceCode, node);
|
|
292
280
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
296
|
-
function scanElseIfInsideBlockComments(sourceCode, node) {
|
|
297
|
-
const lines = sourceCode.lines;
|
|
298
|
-
const consequentStartLine = node.consequent.loc.start.line;
|
|
299
|
-
const comments = [];
|
|
300
|
-
// Intentionally start from the block's start line (using the same 1-based line value as provided by the parser)
|
|
301
|
-
// so that, when indexing into sourceCode.lines, this corresponds to the first logical line inside the block body
|
|
302
|
-
// for typical formatter layouts.
|
|
303
|
-
let lineIndex = consequentStartLine;
|
|
304
|
-
while (lineIndex < lines.length) {
|
|
305
|
-
if (!collectCommentLine(lines, lineIndex, comments)) {
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
lineIndex++;
|
|
281
|
+
if (node.type === "CatchClause") {
|
|
282
|
+
return gatherCatchClauseCommentText(sourceCode, node, annotationPlacement, beforeText);
|
|
309
283
|
}
|
|
310
|
-
|
|
284
|
+
if (node.type === "ForStatement" ||
|
|
285
|
+
node.type === "ForInStatement" ||
|
|
286
|
+
node.type === "ForOfStatement" ||
|
|
287
|
+
node.type === "WhileStatement" ||
|
|
288
|
+
node.type === "DoWhileStatement") {
|
|
289
|
+
return (0, branch_annotation_loop_helpers_1.gatherLoopCommentText)(sourceCode, node, annotationPlacement, beforeText);
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
311
292
|
}
|
|
312
293
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
294
|
+
* Helper that gathers comment text for IfStatement branches, including both
|
|
295
|
+
* simple if and else-if specific logic.
|
|
296
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
297
|
+
* @supports REQ-COMMENT-ASSOCIATION
|
|
316
298
|
* @story docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
|
|
317
299
|
* @supports REQ-DUAL-POSITION-DETECTION
|
|
318
|
-
* @
|
|
300
|
+
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
301
|
+
* @supports REQ-PLACEMENT-CONFIG
|
|
302
|
+
* @supports REQ-DEFAULT-BACKWARD-COMPAT
|
|
319
303
|
*/
|
|
320
|
-
function
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
/@supports\b/.test(beforeText))) {
|
|
325
|
-
return beforeText;
|
|
326
|
-
}
|
|
327
|
-
if (!isElseIfBranch(node, parent)) {
|
|
328
|
-
return beforeText;
|
|
329
|
-
}
|
|
330
|
-
const beforeElseText = scanElseIfPrecedingComments(sourceCode, node);
|
|
331
|
-
if (beforeElseText &&
|
|
332
|
-
(/@story\b/.test(beforeElseText) ||
|
|
333
|
-
/@req\b/.test(beforeElseText) ||
|
|
334
|
-
/@supports\b/.test(beforeElseText))) {
|
|
335
|
-
return beforeElseText;
|
|
304
|
+
function gatherIfBranchCommentText(sourceCode, node, parent, context) {
|
|
305
|
+
const { annotationPlacement, beforeText } = context;
|
|
306
|
+
if (node.type !== "IfStatement") {
|
|
307
|
+
return null;
|
|
336
308
|
}
|
|
337
|
-
if (
|
|
338
|
-
return
|
|
309
|
+
if ((0, branch_annotation_if_helpers_1.isElseIfBranch)(node, parent)) {
|
|
310
|
+
return (0, branch_annotation_if_helpers_1.gatherElseIfCommentText)(sourceCode, node, parent, {
|
|
311
|
+
annotationPlacement,
|
|
312
|
+
beforeText,
|
|
313
|
+
});
|
|
339
314
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
315
|
+
return gatherSimpleIfCommentText(sourceCode, node, annotationPlacement, beforeText);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Internal helper that performs type-based dispatch for gathering branch comment text.
|
|
319
|
+
* This keeps the public gatherBranchCommentTextByType wrapper small for ESLint limits.
|
|
320
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
321
|
+
* @supports REQ-COMMENT-ASSOCIATION
|
|
322
|
+
* @story docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
|
|
323
|
+
* @supports REQ-DUAL-POSITION-DETECTION
|
|
324
|
+
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
325
|
+
* @supports REQ-PLACEMENT-CONFIG
|
|
326
|
+
*/
|
|
327
|
+
function gatherBranchCommentTextByTypeInternal(sourceCode, node, parent, context) {
|
|
328
|
+
const nonIfResult = gatherNonIfBranchCommentText(sourceCode, node, context);
|
|
329
|
+
if (nonIfResult != null) {
|
|
330
|
+
return nonIfResult;
|
|
343
331
|
}
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
346
|
-
return
|
|
332
|
+
const ifResult = gatherIfBranchCommentText(sourceCode, node, parent, context);
|
|
333
|
+
if (ifResult != null) {
|
|
334
|
+
return ifResult;
|
|
347
335
|
}
|
|
348
|
-
return
|
|
336
|
+
return null;
|
|
349
337
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const lines = sourceCode.lines;
|
|
353
|
-
const startLine = node.loc.start.line;
|
|
354
|
-
let i = startLine - PRE_COMMENT_OFFSET;
|
|
355
|
-
const comments = [];
|
|
356
|
-
while (i >= 0 && /^\s*(\/\/|\/\*)/.test(lines[i])) {
|
|
357
|
-
comments.unshift(lines[i].trim());
|
|
358
|
-
i--;
|
|
359
|
-
}
|
|
360
|
-
return comments.join(" ");
|
|
338
|
+
function gatherBranchCommentTextByType(sourceCode, node, parent, context) {
|
|
339
|
+
return gatherBranchCommentTextByTypeInternal(sourceCode, node, parent, context);
|
|
361
340
|
}
|
|
362
341
|
/**
|
|
363
342
|
* Gather leading comment text for a branch node.
|
|
@@ -368,47 +347,14 @@ function gatherSwitchCaseCommentText(sourceCode, node) {
|
|
|
368
347
|
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG
|
|
369
348
|
*/
|
|
370
349
|
function gatherBranchCommentText(sourceCode, node, parent, annotationPlacement = "before") {
|
|
371
|
-
/**
|
|
372
|
-
* Conditional branch for SwitchCase nodes that may include inline comments.
|
|
373
|
-
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
374
|
-
* @req REQ-TRACEABILITY-SWITCHCASE-COMMENTS - Trace collection of preceding comments for SwitchCase
|
|
375
|
-
*/
|
|
376
|
-
if (node.type === "SwitchCase") {
|
|
377
|
-
return gatherSwitchCaseCommentText(sourceCode, node);
|
|
378
|
-
}
|
|
379
350
|
const beforeComments = sourceCode.getCommentsBefore(node) || [];
|
|
380
351
|
const beforeText = beforeComments.map(extractCommentValue).join(" ");
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
* the configured annotation placement (before or inside braces).
|
|
388
|
-
* @story docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
|
|
389
|
-
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
390
|
-
* @supports REQ-DUAL-POSITION-DETECTION
|
|
391
|
-
* @supports REQ-INSIDE-BRACE-PLACEMENT
|
|
392
|
-
* @supports REQ-PLACEMENT-CONFIG
|
|
393
|
-
* @supports REQ-DEFAULT-BACKWARD-COMPAT
|
|
394
|
-
*/
|
|
395
|
-
if (node.type === "IfStatement") {
|
|
396
|
-
if (isElseIfBranch(node, parent)) {
|
|
397
|
-
return gatherElseIfCommentText(sourceCode, node, parent, beforeText);
|
|
398
|
-
}
|
|
399
|
-
return gatherSimpleIfCommentText(sourceCode, node, annotationPlacement, beforeText);
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Conditional branch for loop nodes that may include annotations either on the loop
|
|
403
|
-
* statement itself or at the top of the loop body, allowing flexible placement.
|
|
404
|
-
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-LOOP-ANNOTATION REQ-LOOP-PLACEMENT-FLEXIBLE
|
|
405
|
-
*/
|
|
406
|
-
if (node.type === "ForStatement" ||
|
|
407
|
-
node.type === "ForInStatement" ||
|
|
408
|
-
node.type === "ForOfStatement" ||
|
|
409
|
-
node.type === "WhileStatement" ||
|
|
410
|
-
node.type === "DoWhileStatement") {
|
|
411
|
-
return (0, branch_annotation_loop_helpers_1.gatherLoopCommentText)(sourceCode, node, beforeText);
|
|
352
|
+
const handled = gatherBranchCommentTextByType(sourceCode, node, parent, {
|
|
353
|
+
annotationPlacement,
|
|
354
|
+
beforeText,
|
|
355
|
+
});
|
|
356
|
+
if (handled != null) {
|
|
357
|
+
return handled;
|
|
412
358
|
}
|
|
413
359
|
return beforeText;
|
|
414
360
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
import type { AnnotationPlacement } from "./branch-annotation-helpers";
|
|
3
|
+
export declare function isElseIfBranch(node: any, parent: any | undefined): boolean;
|
|
4
|
+
export declare function hasValidElseIfBlockLoc(node: any): boolean;
|
|
5
|
+
export declare function scanElseIfPrecedingComments(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any): string;
|
|
6
|
+
export declare function scanElseIfBetweenConditionAndBody(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any): string;
|
|
7
|
+
export declare function scanElseIfInsideBlockComments(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any): string;
|
|
8
|
+
export declare function getInsideElseIfCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any): string;
|
|
9
|
+
export declare function gatherElseIfCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any, parent: any | undefined, options: {
|
|
10
|
+
annotationPlacement: AnnotationPlacement;
|
|
11
|
+
beforeText: string;
|
|
12
|
+
}): string;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isElseIfBranch = isElseIfBranch;
|
|
4
|
+
exports.hasValidElseIfBlockLoc = hasValidElseIfBlockLoc;
|
|
5
|
+
exports.scanElseIfPrecedingComments = scanElseIfPrecedingComments;
|
|
6
|
+
exports.scanElseIfBetweenConditionAndBody = scanElseIfBetweenConditionAndBody;
|
|
7
|
+
exports.scanElseIfInsideBlockComments = scanElseIfInsideBlockComments;
|
|
8
|
+
exports.getInsideElseIfCommentText = getInsideElseIfCommentText;
|
|
9
|
+
exports.gatherElseIfCommentText = gatherElseIfCommentText;
|
|
10
|
+
const branch_annotation_helpers_1 = require("./branch-annotation-helpers");
|
|
11
|
+
/**
|
|
12
|
+
* Small shared helpers for IfStatement/else-if specific annotation handling.
|
|
13
|
+
* Extracted from branch-annotation-helpers to keep that file within ESLint
|
|
14
|
+
* max-lines limits while preserving behaviour.
|
|
15
|
+
*
|
|
16
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
17
|
+
* @story docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
|
|
18
|
+
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
19
|
+
*/
|
|
20
|
+
const PRE_COMMENT_OFFSET = 2; // kept in sync with main helpers
|
|
21
|
+
function getCommentTextAtLine(lines, index) {
|
|
22
|
+
const line = lines[index];
|
|
23
|
+
if (!line || !line.trim()) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (!/^\s*(\/\/|\/\*)/.test(line)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return line.trim();
|
|
30
|
+
}
|
|
31
|
+
function isElseIfBranch(node, parent) {
|
|
32
|
+
return (node &&
|
|
33
|
+
node.type === "IfStatement" &&
|
|
34
|
+
parent &&
|
|
35
|
+
parent.type === "IfStatement" &&
|
|
36
|
+
parent.alternate === node);
|
|
37
|
+
}
|
|
38
|
+
function hasValidElseIfBlockLoc(node) {
|
|
39
|
+
const hasBlockConsequent = node.consequent &&
|
|
40
|
+
node.consequent.type === "BlockStatement" &&
|
|
41
|
+
node.consequent.loc &&
|
|
42
|
+
node.consequent.loc.start;
|
|
43
|
+
return !!(node.test &&
|
|
44
|
+
node.test.loc &&
|
|
45
|
+
node.test.loc.end &&
|
|
46
|
+
hasBlockConsequent);
|
|
47
|
+
}
|
|
48
|
+
function scanElseIfPrecedingComments(sourceCode, node) {
|
|
49
|
+
const lines = sourceCode.lines;
|
|
50
|
+
if (!node.loc || !node.loc.start || typeof node.loc.start.line !== "number") {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
const startLine = node.loc.start.line - 1;
|
|
54
|
+
const comments = [];
|
|
55
|
+
let i = startLine - 1;
|
|
56
|
+
let scanned = 0;
|
|
57
|
+
while (i >= 0 && scanned < PRE_COMMENT_OFFSET) {
|
|
58
|
+
const commentText = getCommentTextAtLine(lines, i);
|
|
59
|
+
if (!commentText) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
comments.unshift(commentText);
|
|
63
|
+
i--;
|
|
64
|
+
scanned++;
|
|
65
|
+
}
|
|
66
|
+
return comments.join(" ");
|
|
67
|
+
}
|
|
68
|
+
function scanElseIfBetweenConditionAndBody(sourceCode, node) {
|
|
69
|
+
const lines = sourceCode.lines;
|
|
70
|
+
const conditionEndLine = node.test.loc.end.line;
|
|
71
|
+
const consequentStartLine = node.consequent.loc.start.line;
|
|
72
|
+
const startIndex = conditionEndLine;
|
|
73
|
+
const endIndexExclusive = consequentStartLine - 1;
|
|
74
|
+
if (endIndexExclusive <= startIndex) {
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
return (0, branch_annotation_helpers_1.scanCommentLinesInRange)(lines, startIndex, endIndexExclusive - 1);
|
|
78
|
+
}
|
|
79
|
+
function scanElseIfInsideBlockComments(sourceCode, node) {
|
|
80
|
+
const lines = sourceCode.lines;
|
|
81
|
+
const consequentStartLine = node.consequent.loc.start.line;
|
|
82
|
+
const comments = [];
|
|
83
|
+
let lineIndex = consequentStartLine;
|
|
84
|
+
while (lineIndex < lines.length) {
|
|
85
|
+
const lineText = getCommentTextAtLine(lines, lineIndex);
|
|
86
|
+
if (!lineText) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
comments.push(lineText);
|
|
90
|
+
lineIndex++;
|
|
91
|
+
}
|
|
92
|
+
return comments.join(" ");
|
|
93
|
+
}
|
|
94
|
+
function getInsideElseIfCommentText(sourceCode, node) {
|
|
95
|
+
if (!hasValidElseIfBlockLoc(node)) {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
const insideText = scanElseIfInsideBlockComments(sourceCode, node);
|
|
99
|
+
if (insideText) {
|
|
100
|
+
return insideText;
|
|
101
|
+
}
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
function gatherElseIfCommentText(sourceCode, node, parent, options) {
|
|
105
|
+
const { annotationPlacement, beforeText } = options;
|
|
106
|
+
if (!isElseIfBranch(node, parent)) {
|
|
107
|
+
return beforeText;
|
|
108
|
+
}
|
|
109
|
+
if (annotationPlacement === "inside") {
|
|
110
|
+
return getInsideElseIfCommentText(sourceCode, node);
|
|
111
|
+
}
|
|
112
|
+
if (beforeText &&
|
|
113
|
+
(/@story\b/.test(beforeText) ||
|
|
114
|
+
/@req\b/.test(beforeText) ||
|
|
115
|
+
/@supports\b/.test(beforeText))) {
|
|
116
|
+
return beforeText;
|
|
117
|
+
}
|
|
118
|
+
const beforeElseText = scanElseIfPrecedingComments(sourceCode, node);
|
|
119
|
+
if (beforeElseText &&
|
|
120
|
+
(/@story\b/.test(beforeElseText) ||
|
|
121
|
+
/@req\b/.test(beforeElseText) ||
|
|
122
|
+
/@supports\b/.test(beforeElseText))) {
|
|
123
|
+
return beforeElseText;
|
|
124
|
+
}
|
|
125
|
+
if (!hasValidElseIfBlockLoc(node)) {
|
|
126
|
+
return beforeText;
|
|
127
|
+
}
|
|
128
|
+
const betweenText = scanElseIfBetweenConditionAndBody(sourceCode, node);
|
|
129
|
+
if (betweenText) {
|
|
130
|
+
return betweenText;
|
|
131
|
+
}
|
|
132
|
+
const insideText = scanElseIfInsideBlockComments(sourceCode, node);
|
|
133
|
+
if (insideText) {
|
|
134
|
+
return insideText;
|
|
135
|
+
}
|
|
136
|
+
return beforeText;
|
|
137
|
+
}
|
|
@@ -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
|
}
|
|
@@ -83,7 +83,8 @@ function getIfStatementIndentAndInsertPos(sourceCode, node, options, context) {
|
|
|
83
83
|
}
|
|
84
84
|
const isElseIf = isElseIfBranchForInsert(node, parent);
|
|
85
85
|
const isSimpleIfInsidePlacement = annotationPlacement === "inside" && !isElseIf;
|
|
86
|
-
if (
|
|
86
|
+
if (annotationPlacement === "inside" &&
|
|
87
|
+
(isSimpleIfInsidePlacement || isElseIf)) {
|
|
87
88
|
const commentLine = node.consequent.loc.start.line + 1;
|
|
88
89
|
const commentLineInfo = getIndentAndInsertPosForLine(sourceCode, commentLine, indent);
|
|
89
90
|
indent = commentLineInfo.indent;
|
|
@@ -201,6 +201,28 @@ if (condition) {}`,
|
|
|
201
201
|
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
202
202
|
// @req REQ-INSIDE-BRACE-PLACEMENT
|
|
203
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)",
|
|
209
|
+
code: `// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
210
|
+
// @req REQ-BRANCH-TRY
|
|
211
|
+
try {
|
|
212
|
+
doSomething();
|
|
213
|
+
} catch (error) {
|
|
214
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
215
|
+
// @req REQ-INSIDE-CATCH
|
|
216
|
+
handleError(error);
|
|
217
|
+
}`,
|
|
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);
|
|
204
226
|
}`,
|
|
205
227
|
options: [{ annotationPlacement: "inside" }],
|
|
206
228
|
},
|
|
@@ -437,6 +459,102 @@ if (condition) {
|
|
|
437
459
|
if (condition) {
|
|
438
460
|
// @story <story-file>.story.md
|
|
439
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
|
+
},
|
|
507
|
+
{
|
|
508
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-BEFORE-BRACE-ERROR][REQ-PLACEMENT-CONFIG] before-else-if annotations ignored when annotationPlacement: 'inside' for else-if branch (Story 028.0)",
|
|
509
|
+
code: `if (a) {
|
|
510
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
511
|
+
// @req REQ-OUTER-IF-INSIDE
|
|
512
|
+
doA();
|
|
513
|
+
}
|
|
514
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
515
|
+
// @req REQ-ELSE-IF-BEFORE
|
|
516
|
+
else if (b) {
|
|
517
|
+
doB();
|
|
518
|
+
}`,
|
|
519
|
+
options: [{ annotationPlacement: "inside" }],
|
|
520
|
+
output: `if (a) {
|
|
521
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
522
|
+
// @req REQ-OUTER-IF-INSIDE
|
|
523
|
+
doA();
|
|
524
|
+
}
|
|
525
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
526
|
+
// @req REQ-ELSE-IF-BEFORE
|
|
527
|
+
else if (b) {
|
|
528
|
+
// @story <story-file>.story.md
|
|
529
|
+
doB();
|
|
530
|
+
}`,
|
|
531
|
+
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: "[REQ-INSIDE-BRACE-PLACEMENT][REQ-PLACEMENT-CONFIG] else-if branch annotated inside block but initial if branch missing annotation under annotationPlacement: 'inside' (Story 028.0)",
|
|
535
|
+
code: `// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
536
|
+
// @req REQ-INSIDE-OUTER-IF
|
|
537
|
+
if (a) {
|
|
538
|
+
doA();
|
|
539
|
+
} else if (b) {
|
|
540
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
541
|
+
// @req REQ-INSIDE-ELSE-IF
|
|
542
|
+
doB();
|
|
543
|
+
} else {
|
|
544
|
+
doC();
|
|
545
|
+
}`,
|
|
546
|
+
options: [{ annotationPlacement: "inside" }],
|
|
547
|
+
output: `// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
548
|
+
// @req REQ-INSIDE-OUTER-IF
|
|
549
|
+
if (a) {
|
|
550
|
+
// @story <story-file>.story.md
|
|
551
|
+
doA();
|
|
552
|
+
} else if (b) {
|
|
553
|
+
// @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
554
|
+
// @req REQ-INSIDE-ELSE-IF
|
|
555
|
+
doB();
|
|
556
|
+
} else {
|
|
557
|
+
doC();
|
|
440
558
|
}`,
|
|
441
559
|
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
442
560
|
},
|
|
@@ -8,7 +8,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
8
8
|
*/
|
|
9
9
|
const branch_annotation_helpers_1 = require("../../src/utils/branch-annotation-helpers");
|
|
10
10
|
describe("Else-if insert position (Story 026.0-DEV-ELSE-IF-ANNOTATION-POSITION)", () => {
|
|
11
|
-
it("[REQ-PRETTIER-AUTOFIX-ELSE-IF] inserts annotations
|
|
11
|
+
it("[REQ-PRETTIER-AUTOFIX-ELSE-IF] inserts annotations before the else-if line in Prettier-compatible default 'before' mode", () => {
|
|
12
12
|
const lines = [
|
|
13
13
|
"if (a) {",
|
|
14
14
|
" doA();",
|
|
@@ -24,6 +24,7 @@ describe("Else-if insert position (Story 026.0-DEV-ELSE-IF-ANNOTATION-POSITION)"
|
|
|
24
24
|
})),
|
|
25
25
|
};
|
|
26
26
|
const context = {
|
|
27
|
+
options: [{ annotationPlacement: "before" }],
|
|
27
28
|
getSourceCode() {
|
|
28
29
|
return {
|
|
29
30
|
lines,
|
|
@@ -69,12 +70,12 @@ describe("Else-if insert position (Story 026.0-DEV-ELSE-IF-ANNOTATION-POSITION)"
|
|
|
69
70
|
expect(fixer.insertTextBeforeRange).toHaveBeenCalledTimes(1);
|
|
70
71
|
const [range, text] = fixer.insertTextBeforeRange.mock
|
|
71
72
|
.calls[0];
|
|
72
|
-
// ensure we are inserting before the
|
|
73
|
+
// ensure we are inserting before the else-if line (line 4) when placement is 'before'
|
|
73
74
|
const expectedIndex = context
|
|
74
75
|
.getSourceCode()
|
|
75
|
-
.getIndexFromLoc({ line:
|
|
76
|
+
.getIndexFromLoc({ line: 4, column: 0 });
|
|
76
77
|
expect(range).toEqual([expectedIndex, expectedIndex]);
|
|
77
|
-
// and that the inserted text is prefixed with the
|
|
78
|
-
expect(text.startsWith("
|
|
78
|
+
// and that the inserted text is prefixed with the base indentation from line 4
|
|
79
|
+
expect(text.startsWith("")).toBe(true);
|
|
79
80
|
});
|
|
80
81
|
});
|
|
@@ -110,6 +110,87 @@ 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
|
+
});
|
|
113
194
|
});
|
|
114
195
|
/**
|
|
115
196
|
* Tests for annotationPlacement wiring at helper level
|
|
@@ -162,4 +243,72 @@ describe("gatherBranchCommentText annotationPlacement wiring (Story 028.0-DEV-AN
|
|
|
162
243
|
expect(insideText).toContain("@req REQ-INSIDE");
|
|
163
244
|
expect(insideText).not.toContain("@req REQ-BEFORE");
|
|
164
245
|
});
|
|
246
|
+
it("[REQ-PLACEMENT-CONFIG][REQ-DEFAULT-BACKWARD-COMPAT] honors Story 028.0 inside-placement semantics for else-if branches while preserving Story 026.0 before-else behavior", () => {
|
|
247
|
+
const sourceCode = {
|
|
248
|
+
lines: [
|
|
249
|
+
"function demoElseIf(x) {", // 1
|
|
250
|
+
" if (x === 1) {", // 2
|
|
251
|
+
" // @story inside-if", // 3
|
|
252
|
+
" doOne();", // 4
|
|
253
|
+
" }", // 5
|
|
254
|
+
" // @story docs/stories/026.0-DEV-BRANCH-ANNOTATIONS-ELSE-BRANCHES.story.md", // 6 (before else-if)
|
|
255
|
+
" // @req REQ-BEFORE-ELSE", // 7
|
|
256
|
+
" else if (x === 2) {", // 8
|
|
257
|
+
" // @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md", // 9 (inside else-if)
|
|
258
|
+
" // @req REQ-ELSE-IF-INSIDE", // 10
|
|
259
|
+
" doTwo();", // 11
|
|
260
|
+
" }", // 12
|
|
261
|
+
"}", // 13
|
|
262
|
+
],
|
|
263
|
+
getCommentsBefore: jest.fn().mockImplementation((node) => {
|
|
264
|
+
// Simulate ESLint getCommentsBefore only returning comments that are truly
|
|
265
|
+
// "before" the node they are querying.
|
|
266
|
+
// Our chain has:
|
|
267
|
+
// - before-if comments not used in this test
|
|
268
|
+
// - line 6-7 as before-else-if comments
|
|
269
|
+
if (node && node.loc && node.loc.start && node.loc.start.line === 2) {
|
|
270
|
+
// before the initial if (not used in assertions here)
|
|
271
|
+
return [
|
|
272
|
+
{ value: "@story BEFORE-IF" },
|
|
273
|
+
{ value: "@req REQ-BEFORE-IF" },
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
if (node && node.loc && node.loc.start && node.loc.start.line === 8) {
|
|
277
|
+
// before the else-if branch (Story 026.0 semantics)
|
|
278
|
+
return [
|
|
279
|
+
{
|
|
280
|
+
value: "@story docs/stories/026.0-DEV-BRANCH-ANNOTATIONS-ELSE-BRANCHES.story.md",
|
|
281
|
+
},
|
|
282
|
+
{ value: "@req REQ-BEFORE-ELSE" },
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
return [];
|
|
286
|
+
}),
|
|
287
|
+
};
|
|
288
|
+
const elseIfNode = {
|
|
289
|
+
type: "IfStatement",
|
|
290
|
+
loc: {
|
|
291
|
+
start: { line: 8, column: 2 },
|
|
292
|
+
end: { line: 12, column: 3 },
|
|
293
|
+
},
|
|
294
|
+
consequent: {
|
|
295
|
+
type: "BlockStatement",
|
|
296
|
+
loc: {
|
|
297
|
+
start: { line: 8, column: 22 },
|
|
298
|
+
end: { line: 12, column: 3 },
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
const parent = {
|
|
303
|
+
type: "IfStatement",
|
|
304
|
+
alternate: elseIfNode,
|
|
305
|
+
};
|
|
306
|
+
const beforeText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, elseIfNode, parent, "before");
|
|
307
|
+
expect(beforeText).toContain("@story docs/stories/026.0-DEV-BRANCH-ANNOTATIONS-ELSE-BRANCHES.story.md");
|
|
308
|
+
expect(beforeText).toContain("@req REQ-BEFORE-ELSE");
|
|
309
|
+
const insideText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, elseIfNode, parent, "inside");
|
|
310
|
+
expect(insideText).toBe("");
|
|
311
|
+
expect(insideText).not.toContain("REQ-BEFORE-ELSE");
|
|
312
|
+
expect(insideText).not.toContain("docs/stories/026.0-DEV-BRANCH-ANNOTATIONS-ELSE-BRANCHES.story.md");
|
|
313
|
+
});
|
|
165
314
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.2",
|
|
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",
|