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.
- package/CHANGELOG.md +2 -2
- package/lib/src/index.js +53 -33
- package/lib/src/maintenance/commands.d.ts +4 -0
- package/lib/src/maintenance/commands.js +4 -0
- package/lib/src/maintenance/index.d.ts +1 -0
- package/lib/src/maintenance/index.js +1 -0
- package/lib/src/maintenance/report.js +2 -2
- package/lib/src/maintenance/update.js +4 -2
- package/lib/src/rules/helpers/require-story-helpers.d.ts +5 -11
- package/lib/src/rules/helpers/require-story-helpers.js +7 -74
- package/lib/src/rules/helpers/test-callback-exclusion.d.ts +43 -0
- package/lib/src/rules/helpers/test-callback-exclusion.js +100 -0
- package/lib/src/rules/helpers/valid-annotation-format-validators.js +8 -2
- package/lib/src/rules/no-redundant-annotation.js +4 -0
- package/lib/src/rules/prefer-implements-annotation.js +25 -20
- package/lib/src/rules/require-story-annotation.js +14 -1
- package/lib/src/rules/valid-annotation-format.js +62 -42
- package/lib/tests/integration/no-redundant-annotation.integration.test.js +31 -0
- package/lib/tests/integration/require-traceability-test-callbacks.integration.test.d.ts +1 -0
- package/lib/tests/integration/require-traceability-test-callbacks.integration.test.js +148 -0
- package/lib/tests/maintenance/detect-isolated.test.js +22 -14
- package/lib/tests/perf/maintenance-cli-large-workspace.test.js +145 -64
- package/lib/tests/perf/maintenance-large-workspace.test.js +65 -46
- package/lib/tests/rules/no-redundant-annotation.test.js +5 -0
- package/lib/tests/rules/require-story-annotation.test.js +21 -0
- package/lib/tests/rules/require-story-helpers.test.js +69 -0
- package/lib/tests/utils/{annotation-checker-branches.test.d.ts → annotation-checker-autofix-behavior.test.d.ts} +1 -1
- package/lib/tests/utils/{annotation-checker-branches.test.js → annotation-checker-autofix-behavior.test.js} +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
## [1.
|
|
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
|
-
*
|
|
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
|
-
|
|
137
|
+
const mergedMeta = createAliasRuleMeta(unifiedRule, legacyRule);
|
|
138
|
+
if (!mergedMeta) {
|
|
97
139
|
return unifiedRule;
|
|
98
140
|
}
|
|
99
|
-
|
|
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
|
-
// @
|
|
16
|
-
// @
|
|
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
|
-
// @
|
|
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
|
-
|
|
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, {
|
|
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
|
|
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)
|