eslint-plugin-traceability 1.15.0 → 1.16.0
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 +61 -13
- package/lib/src/index.js +59 -0
- package/lib/src/rules/helpers/require-story-core.js +1 -1
- package/lib/src/rules/helpers/require-story-helpers.d.ts +10 -3
- package/lib/src/rules/helpers/require-story-helpers.js +66 -7
- package/lib/src/rules/no-redundant-annotation.js +73 -55
- package/lib/src/rules/require-branch-annotation.js +2 -2
- package/lib/src/rules/require-req-annotation.js +2 -2
- package/lib/src/rules/require-story-annotation.js +8 -3
- package/lib/src/rules/require-traceability.js +8 -9
- package/lib/src/utils/annotation-checker.js +23 -4
- package/lib/tests/cli-error-handling.test.js +10 -1
- package/lib/tests/integration/cli-integration.test.js +5 -0
- package/lib/tests/integration/require-traceability-aliases.integration.test.js +126 -0
- package/lib/tests/plugin-default-export-and-configs.test.js +23 -0
- package/lib/tests/rules/auto-fix-behavior-008.test.js +7 -7
- package/lib/tests/rules/error-reporting.test.js +1 -1
- package/lib/tests/rules/no-redundant-annotation.test.js +20 -0
- package/lib/tests/rules/require-story-annotation.test.js +49 -10
- package/lib/tests/rules/require-story-helpers.test.js +32 -0
- package/lib/tests/rules/require-story-utils.test.d.ts +7 -0
- package/lib/tests/rules/require-story-utils.test.js +158 -0
- package/lib/tests/utils/annotation-checker-branches.test.d.ts +5 -0
- package/lib/tests/utils/annotation-checker-branches.test.js +103 -0
- package/lib/tests/utils/annotation-scope-analyzer.test.js +134 -0
- package/lib/tests/utils/branch-annotation-helpers.test.js +66 -0
- package/package.json +2 -2
- package/user-docs/api-reference.md +71 -15
- package/user-docs/examples.md +24 -13
- package/user-docs/migration-guide.md +127 -4
- package/user-docs/traceability-overview.md +116 -0
- package/lib/tests/integration/dogfooding-validation.test.js +0 -129
- /package/lib/tests/integration/{dogfooding-validation.test.d.ts → require-traceability-aliases.integration.test.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# [1.
|
|
1
|
+
# [1.16.0](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.15.0...v1.16.0) (2025-12-09)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Features
|
|
5
5
|
|
|
6
|
-
* add
|
|
6
|
+
* add excludeTestCallbacks option for test framework callbacks ([108510d](https://github.com/voder-ai/eslint-plugin-traceability/commit/108510d361f45c9ff75118ee266970dcbabd3fb8))
|
|
7
7
|
|
|
8
8
|
# Changelog
|
|
9
9
|
|
package/README.md
CHANGED
|
@@ -46,16 +46,59 @@ export default [
|
|
|
46
46
|
];
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
### Canonical function-level rule and legacy aliases
|
|
50
|
+
|
|
51
|
+
For function-level checks, `traceability/require-traceability` is the **canonical** rule. It ensures that in-scope functions and methods have both story coverage and requirement coverage, and it understands both the modern `@supports` format and the legacy `@story` / `@req` pairs.
|
|
52
|
+
|
|
53
|
+
The older rule keys:
|
|
54
|
+
|
|
55
|
+
- `traceability/require-story-annotation`
|
|
56
|
+
- `traceability/require-req-annotation`
|
|
57
|
+
|
|
58
|
+
remain available as **backward-compatible aliases** that are wired to the same underlying engine. Existing configurations that reference these legacy keys will continue to behave as before. New configurations should normally prefer the unified `traceability/require-traceability` rule and treat the legacy keys as compatibility shims or for gradual migration.
|
|
59
|
+
|
|
60
|
+
A concise flat-config example that enables the unified function-level rule and commonly used supporting rules explicitly:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// eslint.config.js
|
|
64
|
+
import traceability from "eslint-plugin-traceability";
|
|
65
|
+
|
|
66
|
+
export default [
|
|
67
|
+
{
|
|
68
|
+
plugins: {
|
|
69
|
+
traceability,
|
|
70
|
+
},
|
|
71
|
+
rules: {
|
|
72
|
+
// Canonical function-level rule (preferred for new configs)
|
|
73
|
+
"traceability/require-traceability": "error",
|
|
74
|
+
|
|
75
|
+
// Common supporting rules
|
|
76
|
+
"traceability/require-branch-annotation": "warn",
|
|
77
|
+
"traceability/valid-annotation-format": "error",
|
|
78
|
+
"traceability/valid-story-reference": "error",
|
|
79
|
+
"traceability/valid-req-reference": "error",
|
|
80
|
+
|
|
81
|
+
// Optional: enforce test traceability conventions
|
|
82
|
+
"traceability/require-test-traceability": "warn",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
```
|
|
87
|
+
|
|
49
88
|
### Available Rules
|
|
50
89
|
|
|
51
|
-
- `
|
|
52
|
-
|
|
53
|
-
- `traceability/require-
|
|
54
|
-
- `traceability/
|
|
55
|
-
- `traceability/
|
|
56
|
-
- `traceability/
|
|
57
|
-
- `traceability/
|
|
58
|
-
- `traceability/
|
|
90
|
+
The plugin exposes several rules. For **new configurations**, the unified function-level rule and `@supports` annotations are the canonical choice; the `@story` and `@req` forms remain available primarily for backward compatibility and gradual migration.
|
|
91
|
+
|
|
92
|
+
- `traceability/require-traceability` – **Unified function-level traceability rule.** Ensures that in-scope functions and methods have both story coverage and requirement coverage. It accepts either `@supports` (preferred for new code) or legacy `@story` / `@req` annotations and is enabled by default in the plugin's `recommended` and `strict` presets.
|
|
93
|
+
- `traceability/require-story-annotation` – Legacy function-level rule key that focuses on the **story** side of function-level traceability. It is kept for backward compatibility and is wired to the same underlying engine as `traceability/require-traceability`, so existing configurations that refer to this rule continue to work. New configurations should normally rely on `traceability/require-traceability` instead of enabling this rule directly.
|
|
94
|
+
- `traceability/require-req-annotation` – Legacy function-level rule key that focuses on the **requirement** side of function-level traceability. Like `traceability/require-story-annotation`, it is retained for backward compatibility and conceptually composes the same checks exposed by `traceability/require-traceability`. New configurations can usually rely on the unified rule alone unless you have specific reasons to tune the legacy keys separately.
|
|
95
|
+
- `traceability/require-branch-annotation` – Enforces presence of branch annotations on significant control-flow branches (if/else, switch cases, loops, try/catch). Branch annotations can use a single `@supports` line (preferred) or the older `@story`/`@req` pair for backward compatibility. (See the rule documentation in the plugin's user guide.)
|
|
96
|
+
- `traceability/valid-annotation-format` – Enforces correct format of traceability annotations, including `@supports` (preferred), `@story`, and `@req`. (See the rule documentation in the plugin's user guide.)
|
|
97
|
+
- `traceability/valid-story-reference` – Validates that story references (whether written via `@story` or embedded in `@supports`) point to existing story files. (See the rule documentation in the plugin's user guide.)
|
|
98
|
+
- `traceability/valid-req-reference` – Validates that requirement identifiers (whether written via `@req` or embedded in `@supports`) point to existing requirement IDs in your story files. (See the rule documentation in the plugin's user guide.)
|
|
99
|
+
- `traceability/require-test-traceability` – Enforces traceability conventions in test files by requiring file-level `@supports` annotations, story references in `describe` blocks, and `[REQ-...]` prefixes in `it`/`test` names. (See the rule documentation in the plugin's user guide.)
|
|
100
|
+
- `traceability/no-redundant-annotation` – Detects and optionally removes redundant traceability annotations on simple leaf statements that are already covered by an enclosing annotated scope. It is enabled at severity `warn` in both the `recommended` and `strict` presets by default; consumers can override its severity (including promoting it to `error`) or disable it explicitly in their ESLint configuration if they prefer.
|
|
101
|
+
- `traceability/prefer-supports-annotation` – Optional migration helper that recommends converting legacy single-story `@story`/`@req` JSDoc blocks and inline comments into the newer `@supports` format. It is disabled by default and must be explicitly enabled. The legacy rule name `traceability/prefer-implements-annotation` remains available as a deprecated alias. (See the rule documentation in the plugin's user guide.)
|
|
59
102
|
|
|
60
103
|
Configuration options: For detailed per-rule options (such as scopes, branch types, and story directory settings), see the individual rule docs in the plugin's user guide and the consolidated [API Reference](user-docs/api-reference.md).
|
|
61
104
|
|
|
@@ -83,15 +126,17 @@ export default [
|
|
|
83
126
|
|
|
84
127
|
```js
|
|
85
128
|
/**
|
|
86
|
-
* @
|
|
87
|
-
* //
|
|
88
|
-
*
|
|
129
|
+
* @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED
|
|
130
|
+
* // Prefer @supports for new implementations; @story/@req remain supported for
|
|
131
|
+
* // legacy and simple single-story code paths.
|
|
89
132
|
*/
|
|
90
133
|
function initAuth() {
|
|
91
134
|
// implementation...
|
|
92
135
|
}
|
|
93
136
|
```
|
|
94
137
|
|
|
138
|
+
`@supports` is the canonical format for new, multi-story integrations and richer traceability. The legacy `@story` and `@req` forms are kept for backward compatibility and remain appropriate for simple, single-story functions or where a gradual migration is preferred.
|
|
139
|
+
|
|
95
140
|
3. Run ESLint:
|
|
96
141
|
|
|
97
142
|
```bash
|
|
@@ -148,10 +193,12 @@ For a full description of options and JSON payloads, see the [Maintenance API an
|
|
|
148
193
|
You can validate the plugin by running ESLint CLI with the plugin on a sample file:
|
|
149
194
|
|
|
150
195
|
```bash
|
|
151
|
-
# Validate missing
|
|
152
|
-
npx eslint --no-eslintrc --config eslint.config.js sample.js --rule 'traceability/require-
|
|
196
|
+
# Validate missing function-level traceability (should report an error)
|
|
197
|
+
npx eslint --no-eslintrc --config eslint.config.js sample.js --rule 'traceability/require-traceability:error'
|
|
153
198
|
```
|
|
154
199
|
|
|
200
|
+
If you have existing configurations that reference the legacy function-level keys, you can also validate them directly by enabling `traceability/require-story-annotation` and `traceability/require-req-annotation` instead.
|
|
201
|
+
|
|
155
202
|
This command runs ESLint with the plugin, pointing at `eslint.config.js` flat config.
|
|
156
203
|
|
|
157
204
|
Replace `sample.js` with your JavaScript or TypeScript file.
|
|
@@ -237,6 +284,7 @@ For the canonical, user-facing security policy (including how to report vulnerab
|
|
|
237
284
|
- ESLint v9 Setup Guide: [user-docs/eslint-9-setup-guide.md](user-docs/eslint-9-setup-guide.md)
|
|
238
285
|
- API Reference: [user-docs/api-reference.md](user-docs/api-reference.md)
|
|
239
286
|
- Examples: [user-docs/examples.md](user-docs/examples.md)
|
|
287
|
+
- Traceability Overview and FAQ: [user-docs/traceability-overview.md](user-docs/traceability-overview.md)
|
|
240
288
|
- Migration Guide: [user-docs/migration-guide.md](user-docs/migration-guide.md)
|
|
241
289
|
- Full README: <https://github.com/voder-ai/eslint-plugin-traceability#readme>
|
|
242
290
|
- Contribution guide: <https://github.com/voder-ai/eslint-plugin-traceability/blob/main/CONTRIBUTING.md>
|
package/lib/src/index.js
CHANGED
|
@@ -76,6 +76,65 @@ RULE_NAMES.forEach(
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
|
+
/**
|
|
80
|
+
* Wire up the unified function-annotation rule and its backward-compatible
|
|
81
|
+
* aliases so that:
|
|
82
|
+
* - traceability/require-traceability is the canonical rule implementation
|
|
83
|
+
* - traceability/require-story-annotation and
|
|
84
|
+
* traceability/require-req-annotation act as aliases that share the same
|
|
85
|
+
* underlying logic while preserving their legacy metadata (docs, schema,
|
|
86
|
+
* and diagnostics).
|
|
87
|
+
*
|
|
88
|
+
* @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED REQ-CONFIGURABLE-SCOPE REQ-EXPORT-PRIORITY
|
|
89
|
+
*/
|
|
90
|
+
{
|
|
91
|
+
const unifiedRule = rules["require-traceability"];
|
|
92
|
+
const legacyStoryRule = rules["require-story-annotation"];
|
|
93
|
+
const legacyReqRule = rules["require-req-annotation"];
|
|
94
|
+
if (unifiedRule) {
|
|
95
|
+
const createAliasRule = (legacyRule) => {
|
|
96
|
+
if (!legacyRule) {
|
|
97
|
+
return unifiedRule;
|
|
98
|
+
}
|
|
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 = {
|
|
128
|
+
...unifiedRule,
|
|
129
|
+
meta: mergedMeta,
|
|
130
|
+
create: unifiedRule.create,
|
|
131
|
+
};
|
|
132
|
+
return aliasRule;
|
|
133
|
+
};
|
|
134
|
+
rules["require-story-annotation"] = createAliasRule(legacyStoryRule);
|
|
135
|
+
rules["require-req-annotation"] = createAliasRule(legacyReqRule);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
79
138
|
/**
|
|
80
139
|
* @supports docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md REQ-RULE-NAME
|
|
81
140
|
* Wire up traceability/prefer-supports-annotation as the primary rule name and
|
|
@@ -133,7 +133,7 @@ function createMissingStoryReportDescriptor(config) {
|
|
|
133
133
|
fix: allowFix ? baseFix : undefined,
|
|
134
134
|
suggest: [
|
|
135
135
|
{
|
|
136
|
-
desc: `Add
|
|
136
|
+
desc: `Add traceability annotation for function '${name}' using @supports (preferred) or @story (legacy), for example: ${effectiveTemplate.replace("@story", "@supports")}`,
|
|
137
137
|
fix: baseFix,
|
|
138
138
|
},
|
|
139
139
|
],
|
|
@@ -17,10 +17,15 @@ import { DEFAULT_SCOPE, EXPORT_PRIORITY_VALUES, STORY_PATH } from "./require-sto
|
|
|
17
17
|
interface ReportOptions {
|
|
18
18
|
annotationTemplateOverride?: string;
|
|
19
19
|
autoFixToggle?: boolean;
|
|
20
|
+
excludeTestCallbacks?: boolean;
|
|
20
21
|
}
|
|
21
22
|
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
22
|
-
declare function getAnnotationTemplate(override?: string
|
|
23
|
-
|
|
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;
|
|
24
29
|
/**
|
|
25
30
|
* Determine if a node is in an export declaration
|
|
26
31
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
@@ -65,7 +70,9 @@ declare function resolveTargetNode(sourceCode: any, node: any): any;
|
|
|
65
70
|
*/
|
|
66
71
|
declare function extractName(node: any): string;
|
|
67
72
|
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
68
|
-
declare function shouldProcessNode(node: any, scope: string[], exportPriority?: string
|
|
73
|
+
declare function shouldProcessNode(node: any, scope: string[], exportPriority?: string, options?: {
|
|
74
|
+
excludeTestCallbacks?: boolean;
|
|
75
|
+
}): boolean;
|
|
69
76
|
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
70
77
|
declare function reportMissing(context: Rule.RuleContext, sourceCode: any, config: {
|
|
71
78
|
node: any;
|
|
@@ -23,14 +23,31 @@ 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
|
+
"it",
|
|
33
|
+
"test",
|
|
34
|
+
"describe",
|
|
35
|
+
"fit",
|
|
36
|
+
"xit",
|
|
37
|
+
"ftest",
|
|
38
|
+
"xtest",
|
|
39
|
+
"fdescribe",
|
|
40
|
+
"xdescribe",
|
|
41
|
+
]);
|
|
42
|
+
const TEST_FUNCTION_CONCURRENT_PROP = "concurrent";
|
|
26
43
|
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
27
|
-
function getAnnotationTemplate(override) {
|
|
44
|
+
function getAnnotationTemplate(override, _options) {
|
|
28
45
|
if (typeof override === "string" && override.trim().length > 0) {
|
|
29
46
|
return override.trim();
|
|
30
47
|
}
|
|
31
48
|
return `/** @story ${require_story_core_1.STORY_PATH} */`;
|
|
32
49
|
}
|
|
33
|
-
function shouldApplyAutoFix(autoFix) {
|
|
50
|
+
function shouldApplyAutoFix(autoFix, _options) {
|
|
34
51
|
if (autoFix === false) {
|
|
35
52
|
return false;
|
|
36
53
|
}
|
|
@@ -42,8 +59,10 @@ function shouldApplyAutoFix(autoFix) {
|
|
|
42
59
|
*/
|
|
43
60
|
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
44
61
|
function buildTemplateConfig(options) {
|
|
45
|
-
const effectiveTemplate = getAnnotationTemplate(options?.annotationTemplateOverride);
|
|
46
|
-
const allowFix = shouldApplyAutoFix(options?.autoFixToggle
|
|
62
|
+
const effectiveTemplate = getAnnotationTemplate(options?.annotationTemplateOverride, { excludeTestCallbacks: options?.excludeTestCallbacks });
|
|
63
|
+
const allowFix = shouldApplyAutoFix(options?.autoFixToggle, {
|
|
64
|
+
excludeTestCallbacks: options?.excludeTestCallbacks,
|
|
65
|
+
});
|
|
47
66
|
return { effectiveTemplate, allowFix };
|
|
48
67
|
}
|
|
49
68
|
/**
|
|
@@ -89,6 +108,43 @@ function isEffectivelyAnonymousFunction(node) {
|
|
|
89
108
|
}
|
|
90
109
|
return true;
|
|
91
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Determine whether a node represents a callback passed to a known test
|
|
113
|
+
* framework function (Jest, Mocha, Vitest, etc).
|
|
114
|
+
*
|
|
115
|
+
* Supports:
|
|
116
|
+
* - it(), test(), describe(), fit(), xit(), ftest(), xtest(), fdescribe(), xdescribe()
|
|
117
|
+
* - their .concurrent variants (e.g., it.concurrent(), test.concurrent())
|
|
118
|
+
*
|
|
119
|
+
* @req REQ-TEST-CALLBACK-EXCLUSION
|
|
120
|
+
*/
|
|
121
|
+
function isTestFrameworkCallback(node, options) {
|
|
122
|
+
if (options?.excludeTestCallbacks === false) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (!node || node.type !== "ArrowFunctionExpression") {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const parent = node.parent;
|
|
129
|
+
if (!parent || parent.type !== "CallExpression") {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const callee = parent.callee;
|
|
133
|
+
if (callee.type === "Identifier") {
|
|
134
|
+
return TEST_FUNCTION_NAMES.has(callee.name);
|
|
135
|
+
}
|
|
136
|
+
if (callee.type === "MemberExpression" &&
|
|
137
|
+
!callee.computed &&
|
|
138
|
+
callee.property &&
|
|
139
|
+
callee.property.type === "Identifier" &&
|
|
140
|
+
callee.property.name === TEST_FUNCTION_CONCURRENT_PROP) {
|
|
141
|
+
const obj = callee.object;
|
|
142
|
+
if (obj && obj.type === "Identifier") {
|
|
143
|
+
return TEST_FUNCTION_NAMES.has(obj.name);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
92
148
|
/**
|
|
93
149
|
* Determine whether a function node is required to carry its own annotation
|
|
94
150
|
* according to Story 004.0-DEV-BRANCH-ANNOTATIONS rules.
|
|
@@ -102,7 +158,10 @@ function isEffectivelyAnonymousFunction(node) {
|
|
|
102
158
|
*
|
|
103
159
|
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-ARROW-FUNCTION-EXCLUDED REQ-NESTED-FUNCTION-INHERITANCE
|
|
104
160
|
*/
|
|
105
|
-
function requiresOwnFunctionAnnotation(node) {
|
|
161
|
+
function requiresOwnFunctionAnnotation(node, options) {
|
|
162
|
+
if (isTestFrameworkCallback(node, options)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
106
165
|
// Anonymous arrow functions used as callbacks are excluded from function-level
|
|
107
166
|
// requirements when they are nested inside another function or method.
|
|
108
167
|
if (isAnonymousArrowFunction(node) &&
|
|
@@ -306,12 +365,12 @@ function extractName(node) {
|
|
|
306
365
|
return "(anonymous)";
|
|
307
366
|
}
|
|
308
367
|
/** @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md */
|
|
309
|
-
function shouldProcessNode(node, scope, exportPriority = "all") {
|
|
368
|
+
function shouldProcessNode(node, scope, exportPriority = "all", options) {
|
|
310
369
|
if (node &&
|
|
311
370
|
(node.type === "FunctionDeclaration" ||
|
|
312
371
|
node.type === "FunctionExpression" ||
|
|
313
372
|
node.type === "ArrowFunctionExpression") &&
|
|
314
|
-
!requiresOwnFunctionAnnotation(node)) {
|
|
373
|
+
!requiresOwnFunctionAnnotation(node, options)) {
|
|
315
374
|
return false;
|
|
316
375
|
}
|
|
317
376
|
if (!scope.includes(node.type)) {
|
|
@@ -48,49 +48,13 @@ function normalizeOptions(raw) {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* For branch scopes we reuse the same comment-gathering helper used by
|
|
55
|
-
* the require-branch-annotation rule so that REQ-SCOPE-INHERITANCE
|
|
56
|
-
* aligns with existing behavior.
|
|
51
|
+
* Collect comments around a scope node using JSDoc, leading comments,
|
|
52
|
+
* and any comments that appear immediately before the node.
|
|
57
53
|
*
|
|
58
54
|
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE
|
|
59
55
|
*/
|
|
60
|
-
function
|
|
61
|
-
const sourceCode = context.getSourceCode();
|
|
62
|
-
// Branch-style scope: use the branch helpers to collect comment text.
|
|
63
|
-
if (branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES.includes(scopeNode.type)) {
|
|
64
|
-
const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, scopeNode, parent);
|
|
65
|
-
return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromText)(text);
|
|
66
|
-
}
|
|
67
|
-
// Function-like scopes: collect from JSDoc and leading/before comments
|
|
68
|
-
const FUNCTION_LIKE_TYPES = new Set([
|
|
69
|
-
"FunctionDeclaration",
|
|
70
|
-
"FunctionExpression",
|
|
71
|
-
"ArrowFunctionExpression",
|
|
72
|
-
"MethodDefinition",
|
|
73
|
-
"TSDeclareFunction",
|
|
74
|
-
"TSMethodSignature",
|
|
75
|
-
]);
|
|
56
|
+
function getScopeCommentsFromJSDocAndLeading(sourceCode, scopeNode) {
|
|
76
57
|
const comments = [];
|
|
77
|
-
if (FUNCTION_LIKE_TYPES.has(scopeNode.type)) {
|
|
78
|
-
const jsdoc = sourceCode.getJSDocComment
|
|
79
|
-
? sourceCode.getJSDocComment(scopeNode)
|
|
80
|
-
: null;
|
|
81
|
-
const before = sourceCode.getCommentsBefore
|
|
82
|
-
? sourceCode.getCommentsBefore(scopeNode) || []
|
|
83
|
-
: [];
|
|
84
|
-
if (jsdoc) {
|
|
85
|
-
comments.push(jsdoc);
|
|
86
|
-
}
|
|
87
|
-
if (Array.isArray(scopeNode.leadingComments)) {
|
|
88
|
-
comments.push(...scopeNode.leadingComments);
|
|
89
|
-
}
|
|
90
|
-
comments.push(...before);
|
|
91
|
-
return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromComments)(comments);
|
|
92
|
-
}
|
|
93
|
-
// Fallback: inspect JSDoc and leading comments around the scope node.
|
|
94
58
|
const jsdoc = sourceCode.getJSDocComment
|
|
95
59
|
? sourceCode.getJSDocComment(scopeNode)
|
|
96
60
|
: null;
|
|
@@ -104,6 +68,28 @@ function getScopePairs(context, scopeNode, parent) {
|
|
|
104
68
|
comments.push(...scopeNode.leadingComments);
|
|
105
69
|
}
|
|
106
70
|
comments.push(...before);
|
|
71
|
+
return comments;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Compute the story/requirement pairs for annotations that apply to the
|
|
75
|
+
* given scope node.
|
|
76
|
+
*
|
|
77
|
+
* For branch scopes we reuse the same comment-gathering helper used by
|
|
78
|
+
* the require-branch-annotation rule so that REQ-SCOPE-INHERITANCE
|
|
79
|
+
* aligns with existing behavior. For non-branch scopes, we reuse a
|
|
80
|
+
* shared helper that collects JSDoc, leading, and immediately-before
|
|
81
|
+
* comments around the scope node.
|
|
82
|
+
*
|
|
83
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE
|
|
84
|
+
*/
|
|
85
|
+
function getScopePairs(context, scopeNode, parent) {
|
|
86
|
+
const sourceCode = context.getSourceCode();
|
|
87
|
+
// Branch-style scope: use the branch helpers to collect comment text.
|
|
88
|
+
if (branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES.includes(scopeNode.type)) {
|
|
89
|
+
const text = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, scopeNode, parent);
|
|
90
|
+
return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromText)(text);
|
|
91
|
+
}
|
|
92
|
+
const comments = getScopeCommentsFromJSDocAndLeading(sourceCode, scopeNode);
|
|
107
93
|
return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromComments)(comments);
|
|
108
94
|
}
|
|
109
95
|
/**
|
|
@@ -162,14 +148,12 @@ function collectScopePairs(context, startingScopeNode, maxScopeDepth) {
|
|
|
162
148
|
return result;
|
|
163
149
|
}
|
|
164
150
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* comments. Returns null when the statement should not be treated as
|
|
168
|
-
* redundant.
|
|
151
|
+
* Extract statement-level comments and story/requirement pairs that are
|
|
152
|
+
* relevant for redundancy analysis within a given scope.
|
|
169
153
|
*
|
|
170
|
-
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-
|
|
154
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-STATEMENT-SIGNIFICANCE REQ-SCOPE-ANALYSIS
|
|
171
155
|
*/
|
|
172
|
-
function
|
|
156
|
+
function getStatementPairsForRedundancy(context, stmt, scopePairs, options) {
|
|
173
157
|
if (scopePairs.size === 0) {
|
|
174
158
|
return null;
|
|
175
159
|
}
|
|
@@ -187,22 +171,56 @@ function getRedundantStatementContext(context, stmt, scopePairs, options) {
|
|
|
187
171
|
if (stmtPairs.size === 0) {
|
|
188
172
|
return null;
|
|
189
173
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
174
|
+
return { comments: stmtComments, pairs: stmtPairs };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Decide whether the provided statement-level pairs should be considered
|
|
178
|
+
* redundant within the given scope, respecting configuration options.
|
|
179
|
+
*
|
|
180
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-CONFIGURABLE-STRICTNESS
|
|
181
|
+
*/
|
|
182
|
+
function isStatementRedundantWithinScope(stmtPairs, scopePairs, options) {
|
|
183
|
+
if (options.allowEmphasisDuplication &&
|
|
184
|
+
stmtPairs.size === 1 &&
|
|
185
|
+
(0, annotation_scope_analyzer_1.arePairsFullyCovered)(stmtPairs, scopePairs)) {
|
|
186
|
+
return false;
|
|
196
187
|
}
|
|
197
188
|
if (!(0, annotation_scope_analyzer_1.arePairsFullyCovered)(stmtPairs, scopePairs)) {
|
|
198
|
-
return
|
|
189
|
+
return false;
|
|
199
190
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Filter a list of comments down to those that contain traceability
|
|
195
|
+
* annotations relevant for redundancy detection.
|
|
196
|
+
*
|
|
197
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SAFE-REMOVAL REQ-REDUNDANCY-PATTERNS
|
|
198
|
+
*/
|
|
199
|
+
function getAnnotationCommentsFromStatement(comments) {
|
|
200
|
+
return comments.filter((comment) => {
|
|
203
201
|
const commentText = typeof comment.value === "string" ? comment.value : "";
|
|
204
202
|
return /@story\b|@req\b|@supports\b/.test(commentText);
|
|
205
203
|
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Determine whether a statement is redundant relative to the provided
|
|
207
|
+
* scopePairs and options, using helper functions to gather statement
|
|
208
|
+
* pairs, apply redundancy rules, and collect the associated annotation
|
|
209
|
+
* comments. Returns null when the statement should not be treated as
|
|
210
|
+
* redundant.
|
|
211
|
+
*
|
|
212
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-STATEMENT-SIGNIFICANCE REQ-CONFIGURABLE-STRICTNESS
|
|
213
|
+
*/
|
|
214
|
+
function getRedundantStatementContext(context, stmt, scopePairs, options) {
|
|
215
|
+
const stmtInfo = getStatementPairsForRedundancy(context, stmt, scopePairs, options);
|
|
216
|
+
if (!stmtInfo) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
const { comments, pairs } = stmtInfo;
|
|
220
|
+
if (!isStatementRedundantWithinScope(pairs, scopePairs, options)) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const annotationComments = getAnnotationCommentsFromStatement(comments);
|
|
206
224
|
if (annotationComments.length === 0) {
|
|
207
225
|
return null;
|
|
208
226
|
}
|
|
@@ -79,7 +79,7 @@ const rule = {
|
|
|
79
79
|
meta: {
|
|
80
80
|
type: "problem",
|
|
81
81
|
docs: {
|
|
82
|
-
description: "Require @story and @
|
|
82
|
+
description: "Require traceability annotations on significant code branches, preferring @supports for combined story and requirement coverage while still accepting legacy @story and @req comments.",
|
|
83
83
|
recommended: "error",
|
|
84
84
|
},
|
|
85
85
|
fixable: "code",
|
|
@@ -88,7 +88,7 @@ const rule = {
|
|
|
88
88
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
89
89
|
* @req REQ-ERROR-CONSISTENCY - Use shared branch error message convention with {{missing}} placeholder
|
|
90
90
|
*/
|
|
91
|
-
missingAnnotation: "Branch is missing required annotation: {{missing}}.",
|
|
91
|
+
missingAnnotation: "Branch is missing required traceability annotation: {{missing}}. Prefer using a single @supports line that links this branch to its story and requirements (for example, '@supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-BRANCH-DETECTION'), or add the missing legacy tag if you are not yet using @supports.",
|
|
92
92
|
},
|
|
93
93
|
schema: [
|
|
94
94
|
{
|
|
@@ -7,7 +7,7 @@ const rule = {
|
|
|
7
7
|
type: "problem",
|
|
8
8
|
fixable: "code",
|
|
9
9
|
docs: {
|
|
10
|
-
description: "Require
|
|
10
|
+
description: "Require traceability annotations on function-like exports, preferring @supports for requirement coverage while still accepting legacy @req annotations.",
|
|
11
11
|
recommended: "error",
|
|
12
12
|
},
|
|
13
13
|
messages: {
|
|
@@ -23,7 +23,7 @@ const rule = {
|
|
|
23
23
|
* This rule uses ESLint's message data placeholders for the function name,
|
|
24
24
|
* specifically the {{name}} placeholder populated via context.report.
|
|
25
25
|
*/
|
|
26
|
-
missingReq: "Function '{{functionName}}' is missing
|
|
26
|
+
missingReq: "Function '{{functionName}}' is missing required traceability annotations. Prefer adding an @supports line that links this function to at least one requirement (for example, '@supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-EXAMPLE'), or, when you are limited to a single-story context, add a legacy @req annotation such as '@req REQ-EXAMPLE' referencing the appropriate requirement from the story file.",
|
|
27
27
|
},
|
|
28
28
|
schema: [
|
|
29
29
|
{
|
|
@@ -18,7 +18,7 @@ const rule = {
|
|
|
18
18
|
meta: {
|
|
19
19
|
type: "problem",
|
|
20
20
|
docs: {
|
|
21
|
-
description: "Require
|
|
21
|
+
description: "Require traceability annotations on functions and methods, preferring @supports for story coverage while still accepting legacy @story annotations, and provide optional auto-fix for missing annotations.",
|
|
22
22
|
recommended: "error",
|
|
23
23
|
},
|
|
24
24
|
hasSuggestions: true,
|
|
@@ -33,7 +33,7 @@ const rule = {
|
|
|
33
33
|
*/
|
|
34
34
|
fixable: "code",
|
|
35
35
|
messages: {
|
|
36
|
-
missingStory: "Function '{{name}}' must
|
|
36
|
+
missingStory: "Function '{{name}}' must declare a traceability annotation. Prefer adding an @supports line that links this function to at least one story (for example, '@supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED'), or, when you only need a single-story reference, add a legacy @story annotation that points to the implementing story file, such as docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md.",
|
|
37
37
|
},
|
|
38
38
|
schema: [
|
|
39
39
|
{
|
|
@@ -48,6 +48,7 @@ const rule = {
|
|
|
48
48
|
annotationTemplate: { type: "string" },
|
|
49
49
|
methodAnnotationTemplate: { type: "string" },
|
|
50
50
|
autoFix: { type: "boolean" },
|
|
51
|
+
excludeTestCallbacks: { type: "boolean" },
|
|
51
52
|
},
|
|
52
53
|
additionalProperties: false,
|
|
53
54
|
},
|
|
@@ -75,6 +76,9 @@ const rule = {
|
|
|
75
76
|
? opts.methodAnnotationTemplate.trim()
|
|
76
77
|
: undefined;
|
|
77
78
|
const autoFix = typeof opts.autoFix === "boolean" ? opts.autoFix : true;
|
|
79
|
+
const excludeTestCallbacks = typeof opts.excludeTestCallbacks === "boolean"
|
|
80
|
+
? opts.excludeTestCallbacks
|
|
81
|
+
: true;
|
|
78
82
|
/**
|
|
79
83
|
* Optional debug logging for troubleshooting this rule.
|
|
80
84
|
* Developers can temporarily uncomment the block below to log when the rule
|
|
@@ -90,7 +94,7 @@ const rule = {
|
|
|
90
94
|
// : "<unknown>",
|
|
91
95
|
// );
|
|
92
96
|
// Local closure that binds configured scope and export priority to the helper.
|
|
93
|
-
const should = (node) => (0, require_story_helpers_1.shouldProcessNode)(node, scope, exportPriority);
|
|
97
|
+
const should = (node) => (0, require_story_helpers_1.shouldProcessNode)(node, scope, exportPriority, { excludeTestCallbacks });
|
|
94
98
|
// Delegate visitor construction to helper to keep this file concise.
|
|
95
99
|
return (0, require_story_visitors_1.buildVisitors)(context, sourceCode, {
|
|
96
100
|
shouldProcessNode: should,
|
|
@@ -99,6 +103,7 @@ const rule = {
|
|
|
99
103
|
annotationTemplate,
|
|
100
104
|
methodAnnotationTemplate,
|
|
101
105
|
autoFix,
|
|
106
|
+
excludeTestCallbacks,
|
|
102
107
|
});
|
|
103
108
|
},
|
|
104
109
|
};
|
|
@@ -27,21 +27,20 @@ const rule = {
|
|
|
27
27
|
meta: {
|
|
28
28
|
type: "problem",
|
|
29
29
|
docs: {
|
|
30
|
-
description: "Require
|
|
30
|
+
description: "Require both story and requirement traceability annotations on functions and methods via the unified alias rule",
|
|
31
31
|
recommended: "error",
|
|
32
32
|
},
|
|
33
|
-
hasSuggestions:
|
|
34
|
-
|
|
35
|
-
fixable: (storyRule.meta && storyRule.meta.fixable) ||
|
|
36
|
-
(reqRule.meta && reqRule.meta.fixable) ||
|
|
37
|
-
undefined,
|
|
33
|
+
hasSuggestions: true,
|
|
34
|
+
fixable: undefined,
|
|
38
35
|
messages: {
|
|
36
|
+
// Unified messageId for potential future direct use by this rule.
|
|
37
|
+
missingTraceability: "Function '{{name}}' must declare both story and requirement traceability annotations.",
|
|
38
|
+
// Preserve underlying rule messageIds so that composed listeners can
|
|
39
|
+
// continue to report using their original IDs.
|
|
39
40
|
...(storyRule.meta?.messages ?? {}),
|
|
40
41
|
...(reqRule.meta?.messages ?? {}),
|
|
41
42
|
},
|
|
42
|
-
schema:
|
|
43
|
-
(reqRule.meta && reqRule.meta.schema) ??
|
|
44
|
-
[],
|
|
43
|
+
schema: [],
|
|
45
44
|
},
|
|
46
45
|
create(context) {
|
|
47
46
|
const storyListeners = storyRule.create(context) || {};
|