eslint-plugin-traceability 1.7.1 → 1.8.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 +82 -0
- package/README.md +76 -37
- package/SECURITY.md +132 -0
- package/lib/src/index.d.ts +6 -35
- package/lib/src/index.js +8 -5
- package/lib/src/maintenance/batch.d.ts +5 -0
- package/lib/src/maintenance/batch.js +5 -0
- package/lib/src/maintenance/cli.js +34 -212
- package/lib/src/maintenance/commands.d.ts +32 -0
- package/lib/src/maintenance/commands.js +139 -0
- package/lib/src/maintenance/detect.d.ts +2 -0
- package/lib/src/maintenance/detect.js +4 -0
- package/lib/src/maintenance/flags.d.ts +99 -0
- package/lib/src/maintenance/flags.js +121 -0
- package/lib/src/maintenance/report.d.ts +2 -0
- package/lib/src/maintenance/report.js +2 -0
- package/lib/src/maintenance/update.d.ts +4 -0
- package/lib/src/maintenance/update.js +4 -0
- package/lib/src/rules/helpers/require-story-io.d.ts +3 -0
- package/lib/src/rules/helpers/require-story-io.js +20 -6
- package/lib/src/rules/helpers/valid-annotation-options.js +15 -4
- package/lib/src/rules/helpers/valid-annotation-utils.js +5 -0
- package/lib/src/rules/helpers/valid-story-reference-helpers.d.ts +3 -4
- package/lib/src/utils/reqAnnotationDetection.d.ts +4 -1
- package/lib/src/utils/reqAnnotationDetection.js +43 -15
- package/lib/tests/config/flat-config-presets-integration.test.d.ts +1 -0
- package/lib/tests/config/flat-config-presets-integration.test.js +75 -0
- package/lib/tests/maintenance/cli.test.js +89 -0
- package/lib/tests/plugin-default-export-and-configs.test.js +0 -2
- package/lib/tests/rules/prefer-implements-annotation.test.js +28 -0
- package/lib/tests/rules/require-req-annotation.test.js +8 -1
- package/lib/tests/rules/require-story-annotation.test.js +9 -4
- package/lib/tests/utils/ts-language-options.d.ts +1 -7
- package/lib/tests/utils/ts-language-options.js +8 -5
- package/package.json +11 -7
- package/user-docs/api-reference.md +527 -0
- package/user-docs/eslint-9-setup-guide.md +722 -0
- package/user-docs/examples.md +74 -0
- package/user-docs/migration-guide.md +174 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseCliInput = parseCliInput;
|
|
7
|
+
exports.normalizeCliArgs = normalizeCliArgs;
|
|
8
|
+
exports.parseFlags = parseFlags;
|
|
9
|
+
/**
|
|
10
|
+
* Flag parsing and normalization logic for the traceability-maint CLI.
|
|
11
|
+
*
|
|
12
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
13
|
+
* @req REQ-MAINT-SAFE - Provide predictable, minimal argument parsing
|
|
14
|
+
*/
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
/**
|
|
17
|
+
* Parse raw Node.js argv into a structured CLI input for the maintenance tools.
|
|
18
|
+
*
|
|
19
|
+
* This performs only minimal, predictable splitting to support higher-level
|
|
20
|
+
* flag parsing in a safe and testable way.
|
|
21
|
+
*
|
|
22
|
+
* @param argv - Raw argv array, usually process.argv.
|
|
23
|
+
* @returns ParsedCliInput with node, script, subcommand, and remaining args.
|
|
24
|
+
*
|
|
25
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
26
|
+
* @req REQ-MAINT-SAFE - Provide predictable, minimal argument parsing
|
|
27
|
+
*/
|
|
28
|
+
function parseCliInput(argv) {
|
|
29
|
+
const [node = "", script = "", ...rest] = argv;
|
|
30
|
+
const [subcommand, ...args] = rest;
|
|
31
|
+
return { argv, node, script, subcommand, args };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Normalize raw argv into a subcommand-centric view for flag parsing.
|
|
35
|
+
*
|
|
36
|
+
* Uses parseCliInput to safely separate Node/V8 internals from the
|
|
37
|
+
* subcommand and its arguments, then exposes only the pieces relevant
|
|
38
|
+
* to subcommand-specific flag parsing.
|
|
39
|
+
*
|
|
40
|
+
* @param rawArgv - Raw argv array, usually process.argv.
|
|
41
|
+
* @returns NormalizedCliArgs with subcommand and remaining args.
|
|
42
|
+
*
|
|
43
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
44
|
+
* @req REQ-MAINT-SAFE - Provide predictable, minimal argument parsing
|
|
45
|
+
*/
|
|
46
|
+
function normalizeCliArgs(rawArgv) {
|
|
47
|
+
const { subcommand, args } = parseCliInput(rawArgv);
|
|
48
|
+
return { subcommand, args };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Initialize default flags for the maintenance CLI.
|
|
52
|
+
*
|
|
53
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
54
|
+
* @req REQ-MAINT-SAFE - Provide predictable, minimal argument parsing
|
|
55
|
+
*/
|
|
56
|
+
function createDefaultFlags() {
|
|
57
|
+
return {
|
|
58
|
+
root: process.cwd(),
|
|
59
|
+
json: false,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Handle a single CLI argument and update the flags accordingly.
|
|
64
|
+
*
|
|
65
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
66
|
+
* @req REQ-MAINT-SAFE - Provide predictable, minimal argument parsing
|
|
67
|
+
*/
|
|
68
|
+
function applyFlag(flags, args, index) {
|
|
69
|
+
const arg = args[index];
|
|
70
|
+
if (arg === "--root" && typeof args[index + 1] === "string") {
|
|
71
|
+
flags.root = path_1.default.resolve(args[index + 1]);
|
|
72
|
+
return index + 1;
|
|
73
|
+
}
|
|
74
|
+
if (arg === "--json") {
|
|
75
|
+
flags.json = true;
|
|
76
|
+
return index;
|
|
77
|
+
}
|
|
78
|
+
if (arg === "--format" && typeof args[index + 1] === "string") {
|
|
79
|
+
const value = args[index + 1];
|
|
80
|
+
if (value === "text" || value === "json") {
|
|
81
|
+
flags.format = value;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
throw new Error(`Invalid format: ${value}. Expected 'text' or 'json'.`);
|
|
85
|
+
}
|
|
86
|
+
return index + 1;
|
|
87
|
+
}
|
|
88
|
+
if (arg === "--from" && typeof args[index + 1] === "string") {
|
|
89
|
+
flags.from = args[index + 1];
|
|
90
|
+
return index + 1;
|
|
91
|
+
}
|
|
92
|
+
if (arg === "--to" && typeof args[index + 1] === "string") {
|
|
93
|
+
flags.to = args[index + 1];
|
|
94
|
+
return index + 1;
|
|
95
|
+
}
|
|
96
|
+
if (arg === "--dry-run") {
|
|
97
|
+
flags.dryRun = true;
|
|
98
|
+
return index;
|
|
99
|
+
}
|
|
100
|
+
return index;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Basic flag parser for maintenance CLI subcommands.
|
|
104
|
+
*
|
|
105
|
+
* Consumes already-normalized CLI arguments (subcommand and its raw args)
|
|
106
|
+
* and produces a ParsedFlags structure with minimal, predictable behavior.
|
|
107
|
+
*
|
|
108
|
+
* @param normalized - Normalized CLI arguments for a maintenance subcommand.
|
|
109
|
+
* @returns ParsedFlags with defaults applied and any recognized flags set.
|
|
110
|
+
*
|
|
111
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
112
|
+
* @req REQ-MAINT-SAFE - Provide predictable, minimal argument parsing
|
|
113
|
+
*/
|
|
114
|
+
function parseFlags(normalized) {
|
|
115
|
+
const flags = createDefaultFlags();
|
|
116
|
+
const { args } = normalized;
|
|
117
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
118
|
+
i = applyFlag(flags, args, i);
|
|
119
|
+
}
|
|
120
|
+
return flags;
|
|
121
|
+
}
|
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
4
4
|
* @req REQ-MAINT-REPORT - Generate maintenance report
|
|
5
5
|
* @req REQ-MAINT-SAFE - Ensure operations are safe and reversible
|
|
6
|
+
* @param codebasePath The workspace root to scan for stale maintenance annotations.
|
|
7
|
+
* @returns An empty string when no stale annotations are found, or a newline-separated list of stale `@story` paths.
|
|
6
8
|
*/
|
|
7
9
|
export declare function generateMaintenanceReport(codebasePath: string): string;
|
|
@@ -7,6 +7,8 @@ const detect_1 = require("./detect");
|
|
|
7
7
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
8
8
|
* @req REQ-MAINT-REPORT - Generate maintenance report
|
|
9
9
|
* @req REQ-MAINT-SAFE - Ensure operations are safe and reversible
|
|
10
|
+
* @param codebasePath The workspace root to scan for stale maintenance annotations.
|
|
11
|
+
* @returns An empty string when no stale annotations are found, or a newline-separated list of stale `@story` paths.
|
|
10
12
|
*/
|
|
11
13
|
function generateMaintenanceReport(codebasePath) {
|
|
12
14
|
const staleAnnotations = (0, detect_1.detectStaleAnnotations)(codebasePath);
|
|
@@ -2,5 +2,9 @@
|
|
|
2
2
|
* Update annotation references when story files are moved or renamed
|
|
3
3
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
4
4
|
* @req REQ-MAINT-UPDATE - Update annotation references
|
|
5
|
+
* @param codebasePath Absolute or workspace-root path whose files will be updated in-place.
|
|
6
|
+
* @param oldPath The original @story path to search for in annotation comments.
|
|
7
|
+
* @param newPath The replacement @story path that will replace occurrences of oldPath.
|
|
8
|
+
* @returns The number of @story annotations that were updated across the codebase.
|
|
5
9
|
*/
|
|
6
10
|
export declare function updateAnnotationReferences(codebasePath: string, oldPath: string, newPath: string): number;
|
|
@@ -79,6 +79,10 @@ function processFileForAnnotationUpdates(fullPath, regex, newPath, replacementCo
|
|
|
79
79
|
* Update annotation references when story files are moved or renamed
|
|
80
80
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
81
81
|
* @req REQ-MAINT-UPDATE - Update annotation references
|
|
82
|
+
* @param codebasePath Absolute or workspace-root path whose files will be updated in-place.
|
|
83
|
+
* @param oldPath The original @story path to search for in annotation comments.
|
|
84
|
+
* @param newPath The replacement @story path that will replace occurrences of oldPath.
|
|
85
|
+
* @returns The number of @story annotations that were updated across the codebase.
|
|
82
86
|
*/
|
|
83
87
|
function updateAnnotationReferences(codebasePath, oldPath, newPath) {
|
|
84
88
|
/**
|
|
@@ -29,7 +29,10 @@ export declare function linesBeforeHasStory(sourceCode: any, node: any, lookback
|
|
|
29
29
|
export declare function parentChainHasStory(sourceCode: any, node: any): boolean;
|
|
30
30
|
/**
|
|
31
31
|
* Fallback: inspect text immediately preceding the node in sourceCode.getText to find @story
|
|
32
|
+
* Also accepts @implements annotations as satisfying story presence for this rule.
|
|
32
33
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
34
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
33
35
|
* @req REQ-ANNOTATION-REQUIRED - Provide fallback textual inspection when other heuristics fail
|
|
36
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Treat @implements annotations as satisfying story presence in fallback checks
|
|
34
37
|
*/
|
|
35
38
|
export declare function fallbackTextBeforeHasStory(sourceCode: any, node: any): boolean;
|
|
@@ -23,11 +23,17 @@ exports.LOOKBACK_LINES = 4;
|
|
|
23
23
|
exports.FALLBACK_WINDOW = 800;
|
|
24
24
|
/**
|
|
25
25
|
* Shared predicate to determine if a given comment node contains an @story marker.
|
|
26
|
+
* Also treats @implements annotations as satisfying story presence checks.
|
|
26
27
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
28
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
27
29
|
* @req REQ-ANNOTATION-REQUIRED - Centralize @story detection logic for comment value inspection
|
|
30
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Treat @implements annotations as satisfying story presence checks
|
|
28
31
|
*/
|
|
29
32
|
function commentContainsStory(comment) {
|
|
30
|
-
|
|
33
|
+
if (typeof comment?.value !== "string") {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return (comment.value.includes("@story") || comment.value.includes("@implements"));
|
|
31
37
|
}
|
|
32
38
|
/**
|
|
33
39
|
* Safely extract the physical source lines array from sourceCode for scanning.
|
|
@@ -55,17 +61,20 @@ function getNodeStartLine(node) {
|
|
|
55
61
|
* Generic helper to scan a range of physical source lines for the presence of an @story marker.
|
|
56
62
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
57
63
|
* @req REQ-ANNOTATION-REQUIRED - Reuse line scanning logic for story annotations across helpers
|
|
64
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements annotations as valid markers during line scanning
|
|
58
65
|
*/
|
|
59
66
|
function scanLinesForMarker(lines, from, to) {
|
|
60
|
-
// Walk each physical line in the configured lookback window to search for an inline @story marker.
|
|
67
|
+
// Walk each physical line in the configured lookback window to search for an inline @story or @implements marker.
|
|
61
68
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
62
69
|
// @req REQ-ANNOTATION-REQUIRED - Scan preceding lines for existing story annotations
|
|
63
70
|
for (let i = from; i < to; i++) {
|
|
64
71
|
const text = lines[i];
|
|
65
|
-
// Treat any line containing "@story" as evidence that the function is already annotated.
|
|
72
|
+
// Treat any line containing "@story" or "@implements" as evidence that the function is already annotated.
|
|
66
73
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
67
74
|
// @req REQ-ANNOTATION-REQUIRED - Detect explicit @story markers in raw source text
|
|
68
|
-
|
|
75
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Detect explicit @implements markers in raw source text
|
|
76
|
+
if (typeof text === "string" &&
|
|
77
|
+
(text.includes("@story") || text.includes("@implements"))) {
|
|
69
78
|
return true;
|
|
70
79
|
}
|
|
71
80
|
}
|
|
@@ -125,8 +134,11 @@ function parentChainHasStory(sourceCode, node) {
|
|
|
125
134
|
}
|
|
126
135
|
/**
|
|
127
136
|
* Fallback: inspect text immediately preceding the node in sourceCode.getText to find @story
|
|
137
|
+
* Also accepts @implements annotations as satisfying story presence for this rule.
|
|
128
138
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
139
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
129
140
|
* @req REQ-ANNOTATION-REQUIRED - Provide fallback textual inspection when other heuristics fail
|
|
141
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Treat @implements annotations as satisfying story presence in fallback checks
|
|
130
142
|
*/
|
|
131
143
|
function fallbackTextBeforeHasStory(sourceCode, node) {
|
|
132
144
|
// Skip fallback text inspection when the sourceCode API or node range information is not available.
|
|
@@ -149,10 +161,12 @@ function fallbackTextBeforeHasStory(sourceCode, node) {
|
|
|
149
161
|
// @req REQ-ANNOTATION-REQUIRED - Restrict fallback text scanning to a safe, fixed-size window
|
|
150
162
|
const start = Math.max(0, range[0] - exports.FALLBACK_WINDOW);
|
|
151
163
|
const textBefore = sourceCode.getText().slice(start, range[0]);
|
|
152
|
-
// Detect any @story marker that appears within the bounded fallback window.
|
|
164
|
+
// Detect any @story or @implements marker that appears within the bounded fallback window.
|
|
153
165
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
154
166
|
// @req REQ-ANNOTATION-REQUIRED - Recognize story annotations discovered via fallback text scanning
|
|
155
|
-
|
|
167
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Recognize @implements annotations discovered via fallback text scanning
|
|
168
|
+
if (typeof textBefore === "string" &&
|
|
169
|
+
(textBefore.includes("@story") || textBefore.includes("@implements"))) {
|
|
156
170
|
return true;
|
|
157
171
|
}
|
|
158
172
|
}
|
|
@@ -70,8 +70,7 @@ function normalizeUserOptions(rawOptions) {
|
|
|
70
70
|
* @req REQ-REGEX-VALIDATION
|
|
71
71
|
* @req REQ-BACKWARD-COMPAT
|
|
72
72
|
*/
|
|
73
|
-
|
|
74
|
-
function resolvePattern(nestedPattern, nestedFieldName, flatPattern, flatFieldName, defaultPattern) {
|
|
73
|
+
function resolvePattern({ nestedPattern, nestedFieldName, flatPattern, flatFieldName, defaultPattern, }) {
|
|
75
74
|
const effective = typeof nestedPattern === "string"
|
|
76
75
|
? { value: nestedPattern, field: nestedFieldName }
|
|
77
76
|
: typeof flatPattern === "string"
|
|
@@ -120,8 +119,20 @@ function resolveOptions(rawOptions) {
|
|
|
120
119
|
const flatReqPattern = user?.requirementIdPattern;
|
|
121
120
|
const nestedReqExample = user?.req?.example;
|
|
122
121
|
const flatReqExample = user?.requirementIdExample;
|
|
123
|
-
const storyPattern = resolvePattern(
|
|
124
|
-
|
|
122
|
+
const storyPattern = resolvePattern({
|
|
123
|
+
nestedPattern: nestedStoryPattern,
|
|
124
|
+
nestedFieldName: "story.pattern",
|
|
125
|
+
flatPattern: flatStoryPattern,
|
|
126
|
+
flatFieldName: "storyPathPattern",
|
|
127
|
+
defaultPattern: getDefaultStoryPattern(),
|
|
128
|
+
});
|
|
129
|
+
const reqPattern = resolvePattern({
|
|
130
|
+
nestedPattern: nestedReqPattern,
|
|
131
|
+
nestedFieldName: "req.pattern",
|
|
132
|
+
flatPattern: flatReqPattern,
|
|
133
|
+
flatFieldName: "requirementIdPattern",
|
|
134
|
+
defaultPattern: getDefaultReqPattern(),
|
|
135
|
+
});
|
|
125
136
|
const storyExample = resolveExample(nestedStoryExample, flatStoryExample, getDefaultStoryExample());
|
|
126
137
|
const reqExample = resolveExample(nestedReqExample, flatReqExample, getDefaultReqExample());
|
|
127
138
|
resolvedDefaults = {
|
|
@@ -57,18 +57,23 @@ function collapseAnnotationValue(value) {
|
|
|
57
57
|
* @req REQ-AUTOFIX-PRESERVE - Preserve surrounding formatting when normalizing story path suffixes
|
|
58
58
|
*/
|
|
59
59
|
function getFixedStoryPath(original) {
|
|
60
|
+
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md | REQ-AUTOFIX-SAFE - Skip auto-fix entirely for paths containing directory traversal segments ("..").
|
|
60
61
|
if (original.includes("..")) {
|
|
61
62
|
return null;
|
|
62
63
|
}
|
|
64
|
+
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md | REQ-AUTOFIX-FORMAT - Do not modify paths that already end with ".story.md" since they are already in the correct format.
|
|
63
65
|
if (/\.story\.md$/.test(original)) {
|
|
64
66
|
return null;
|
|
65
67
|
}
|
|
68
|
+
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md | REQ-AUTOFIX-FORMAT REQ-AUTOFIX-PRESERVE - Append the missing ".md" extension when the path already ends with ".story", preserving the existing base path.
|
|
66
69
|
if (/\.story$/.test(original)) {
|
|
67
70
|
return `${original}.md`;
|
|
68
71
|
}
|
|
72
|
+
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md | REQ-AUTOFIX-FORMAT REQ-AUTOFIX-PRESERVE - Upgrade plain ".md" paths to ".story.md" while preserving the rest of the path unchanged.
|
|
69
73
|
if (/\.md$/.test(original)) {
|
|
70
74
|
return original.replace(/\.md$/, ".story.md");
|
|
71
75
|
}
|
|
76
|
+
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md | REQ-AUTOFIX-FORMAT REQ-AUTOFIX-PRESERVE REQ-AUTOFIX-SAFE - For paths with no extension, conservatively append ".story.md" to create a canonical story file path.
|
|
72
77
|
return `${original}.story.md`;
|
|
73
78
|
}
|
|
74
79
|
/**
|
|
@@ -5,12 +5,11 @@
|
|
|
5
5
|
* @req REQ-PROJECT-BOUNDARY - Ensure resolved candidate paths remain within the project root
|
|
6
6
|
* @req REQ-SECURITY-VALIDATION - Prevent path traversal and absolute path usage
|
|
7
7
|
*/
|
|
8
|
-
export interface
|
|
8
|
+
export interface _ReportInvalidPathArgs {
|
|
9
9
|
storyPath: string;
|
|
10
10
|
commentNode: any;
|
|
11
11
|
context: any;
|
|
12
12
|
}
|
|
13
|
-
export type ReportInvalidPathFn = (args: ReportInvalidPathArgs) => void;
|
|
14
13
|
export interface HandleBoundaryOptions {
|
|
15
14
|
storyPath: string;
|
|
16
15
|
commentNode: any;
|
|
@@ -21,7 +20,7 @@ export interface HandleBoundaryOptions {
|
|
|
21
20
|
status: "exists" | "missing" | "fs-error" | null;
|
|
22
21
|
matchedPath?: string | null;
|
|
23
22
|
} | null;
|
|
24
|
-
reportInvalidPath:
|
|
23
|
+
reportInvalidPath: (_args: _ReportInvalidPathArgs) => void;
|
|
25
24
|
}
|
|
26
25
|
export interface SecurityValidationOptions {
|
|
27
26
|
storyPath: string;
|
|
@@ -29,7 +28,7 @@ export interface SecurityValidationOptions {
|
|
|
29
28
|
context: any;
|
|
30
29
|
cwd: string;
|
|
31
30
|
allowAbsolute: boolean;
|
|
32
|
-
reportInvalidPath:
|
|
31
|
+
reportInvalidPath: (_args: _ReportInvalidPathArgs) => void;
|
|
33
32
|
}
|
|
34
33
|
/**
|
|
35
34
|
* Analyze candidate paths against the project boundary, returning whether any
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Helper to determine whether a JSDoc or any nearby comments contain a
|
|
2
|
+
* Helper to determine whether a JSDoc or any nearby comments contain a requirement annotation.
|
|
3
|
+
* Treats both @req and @implements annotations as evidence of requirement coverage.
|
|
3
4
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
5
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
4
6
|
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
7
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements as requirement coverage
|
|
5
8
|
*/
|
|
6
9
|
export declare function hasReqAnnotation(jsdoc: any, comments: any[], context?: any, node?: any): boolean;
|
|
@@ -8,17 +8,24 @@ exports.hasReqAnnotation = hasReqAnnotation;
|
|
|
8
8
|
*/
|
|
9
9
|
const require_story_io_1 = require("../rules/helpers/require-story-io");
|
|
10
10
|
/**
|
|
11
|
-
* Predicate helper to check whether a comment contains a
|
|
11
|
+
* Predicate helper to check whether a comment contains a requirement annotation.
|
|
12
|
+
* Treats both @req and @implements annotations as satisfying requirement presence checks.
|
|
12
13
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
14
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
13
15
|
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req tag inside a comment
|
|
16
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements as requirement annotation
|
|
14
17
|
*/
|
|
15
18
|
function commentContainsReq(c) {
|
|
16
|
-
return c &&
|
|
19
|
+
return (c &&
|
|
20
|
+
typeof c.value === "string" &&
|
|
21
|
+
(c.value.includes("@req") || c.value.includes("@implements")));
|
|
17
22
|
}
|
|
18
23
|
/**
|
|
19
|
-
* Line-based helper adapted from linesBeforeHasStory to detect
|
|
24
|
+
* Line-based helper adapted from linesBeforeHasStory to detect requirement annotations.
|
|
25
|
+
* Lines containing either @req or @implements are treated as annotated.
|
|
20
26
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
21
27
|
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req in preceding source lines
|
|
28
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements in preceding source lines
|
|
22
29
|
*/
|
|
23
30
|
function linesBeforeHasReq(sourceCode, node) {
|
|
24
31
|
const lines = sourceCode && sourceCode.lines;
|
|
@@ -33,44 +40,53 @@ function linesBeforeHasReq(sourceCode, node) {
|
|
|
33
40
|
}
|
|
34
41
|
const from = Math.max(0, startLine - 1 - require_story_io_1.LOOKBACK_LINES);
|
|
35
42
|
const to = Math.max(0, startLine - 1);
|
|
36
|
-
// Scan each physical line in the configured lookback window for
|
|
43
|
+
// Scan each physical line in the configured lookback window for @req or @implements markers.
|
|
37
44
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
38
45
|
// @req REQ-ANNOTATION-REQ-DETECTION - Search preceding lines for @req text
|
|
46
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Search preceding lines for @implements text
|
|
39
47
|
for (let i = from; i < to; i++) {
|
|
40
48
|
const text = lines[i];
|
|
41
|
-
// When a line contains @req we treat the function as already annotated.
|
|
49
|
+
// When a line contains @req or @implements we treat the function as already annotated.
|
|
42
50
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
43
51
|
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req marker in raw source lines
|
|
44
|
-
|
|
52
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Detect @implements marker in raw source lines
|
|
53
|
+
if (typeof text === "string" &&
|
|
54
|
+
(text.includes("@req") || text.includes("@implements"))) {
|
|
45
55
|
return true;
|
|
46
56
|
}
|
|
47
57
|
}
|
|
48
58
|
return false;
|
|
49
59
|
}
|
|
50
60
|
/**
|
|
51
|
-
* Parent-chain helper adapted from parentChainHasStory to detect
|
|
61
|
+
* Parent-chain helper adapted from parentChainHasStory to detect requirement annotations.
|
|
62
|
+
* Accepts both @req and @implements in parent-chain comments as satisfying requirement presence.
|
|
52
63
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
53
64
|
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req in parent-chain comments
|
|
65
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements in parent-chain comments
|
|
54
66
|
*/
|
|
55
67
|
function parentChainHasReq(sourceCode, node) {
|
|
56
68
|
let p = node && node.parent;
|
|
57
69
|
// Walk up the parent chain and inspect comments attached to each ancestor.
|
|
70
|
+
// Accept both @req and @implements markers when local comments are absent.
|
|
58
71
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
59
72
|
// @req REQ-ANNOTATION-REQ-DETECTION - Traverse parent nodes when local comments are absent
|
|
73
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Allow @implements to satisfy requirement on parents
|
|
60
74
|
while (p) {
|
|
61
75
|
const pComments = typeof sourceCode?.getCommentsBefore === "function"
|
|
62
76
|
? sourceCode.getCommentsBefore(p) || []
|
|
63
77
|
: [];
|
|
64
|
-
// Look for @req in comments immediately preceding each parent node.
|
|
78
|
+
// Look for @req or @implements in comments immediately preceding each parent node.
|
|
65
79
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
66
80
|
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req markers in parent comments
|
|
81
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Detect @implements markers in parent comments
|
|
67
82
|
if (Array.isArray(pComments) && pComments.some(commentContainsReq)) {
|
|
68
83
|
return true;
|
|
69
84
|
}
|
|
70
85
|
const pLeading = p.leadingComments || [];
|
|
71
|
-
// Also inspect leadingComments attached directly to the parent node.
|
|
86
|
+
// Also inspect leadingComments attached directly to the parent node, accepting @req or @implements.
|
|
72
87
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
73
88
|
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req markers in parent leadingComments
|
|
89
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Detect @implements markers in parent leadingComments
|
|
74
90
|
if (Array.isArray(pLeading) && pLeading.some(commentContainsReq)) {
|
|
75
91
|
return true;
|
|
76
92
|
}
|
|
@@ -79,9 +95,12 @@ function parentChainHasReq(sourceCode, node) {
|
|
|
79
95
|
return false;
|
|
80
96
|
}
|
|
81
97
|
/**
|
|
82
|
-
* Fallback text window helper adapted from fallbackTextBeforeHasStory to detect
|
|
98
|
+
* Fallback text window helper adapted from fallbackTextBeforeHasStory to detect requirement annotations.
|
|
99
|
+
* Treats both @req and @implements in the fallback text window as requirement presence.
|
|
83
100
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
101
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
84
102
|
* @req REQ-ANNOTATION-REQ-DETECTION - Detect @req in fallback text window before node
|
|
103
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements in fallback text window before node
|
|
85
104
|
*/
|
|
86
105
|
function fallbackTextBeforeHasReq(sourceCode, node) {
|
|
87
106
|
// Guard against unsupported sourceCode or nodes without a usable range.
|
|
@@ -101,10 +120,13 @@ function fallbackTextBeforeHasReq(sourceCode, node) {
|
|
|
101
120
|
try {
|
|
102
121
|
const start = Math.max(0, range[0] - require_story_io_1.FALLBACK_WINDOW);
|
|
103
122
|
const textBefore = sourceCode.getText().slice(start, range[0]);
|
|
104
|
-
// Detect @req in the bounded text window immediately preceding the node.
|
|
123
|
+
// Detect @req or @implements in the bounded text window immediately preceding the node.
|
|
105
124
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
125
|
+
// @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
106
126
|
// @req REQ-ANNOTATION-REQ-DETECTION - Detect @req marker in fallback text window
|
|
107
|
-
|
|
127
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Detect @implements marker in fallback text window
|
|
128
|
+
if (typeof textBefore === "string" &&
|
|
129
|
+
(textBefore.includes("@req") || textBefore.includes("@implements"))) {
|
|
108
130
|
return true;
|
|
109
131
|
}
|
|
110
132
|
}
|
|
@@ -117,9 +139,12 @@ function fallbackTextBeforeHasReq(sourceCode, node) {
|
|
|
117
139
|
return false;
|
|
118
140
|
}
|
|
119
141
|
/**
|
|
120
|
-
* Helper to determine whether a JSDoc or any nearby comments contain a
|
|
142
|
+
* Helper to determine whether a JSDoc or any nearby comments contain a requirement annotation.
|
|
143
|
+
* Treats both @req and @implements annotations as evidence of requirement coverage.
|
|
121
144
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
145
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
122
146
|
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
|
|
147
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @implements as requirement coverage
|
|
123
148
|
*/
|
|
124
149
|
function hasReqAnnotation(jsdoc, comments, context, node) {
|
|
125
150
|
try {
|
|
@@ -129,6 +154,7 @@ function hasReqAnnotation(jsdoc, comments, context, node) {
|
|
|
129
154
|
// Prefer robust, location-based heuristics when sourceCode and node are available.
|
|
130
155
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
131
156
|
// @req REQ-ANNOTATION-REQ-DETECTION - Use multiple heuristics to detect @req markers around the node
|
|
157
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Use multiple heuristics to detect @implements markers around the node
|
|
132
158
|
if (sourceCode && node) {
|
|
133
159
|
if (linesBeforeHasReq(sourceCode, node) ||
|
|
134
160
|
parentChainHasReq(sourceCode, node) ||
|
|
@@ -142,11 +168,13 @@ function hasReqAnnotation(jsdoc, comments, context, node) {
|
|
|
142
168
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
143
169
|
// @req REQ-ANNOTATION-REQ-DETECTION - Fail gracefully when advanced detection heuristics throw
|
|
144
170
|
}
|
|
145
|
-
// BRANCH
|
|
171
|
+
// BRANCH requirement detection on JSDoc or comments, accepting both @req and @implements.
|
|
146
172
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
173
|
+
// @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
147
174
|
// @req REQ-ANNOTATION-REQ-DETECTION
|
|
175
|
+
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS
|
|
148
176
|
return ((jsdoc &&
|
|
149
177
|
typeof jsdoc.value === "string" &&
|
|
150
|
-
jsdoc.value.includes("@req")) ||
|
|
178
|
+
(jsdoc.value.includes("@req") || jsdoc.value.includes("@implements"))) ||
|
|
151
179
|
comments.some(commentContainsReq));
|
|
152
180
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
/**
|
|
37
|
+
* Tests for: docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
|
|
38
|
+
* @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
|
|
39
|
+
* @req REQ-CONFIG-PRESETS - Validate flat-config presets register traceability plugin and rules
|
|
40
|
+
* @req REQ-FLAT-CONFIG - Ensure presets work with ESLint v9 flat config
|
|
41
|
+
* @req REQ-PROJECT-INTEGRATION - Support seamless integration via documented preset usage
|
|
42
|
+
*/
|
|
43
|
+
const use_at_your_own_risk_1 = require("eslint/use-at-your-own-risk");
|
|
44
|
+
const index_1 = __importStar(require("../../src/index"));
|
|
45
|
+
const baseConfig = {
|
|
46
|
+
plugins: {
|
|
47
|
+
traceability: index_1.default,
|
|
48
|
+
},
|
|
49
|
+
rules: {},
|
|
50
|
+
};
|
|
51
|
+
async function lintTextWithConfig(text, config) {
|
|
52
|
+
const eslint = new use_at_your_own_risk_1.FlatESLint({
|
|
53
|
+
overrideConfig: config,
|
|
54
|
+
overrideConfigFile: true,
|
|
55
|
+
ignore: false,
|
|
56
|
+
});
|
|
57
|
+
const [result] = await eslint.lintText(text, { filePath: "example.js" });
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
describe("Flat config presets integration (Story 002.0-DEV-ESLINT-CONFIG)", () => {
|
|
61
|
+
it("[REQ-CONFIG-PRESETS] recommended preset enables traceability rules via documented usage", async () => {
|
|
62
|
+
const config = [baseConfig, ...index_1.configs.recommended];
|
|
63
|
+
const code = "function foo() {}";
|
|
64
|
+
const result = await lintTextWithConfig(code, config);
|
|
65
|
+
const ruleIds = result.messages.map((m) => m.ruleId).sort();
|
|
66
|
+
expect(ruleIds).toContain("traceability/require-story-annotation");
|
|
67
|
+
});
|
|
68
|
+
it("[REQ-CONFIG-PRESETS] strict preset also enables traceability rules via documented usage", async () => {
|
|
69
|
+
const config = [baseConfig, ...index_1.configs.strict];
|
|
70
|
+
const code = "function bar() {}";
|
|
71
|
+
const result = await lintTextWithConfig(code, config);
|
|
72
|
+
const ruleIds = result.messages.map((m) => m.ruleId).sort();
|
|
73
|
+
expect(ruleIds).toContain("traceability/require-story-annotation");
|
|
74
|
+
});
|
|
75
|
+
});
|