eslint-plugin-traceability 1.15.0 → 1.16.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/README.md +61 -13
  3. package/lib/src/index.js +59 -0
  4. package/lib/src/rules/helpers/require-story-core.js +1 -1
  5. package/lib/src/rules/helpers/require-story-helpers.d.ts +10 -3
  6. package/lib/src/rules/helpers/require-story-helpers.js +84 -7
  7. package/lib/src/rules/no-redundant-annotation.js +73 -55
  8. package/lib/src/rules/require-branch-annotation.js +2 -2
  9. package/lib/src/rules/require-req-annotation.js +2 -2
  10. package/lib/src/rules/require-story-annotation.js +8 -3
  11. package/lib/src/rules/require-traceability.js +8 -9
  12. package/lib/src/utils/annotation-checker.js +23 -4
  13. package/lib/tests/cli-error-handling.test.js +10 -1
  14. package/lib/tests/integration/cli-integration.test.js +5 -0
  15. package/lib/tests/integration/require-traceability-aliases.integration.test.js +126 -0
  16. package/lib/tests/plugin-default-export-and-configs.test.js +23 -0
  17. package/lib/tests/rules/auto-fix-behavior-008.test.js +7 -7
  18. package/lib/tests/rules/error-reporting.test.js +1 -1
  19. package/lib/tests/rules/no-redundant-annotation.test.js +20 -0
  20. package/lib/tests/rules/require-story-annotation.test.js +75 -10
  21. package/lib/tests/rules/require-story-helpers.test.js +224 -0
  22. package/lib/tests/rules/require-story-utils.test.d.ts +7 -0
  23. package/lib/tests/rules/require-story-utils.test.js +158 -0
  24. package/lib/tests/utils/annotation-checker-branches.test.d.ts +5 -0
  25. package/lib/tests/utils/annotation-checker-branches.test.js +103 -0
  26. package/lib/tests/utils/annotation-scope-analyzer.test.js +134 -0
  27. package/lib/tests/utils/branch-annotation-helpers.test.js +66 -0
  28. package/package.json +2 -2
  29. package/user-docs/api-reference.md +71 -15
  30. package/user-docs/examples.md +24 -13
  31. package/user-docs/migration-guide.md +127 -4
  32. package/user-docs/traceability-overview.md +116 -0
  33. package/lib/tests/integration/dogfooding-validation.test.js +0 -129
  34. /package/lib/tests/integration/{dogfooding-validation.test.d.ts → require-traceability-aliases.integration.test.d.ts} +0 -0
@@ -177,4 +177,228 @@ describe("Require Story Helpers (Story 003.0)", () => {
177
177
  const res = (0, require_story_helpers_1.parentChainHasStory)(fakeSource, node);
178
178
  expect(res).toBeTruthy();
179
179
  });
180
+ /**
181
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
182
+ * @req REQ-TEST-CALLBACK-EXCLUSION - Verify arrow function test callbacks can be excluded by default
183
+ */
184
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow function used as test callback is excluded by default", () => {
185
+ const node = {
186
+ type: "ArrowFunctionExpression",
187
+ parent: {
188
+ type: "CallExpression",
189
+ callee: { type: "Identifier", name: "it" },
190
+ },
191
+ };
192
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE);
193
+ expect(result).toBeFalsy();
194
+ });
195
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow function used as beforeEach callback is excluded by default", () => {
196
+ const node = {
197
+ type: "ArrowFunctionExpression",
198
+ parent: {
199
+ type: "CallExpression",
200
+ callee: { type: "Identifier", name: "beforeEach" },
201
+ },
202
+ };
203
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE);
204
+ expect(result).toBeFalsy();
205
+ });
206
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow function used as afterEach callback is excluded by default", () => {
207
+ const node = {
208
+ type: "ArrowFunctionExpression",
209
+ parent: {
210
+ type: "CallExpression",
211
+ callee: { type: "Identifier", name: "afterEach" },
212
+ },
213
+ };
214
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE);
215
+ expect(result).toBeFalsy();
216
+ });
217
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow function used as beforeAll callback is excluded by default", () => {
218
+ const node = {
219
+ type: "ArrowFunctionExpression",
220
+ parent: {
221
+ type: "CallExpression",
222
+ callee: { type: "Identifier", name: "beforeAll" },
223
+ },
224
+ };
225
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE);
226
+ expect(result).toBeFalsy();
227
+ });
228
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow function used as afterAll callback is excluded by default", () => {
229
+ const node = {
230
+ type: "ArrowFunctionExpression",
231
+ parent: {
232
+ type: "CallExpression",
233
+ callee: { type: "Identifier", name: "afterAll" },
234
+ },
235
+ };
236
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE);
237
+ expect(result).toBeFalsy();
238
+ });
239
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow function used as suite callback is excluded by default", () => {
240
+ const node = {
241
+ type: "ArrowFunctionExpression",
242
+ parent: {
243
+ type: "CallExpression",
244
+ callee: { type: "Identifier", name: "suite" },
245
+ },
246
+ };
247
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE);
248
+ expect(result).toBeFalsy();
249
+ });
250
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow function used as context callback is excluded by default", () => {
251
+ const node = {
252
+ type: "ArrowFunctionExpression",
253
+ parent: {
254
+ type: "CallExpression",
255
+ callee: { type: "Identifier", name: "context" },
256
+ },
257
+ };
258
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE);
259
+ expect(result).toBeFalsy();
260
+ });
261
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow function used as specify callback is excluded by default", () => {
262
+ const node = {
263
+ type: "ArrowFunctionExpression",
264
+ parent: {
265
+ type: "CallExpression",
266
+ callee: { type: "Identifier", name: "specify" },
267
+ },
268
+ };
269
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE);
270
+ expect(result).toBeFalsy();
271
+ });
272
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow function used as bench callback is checked by default", () => {
273
+ const node = {
274
+ type: "ArrowFunctionExpression",
275
+ parent: {
276
+ type: "CallExpression",
277
+ callee: { type: "Identifier", name: "bench" },
278
+ },
279
+ };
280
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE);
281
+ expect(result).toBeTruthy();
282
+ });
283
+ /**
284
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
285
+ * @req REQ-TEST-CALLBACK-EXCLUSION - Verify arrow function test callbacks are checked when exclusion is disabled
286
+ */
287
+ test("[REQ-TEST-CALLBACK-EXCLUSION] Arrow function test callback is checked when excludeTestCallbacks is false", () => {
288
+ const node = {
289
+ type: "ArrowFunctionExpression",
290
+ parent: {
291
+ type: "CallExpression",
292
+ callee: { type: "Identifier", name: "it" },
293
+ },
294
+ };
295
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
296
+ excludeTestCallbacks: false,
297
+ });
298
+ expect(result).toBeTruthy();
299
+ });
300
+ test("[REQ-TEST-CALLBACK-EXCLUSION] beforeEach arrow function callback is checked when excludeTestCallbacks is false", () => {
301
+ const node = {
302
+ type: "ArrowFunctionExpression",
303
+ parent: {
304
+ type: "CallExpression",
305
+ callee: { type: "Identifier", name: "beforeEach" },
306
+ },
307
+ };
308
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
309
+ excludeTestCallbacks: false,
310
+ });
311
+ expect(result).toBeTruthy();
312
+ });
313
+ test("[REQ-TEST-CALLBACK-EXCLUSION] afterEach arrow function callback is checked when excludeTestCallbacks is false", () => {
314
+ const node = {
315
+ type: "ArrowFunctionExpression",
316
+ parent: {
317
+ type: "CallExpression",
318
+ callee: { type: "Identifier", name: "afterEach" },
319
+ },
320
+ };
321
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
322
+ excludeTestCallbacks: false,
323
+ });
324
+ expect(result).toBeTruthy();
325
+ });
326
+ test("[REQ-TEST-CALLBACK-EXCLUSION] beforeAll arrow function callback is checked when excludeTestCallbacks is false", () => {
327
+ const node = {
328
+ type: "ArrowFunctionExpression",
329
+ parent: {
330
+ type: "CallExpression",
331
+ callee: { type: "Identifier", name: "beforeAll" },
332
+ },
333
+ };
334
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
335
+ excludeTestCallbacks: false,
336
+ });
337
+ expect(result).toBeTruthy();
338
+ });
339
+ test("[REQ-TEST-CALLBACK-EXCLUSION] afterAll arrow function callback is checked when excludeTestCallbacks is false", () => {
340
+ const node = {
341
+ type: "ArrowFunctionExpression",
342
+ parent: {
343
+ type: "CallExpression",
344
+ callee: { type: "Identifier", name: "afterAll" },
345
+ },
346
+ };
347
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
348
+ excludeTestCallbacks: false,
349
+ });
350
+ expect(result).toBeTruthy();
351
+ });
352
+ test("[REQ-TEST-CALLBACK-EXCLUSION] suite arrow function callback is checked when excludeTestCallbacks is false", () => {
353
+ const node = {
354
+ type: "ArrowFunctionExpression",
355
+ parent: {
356
+ type: "CallExpression",
357
+ callee: { type: "Identifier", name: "suite" },
358
+ },
359
+ };
360
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
361
+ excludeTestCallbacks: false,
362
+ });
363
+ expect(result).toBeTruthy();
364
+ });
365
+ test("[REQ-TEST-CALLBACK-EXCLUSION] context arrow function callback is checked when excludeTestCallbacks is false", () => {
366
+ const node = {
367
+ type: "ArrowFunctionExpression",
368
+ parent: {
369
+ type: "CallExpression",
370
+ callee: { type: "Identifier", name: "context" },
371
+ },
372
+ };
373
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
374
+ excludeTestCallbacks: false,
375
+ });
376
+ expect(result).toBeTruthy();
377
+ });
378
+ test("[REQ-TEST-CALLBACK-EXCLUSION] specify arrow function callback is checked when excludeTestCallbacks is false", () => {
379
+ const node = {
380
+ type: "ArrowFunctionExpression",
381
+ parent: {
382
+ type: "CallExpression",
383
+ callee: { type: "Identifier", name: "specify" },
384
+ },
385
+ };
386
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
387
+ excludeTestCallbacks: false,
388
+ });
389
+ expect(result).toBeTruthy();
390
+ });
391
+ test("[REQ-TEST-CALLBACK-EXCLUSION] bench arrow function callback is always checked (also when excludeTestCallbacks is false)", () => {
392
+ const node = {
393
+ type: "ArrowFunctionExpression",
394
+ parent: {
395
+ type: "CallExpression",
396
+ callee: { type: "Identifier", name: "bench" },
397
+ },
398
+ };
399
+ const result = (0, require_story_helpers_1.shouldProcessNode)(node, require_story_helpers_1.DEFAULT_SCOPE, "all", {
400
+ excludeTestCallbacks: false,
401
+ });
402
+ expect(result).toBeTruthy();
403
+ });
180
404
  });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
3
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
+ * @req REQ-ANNOTATION-REQUIRED - Verify getNodeName resolves names for diverse AST node shapes
5
+ * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED
6
+ */
7
+ export {};
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
5
+ * @req REQ-ANNOTATION-REQUIRED - Verify getNodeName resolves names for diverse AST node shapes
6
+ * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const require_story_utils_1 = require("../../src/rules/helpers/require-story-utils");
10
+ describe("Require Story Utils - getNodeName (Story 003.0)", () => {
11
+ it("[REQ-ANNOTATION-REQUIRED] returns identifier name for Identifier and JSXIdentifier", () => {
12
+ const idNode = { type: "Identifier", name: "foo" };
13
+ const jsxIdNode = { type: "JSXIdentifier", name: "Bar" };
14
+ expect((0, require_story_utils_1.getNodeName)(idNode)).toBe("foo");
15
+ expect((0, require_story_utils_1.getNodeName)(jsxIdNode)).toBe("Bar");
16
+ });
17
+ it("[REQ-ANNOTATION-REQUIRED] returns null for identifier-like nodes without string name", () => {
18
+ const badId = { type: "Identifier", name: 123 };
19
+ const badJsxId = { type: "JSXIdentifier", name: null };
20
+ expect((0, require_story_utils_1.getNodeName)(badId)).toBeNull();
21
+ expect((0, require_story_utils_1.getNodeName)(badJsxId)).toBeNull();
22
+ });
23
+ it("[REQ-ANNOTATION-REQUIRED] converts simple Literal values into string names", () => {
24
+ const stringLit = { type: "Literal", value: "name" };
25
+ const numberLit = { type: "Literal", value: 42 };
26
+ const boolLit = { type: "Literal", value: true };
27
+ const nullLit = { type: "Literal", value: null };
28
+ const objLit = { type: "Literal", value: { a: 1 } };
29
+ expect((0, require_story_utils_1.getNodeName)(stringLit)).toBe("name");
30
+ expect((0, require_story_utils_1.getNodeName)(numberLit)).toBe("42");
31
+ expect((0, require_story_utils_1.getNodeName)(boolLit)).toBe("true");
32
+ expect((0, require_story_utils_1.getNodeName)(nullLit)).toBeNull();
33
+ expect((0, require_story_utils_1.getNodeName)(objLit)).toBeNull();
34
+ });
35
+ it("[REQ-ANNOTATION-REQUIRED] resolves simple, expression-free TemplateLiteral names", () => {
36
+ const tplNode = {
37
+ type: "TemplateLiteral",
38
+ expressions: [],
39
+ quasis: [
40
+ { value: { cooked: "hello", raw: "hello" } },
41
+ { value: { cooked: "-world", raw: "-world" } },
42
+ ],
43
+ };
44
+ const withExpressions = {
45
+ type: "TemplateLiteral",
46
+ expressions: [{}],
47
+ quasis: [{ value: { cooked: "ignored", raw: "ignored" } }],
48
+ };
49
+ expect((0, require_story_utils_1.getNodeName)(tplNode)).toBe("hello-world");
50
+ expect((0, require_story_utils_1.getNodeName)(withExpressions)).toBeNull();
51
+ });
52
+ it("[REQ-ANNOTATION-REQUIRED] resolves non-computed member/qualified names and rejects computed", () => {
53
+ const memberExpr = {
54
+ type: "MemberExpression",
55
+ object: { type: "Identifier", name: "obj" },
56
+ property: { type: "Identifier", name: "prop" },
57
+ computed: false,
58
+ };
59
+ const computedMember = {
60
+ type: "MemberExpression",
61
+ object: { type: "Identifier", name: "obj" },
62
+ property: { type: "Literal", value: "dynamic" },
63
+ computed: true,
64
+ };
65
+ const tsQualified = {
66
+ type: "TSQualifiedName",
67
+ left: { type: "Identifier", name: "Ns" },
68
+ right: { type: "Identifier", name: "Type" },
69
+ };
70
+ const jsxMember = {
71
+ type: "JSXMemberExpression",
72
+ object: { type: "JSXIdentifier", name: "Ns" },
73
+ property: { type: "JSXIdentifier", name: "Component" },
74
+ };
75
+ expect((0, require_story_utils_1.getNodeName)(memberExpr)).toBe("prop");
76
+ expect((0, require_story_utils_1.getNodeName)(computedMember)).toBeNull();
77
+ expect((0, require_story_utils_1.getNodeName)(tsQualified)).toBe("Type");
78
+ expect((0, require_story_utils_1.getNodeName)(jsxMember)).toBe("Component");
79
+ });
80
+ it("[REQ-ANNOTATION-REQUIRED] extracts names from Property/ObjectProperty keys", () => {
81
+ const prop = {
82
+ type: "Property",
83
+ key: { type: "Identifier", name: "propName" },
84
+ };
85
+ const objProp = {
86
+ type: "ObjectProperty",
87
+ key: { type: "Literal", value: "literalKey" },
88
+ };
89
+ const notProp = { type: "MethodDefinition", key: { name: "method" } };
90
+ expect((0, require_story_utils_1.getNodeName)(prop)).toBe("propName");
91
+ expect((0, require_story_utils_1.getNodeName)(objProp)).toBe("literalKey");
92
+ expect((0, require_story_utils_1.getNodeName)(notProp)).toBe("method");
93
+ });
94
+ it("[REQ-ANNOTATION-REQUIRED] prefers direct id/key names before deeper inspection", () => {
95
+ const funcNode = {
96
+ type: "FunctionDeclaration",
97
+ id: { type: "Identifier", name: "directName" },
98
+ key: { type: "Identifier", name: "ignored" },
99
+ };
100
+ const keyNode = {
101
+ type: "MethodDefinition",
102
+ key: { type: "Identifier", name: "keyName" },
103
+ };
104
+ expect((0, require_story_utils_1.getNodeName)(funcNode)).toBe("directName");
105
+ expect((0, require_story_utils_1.getNodeName)(keyNode)).toBe("keyName");
106
+ });
107
+ it("[REQ-ANNOTATION-REQUIRED] unwraps TSLiteralType and JSXNamespacedName wrappers", () => {
108
+ const tsLiteral = {
109
+ type: "TSLiteralType",
110
+ literal: { type: "Literal", value: "wrapped" },
111
+ };
112
+ const jsxNamespaced = {
113
+ type: "JSXNamespacedName",
114
+ name: { type: "JSXIdentifier", name: "NsComponent" },
115
+ };
116
+ expect((0, require_story_utils_1.getNodeName)(tsLiteral)).toBe("wrapped");
117
+ expect((0, require_story_utils_1.getNodeName)(jsxNamespaced)).toBe("NsComponent");
118
+ });
119
+ it("[REQ-ANNOTATION-REQUIRED] returns null for non-TemplateLiteral nodes passed to templateLiteralToString via getNodeName", () => {
120
+ const fakeTemplate = {
121
+ type: "Literal",
122
+ value: "no-template",
123
+ quasis: [{ value: { cooked: "ignored", raw: "ignored" } }],
124
+ };
125
+ const realTemplateWithExpr = {
126
+ type: "TemplateLiteral",
127
+ expressions: [{ type: "Identifier", name: "expr" }],
128
+ quasis: [{ value: { cooked: "start", raw: "start" } }],
129
+ };
130
+ expect((0, require_story_utils_1.getNodeName)(fakeTemplate)).toBe("no-template");
131
+ expect((0, require_story_utils_1.getNodeName)(realTemplateWithExpr)).toBeNull();
132
+ });
133
+ it("[REQ-ANNOTATION-REQUIRED] handles nullish and missing .value in TemplateLiteral quasis defensively", () => {
134
+ const defensiveTemplate = {
135
+ type: "TemplateLiteral",
136
+ expressions: [],
137
+ quasis: [
138
+ null,
139
+ { value: null },
140
+ { value: { cooked: "part1", raw: "raw1" } },
141
+ { value: { raw: "-only-raw" } },
142
+ {},
143
+ ],
144
+ };
145
+ expect((0, require_story_utils_1.getNodeName)(defensiveTemplate)).toBe("part1-only-raw");
146
+ });
147
+ it("[REQ-ANNOTATION-REQUIRED] follows generic .key fallback for other shapes", () => {
148
+ const genericWithKey = {
149
+ type: "SomeNode",
150
+ key: { type: "Identifier", name: "viaKey" },
151
+ };
152
+ const genericWithoutKey = {
153
+ type: "SomeNode",
154
+ };
155
+ expect((0, require_story_utils_1.getNodeName)(genericWithKey)).toBe("viaKey");
156
+ expect((0, require_story_utils_1.getNodeName)(genericWithoutKey)).toBeNull();
157
+ });
158
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Focused branch coverage tests for annotation-checker helper.
3
+ * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-AUTOFIX REQ-ANNOTATION-REPORTING
4
+ */
5
+ export {};
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ /**
3
+ * Focused branch coverage tests for annotation-checker helper.
4
+ * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-AUTOFIX REQ-ANNOTATION-REPORTING
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ jest.mock("../../src/utils/reqAnnotationDetection", () => ({
8
+ // Always report that no requirement annotation is present so we exercise
9
+ // the missing-annotation reporting and autofix paths in the helper.
10
+ hasReqAnnotation: jest.fn(() => false),
11
+ }));
12
+ jest.mock("../../src/rules/helpers/require-story-utils", () => ({
13
+ // Provide a stable, human-readable name so reporting paths are predictable
14
+ // without depending on the full real implementation.
15
+ getNodeName: jest.fn(() => "mockName"),
16
+ }));
17
+ const annotation_checker_1 = require("../../src/utils/annotation-checker");
18
+ /**
19
+ * Build a minimal ESLint rule context stub that captures report() calls.
20
+ *
21
+ * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REPORTING
22
+ */
23
+ function createContextStub() {
24
+ const report = jest.fn();
25
+ const sourceCode = {
26
+ getJSDocComment: jest.fn(() => null),
27
+ getCommentsBefore: jest.fn(() => []),
28
+ };
29
+ const context = {
30
+ getSourceCode() {
31
+ return sourceCode;
32
+ },
33
+ report,
34
+ };
35
+ return { context, report };
36
+ }
37
+ describe("annotation-checker helper branch coverage (Story 003.0-DEV-FUNCTION-ANNOTATIONS)", () => {
38
+ it("[REQ-ANNOTATION-AUTOFIX] attaches fix directly to node when parent is missing", () => {
39
+ const { context, report } = createContextStub();
40
+ const node = { type: "FunctionDeclaration" }; // no parent property
41
+ (0, annotation_checker_1.checkReqAnnotation)(context, node, { enableFix: true });
42
+ expect(report).toHaveBeenCalledTimes(1);
43
+ const reportArg = report.mock.calls[0][0];
44
+ expect(reportArg).toHaveProperty("fix");
45
+ const fixer = { insertTextBefore: jest.fn() };
46
+ reportArg.fix(fixer);
47
+ expect(fixer.insertTextBefore).toHaveBeenCalledWith(node, "/** @req <REQ-ID> */\n");
48
+ });
49
+ it("[REQ-ANNOTATION-AUTOFIX] attaches fix to MethodDefinition wrapper when parent is a method", () => {
50
+ const { context, report } = createContextStub();
51
+ const methodParent = { type: "MethodDefinition" };
52
+ const node = {
53
+ type: "FunctionExpression",
54
+ parent: methodParent,
55
+ id: { type: "Identifier", name: "methodImpl" },
56
+ };
57
+ (0, annotation_checker_1.checkReqAnnotation)(context, node, { enableFix: true });
58
+ expect(report).toHaveBeenCalledTimes(1);
59
+ const reportArg = report.mock.calls[0][0];
60
+ const fixer = { insertTextBefore: jest.fn() };
61
+ reportArg.fix(fixer);
62
+ expect(fixer.insertTextBefore).toHaveBeenCalledWith(methodParent, "/** @req <REQ-ID> */\n");
63
+ });
64
+ it("[REQ-ANNOTATION-AUTOFIX] attaches fix to VariableDeclarator when node is its init", () => {
65
+ const { context, report } = createContextStub();
66
+ const declarator = { type: "VariableDeclarator" };
67
+ const node = { type: "FunctionExpression", parent: declarator };
68
+ declarator.init = node;
69
+ (0, annotation_checker_1.checkReqAnnotation)(context, node, { enableFix: true });
70
+ expect(report).toHaveBeenCalledTimes(1);
71
+ const reportArg = report.mock.calls[0][0];
72
+ const fixer = { insertTextBefore: jest.fn() };
73
+ reportArg.fix(fixer);
74
+ expect(fixer.insertTextBefore).toHaveBeenCalledWith(declarator, "/** @req <REQ-ID> */\n");
75
+ });
76
+ it("[REQ-ANNOTATION-AUTOFIX] attaches fix to ExpressionStatement wrapper when parent is an expression", () => {
77
+ const { context, report } = createContextStub();
78
+ const expressionParent = { type: "ExpressionStatement" };
79
+ const node = {
80
+ type: "FunctionExpression",
81
+ parent: expressionParent,
82
+ id: { type: "Identifier", name: "iife" },
83
+ };
84
+ (0, annotation_checker_1.checkReqAnnotation)(context, node, { enableFix: true });
85
+ expect(report).toHaveBeenCalledTimes(1);
86
+ const reportArg = report.mock.calls[0][0];
87
+ const fixer = { insertTextBefore: jest.fn() };
88
+ reportArg.fix(fixer);
89
+ expect(fixer.insertTextBefore).toHaveBeenCalledWith(expressionParent, "/** @req <REQ-ID> */\n");
90
+ });
91
+ it("[REQ-ANNOTATION-AUTOFIX] omits fix when enableFix is false", () => {
92
+ const { context, report } = createContextStub();
93
+ const node = {
94
+ type: "FunctionDeclaration",
95
+ parent: { type: "Program" },
96
+ id: { type: "Identifier", name: "noFix" },
97
+ };
98
+ (0, annotation_checker_1.checkReqAnnotation)(context, node, { enableFix: false });
99
+ expect(report).toHaveBeenCalledTimes(1);
100
+ const reportArg = report.mock.calls[0][0];
101
+ expect(reportArg.fix).toBeUndefined();
102
+ });
103
+ });