eslint-plugin-traceability 1.16.1 → 1.17.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 (29) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/lib/src/index.js +53 -33
  3. package/lib/src/maintenance/commands.d.ts +4 -0
  4. package/lib/src/maintenance/commands.js +4 -0
  5. package/lib/src/maintenance/index.d.ts +1 -0
  6. package/lib/src/maintenance/index.js +1 -0
  7. package/lib/src/maintenance/report.js +2 -2
  8. package/lib/src/maintenance/update.js +4 -2
  9. package/lib/src/rules/helpers/require-story-helpers.d.ts +5 -11
  10. package/lib/src/rules/helpers/require-story-helpers.js +7 -74
  11. package/lib/src/rules/helpers/test-callback-exclusion.d.ts +43 -0
  12. package/lib/src/rules/helpers/test-callback-exclusion.js +100 -0
  13. package/lib/src/rules/helpers/valid-annotation-format-validators.js +8 -2
  14. package/lib/src/rules/no-redundant-annotation.js +4 -0
  15. package/lib/src/rules/prefer-implements-annotation.js +25 -20
  16. package/lib/src/rules/require-story-annotation.js +14 -1
  17. package/lib/src/rules/valid-annotation-format.js +62 -42
  18. package/lib/tests/integration/no-redundant-annotation.integration.test.js +31 -0
  19. package/lib/tests/integration/require-traceability-test-callbacks.integration.test.d.ts +1 -0
  20. package/lib/tests/integration/require-traceability-test-callbacks.integration.test.js +148 -0
  21. package/lib/tests/maintenance/detect-isolated.test.js +22 -14
  22. package/lib/tests/perf/maintenance-cli-large-workspace.test.js +145 -64
  23. package/lib/tests/perf/maintenance-large-workspace.test.js +65 -46
  24. package/lib/tests/rules/no-redundant-annotation.test.js +5 -0
  25. package/lib/tests/rules/require-story-annotation.test.js +21 -0
  26. package/lib/tests/rules/require-story-helpers.test.js +69 -0
  27. package/lib/tests/utils/{annotation-checker-branches.test.d.ts → annotation-checker-autofix-behavior.test.d.ts} +1 -1
  28. package/lib/tests/utils/{annotation-checker-branches.test.js → annotation-checker-autofix-behavior.test.js} +2 -2
  29. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,9 +1,9 @@
1
- ## [1.16.1](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.16.0...v1.16.1) (2025-12-09)
1
+ ## [1.17.1](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.17.0...v1.17.1) (2025-12-10)
2
2
 
3
3
 
4
4
  ### Bug Fixes
5
5
 
6
- * broaden test callback exclusion coverage for function annotations ([8078745](https://github.com/voder-ai/eslint-plugin-traceability/commit/80787455dcba39724158f378fce54ae78a75b59b))
6
+ * avoid redundant-annotation false positives for catch blocks ([2ac69e2](https://github.com/voder-ai/eslint-plugin-traceability/commit/2ac69e2a03b54cf29bf2bc175771bf3b23aba6e9))
7
7
 
8
8
  # Changelog
9
9
 
package/lib/src/index.js CHANGED
@@ -86,61 +86,77 @@ RULE_NAMES.forEach(
86
86
  * and diagnostics).
87
87
  *
88
88
  * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED REQ-CONFIGURABLE-SCOPE REQ-EXPORT-PRIORITY
89
+ * @supports docs/stories/010.4-DEV-UNIFIED-FUNCTION-RULE-AND-ALIASES.story.md REQ-UNIFIED-ALIAS-ENGINE
89
90
  */
90
- {
91
+ function createAliasRuleMeta(unifiedRule, legacyRule) {
92
+ if (!legacyRule) {
93
+ return null;
94
+ }
95
+ const baseMeta = (unifiedRule.meta ?? {});
96
+ const legacyMeta = (legacyRule.meta ?? {});
97
+ return {
98
+ ...baseMeta,
99
+ ...legacyMeta,
100
+ docs: {
101
+ ...(baseMeta.docs ?? {}),
102
+ ...(legacyMeta.docs ?? {}),
103
+ },
104
+ messages: {
105
+ ...(baseMeta.messages ?? {}),
106
+ ...(legacyMeta.messages ?? {}),
107
+ },
108
+ schema: legacyMeta.schema ??
109
+ baseMeta.schema ??
110
+ [],
111
+ hasSuggestions: legacyMeta.hasSuggestions ??
112
+ baseMeta.hasSuggestions,
113
+ fixable: legacyMeta.fixable ??
114
+ baseMeta.fixable,
115
+ deprecated: legacyMeta.deprecated ??
116
+ baseMeta.deprecated,
117
+ replacedBy: legacyMeta.replacedBy ??
118
+ baseMeta.replacedBy,
119
+ type: legacyMeta.type ??
120
+ baseMeta.type ??
121
+ "problem",
122
+ };
123
+ }
124
+ /**
125
+ * Wire up the unified `require-traceability` rule and its legacy alias rules
126
+ * so that they share the same implementation while preserving legacy metadata.
127
+ *
128
+ * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED REQ-EXPORT-PRIORITY
129
+ * @supports docs/stories/010.4-DEV-UNIFIED-FUNCTION-RULE-AND-ALIASES.story.md REQ-UNIFIED-ALIAS-ENGINE
130
+ */
131
+ function wireUnifiedFunctionAnnotationAliases() {
91
132
  const unifiedRule = rules["require-traceability"];
92
133
  const legacyStoryRule = rules["require-story-annotation"];
93
134
  const legacyReqRule = rules["require-req-annotation"];
94
135
  if (unifiedRule) {
95
136
  const createAliasRule = (legacyRule) => {
96
- if (!legacyRule) {
137
+ const mergedMeta = createAliasRuleMeta(unifiedRule, legacyRule);
138
+ if (!mergedMeta) {
97
139
  return unifiedRule;
98
140
  }
99
- const baseMeta = (unifiedRule.meta ?? {});
100
- const legacyMeta = (legacyRule.meta ?? {});
101
- const mergedMeta = {
102
- ...baseMeta,
103
- ...legacyMeta,
104
- docs: {
105
- ...(baseMeta.docs ?? {}),
106
- ...(legacyMeta.docs ?? {}),
107
- },
108
- messages: {
109
- ...(baseMeta.messages ?? {}),
110
- ...(legacyMeta.messages ?? {}),
111
- },
112
- schema: legacyMeta.schema ??
113
- baseMeta.schema ??
114
- [],
115
- hasSuggestions: legacyMeta.hasSuggestions ??
116
- baseMeta.hasSuggestions,
117
- fixable: legacyMeta.fixable ??
118
- baseMeta.fixable,
119
- deprecated: legacyMeta.deprecated ??
120
- baseMeta.deprecated,
121
- replacedBy: legacyMeta.replacedBy ??
122
- baseMeta.replacedBy,
123
- type: legacyMeta.type ??
124
- baseMeta.type ??
125
- "problem",
126
- };
127
- const aliasRule = {
141
+ return {
128
142
  ...unifiedRule,
129
143
  meta: mergedMeta,
130
144
  create: unifiedRule.create,
131
145
  };
132
- return aliasRule;
133
146
  };
134
147
  rules["require-story-annotation"] = createAliasRule(legacyStoryRule);
135
148
  rules["require-req-annotation"] = createAliasRule(legacyReqRule);
136
149
  }
137
150
  }
151
+ wireUnifiedFunctionAnnotationAliases();
138
152
  /**
139
153
  * @supports docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md REQ-RULE-NAME
140
154
  * Wire up traceability/prefer-supports-annotation as the primary rule name and
141
155
  * traceability/prefer-implements-annotation as its deprecated alias.
156
+ *
157
+ * @supports docs/stories/010.4-DEV-UNIFIED-FUNCTION-RULE-AND-ALIASES.story.md REQ-MIGRATION-RULE-NAMING
142
158
  */
143
- {
159
+ function wirePreferSupportsAlias() {
144
160
  const implementsRule = rules["prefer-implements-annotation"];
145
161
  if (implementsRule) {
146
162
  const originalMeta = implementsRule.meta ?? {};
@@ -163,6 +179,7 @@ RULE_NAMES.forEach(
163
179
  }
164
180
  }
165
181
  }
182
+ wirePreferSupportsAlias();
166
183
  /**
167
184
  * Plugin metadata used by ESLint for debugging and caching.
168
185
  *
@@ -226,6 +243,9 @@ const TRACEABILITY_RULE_SEVERITIES = {
226
243
  * @req REQ-ERROR-SEVERITY - Map rule types to appropriate ESLint severity levels (errors vs warnings)
227
244
  * @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
228
245
  * @req REQ-CONFIG-PRESETS - Provide flat-config presets that self-register the plugin and core rules
246
+ *
247
+ * @supports docs/stories/007.0-DEV-ERROR-REPORTING.story.md REQ-ERROR-SEVERITY
248
+ * @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md REQ-CONFIG-PRESETS
229
249
  */
230
250
  function createTraceabilityFlatConfig() {
231
251
  return {
@@ -7,6 +7,7 @@ export declare const EXIT_USAGE = 2;
7
7
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
8
8
  * @req REQ-MAINT-DETECT - CLI surface for detection of stale annotations
9
9
  * @req REQ-MAINT-SAFE - Return specific exit codes for stale vs clean states
10
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-SAFE
10
11
  */
11
12
  export declare function handleDetect(normalized: NormalizedCliArgs): number;
12
13
  /**
@@ -14,6 +15,7 @@ export declare function handleDetect(normalized: NormalizedCliArgs): number;
14
15
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
15
16
  * @req REQ-MAINT-VERIFY - CLI surface for verification of annotations
16
17
  * @req REQ-MAINT-SAFE - Return distinct exit codes for verification failures
18
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-VERIFY REQ-MAINT-SAFE
17
19
  */
18
20
  export declare function handleVerify(normalized: NormalizedCliArgs): number;
19
21
  /**
@@ -21,6 +23,7 @@ export declare function handleVerify(normalized: NormalizedCliArgs): number;
21
23
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
22
24
  * @req REQ-MAINT-REPORT - CLI surface for human-readable maintenance reports
23
25
  * @req REQ-MAINT-SAFE - Support machine-readable formats for safe automation
26
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT REQ-MAINT-SAFE
24
27
  */
25
28
  export declare function handleReport(normalized: NormalizedCliArgs): number;
26
29
  /**
@@ -28,5 +31,6 @@ export declare function handleReport(normalized: NormalizedCliArgs): number;
28
31
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
29
32
  * @req REQ-MAINT-UPDATE - CLI surface for updating annotation references
30
33
  * @req REQ-MAINT-SAFE - Provide dry-run mode and explicit parameter checks
34
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE REQ-MAINT-SAFE
31
35
  */
32
36
  export declare function handleUpdate(normalized: NormalizedCliArgs): number;
@@ -28,6 +28,7 @@ exports.EXIT_USAGE = 2;
28
28
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
29
29
  * @req REQ-MAINT-DETECT - CLI surface for detection of stale annotations
30
30
  * @req REQ-MAINT-SAFE - Return specific exit codes for stale vs clean states
31
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-SAFE
31
32
  */
32
33
  function handleDetect(normalized) {
33
34
  const flags = (0, flags_1.parseFlags)(normalized);
@@ -54,6 +55,7 @@ Run 'traceability-maint report' for a structured summary.`);
54
55
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
55
56
  * @req REQ-MAINT-VERIFY - CLI surface for verification of annotations
56
57
  * @req REQ-MAINT-SAFE - Return distinct exit codes for verification failures
58
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-VERIFY REQ-MAINT-SAFE
57
59
  */
58
60
  function handleVerify(normalized) {
59
61
  const flags = (0, flags_1.parseFlags)(normalized);
@@ -71,6 +73,7 @@ function handleVerify(normalized) {
71
73
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
72
74
  * @req REQ-MAINT-REPORT - CLI surface for human-readable maintenance reports
73
75
  * @req REQ-MAINT-SAFE - Support machine-readable formats for safe automation
76
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT REQ-MAINT-SAFE
74
77
  */
75
78
  function handleReport(normalized) {
76
79
  const flags = (0, flags_1.parseFlags)(normalized);
@@ -96,6 +99,7 @@ function handleReport(normalized) {
96
99
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
97
100
  * @req REQ-MAINT-UPDATE - CLI surface for updating annotation references
98
101
  * @req REQ-MAINT-SAFE - Provide dry-run mode and explicit parameter checks
102
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE REQ-MAINT-SAFE
99
103
  */
100
104
  function handleUpdate(normalized) {
101
105
  const flags = (0, flags_1.parseFlags)(normalized);
@@ -7,6 +7,7 @@
7
7
  * @req REQ-MAINT-VERIFY
8
8
  * @req REQ-MAINT-REPORT
9
9
  * @req REQ-MAINT-SAFE
10
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-UPDATE REQ-MAINT-BATCH REQ-MAINT-VERIFY REQ-MAINT-REPORT REQ-MAINT-SAFE
10
11
  */
11
12
  export { detectStaleAnnotations } from "./detect";
12
13
  export { updateAnnotationReferences } from "./update";
@@ -10,6 +10,7 @@ exports.generateMaintenanceReport = exports.verifyAnnotations = exports.batchUpd
10
10
  * @req REQ-MAINT-VERIFY
11
11
  * @req REQ-MAINT-REPORT
12
12
  * @req REQ-MAINT-SAFE
13
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-UPDATE REQ-MAINT-BATCH REQ-MAINT-VERIFY REQ-MAINT-REPORT REQ-MAINT-SAFE
13
14
  */
14
15
  var detect_1 = require("./detect");
15
16
  Object.defineProperty(exports, "detectStaleAnnotations", { enumerable: true, get: function () { return detect_1.detectStaleAnnotations; } });
@@ -12,8 +12,8 @@ const detect_1 = require("./detect");
12
12
  */
13
13
  function generateMaintenanceReport(codebasePath) {
14
14
  const staleAnnotations = (0, detect_1.detectStaleAnnotations)(codebasePath);
15
- // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md // @req REQ-MAINT-SAFE - When no stale annotations are found, return empty string to indicate no actions required
16
- // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md // @req REQ-MAINT-REPORT - When stale annotations exist, produce a newline-separated report
15
+ // @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-SAFE - When no stale annotations are found, return empty string to indicate no actions required
16
+ // @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT - When stale annotations exist, produce a newline-separated report
17
17
  if (staleAnnotations.length === 0) {
18
18
  return "";
19
19
  }
@@ -40,6 +40,7 @@ const utils_1 = require("./utils");
40
40
  * Helper to process a single file for annotation reference updates
41
41
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
42
42
  * @req REQ-MAINT-UPDATE
43
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
43
44
  */
44
45
  function processFileForAnnotationUpdates(fullPath, regex, newPath, replacementCountRef) {
45
46
  const content = fs.readFileSync(fullPath, "utf8"); // getAllFiles already returns regular files
@@ -78,8 +79,7 @@ function updateAnnotationReferences(codebasePath, oldPath, newPath) {
78
79
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
79
80
  * @req REQ-MAINT-UPDATE
80
81
  */
81
- // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
82
- // @req REQ-MAINT-UPDATE
82
+ // @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
83
83
  if (!fs.existsSync(codebasePath) ||
84
84
  !fs.statSync(codebasePath).isDirectory()) {
85
85
  return 0;
@@ -92,11 +92,13 @@ function updateAnnotationReferences(codebasePath, oldPath, newPath) {
92
92
  * Iterate over all files and replace annotation references
93
93
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
94
94
  * @req REQ-MAINT-UPDATE
95
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
95
96
  */
96
97
  /**
97
98
  * Loop over each discovered file path
98
99
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
99
100
  * @req REQ-MAINT-UPDATE
101
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
100
102
  */
101
103
  for (const fullPath of files) {
102
104
  processFileForAnnotationUpdates(fullPath, regex, newPath, replacementCountRef);
@@ -11,21 +11,17 @@ import type { Rule } from "eslint";
11
11
  import { linesBeforeHasStory, parentChainHasStory, fallbackTextBeforeHasStory } from "./require-story-io";
12
12
  import { getNodeName } from "./require-story-utils";
13
13
  import { DEFAULT_SCOPE, EXPORT_PRIORITY_VALUES, STORY_PATH } from "./require-story-core";
14
+ import { type CallbackExclusionOptions } from "./test-callback-exclusion";
14
15
  /**
15
16
  * Shared configuration helpers
16
17
  */
17
- interface ReportOptions {
18
+ interface ReportOptions extends CallbackExclusionOptions {
18
19
  annotationTemplateOverride?: string;
19
20
  autoFixToggle?: boolean;
20
- excludeTestCallbacks?: boolean;
21
21
  }
22
22
  /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
23
- declare function getAnnotationTemplate(override?: string, _options?: {
24
- excludeTestCallbacks?: boolean;
25
- }): string;
26
- declare function shouldApplyAutoFix(autoFix: boolean | undefined, _options?: {
27
- excludeTestCallbacks?: boolean;
28
- }): boolean;
23
+ declare function getAnnotationTemplate(override?: string, _options?: CallbackExclusionOptions): string;
24
+ declare function shouldApplyAutoFix(autoFix: boolean | undefined, _options?: CallbackExclusionOptions): boolean;
29
25
  /**
30
26
  * Determine if a node is in an export declaration
31
27
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
@@ -70,9 +66,7 @@ declare function resolveTargetNode(sourceCode: any, node: any): any;
70
66
  */
71
67
  declare function extractName(node: any): string;
72
68
  /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
73
- declare function shouldProcessNode(node: any, scope: string[], exportPriority?: string, options?: {
74
- excludeTestCallbacks?: boolean;
75
- }): boolean;
69
+ declare function shouldProcessNode(node: any, scope: string[], exportPriority?: string, options?: CallbackExclusionOptions): boolean;
76
70
  /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
77
71
  declare function reportMissing(context: Rule.RuleContext, sourceCode: any, config: {
78
72
  node: any;
@@ -23,38 +23,7 @@ const require_story_core_1 = require("./require-story-core");
23
23
  Object.defineProperty(exports, "DEFAULT_SCOPE", { enumerable: true, get: function () { return require_story_core_1.DEFAULT_SCOPE; } });
24
24
  Object.defineProperty(exports, "EXPORT_PRIORITY_VALUES", { enumerable: true, get: function () { return require_story_core_1.EXPORT_PRIORITY_VALUES; } });
25
25
  Object.defineProperty(exports, "STORY_PATH", { enumerable: true, get: function () { return require_story_core_1.STORY_PATH; } });
26
- /**
27
- * Known test framework function names and variants.
28
- * Includes Jest, Mocha, Vitest and their focused/skipped/concurrent variants.
29
- * @req REQ-TEST-CALLBACK-EXCLUSION
30
- */
31
- const TEST_FUNCTION_NAMES = new Set([
32
- // Core test/describe-style functions (Jest, Mocha, Vitest share many of these)
33
- "it",
34
- "test",
35
- "describe",
36
- "suite",
37
- // Focused variants
38
- "fit",
39
- "ftest",
40
- "fdescribe",
41
- "fsuite",
42
- // Skipped variants
43
- "xit",
44
- "xtest",
45
- "xdescribe",
46
- "xsuite",
47
- // Additional common aliases
48
- "context",
49
- "specify",
50
- "before",
51
- "after",
52
- "beforeEach",
53
- "afterEach",
54
- "beforeAll",
55
- "afterAll",
56
- ]);
57
- const TEST_FUNCTION_CONCURRENT_PROP = "concurrent";
26
+ const test_callback_exclusion_1 = require("./test-callback-exclusion");
58
27
  /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
59
28
  function getAnnotationTemplate(override, _options) {
60
29
  if (typeof override === "string" && override.trim().length > 0) {
@@ -74,9 +43,13 @@ function shouldApplyAutoFix(autoFix, _options) {
74
43
  */
75
44
  /** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
76
45
  function buildTemplateConfig(options) {
77
- const effectiveTemplate = getAnnotationTemplate(options?.annotationTemplateOverride, { excludeTestCallbacks: options?.excludeTestCallbacks });
46
+ const effectiveTemplate = getAnnotationTemplate(options?.annotationTemplateOverride, {
47
+ excludeTestCallbacks: options?.excludeTestCallbacks,
48
+ additionalTestHelperNames: options?.additionalTestHelperNames,
49
+ });
78
50
  const allowFix = shouldApplyAutoFix(options?.autoFixToggle, {
79
51
  excludeTestCallbacks: options?.excludeTestCallbacks,
52
+ additionalTestHelperNames: options?.additionalTestHelperNames,
80
53
  });
81
54
  return { effectiveTemplate, allowFix };
82
55
  }
@@ -123,46 +96,6 @@ function isEffectivelyAnonymousFunction(node) {
123
96
  }
124
97
  return true;
125
98
  }
126
- /**
127
- * Determine whether a node represents a callback passed to a known test
128
- * framework function (Jest, Mocha, Vitest, etc).
129
- *
130
- * Supports:
131
- * - it(), test(), describe(), suite(), context(), specify()
132
- * - lifecycle hooks: before(), after(), beforeEach(), afterEach(), beforeAll(), afterAll()
133
- * - focused variants: fit(), ftest(), fdescribe(), fsuite()
134
- * - skipped variants and helpers: xit(), xtest(), xdescribe(), xsuite()
135
- * - their .concurrent variants (e.g., it.concurrent(), test.concurrent())
136
- *
137
- * @req REQ-TEST-CALLBACK-EXCLUSION
138
- */
139
- function isTestFrameworkCallback(node, options) {
140
- if (options?.excludeTestCallbacks === false) {
141
- return false;
142
- }
143
- if (!node || node.type !== "ArrowFunctionExpression") {
144
- return false;
145
- }
146
- const parent = node.parent;
147
- if (!parent || parent.type !== "CallExpression") {
148
- return false;
149
- }
150
- const callee = parent.callee;
151
- if (callee.type === "Identifier") {
152
- return TEST_FUNCTION_NAMES.has(callee.name);
153
- }
154
- if (callee.type === "MemberExpression" &&
155
- !callee.computed &&
156
- callee.property &&
157
- callee.property.type === "Identifier" &&
158
- callee.property.name === TEST_FUNCTION_CONCURRENT_PROP) {
159
- const obj = callee.object;
160
- if (obj && obj.type === "Identifier") {
161
- return TEST_FUNCTION_NAMES.has(obj.name);
162
- }
163
- }
164
- return false;
165
- }
166
99
  /**
167
100
  * Determine whether a function node is required to carry its own annotation
168
101
  * according to Story 004.0-DEV-BRANCH-ANNOTATIONS rules.
@@ -177,7 +110,7 @@ function isTestFrameworkCallback(node, options) {
177
110
  * @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-ARROW-FUNCTION-EXCLUDED REQ-NESTED-FUNCTION-INHERITANCE
178
111
  */
179
112
  function requiresOwnFunctionAnnotation(node, options) {
180
- if (isTestFrameworkCallback(node, options)) {
113
+ if ((0, test_callback_exclusion_1.isTestFrameworkCallback)(node, options)) {
181
114
  return false;
182
115
  }
183
116
  // Anonymous arrow functions used as callbacks are excluded from function-level
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Shared helpers for determining whether a function-like node should be
3
+ * treated as a test framework callback that may be excluded from
4
+ * function-level annotation requirements.
5
+ *
6
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
7
+ * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
8
+ * @story docs/stories/013-exclude-test-framework-callbacks.proposed.md
9
+ * @req REQ-TEST-CALLBACK-EXCLUSION - Provide reusable test callback exclusion logic
10
+ */
11
+ import type { TSESTree } from "@typescript-eslint/utils";
12
+ /**
13
+ * Options controlling how test callbacks are treated by the helpers.
14
+ *
15
+ * - excludeTestCallbacks: when false, no callbacks are excluded and all
16
+ * function-like nodes are treated as regular functions.
17
+ * - additionalTestHelperNames: optional array of additional helper names that
18
+ * should be treated like built-in test functions when excludeTestCallbacks
19
+ * is enabled.
20
+ */
21
+ interface CallbackExclusionOptions {
22
+ excludeTestCallbacks?: boolean;
23
+ additionalTestHelperNames?: string[];
24
+ }
25
+ type TraceabilityNodeWithParent = TSESTree.Node & {
26
+ parent?: TraceabilityNodeWithParent | null;
27
+ };
28
+ /**
29
+ * Determine whether a node represents a callback passed to a known test
30
+ * framework function (Jest, Mocha, Vitest, etc).
31
+ *
32
+ * Supports:
33
+ * - it(), test(), describe(), suite(), context(), specify()
34
+ * - lifecycle hooks: before(), after(), beforeEach(), afterEach(), beforeAll(), afterAll()
35
+ * - focused variants: fit(), ftest(), fdescribe(), fsuite()
36
+ * - skipped variants and helpers: xit(), xtest(), xdescribe(), xsuite()
37
+ * - their .concurrent variants (e.g., it.concurrent(), test.concurrent())
38
+ *
39
+ * @req REQ-TEST-CALLBACK-EXCLUSION
40
+ */
41
+ declare function isTestFrameworkCallback(node: TraceabilityNodeWithParent | null | undefined, options?: CallbackExclusionOptions): boolean;
42
+ export type { CallbackExclusionOptions };
43
+ export { isTestFrameworkCallback };
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isTestFrameworkCallback = isTestFrameworkCallback;
4
+ /**
5
+ * Known test framework function names and variants.
6
+ * Includes Jest, Mocha, Vitest and their focused/skipped/concurrent variants.
7
+ *
8
+ * @req REQ-TEST-CALLBACK-EXCLUSION
9
+ */
10
+ const TEST_FUNCTION_NAMES = new Set([
11
+ // Core test/describe-style functions (Jest, Mocha, Vitest share many of these)
12
+ "it",
13
+ "test",
14
+ "describe",
15
+ "suite",
16
+ // Focused variants
17
+ "fit",
18
+ "ftest",
19
+ "fdescribe",
20
+ "fsuite",
21
+ // Skipped variants
22
+ "xit",
23
+ "xtest",
24
+ "xdescribe",
25
+ "xsuite",
26
+ // Additional common aliases
27
+ "context",
28
+ "specify",
29
+ "before",
30
+ "after",
31
+ "beforeEach",
32
+ "afterEach",
33
+ "beforeAll",
34
+ "afterAll",
35
+ ]);
36
+ const TEST_FUNCTION_CONCURRENT_PROP = "concurrent";
37
+ /**
38
+ * Determine if a function name should be treated as a recognized test helper,
39
+ * including core test functions and any configured additional helper names.
40
+ *
41
+ * Vitest's `bench` is explicitly never treated as an excluded test callback,
42
+ * even if it appears in additionalTestHelperNames, to preserve the story
43
+ * requirement that bench callbacks always require annotations.
44
+ *
45
+ * @req REQ-TEST-CALLBACK-EXCLUSION
46
+ */
47
+ function isRecognizedTestHelperName(name, options) {
48
+ if (name === "bench") {
49
+ return false;
50
+ }
51
+ if (TEST_FUNCTION_NAMES.has(name)) {
52
+ return true;
53
+ }
54
+ if (options?.additionalTestHelperNames &&
55
+ Array.isArray(options.additionalTestHelperNames)) {
56
+ return options.additionalTestHelperNames.includes(name);
57
+ }
58
+ return false;
59
+ }
60
+ /**
61
+ * Determine whether a node represents a callback passed to a known test
62
+ * framework function (Jest, Mocha, Vitest, etc).
63
+ *
64
+ * Supports:
65
+ * - it(), test(), describe(), suite(), context(), specify()
66
+ * - lifecycle hooks: before(), after(), beforeEach(), afterEach(), beforeAll(), afterAll()
67
+ * - focused variants: fit(), ftest(), fdescribe(), fsuite()
68
+ * - skipped variants and helpers: xit(), xtest(), xdescribe(), xsuite()
69
+ * - their .concurrent variants (e.g., it.concurrent(), test.concurrent())
70
+ *
71
+ * @req REQ-TEST-CALLBACK-EXCLUSION
72
+ */
73
+ function isTestFrameworkCallback(node, options) {
74
+ if (options?.excludeTestCallbacks === false) {
75
+ return false;
76
+ }
77
+ if (!node || node.type !== "ArrowFunctionExpression") {
78
+ return false;
79
+ }
80
+ const parent = node.parent;
81
+ if (!parent || parent.type !== "CallExpression") {
82
+ return false;
83
+ }
84
+ const callExpressionParent = parent;
85
+ const callee = callExpressionParent.callee;
86
+ if (callee.type === "Identifier") {
87
+ return isRecognizedTestHelperName(callee.name, options);
88
+ }
89
+ if (callee.type === "MemberExpression" &&
90
+ !callee.computed &&
91
+ callee.property &&
92
+ callee.property.type === "Identifier" &&
93
+ callee.property.name === TEST_FUNCTION_CONCURRENT_PROP) {
94
+ const obj = callee.object;
95
+ if (obj && obj.type === "Identifier") {
96
+ return isRecognizedTestHelperName(obj.name, options);
97
+ }
98
+ }
99
+ return false;
100
+ }
@@ -175,8 +175,8 @@ function validateStoryAnnotation(context, comment, rawValue, options) {
175
175
  return;
176
176
  }
177
177
  // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
178
- // @req REQ-PATH-FORMAT - Reject @story values containing internal whitespace as invalid
179
- if (/\s/.test(trimmed)) {
178
+ // @req REQ-PATH-FORMAT - Reject @story values containing internal whitespace that do not collapse into a valid story path
179
+ if (/\s/.test(trimmed) && !pathPattern.test(collapsed)) {
180
180
  reportInvalidStoryFormat(context, comment, collapsed, options);
181
181
  return;
182
182
  }
@@ -223,6 +223,12 @@ function validateReqAnnotation(context, comment, rawValue, options) {
223
223
  return;
224
224
  }
225
225
  const collapsed = (0, valid_annotation_utils_1.collapseAnnotationValue)(trimmed);
226
+ // Allow mixed @req/@supports lines to pass without additional @req validation,
227
+ // while still validating simple multi-line @req identifiers that collapse
228
+ // to a single token.
229
+ if (collapsed.includes("@supports")) {
230
+ return;
231
+ }
226
232
  const reqPattern = options.reqPattern;
227
233
  // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
228
234
  // @req REQ-REQ-FORMAT - Flag @req identifiers that do not match the configured pattern
@@ -326,6 +326,10 @@ const rule = {
326
326
  if (process.env.TRACEABILITY_DEBUG === "1") {
327
327
  console.log("[no-redundant-annotation] BlockStatement parent=%s statements=%d", parent && parent.type, Array.isArray(node.body) ? node.body.length : 0);
328
328
  }
329
+ // @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-CATCH-BLOCK-HANDLING
330
+ if (parent && parent.type === "CatchClause") {
331
+ return;
332
+ }
329
333
  const scopePairs = collectScopePairs(context, parent, options.maxScopeDepth);
330
334
  debugScopePairs(parent, scopePairs);
331
335
  if (scopePairs.size === 0)