eslint-plugin-traceability 1.11.2 → 1.11.4
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 +2 -2
- package/README.md +2 -2
- package/lib/src/index.d.ts +1 -7
- package/lib/src/index.js +28 -0
- package/lib/src/utils/branch-annotation-helpers.d.ts +5 -1
- package/lib/src/utils/branch-annotation-helpers.js +194 -18
- package/lib/tests/integration/catch-annotation-prettier.integration.test.d.ts +1 -0
- package/lib/tests/integration/catch-annotation-prettier.integration.test.js +131 -0
- package/lib/tests/integration/else-if-annotation-prettier.integration.test.d.ts +1 -0
- package/lib/tests/integration/else-if-annotation-prettier.integration.test.js +116 -0
- package/lib/tests/perf/valid-annotation-format-large-file.test.d.ts +1 -0
- package/lib/tests/perf/valid-annotation-format-large-file.test.js +74 -0
- package/lib/tests/plugin-default-export-and-configs.test.js +1 -0
- package/lib/tests/rules/prefer-implements-annotation.test.js +84 -70
- package/lib/tests/rules/require-branch-annotation.test.js +18 -1
- package/lib/tests/utils/branch-annotation-catch-insert-position.test.d.ts +1 -0
- package/lib/tests/utils/branch-annotation-catch-insert-position.test.js +68 -0
- package/lib/tests/utils/branch-annotation-catch-position.test.d.ts +1 -0
- package/lib/tests/utils/branch-annotation-catch-position.test.js +115 -0
- package/lib/tests/utils/req-annotation-detection.test.d.ts +1 -0
- package/lib/tests/utils/req-annotation-detection.test.js +247 -0
- package/package.json +3 -3
- package/user-docs/api-reference.md +17 -10
- package/user-docs/migration-guide.md +9 -5
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
/**
|
|
7
|
+
* Performance tests for valid-annotation-format on large annotated files.
|
|
8
|
+
*
|
|
9
|
+
* @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-MULTILINE-SUPPORT REQ-FLEXIBLE-PARSING REQ-SYNTAX-VALIDATION
|
|
10
|
+
*/
|
|
11
|
+
const eslint_1 = require("eslint");
|
|
12
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
13
|
+
const valid_annotation_format_1 = __importDefault(require("../../src/rules/valid-annotation-format"));
|
|
14
|
+
/**
|
|
15
|
+
* Build a large source file containing many functions with traceability
|
|
16
|
+
* annotations in both line and block comments.
|
|
17
|
+
*
|
|
18
|
+
* The generated code mixes valid and invalid annotation formats to exercise
|
|
19
|
+
* parsing, multi-line handling, and error-reporting paths at scale without
|
|
20
|
+
* relying on auto-fix.
|
|
21
|
+
*/
|
|
22
|
+
function buildLargeAnnotatedSource(functionCount, annotationsPerFunction) {
|
|
23
|
+
const lines = [];
|
|
24
|
+
for (let i = 0; i < functionCount; i += 1) {
|
|
25
|
+
// JSDoc-style block comment with multi-line @story/@req values.
|
|
26
|
+
lines.push("/**");
|
|
27
|
+
lines.push(" * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md");
|
|
28
|
+
lines.push(" * @req REQ-FORMAT-SPECIFICATION");
|
|
29
|
+
lines.push(" */");
|
|
30
|
+
// Additional line comments with a mix of valid and intentionally
|
|
31
|
+
// invalid formats (missing extensions, traversal, malformed IDs).
|
|
32
|
+
for (let j = 0; j < annotationsPerFunction; j += 1) {
|
|
33
|
+
const selector = (i + j) % 4;
|
|
34
|
+
if (selector === 0) {
|
|
35
|
+
lines.push("// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story");
|
|
36
|
+
}
|
|
37
|
+
else if (selector === 1) {
|
|
38
|
+
lines.push("// @req REQ-EXAMPLE-" + i.toString(10));
|
|
39
|
+
}
|
|
40
|
+
else if (selector === 2) {
|
|
41
|
+
lines.push("// @story ../outside-project.story.md");
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
lines.push("// @req invalid-format-id");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
lines.push(`function annotated_fn_${i}() {`);
|
|
48
|
+
lines.push(' return "ok";\n}');
|
|
49
|
+
}
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
}
|
|
52
|
+
describe("valid-annotation-format performance on large annotated files (Story 005.0-DEV-ANNOTATION-VALIDATION)", () => {
|
|
53
|
+
const ruleName = "traceability/valid-annotation-format";
|
|
54
|
+
it("[REQ-MULTILINE-SUPPORT][REQ-FLEXIBLE-PARSING] analyzes a large annotated file within a generous time budget", () => {
|
|
55
|
+
const linter = new eslint_1.Linter({ configType: "eslintrc" });
|
|
56
|
+
linter.defineRule(ruleName, valid_annotation_format_1.default);
|
|
57
|
+
// 150 functions each with several annotations provides a substantial
|
|
58
|
+
// volume of comments and annotation patterns without being extreme.
|
|
59
|
+
const source = buildLargeAnnotatedSource(150, 3);
|
|
60
|
+
const start = perf_hooks_1.performance.now();
|
|
61
|
+
const messages = linter.verify(source, {
|
|
62
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
63
|
+
rules: {
|
|
64
|
+
[ruleName]: "error",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
const durationMs = perf_hooks_1.performance.now() - start;
|
|
68
|
+
// Sanity check: we expect diagnostics for some invalid annotations so the
|
|
69
|
+
// rule is definitely executing its validation logic.
|
|
70
|
+
expect(messages.length).toBeGreaterThan(0);
|
|
71
|
+
// Guardrail: keep analysis comfortably under ~5 seconds on CI hardware.
|
|
72
|
+
expect(durationMs).toBeLessThan(5000);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -59,6 +59,7 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
|
|
|
59
59
|
"valid-req-reference",
|
|
60
60
|
"prefer-implements-annotation",
|
|
61
61
|
"require-test-traceability",
|
|
62
|
+
"prefer-supports-annotation",
|
|
62
63
|
];
|
|
63
64
|
// Act: get actual rule names from plugin
|
|
64
65
|
const actual = Object.keys(index_1.rules);
|
|
@@ -18,80 +18,87 @@ const ruleTester = new eslint_1.RuleTester({
|
|
|
18
18
|
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
19
19
|
},
|
|
20
20
|
});
|
|
21
|
-
describe("prefer-implements-annotation
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
reason: "comment mixes @story/@req with existing @supports annotations",
|
|
60
|
-
},
|
|
21
|
+
describe("prefer-supports-annotation / prefer-implements-annotation aliasing (Story 010.3-DEV-MIGRATE-TO-SUPPORTS)", () => {
|
|
22
|
+
const valid = [
|
|
23
|
+
{
|
|
24
|
+
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with only @story is ignored",
|
|
25
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n */\nfunction onlyStory() {}`,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with only @req is ignored",
|
|
29
|
+
code: `/**\n * @req REQ-ONLY\n */\nfunction onlyReq() {}`,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with @supports only is ignored",
|
|
33
|
+
code: `/**\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction alreadyImplements() {}`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with @story and @supports but no @req is ignored",
|
|
37
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction storyAndSupportsNoReq() {}`,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with @req and @supports but no @story is ignored",
|
|
41
|
+
code: `/**\n * @req REQ-ANNOTATION-REQUIRED\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction reqAndSupportsNoStory() {}`,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
const invalid = [
|
|
45
|
+
{
|
|
46
|
+
name: "[REQ-OPTIONAL-WARNING] single-story @story + @req block triggers preferImplements message",
|
|
47
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED\n */\nfunction legacy() {}`,
|
|
48
|
+
output: `/**\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction legacy() {}`,
|
|
49
|
+
errors: [{ messageId: "preferImplements" }],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "[REQ-MULTI-STORY-DETECT] mixed @story/@req and @supports triggers cannotAutoFix",
|
|
53
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction mixed() {}`,
|
|
54
|
+
errors: [
|
|
55
|
+
{
|
|
56
|
+
messageId: "cannotAutoFix",
|
|
57
|
+
data: {
|
|
58
|
+
reason: "comment mixes @story/@req with existing @supports annotations",
|
|
61
59
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "[REQ-MULTI-STORY-DETECT] multiple @story paths in same block trigger multiStoryDetected",
|
|
65
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED\n * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md\n * @req REQ-BRANCH-DETECTION\n */\nfunction multiStory() {}`,
|
|
66
|
+
errors: [{ messageId: "multiStoryDetected" }],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "[REQ-AUTO-FIX] single @story + single @req auto-fixes to single @supports line",
|
|
70
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED\n */\nfunction autoFixSingleReq() {}`,
|
|
71
|
+
output: `/**\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction autoFixSingleReq() {}`,
|
|
72
|
+
errors: [{ messageId: "preferImplements" }],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "[REQ-SINGLE-STORY-FIX] single @story with multiple @req lines auto-fixes to single @supports line containing all REQ IDs",
|
|
76
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ONE\n * @req REQ-TWO\n * @req REQ-THREE\n */\nfunction autoFixMultiReq() {}`,
|
|
77
|
+
output: `/**\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ONE REQ-TWO REQ-THREE\n */\nfunction autoFixMultiReq() {}`,
|
|
78
|
+
errors: [{ messageId: "preferImplements" }],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "[REQ-AUTO-FIX] complex @req content (extra description) does not auto-fix but still warns",
|
|
82
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @req REQ-ANNOTATION-REQUIRED must handle extra description\n */\nfunction complexReqNoAutoFix() {}`,
|
|
83
|
+
errors: [{ messageId: "preferImplements" }],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "[REQ-AUTO-FIX] complex @story content (extra description) does not auto-fix but still warns",
|
|
87
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md additional descriptive text\n * @req REQ-ANNOTATION-REQUIRED\n */\nfunction complexStoryNoAutoFix() {}`,
|
|
88
|
+
errors: [{ messageId: "preferImplements" }],
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
ruleTester.run("prefer-implements-annotation", prefer_implements_annotation_1.default, {
|
|
92
|
+
valid,
|
|
93
|
+
invalid,
|
|
94
|
+
});
|
|
95
|
+
ruleTester.run("prefer-supports-annotation", prefer_implements_annotation_1.default, {
|
|
96
|
+
valid,
|
|
97
|
+
invalid,
|
|
92
98
|
});
|
|
93
99
|
});
|
|
94
100
|
describe("prefer-implements-annotation configuration severity (REQ-CONFIG-SEVERITY)", () => {
|
|
101
|
+
// Story 010.3 / REQ-RULE-NAME: verify aliasing semantics for new primary rule name and deprecated alias
|
|
95
102
|
test("rule is disabled by default in recommended and strict presets (not present in preset rule maps)", () => {
|
|
96
103
|
const recommended = src_1.configs.recommended;
|
|
97
104
|
expect(Array.isArray(recommended)).toBe(true);
|
|
@@ -99,27 +106,34 @@ describe("prefer-implements-annotation configuration severity (REQ-CONFIG-SEVERI
|
|
|
99
106
|
expect(firstConfig).toBeDefined();
|
|
100
107
|
const rules = firstConfig.rules || {};
|
|
101
108
|
expect(rules["traceability/prefer-implements-annotation"]).toBeUndefined();
|
|
109
|
+
expect(rules["traceability/prefer-supports-annotation"]).toBeUndefined();
|
|
102
110
|
const strict = src_1.configs.strict;
|
|
103
111
|
expect(Array.isArray(strict)).toBe(true);
|
|
104
112
|
const strictFirstConfig = strict[0];
|
|
105
113
|
expect(strictFirstConfig).toBeDefined();
|
|
106
114
|
const strictRules = strictFirstConfig.rules || {};
|
|
107
115
|
expect(strictRules["traceability/prefer-implements-annotation"]).toBeUndefined();
|
|
116
|
+
expect(strictRules["traceability/prefer-supports-annotation"]).toBeUndefined();
|
|
108
117
|
});
|
|
109
118
|
test("rule can be configured with severity 'warn' or 'error' in flat config", () => {
|
|
119
|
+
// Story 010.3 / REQ-RULE-NAME: both primary and alias rule keys must be accepted in flat config
|
|
110
120
|
const flatWarnConfig = {
|
|
111
121
|
files: ["**/*.ts"],
|
|
112
122
|
rules: {
|
|
113
123
|
"traceability/prefer-implements-annotation": "warn",
|
|
124
|
+
"traceability/prefer-supports-annotation": "warn",
|
|
114
125
|
},
|
|
115
126
|
};
|
|
116
127
|
expect(flatWarnConfig.rules["traceability/prefer-implements-annotation"]).toBe("warn");
|
|
128
|
+
expect(flatWarnConfig.rules["traceability/prefer-supports-annotation"]).toBe("warn");
|
|
117
129
|
const flatErrorConfig = {
|
|
118
130
|
files: ["**/*.ts"],
|
|
119
131
|
rules: {
|
|
120
132
|
"traceability/prefer-implements-annotation": "error",
|
|
133
|
+
"traceability/prefer-supports-annotation": "error",
|
|
121
134
|
},
|
|
122
135
|
};
|
|
123
136
|
expect(flatErrorConfig.rules["traceability/prefer-implements-annotation"]).toBe("error");
|
|
137
|
+
expect(flatErrorConfig.rules["traceability/prefer-supports-annotation"]).toBe("error");
|
|
124
138
|
});
|
|
125
139
|
});
|
|
@@ -4,9 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
/**
|
|
7
|
-
* Tests for: docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md, docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
7
|
+
* Tests for: docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md, docs/stories/007.0-DEV-ERROR-REPORTING.story.md, docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
|
|
8
8
|
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
9
9
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
10
|
+
* @story docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md
|
|
10
11
|
* @req REQ-BRANCH-DETECTION - Verify require-branch-annotation rule enforces branch annotations
|
|
11
12
|
* @req REQ-ERROR-SPECIFIC - Branch-level missing-annotation error messages are specific and informative
|
|
12
13
|
* @req REQ-ERROR-CONSISTENCY - Branch-level missing-annotation error messages follow shared conventions
|
|
@@ -14,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
15
|
* @req REQ-NESTED-HANDLING - Nested branch annotations are correctly enforced without duplicative reporting
|
|
15
16
|
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-BRANCH-DETECTION REQ-NESTED-HANDLING
|
|
16
17
|
* @supports docs/stories/007.0-DEV-ERROR-REPORTING.story.md REQ-ERROR-SPECIFIC REQ-ERROR-CONSISTENCY REQ-ERROR-SUGGESTION
|
|
18
|
+
* @supports docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION-ELSE-IF REQ-FALLBACK-LOGIC-ELSE-IF REQ-POSITION-PRIORITY-ELSE-IF REQ-PRETTIER-AUTOFIX-ELSE-IF
|
|
17
19
|
*/
|
|
18
20
|
const eslint_1 = require("eslint");
|
|
19
21
|
const require_branch_annotation_1 = __importDefault(require("../../src/rules/require-branch-annotation"));
|
|
@@ -289,6 +291,21 @@ if (outer) {
|
|
|
289
291
|
for (let i = 0; i < 3; i++) {}`,
|
|
290
292
|
errors: makeMissingAnnotationErrors("@story", "@req"),
|
|
291
293
|
},
|
|
294
|
+
{
|
|
295
|
+
name: "[REQ-PRETTIER-AUTOFIX-ELSE-IF] missing annotations on else-if branch with Prettier-style autofix insertion",
|
|
296
|
+
code: `if (a) {
|
|
297
|
+
doA();
|
|
298
|
+
} else if (b) {
|
|
299
|
+
doB();
|
|
300
|
+
}`,
|
|
301
|
+
output: `// @story <story-file>.story.md
|
|
302
|
+
if (a) {
|
|
303
|
+
doA();
|
|
304
|
+
} else if (b) {
|
|
305
|
+
doB();
|
|
306
|
+
}`,
|
|
307
|
+
errors: makeMissingAnnotationErrors("@story", "@req", "@story", "@req"),
|
|
308
|
+
},
|
|
292
309
|
],
|
|
293
310
|
});
|
|
294
311
|
runRule({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Unit tests for CatchClause insert position calculation.
|
|
5
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
6
|
+
* @story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md
|
|
7
|
+
* @supports docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md REQ-PRETTIER-AUTOFIX
|
|
8
|
+
*/
|
|
9
|
+
const branch_annotation_helpers_1 = require("../../src/utils/branch-annotation-helpers");
|
|
10
|
+
describe("CatchClause insert position (Story 025.0-DEV-CATCH-ANNOTATION-POSITION)", () => {
|
|
11
|
+
it("[REQ-PRETTIER-AUTOFIX] inserts annotations at the first statement inside the catch body", () => {
|
|
12
|
+
const lines = [
|
|
13
|
+
"try {",
|
|
14
|
+
" doSomething();",
|
|
15
|
+
"}",
|
|
16
|
+
"catch (error) {",
|
|
17
|
+
" handleError(error);",
|
|
18
|
+
"}",
|
|
19
|
+
];
|
|
20
|
+
const fixer = {
|
|
21
|
+
insertTextBeforeRange: jest.fn((r, t) => ({ r, t })),
|
|
22
|
+
};
|
|
23
|
+
const context = {
|
|
24
|
+
getSourceCode() {
|
|
25
|
+
return {
|
|
26
|
+
lines,
|
|
27
|
+
getCommentsBefore() {
|
|
28
|
+
return [];
|
|
29
|
+
},
|
|
30
|
+
getIndexFromLoc({ line, column }) {
|
|
31
|
+
// simple line/column to index mapping for the test: assume each line ends with "\n"
|
|
32
|
+
const prefix = lines.slice(0, line - 1).join("\n");
|
|
33
|
+
return prefix.length + (line > 1 ? 1 : 0) + column;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
report({ fix }) {
|
|
38
|
+
// immediately invoke the fixer to exercise the insert position
|
|
39
|
+
if (typeof fix === "function") {
|
|
40
|
+
fix(fixer);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const node = {
|
|
45
|
+
type: "CatchClause",
|
|
46
|
+
loc: { start: { line: 4 } },
|
|
47
|
+
body: {
|
|
48
|
+
type: "BlockStatement",
|
|
49
|
+
loc: { start: { line: 4 } },
|
|
50
|
+
body: [
|
|
51
|
+
{
|
|
52
|
+
type: "ExpressionStatement",
|
|
53
|
+
loc: { start: { line: 5 } },
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const storyFixCountRef = { count: 0 };
|
|
59
|
+
(0, branch_annotation_helpers_1.reportMissingAnnotations)(context, node, storyFixCountRef);
|
|
60
|
+
expect(fixer.insertTextBeforeRange).toHaveBeenCalledTimes(1);
|
|
61
|
+
const [range, text] = fixer.insertTextBeforeRange.mock.calls[0];
|
|
62
|
+
// ensure we are inserting before the first statement in the catch body (line 5)
|
|
63
|
+
const expectedIndex = context.getSourceCode().getIndexFromLoc({ line: 5, column: 0 });
|
|
64
|
+
expect(range).toEqual([expectedIndex, expectedIndex]);
|
|
65
|
+
// and that the inserted text is prefixed with the inner indentation from line 5
|
|
66
|
+
expect(text.startsWith(" ")).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const branch_annotation_helpers_1 = require("../../src/utils/branch-annotation-helpers");
|
|
4
|
+
function createMockSourceCode(options) {
|
|
5
|
+
const { lines = [], commentsBefore = [], commentsInside = [] } = options;
|
|
6
|
+
return {
|
|
7
|
+
lines,
|
|
8
|
+
getCommentsBefore() {
|
|
9
|
+
return commentsBefore;
|
|
10
|
+
},
|
|
11
|
+
getCommentsInside(node) {
|
|
12
|
+
// exercise the code path that passes node.body into getCommentsInside
|
|
13
|
+
if (node && node.type === "BlockStatement") {
|
|
14
|
+
return commentsInside;
|
|
15
|
+
}
|
|
16
|
+
return [];
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
describe("gatherBranchCommentText CatchClause behavior (Story 025.0-DEV-CATCH-ANNOTATION-POSITION)", () => {
|
|
21
|
+
it("[REQ-DUAL-POSITION-DETECTION] prefers before-catch annotations when present", () => {
|
|
22
|
+
const sourceCode = createMockSourceCode({
|
|
23
|
+
commentsBefore: [
|
|
24
|
+
{ value: "@story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md" },
|
|
25
|
+
{ value: "@req REQ-BRANCH-DETECTION" },
|
|
26
|
+
],
|
|
27
|
+
commentsInside: [
|
|
28
|
+
{ value: "@story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md" },
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
const node = {
|
|
32
|
+
type: "CatchClause",
|
|
33
|
+
loc: { start: { line: 5 } },
|
|
34
|
+
body: { type: "BlockStatement" },
|
|
35
|
+
};
|
|
36
|
+
const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, node);
|
|
37
|
+
expect(text).toContain("@story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md");
|
|
38
|
+
expect(text).toContain("@req REQ-BRANCH-DETECTION");
|
|
39
|
+
});
|
|
40
|
+
it("[REQ-FALLBACK-LOGIC] falls back to inside-catch annotations when before-catch is missing", () => {
|
|
41
|
+
const sourceCode = createMockSourceCode({
|
|
42
|
+
commentsBefore: [],
|
|
43
|
+
commentsInside: [
|
|
44
|
+
{ value: "@story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md" },
|
|
45
|
+
{ value: "@req REQ-CATCH-PATH" },
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
const node = {
|
|
49
|
+
type: "CatchClause",
|
|
50
|
+
loc: { start: { line: 10 } },
|
|
51
|
+
body: { type: "BlockStatement" },
|
|
52
|
+
};
|
|
53
|
+
const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, node);
|
|
54
|
+
expect(text).toContain("@story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md");
|
|
55
|
+
expect(text).toContain("@req REQ-CATCH-PATH");
|
|
56
|
+
});
|
|
57
|
+
it("[REQ-FALLBACK-LOGIC] returns before-catch text when getCommentsInside is not available", () => {
|
|
58
|
+
const lines = [
|
|
59
|
+
"try {",
|
|
60
|
+
" doSomething();",
|
|
61
|
+
"}",
|
|
62
|
+
"catch (error) {",
|
|
63
|
+
" // body",
|
|
64
|
+
"}",
|
|
65
|
+
];
|
|
66
|
+
const sourceCode = {
|
|
67
|
+
lines,
|
|
68
|
+
getCommentsBefore() {
|
|
69
|
+
return [
|
|
70
|
+
{ value: "@story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md" },
|
|
71
|
+
{ value: "@req REQ-BRANCH-DETECTION" },
|
|
72
|
+
];
|
|
73
|
+
},
|
|
74
|
+
// intentionally omit getCommentsInside so that the CatchClause path
|
|
75
|
+
// falls back to the before-catch comments.
|
|
76
|
+
};
|
|
77
|
+
const node = {
|
|
78
|
+
type: "CatchClause",
|
|
79
|
+
loc: { start: { line: 4 } },
|
|
80
|
+
body: { type: "BlockStatement" },
|
|
81
|
+
};
|
|
82
|
+
const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, node);
|
|
83
|
+
expect(text).toContain("@story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md");
|
|
84
|
+
expect(text).toContain("@req REQ-BRANCH-DETECTION");
|
|
85
|
+
});
|
|
86
|
+
it("[REQ-FALLBACK-LOGIC] collects inside-catch comments using line-based fallback when getCommentsInside is unavailable", () => {
|
|
87
|
+
const lines = [
|
|
88
|
+
"try {",
|
|
89
|
+
" doSomething();",
|
|
90
|
+
"} catch (error) {",
|
|
91
|
+
" // @story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md",
|
|
92
|
+
" // @req REQ-CATCH-LINE-FALLBACK",
|
|
93
|
+
" handleError(error);",
|
|
94
|
+
"}",
|
|
95
|
+
];
|
|
96
|
+
const sourceCode = {
|
|
97
|
+
lines,
|
|
98
|
+
getCommentsBefore() {
|
|
99
|
+
return [];
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
const node = {
|
|
103
|
+
type: "CatchClause",
|
|
104
|
+
loc: { start: { line: 3 } },
|
|
105
|
+
body: {
|
|
106
|
+
type: "BlockStatement",
|
|
107
|
+
loc: { start: { line: 3 }, end: { line: 7 } },
|
|
108
|
+
body: [],
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, node);
|
|
112
|
+
expect(text).toContain("@story docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md");
|
|
113
|
+
expect(text).toContain("@req REQ-CATCH-LINE-FALLBACK");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|