eslint-plugin-traceability 1.17.0 → 1.18.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 +107 -11
- 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/test-callback-exclusion.d.ts +5 -1
- package/lib/src/rules/helpers/test-callback-exclusion.js +2 -11
- 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-branch-annotation.js +16 -0
- package/lib/src/rules/valid-annotation-format.js +62 -42
- package/lib/src/utils/branch-annotation-helpers.d.ts +8 -1
- package/lib/src/utils/branch-annotation-helpers.js +2 -1
- package/lib/src/utils/branch-annotation-report-helpers.d.ts +1 -0
- package/lib/src/utils/branch-annotation-report-helpers.js +40 -11
- 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 +15 -0
- package/lib/tests/rules/require-branch-annotation.test.js +18 -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/user-docs/api-reference.md +6 -1
- package/user-docs/examples.md +32 -0
- package/user-docs/migration-guide.md +35 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# [1.
|
|
1
|
+
# [1.18.0](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.17.1...v1.18.0) (2025-12-18)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Features
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* add annotationPlacement option for branch annotations ([9cf4189](https://github.com/voder-ai/eslint-plugin-traceability/commit/9cf41897cdbc962eb41f304847ba8a911987d0fd))
|
|
7
7
|
|
|
8
8
|
# Changelog
|
|
9
9
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# eslint-plugin-traceability
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An ESLint plugin that creates searchable verification indices in your code, enabling systematic validation of requirement-to-code mapping.
|
|
4
|
+
|
|
5
|
+
**The Core Value:** Transforms an impossible question ("Does code exist somewhere that should support requirement X?") into a tractable question ("Does this annotated code actually support the requirement it claims to?").
|
|
6
|
+
|
|
7
|
+
The plugin requires `@supports` annotations at key verification points (functions and control flow branches), creating direct checkpoints where each annotation is a verifiable claim you can search, find, and verify locally without needing to understand the broader codebase context.
|
|
4
8
|
|
|
5
9
|
## Attribution
|
|
6
10
|
|
|
@@ -89,21 +93,89 @@ export default [
|
|
|
89
93
|
|
|
90
94
|
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
95
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
- `traceability/require-
|
|
95
|
-
|
|
96
|
-
- `traceability/
|
|
97
|
-
|
|
98
|
-
- `traceability/
|
|
99
|
-
|
|
100
|
-
- `traceability/
|
|
101
|
-
|
|
96
|
+
#### Core Verification Index Rules
|
|
97
|
+
|
|
98
|
+
- `traceability/require-traceability` – **Unified function-level verification rule.** Ensures every function has a verification checkpoint. Without this, functions could exist without explaining their purpose, breaking verification completeness. 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.
|
|
99
|
+
|
|
100
|
+
- `traceability/require-story-annotation` – Legacy function-level rule key that focuses on the **story** side of function-level verification. It is kept for backward compatibility and is wired to the same underlying engine as `traceability/require-traceability`. New configurations should normally rely on `traceability/require-traceability` instead.
|
|
101
|
+
|
|
102
|
+
- `traceability/require-req-annotation` – Legacy function-level rule key that focuses on the **requirement** side of function-level verification. Like `traceability/require-story-annotation`, it is retained for backward compatibility. New configurations can usually rely on the unified rule alone unless you have specific reasons to tune the legacy keys separately.
|
|
103
|
+
|
|
104
|
+
- `traceability/require-branch-annotation` – **Creates verification checkpoints at every branch** (if/else/switch/try/catch). This enables local verification - you can check each branch independently without understanding the entire function. Without branch annotations, you must read entire functions to find branch logic. With branch annotations, you can verify each branch by searching for the requirement ID. Branch annotations can use `@supports` (preferred) or the older `@story`/`@req` pair for backward compatibility.
|
|
105
|
+
|
|
106
|
+
- `traceability/valid-annotation-format` – **Ensures annotations are searchable** with consistent patterns. Verification workflows depend on being able to search for requirement IDs reliably. Enforces correct format of traceability annotations, including `@supports` (preferred), `@story`, and `@req`.
|
|
107
|
+
|
|
108
|
+
- `traceability/valid-story-reference` – **Validates story file references** to ensure verification checkpoints point to real documentation. Validates that story references (whether written via `@story` or embedded in `@supports`) point to existing story files.
|
|
109
|
+
|
|
110
|
+
- `traceability/valid-req-reference` – **Validates requirement identifiers** to ensure verification checkpoints reference valid requirements. Validates that requirement identifiers (whether written via `@req` or embedded in `@supports`) point to existing requirement IDs in your story files.
|
|
111
|
+
|
|
112
|
+
- `traceability/require-test-traceability` – **Enforces verification conventions in test files** by requiring file-level `@supports` annotations, story references in `describe` blocks, and `[REQ-...]` prefixes in `it`/`test` names. This ensures tests are traceable back to requirements for comprehensive verification coverage.
|
|
113
|
+
|
|
114
|
+
#### Quality Rules
|
|
115
|
+
|
|
116
|
+
- `traceability/no-redundant-annotation` – **Removes annotations on simple statements** already covered by enclosing scope. These don't create useful verification checkpoints (no branches to verify), so removing them reduces noise without hurting verifiability. It is enabled at severity `warn` in both the `recommended` and `strict` presets by default; consumers can override its severity or disable it explicitly.
|
|
117
|
+
|
|
118
|
+
- `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 for better verification trails. It is disabled by default and must be explicitly enabled. The legacy rule name `traceability/prefer-implements-annotation` remains available as a deprecated alias.
|
|
102
119
|
|
|
103
120
|
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).
|
|
104
121
|
|
|
105
122
|
For development and contribution guidelines, see the contribution guide in the repository.
|
|
106
123
|
|
|
124
|
+
## How It Works
|
|
125
|
+
|
|
126
|
+
### The Verification Workflow
|
|
127
|
+
|
|
128
|
+
Traceability annotations enable systematic requirement verification through a simple three-step process:
|
|
129
|
+
|
|
130
|
+
1. **Search:** `grep -r "REQ-AUTH-VALIDATION" src/`
|
|
131
|
+
2. **Find:** Get a list of every location claiming to support that requirement
|
|
132
|
+
3. **Verify:** For each result, read the annotation and adjacent code to confirm they match
|
|
133
|
+
|
|
134
|
+
### Why Annotations at Every Branch
|
|
135
|
+
|
|
136
|
+
Each annotation creates a **local verification checkpoint**:
|
|
137
|
+
|
|
138
|
+
- ✅ **Searchable** - Direct search finds all implementation claims
|
|
139
|
+
- ✅ **Local** - Annotation is adjacent to the code being verified
|
|
140
|
+
- ✅ **No context needed** - Verify the claim without understanding control flow
|
|
141
|
+
- ✅ **Parallelizable** - Multiple reviewers work independently
|
|
142
|
+
- ✅ **Complete coverage** - No code can exist without explaining its purpose
|
|
143
|
+
|
|
144
|
+
This enables you to verify requirements systematically rather than trying to remember which code should implement what.
|
|
145
|
+
|
|
146
|
+
### Example: Verifying a Switch Statement
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
switch (severity) {
|
|
150
|
+
// @supports docs/stories/logging.md REQ-SEVERITY-LEVELS
|
|
151
|
+
case "low":
|
|
152
|
+
logLevel = "info";
|
|
153
|
+
break;
|
|
154
|
+
// @supports docs/stories/logging.md REQ-SEVERITY-LEVELS
|
|
155
|
+
case "moderate":
|
|
156
|
+
logLevel = "warn";
|
|
157
|
+
break;
|
|
158
|
+
// @supports docs/stories/logging.md REQ-SEVERITY-LEVELS
|
|
159
|
+
case "high":
|
|
160
|
+
logLevel = "error";
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Verification process:**
|
|
166
|
+
|
|
167
|
+
- Search finds 3 annotations
|
|
168
|
+
- Verify each: "Does this case handle REQ-SEVERITY-LEVELS correctly?"
|
|
169
|
+
- Time: ~30 seconds per case, ~90 seconds total
|
|
170
|
+
- Can be split across team members
|
|
171
|
+
|
|
172
|
+
**Without explicit annotations:**
|
|
173
|
+
|
|
174
|
+
- Must read entire switch to understand what it does
|
|
175
|
+
- Must remember which requirement you're checking
|
|
176
|
+
- Cannot split the work effectively
|
|
177
|
+
- Risk missing cases or misunderstanding intent
|
|
178
|
+
|
|
107
179
|
## Quick Start
|
|
108
180
|
|
|
109
181
|
1. Create a flat ESLint config file (`eslint.config.js`):
|
|
@@ -143,6 +215,30 @@ function initAuth() {
|
|
|
143
215
|
npx eslint "src/**/*.js"
|
|
144
216
|
```
|
|
145
217
|
|
|
218
|
+
## Common Misconceptions
|
|
219
|
+
|
|
220
|
+
### "The annotations are redundant"
|
|
221
|
+
|
|
222
|
+
**Reality:** The apparent redundancy is intentional indexing. Each annotation is a separate verification checkpoint. When the same requirement appears in multiple branches, each annotation creates an independent checkpoint that can be verified in ~30 seconds without understanding the broader code structure.
|
|
223
|
+
|
|
224
|
+
Removing "duplicate" annotations would break the verification workflow by forcing reviewers to trace through code structure instead of verifying local claims.
|
|
225
|
+
|
|
226
|
+
### "This only works for small codebases"
|
|
227
|
+
|
|
228
|
+
**Reality:** The plugin is specifically designed for codebases where manual review is impractical. Small codebases could use manual review; larger ones need systematic, searchable verification indices to make requirement validation tractable.
|
|
229
|
+
|
|
230
|
+
### "This is just documentation"
|
|
231
|
+
|
|
232
|
+
**Reality:** These are verification indices, not documentation. The goal is searchability and local verification, not explaining code. Documentation aims to reduce redundancy; verification indices intentionally create checkpoints for independent validation.
|
|
233
|
+
|
|
234
|
+
### "Inheritance would reduce boilerplate"
|
|
235
|
+
|
|
236
|
+
**Reality:** Inheritance would break verification tractability. When you search for a requirement, you need to find every place that implements it - not find a parent annotation and manually trace what inherits from it. Direct, explicit annotations at each implementation point enable grep-based verification.
|
|
237
|
+
|
|
238
|
+
## Verification Workflow Guide
|
|
239
|
+
|
|
240
|
+
For detailed verification workflows, examples, and best practices, see the [Verification Workflow Guide](docs/verification-workflow-guide.md).
|
|
241
|
+
|
|
146
242
|
## API Reference
|
|
147
243
|
|
|
148
244
|
Detailed API specification and configuration options can be found in the [API Reference](user-docs/api-reference.md).
|
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);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @story docs/stories/013-exclude-test-framework-callbacks.proposed.md
|
|
9
9
|
* @req REQ-TEST-CALLBACK-EXCLUSION - Provide reusable test callback exclusion logic
|
|
10
10
|
*/
|
|
11
|
+
import type { TSESTree } from "@typescript-eslint/utils";
|
|
11
12
|
/**
|
|
12
13
|
* Options controlling how test callbacks are treated by the helpers.
|
|
13
14
|
*
|
|
@@ -21,6 +22,9 @@ interface CallbackExclusionOptions {
|
|
|
21
22
|
excludeTestCallbacks?: boolean;
|
|
22
23
|
additionalTestHelperNames?: string[];
|
|
23
24
|
}
|
|
25
|
+
type TraceabilityNodeWithParent = TSESTree.Node & {
|
|
26
|
+
parent?: TraceabilityNodeWithParent | null;
|
|
27
|
+
};
|
|
24
28
|
/**
|
|
25
29
|
* Determine whether a node represents a callback passed to a known test
|
|
26
30
|
* framework function (Jest, Mocha, Vitest, etc).
|
|
@@ -34,6 +38,6 @@ interface CallbackExclusionOptions {
|
|
|
34
38
|
*
|
|
35
39
|
* @req REQ-TEST-CALLBACK-EXCLUSION
|
|
36
40
|
*/
|
|
37
|
-
declare function isTestFrameworkCallback(node:
|
|
41
|
+
declare function isTestFrameworkCallback(node: TraceabilityNodeWithParent | null | undefined, options?: CallbackExclusionOptions): boolean;
|
|
38
42
|
export type { CallbackExclusionOptions };
|
|
39
43
|
export { isTestFrameworkCallback };
|
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Shared helpers for determining whether a function-like node should be
|
|
4
|
-
* treated as a test framework callback that may be excluded from
|
|
5
|
-
* function-level annotation requirements.
|
|
6
|
-
*
|
|
7
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
8
|
-
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
9
|
-
* @story docs/stories/013-exclude-test-framework-callbacks.proposed.md
|
|
10
|
-
* @req REQ-TEST-CALLBACK-EXCLUSION - Provide reusable test callback exclusion logic
|
|
11
|
-
*/
|
|
12
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
3
|
exports.isTestFrameworkCallback = isTestFrameworkCallback;
|
|
14
4
|
/**
|
|
@@ -91,7 +81,8 @@ function isTestFrameworkCallback(node, options) {
|
|
|
91
81
|
if (!parent || parent.type !== "CallExpression") {
|
|
92
82
|
return false;
|
|
93
83
|
}
|
|
94
|
-
const
|
|
84
|
+
const callExpressionParent = parent;
|
|
85
|
+
const callee = callExpressionParent.callee;
|
|
95
86
|
if (callee.type === "Identifier") {
|
|
96
87
|
return isRecognizedTestHelperName(callee.name, options);
|
|
97
88
|
}
|
|
@@ -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)
|
|
@@ -286,17 +286,8 @@ function tryBuildInlineAutoFix(context, comments, storyIndex, reqIndices) {
|
|
|
286
286
|
* @story docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
|
|
287
287
|
* @req REQ-MIGRATE-INLINE
|
|
288
288
|
*/
|
|
289
|
-
function
|
|
289
|
+
function collectReqIndicesAfterStory(group, startIndex) {
|
|
290
290
|
const n = group.length;
|
|
291
|
-
const current = group[startIndex];
|
|
292
|
-
const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(current.value || "");
|
|
293
|
-
if (!normalized || !/^@story\b/.test(normalized)) {
|
|
294
|
-
return startIndex + 1;
|
|
295
|
-
}
|
|
296
|
-
if (/^@supports\b/.test(normalized)) {
|
|
297
|
-
return startIndex + 1;
|
|
298
|
-
}
|
|
299
|
-
const storyIndex = startIndex;
|
|
300
291
|
const reqIndices = [];
|
|
301
292
|
let j = startIndex + 1;
|
|
302
293
|
while (j < n) {
|
|
@@ -312,6 +303,19 @@ function handleInlineStorySequence(context, group, startIndex) {
|
|
|
312
303
|
}
|
|
313
304
|
break;
|
|
314
305
|
}
|
|
306
|
+
return { reqIndices, nextIndex: j };
|
|
307
|
+
}
|
|
308
|
+
function handleInlineStorySequence(context, group, startIndex) {
|
|
309
|
+
const current = group[startIndex];
|
|
310
|
+
const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(current.value || "");
|
|
311
|
+
if (!normalized || !/^@story\b/.test(normalized)) {
|
|
312
|
+
return startIndex + 1;
|
|
313
|
+
}
|
|
314
|
+
if (/^@supports\b/.test(normalized)) {
|
|
315
|
+
return startIndex + 1;
|
|
316
|
+
}
|
|
317
|
+
const storyIndex = startIndex;
|
|
318
|
+
const { reqIndices, nextIndex } = collectReqIndicesAfterStory(group, startIndex);
|
|
315
319
|
if (reqIndices.length === 0) {
|
|
316
320
|
context.report({
|
|
317
321
|
node: current,
|
|
@@ -333,7 +337,7 @@ function handleInlineStorySequence(context, group, startIndex) {
|
|
|
333
337
|
messageId: "preferImplements",
|
|
334
338
|
});
|
|
335
339
|
}
|
|
336
|
-
return
|
|
340
|
+
return nextIndex;
|
|
337
341
|
}
|
|
338
342
|
/**
|
|
339
343
|
* Process a contiguous group of inline line comments, identifying legacy
|
|
@@ -343,19 +347,20 @@ function handleInlineStorySequence(context, group, startIndex) {
|
|
|
343
347
|
* @story docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
|
|
344
348
|
* @req REQ-MIGRATE-INLINE
|
|
345
349
|
*/
|
|
350
|
+
function advanceInlineGroupIndex(context, group, currentIndex) {
|
|
351
|
+
const current = group[currentIndex];
|
|
352
|
+
const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(current.value || "");
|
|
353
|
+
if (!normalized || !/^@story\b/.test(normalized)) {
|
|
354
|
+
return currentIndex + 1;
|
|
355
|
+
}
|
|
356
|
+
return handleInlineStorySequence(context, group, currentIndex);
|
|
357
|
+
}
|
|
346
358
|
function processInlineGroup(context, group) {
|
|
347
359
|
if (group.length === 0)
|
|
348
360
|
return;
|
|
349
|
-
const n = group.length;
|
|
350
361
|
let i = 0;
|
|
351
|
-
while (i <
|
|
352
|
-
|
|
353
|
-
const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(current.value || "");
|
|
354
|
-
if (!normalized || !/^@story\b/.test(normalized)) {
|
|
355
|
-
i += 1;
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
i = handleInlineStorySequence(context, group, i);
|
|
362
|
+
while (i < group.length) {
|
|
363
|
+
i = advanceInlineGroupIndex(context, group, i);
|
|
359
364
|
}
|
|
360
365
|
}
|
|
361
366
|
/**
|
|
@@ -99,6 +99,12 @@ const rule = {
|
|
|
99
99
|
items: { type: "string" },
|
|
100
100
|
uniqueItems: true,
|
|
101
101
|
},
|
|
102
|
+
/**
|
|
103
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG REQ-DEFAULT-BACKWARD-COMPAT
|
|
104
|
+
*/
|
|
105
|
+
annotationPlacement: {
|
|
106
|
+
enum: ["before", "inside"],
|
|
107
|
+
},
|
|
102
108
|
},
|
|
103
109
|
additionalProperties: false,
|
|
104
110
|
},
|
|
@@ -124,6 +130,16 @@ const rule = {
|
|
|
124
130
|
return branchTypesOrListener;
|
|
125
131
|
}
|
|
126
132
|
const branchTypes = branchTypesOrListener;
|
|
133
|
+
/**
|
|
134
|
+
* Resolve annotation placement configuration with backward-compatible default.
|
|
135
|
+
*
|
|
136
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG REQ-DEFAULT-BACKWARD-COMPAT
|
|
137
|
+
*/
|
|
138
|
+
const rawOptions = context.options[0] || {};
|
|
139
|
+
const _annotationPlacement = rawOptions.annotationPlacement === "inside" ||
|
|
140
|
+
rawOptions.annotationPlacement === "before"
|
|
141
|
+
? rawOptions.annotationPlacement
|
|
142
|
+
: "before";
|
|
127
143
|
const storyFixCountRef = { count: 0 };
|
|
128
144
|
const handlers = {};
|
|
129
145
|
branchTypes.forEach((type) => {
|