eslint-plugin-traceability 1.6.5 → 1.7.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/README.md +38 -1
- package/lib/src/index.d.ts +28 -25
- package/lib/src/index.js +49 -31
- package/lib/src/maintenance/cli.d.ts +12 -0
- package/lib/src/maintenance/cli.js +279 -0
- package/lib/src/maintenance/detect.js +27 -12
- package/lib/src/maintenance/update.js +42 -34
- package/lib/src/maintenance/utils.js +30 -30
- package/lib/src/rules/helpers/require-story-io.js +51 -15
- package/lib/src/rules/helpers/valid-annotation-options.d.ts +118 -0
- package/lib/src/rules/helpers/valid-annotation-options.js +167 -0
- package/lib/src/rules/helpers/valid-annotation-utils.d.ts +68 -0
- package/lib/src/rules/helpers/valid-annotation-utils.js +103 -0
- package/lib/src/rules/helpers/valid-story-reference-helpers.d.ts +67 -0
- package/lib/src/rules/helpers/valid-story-reference-helpers.js +92 -0
- package/lib/src/rules/valid-annotation-format.js +168 -180
- package/lib/src/rules/valid-req-reference.js +139 -29
- package/lib/src/rules/valid-story-reference.d.ts +7 -0
- package/lib/src/rules/valid-story-reference.js +38 -80
- package/lib/src/utils/annotation-checker.js +2 -145
- package/lib/src/utils/branch-annotation-helpers.js +12 -3
- package/lib/src/utils/reqAnnotationDetection.d.ts +6 -0
- package/lib/src/utils/reqAnnotationDetection.js +152 -0
- package/lib/tests/maintenance/cli.test.d.ts +1 -0
- package/lib/tests/maintenance/cli.test.js +172 -0
- package/lib/tests/rules/require-branch-annotation.test.js +3 -2
- package/lib/tests/rules/require-req-annotation.test.js +57 -68
- package/lib/tests/rules/require-story-annotation.test.js +13 -28
- package/lib/tests/rules/require-story-core-edgecases.test.js +3 -58
- package/lib/tests/rules/require-story-core.autofix.test.js +5 -41
- package/lib/tests/rules/valid-annotation-format.test.js +328 -51
- package/lib/tests/utils/annotation-checker.test.d.ts +23 -0
- package/lib/tests/utils/annotation-checker.test.js +24 -17
- package/lib/tests/utils/require-story-core-test-helpers.d.ts +10 -0
- package/lib/tests/utils/require-story-core-test-helpers.js +75 -0
- package/lib/tests/utils/ts-language-options.d.ts +22 -0
- package/lib/tests/utils/ts-language-options.js +27 -0
- package/package.json +12 -3
|
@@ -56,45 +56,45 @@ function getAllFiles(dir) {
|
|
|
56
56
|
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
57
57
|
return fileList;
|
|
58
58
|
}
|
|
59
|
+
traverseDirectory(dir, fileList);
|
|
60
|
+
return fileList;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Recursively traverse a directory and collect file paths.
|
|
64
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
65
|
+
* @req REQ-MAINT-UTILS-TRAVERSE - Helper traversal function used by getAllFiles
|
|
66
|
+
*/
|
|
67
|
+
function traverseDirectory(currentDir, fileList) {
|
|
68
|
+
const entries = fs.readdirSync(currentDir);
|
|
59
69
|
/**
|
|
60
|
-
*
|
|
70
|
+
* Iterate over directory entries using a for-of loop.
|
|
61
71
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
62
|
-
* @req REQ-MAINT-UTILS-TRAVERSE -
|
|
72
|
+
* @req REQ-MAINT-UTILS-TRAVERSE-FOROF - Traceability for ForOfStatement branch handling entries
|
|
63
73
|
*/
|
|
64
|
-
|
|
65
|
-
const
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const fullPath = path.join(currentDir, entry);
|
|
76
|
+
const stat = fs.statSync(fullPath);
|
|
66
77
|
/**
|
|
67
|
-
*
|
|
78
|
+
* Recurse into directories to continue traversal.
|
|
68
79
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
69
|
-
* @req REQ-MAINT-UTILS-TRAVERSE-
|
|
80
|
+
* @req REQ-MAINT-UTILS-TRAVERSE-DIR - Handle directory entries during traversal
|
|
70
81
|
*/
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const stat = fs.statSync(fullPath);
|
|
82
|
+
if (stat.isDirectory()) {
|
|
83
|
+
traverseDirectory(fullPath, fileList);
|
|
74
84
|
/**
|
|
75
|
-
*
|
|
85
|
+
* Collect regular file entries during traversal.
|
|
76
86
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
77
|
-
* @req REQ-MAINT-UTILS-TRAVERSE-
|
|
87
|
+
* @req REQ-MAINT-UTILS-TRAVERSE-FILE - Handle file entries during traversal
|
|
78
88
|
*/
|
|
79
|
-
if (stat.isDirectory()) {
|
|
80
|
-
traverse(fullPath);
|
|
81
|
-
/**
|
|
82
|
-
* Collect regular file entries during traversal.
|
|
83
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
84
|
-
* @req REQ-MAINT-UTILS-TRAVERSE-FILE - Handle file entries during traversal
|
|
85
|
-
*/
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Skip non-file entries encountered during traversal.
|
|
89
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
90
|
-
* @req REQ-MAINT-UTILS-TRAVERSE-SKIP-NONFILE - Traceability for skipping non-file entries
|
|
91
|
-
*/
|
|
92
|
-
if (!stat.isFile()) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
fileList.push(fullPath);
|
|
96
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Skip non-file entries encountered during traversal.
|
|
92
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
93
|
+
* @req REQ-MAINT-UTILS-TRAVERSE-SKIP-NONFILE - Traceability for skipping non-file entries
|
|
94
|
+
*/
|
|
95
|
+
if (!stat.isFile()) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
fileList.push(fullPath);
|
|
97
99
|
}
|
|
98
|
-
traverse(dir);
|
|
99
|
-
return fileList;
|
|
100
100
|
}
|
|
@@ -22,23 +22,41 @@ exports.LOOKBACK_LINES = 4;
|
|
|
22
22
|
*/
|
|
23
23
|
exports.FALLBACK_WINDOW = 800;
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Shared predicate to determine if a given comment node contains an @story marker.
|
|
26
26
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
27
|
-
* @req REQ-ANNOTATION-REQUIRED -
|
|
27
|
+
* @req REQ-ANNOTATION-REQUIRED - Centralize @story detection logic for comment value inspection
|
|
28
28
|
*/
|
|
29
|
-
function
|
|
29
|
+
function commentContainsStory(comment) {
|
|
30
|
+
return typeof comment?.value === "string" && comment.value.includes("@story");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Safely extract the physical source lines array from sourceCode for scanning.
|
|
34
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
35
|
+
* @req REQ-ANNOTATION-REQUIRED - Centralize guards for safe access to source lines
|
|
36
|
+
*/
|
|
37
|
+
function getSourceLines(sourceCode) {
|
|
30
38
|
const lines = sourceCode && sourceCode.lines;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
return Array.isArray(lines) ? lines : null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Safely resolve the starting line number of a node for use in lookback scans.
|
|
43
|
+
* Returns null when the node does not provide a valid numeric start line.
|
|
44
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
45
|
+
* @req REQ-ANNOTATION-REQUIRED - Centralize guards for safe access to node location metadata
|
|
46
|
+
*/
|
|
47
|
+
function getNodeStartLine(node) {
|
|
48
|
+
if (!node || !node.loc) {
|
|
49
|
+
return null;
|
|
39
50
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
51
|
+
const line = node.loc.start?.line;
|
|
52
|
+
return typeof line === "number" ? line : null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Generic helper to scan a range of physical source lines for the presence of an @story marker.
|
|
56
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
57
|
+
* @req REQ-ANNOTATION-REQUIRED - Reuse line scanning logic for story annotations across helpers
|
|
58
|
+
*/
|
|
59
|
+
function scanLinesForMarker(lines, from, to) {
|
|
42
60
|
// Walk each physical line in the configured lookback window to search for an inline @story marker.
|
|
43
61
|
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
44
62
|
// @req REQ-ANNOTATION-REQUIRED - Scan preceding lines for existing story annotations
|
|
@@ -53,6 +71,24 @@ function linesBeforeHasStory(sourceCode, node, lookback = exports.LOOKBACK_LINES
|
|
|
53
71
|
}
|
|
54
72
|
return false;
|
|
55
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Inspect a fixed number of physical source lines before the node for @story text
|
|
76
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
77
|
+
* @req REQ-ANNOTATION-REQUIRED - Extract line-based detection into helper
|
|
78
|
+
*/
|
|
79
|
+
function linesBeforeHasStory(sourceCode, node, lookback = exports.LOOKBACK_LINES) {
|
|
80
|
+
const lines = getSourceLines(sourceCode);
|
|
81
|
+
const startLine = getNodeStartLine(node);
|
|
82
|
+
// Guard against missing or non-array source lines or an invalid start line before scanning.
|
|
83
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
84
|
+
// @req REQ-ANNOTATION-REQUIRED - Fail gracefully when source lines or locations are unavailable
|
|
85
|
+
if (!lines || typeof startLine !== "number") {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const from = Math.max(0, startLine - 1 - lookback);
|
|
89
|
+
const to = Math.max(0, startLine - 1);
|
|
90
|
+
return scanLinesForMarker(lines, from, to);
|
|
91
|
+
}
|
|
56
92
|
/**
|
|
57
93
|
* Walk parent chain and check comments before each parent and their leadingComments
|
|
58
94
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
@@ -70,7 +106,7 @@ function parentChainHasStory(sourceCode, node) {
|
|
|
70
106
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
71
107
|
* @req REQ-ANNOTATION-REQUIRED - Detect @story in parent comments via value inspection
|
|
72
108
|
*/
|
|
73
|
-
(c) =>
|
|
109
|
+
(c) => commentContainsStory(c))) {
|
|
74
110
|
return true;
|
|
75
111
|
}
|
|
76
112
|
const pLeading = p.leadingComments || [];
|
|
@@ -80,7 +116,7 @@ function parentChainHasStory(sourceCode, node) {
|
|
|
80
116
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
81
117
|
* @req REQ-ANNOTATION-REQUIRED - Detect @story in parent leadingComments via value inspection
|
|
82
118
|
*/
|
|
83
|
-
(c) =>
|
|
119
|
+
(c) => commentContainsStory(c))) {
|
|
84
120
|
return true;
|
|
85
121
|
}
|
|
86
122
|
p = p.parent;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared option handling for the valid-annotation-format rule.
|
|
3
|
+
*
|
|
4
|
+
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
5
|
+
* @req REQ-PATTERN-CONFIG - Support configuration of custom story path and requirement ID patterns
|
|
6
|
+
* @req REQ-REGEX-VALIDATION - Validate that configured patterns are valid regular expressions
|
|
7
|
+
* @req REQ-BACKWARD-COMPAT - Maintain current behavior when no custom patterns configured
|
|
8
|
+
* @req REQ-EXAMPLE-MESSAGES - Support optional example strings in error messages
|
|
9
|
+
* @req REQ-SCHEMA-VALIDATION - Use JSON Schema to validate configuration options
|
|
10
|
+
*/
|
|
11
|
+
export interface AnnotationRuleOptions {
|
|
12
|
+
story?: {
|
|
13
|
+
/**
|
|
14
|
+
* Regex (string) the collapsed story path must match.
|
|
15
|
+
* Default: /^docs\/stories\/[0-9]+\.[0-9]+-DEV-[\w-]+\.story\.md$/
|
|
16
|
+
*/
|
|
17
|
+
pattern?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Human-readable example path used in error messages.
|
|
20
|
+
* Default: "docs/stories/005.0-DEV-EXAMPLE.story.md"
|
|
21
|
+
*/
|
|
22
|
+
example?: string;
|
|
23
|
+
};
|
|
24
|
+
req?: {
|
|
25
|
+
/**
|
|
26
|
+
* Regex (string) the collapsed requirement ID must match.
|
|
27
|
+
* Default: /^REQ-[A-Z0-9-]+$/
|
|
28
|
+
*/
|
|
29
|
+
pattern?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Human-readable example requirement ID used in error messages.
|
|
32
|
+
* Default: "REQ-EXAMPLE"
|
|
33
|
+
*/
|
|
34
|
+
example?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Shorthand for story.pattern.
|
|
38
|
+
* Regex (string) the collapsed story path must match.
|
|
39
|
+
*/
|
|
40
|
+
storyPathPattern?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Shorthand for story.example.
|
|
43
|
+
* Human-readable example story path used in error messages.
|
|
44
|
+
*/
|
|
45
|
+
storyPathExample?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Shorthand for req.pattern.
|
|
48
|
+
* Regex (string) the collapsed requirement ID must match.
|
|
49
|
+
*/
|
|
50
|
+
requirementIdPattern?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Shorthand for req.example.
|
|
53
|
+
* Human-readable example requirement ID used in error messages.
|
|
54
|
+
*/
|
|
55
|
+
requirementIdExample?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolved, runtime-ready options for the rule.
|
|
59
|
+
*/
|
|
60
|
+
export interface ResolvedAnnotationOptions {
|
|
61
|
+
storyPattern: RegExp;
|
|
62
|
+
storyExample: string;
|
|
63
|
+
reqPattern: RegExp;
|
|
64
|
+
reqExample: string;
|
|
65
|
+
}
|
|
66
|
+
export declare function getDefaultReqExample(): string;
|
|
67
|
+
export declare function getResolvedDefaults(): ResolvedAnnotationOptions;
|
|
68
|
+
export declare function getOptionErrors(): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Resolve user options into concrete, validated configuration.
|
|
71
|
+
* Falls back to existing defaults when options are not provided or invalid.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveOptions(rawOptions: unknown[]): ResolvedAnnotationOptions;
|
|
74
|
+
/**
|
|
75
|
+
* Build the JSON schema for rule options.
|
|
76
|
+
*/
|
|
77
|
+
export declare function getRuleSchema(): {
|
|
78
|
+
type: string;
|
|
79
|
+
properties: {
|
|
80
|
+
story: {
|
|
81
|
+
type: string;
|
|
82
|
+
properties: {
|
|
83
|
+
pattern: {
|
|
84
|
+
type: string;
|
|
85
|
+
};
|
|
86
|
+
example: {
|
|
87
|
+
type: string;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
additionalProperties: boolean;
|
|
91
|
+
};
|
|
92
|
+
req: {
|
|
93
|
+
type: string;
|
|
94
|
+
properties: {
|
|
95
|
+
pattern: {
|
|
96
|
+
type: string;
|
|
97
|
+
};
|
|
98
|
+
example: {
|
|
99
|
+
type: string;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
additionalProperties: boolean;
|
|
103
|
+
};
|
|
104
|
+
storyPathPattern: {
|
|
105
|
+
type: string;
|
|
106
|
+
};
|
|
107
|
+
storyPathExample: {
|
|
108
|
+
type: string;
|
|
109
|
+
};
|
|
110
|
+
requirementIdPattern: {
|
|
111
|
+
type: string;
|
|
112
|
+
};
|
|
113
|
+
requirementIdExample: {
|
|
114
|
+
type: string;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
additionalProperties: boolean;
|
|
118
|
+
}[];
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDefaultReqExample = getDefaultReqExample;
|
|
4
|
+
exports.getResolvedDefaults = getResolvedDefaults;
|
|
5
|
+
exports.getOptionErrors = getOptionErrors;
|
|
6
|
+
exports.resolveOptions = resolveOptions;
|
|
7
|
+
exports.getRuleSchema = getRuleSchema;
|
|
8
|
+
function getDefaultStoryPattern() {
|
|
9
|
+
return /^docs\/stories\/[0-9]+\.[0-9]+-DEV-[\w-]+\.story\.md$/;
|
|
10
|
+
}
|
|
11
|
+
function getDefaultStoryExample() {
|
|
12
|
+
return "docs/stories/005.0-DEV-EXAMPLE.story.md";
|
|
13
|
+
}
|
|
14
|
+
function getDefaultReqPattern() {
|
|
15
|
+
return /^REQ-[A-Z0-9-]+$/;
|
|
16
|
+
}
|
|
17
|
+
function getDefaultReqExample() {
|
|
18
|
+
return "REQ-EXAMPLE";
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Global cache of the last resolved options for helpers that need access
|
|
22
|
+
* without having options explicitly passed in.
|
|
23
|
+
*/
|
|
24
|
+
let resolvedDefaults = {
|
|
25
|
+
storyPattern: getDefaultStoryPattern(),
|
|
26
|
+
storyExample: getDefaultStoryExample(),
|
|
27
|
+
reqPattern: getDefaultReqPattern(),
|
|
28
|
+
reqExample: getDefaultReqExample(),
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Collected configuration errors encountered while resolving options.
|
|
32
|
+
*/
|
|
33
|
+
let optionErrors = [];
|
|
34
|
+
function getResolvedDefaults() {
|
|
35
|
+
return resolvedDefaults;
|
|
36
|
+
}
|
|
37
|
+
function getOptionErrors() {
|
|
38
|
+
return optionErrors;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build a stable, engine-independent configuration error message
|
|
42
|
+
* for invalid regex options.
|
|
43
|
+
*/
|
|
44
|
+
function buildInvalidRegexError(field, pattern) {
|
|
45
|
+
return `Invalid regular expression for option "${field}": "${pattern}"`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Normalize raw rule options into a single AnnotationRuleOptions object.
|
|
49
|
+
*
|
|
50
|
+
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
51
|
+
* @req REQ-PATTERN-CONFIG
|
|
52
|
+
* @req REQ-BACKWARD-COMPAT
|
|
53
|
+
*/
|
|
54
|
+
function normalizeUserOptions(rawOptions) {
|
|
55
|
+
if (!rawOptions || rawOptions.length === 0) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const first = rawOptions[0];
|
|
59
|
+
if (!first || typeof first !== "object") {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return first;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a user-configured regex pattern, handling both nested and flat
|
|
66
|
+
* configuration shapes and accumulating validation errors.
|
|
67
|
+
*
|
|
68
|
+
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
69
|
+
* @req REQ-PATTERN-CONFIG
|
|
70
|
+
* @req REQ-REGEX-VALIDATION
|
|
71
|
+
* @req REQ-BACKWARD-COMPAT
|
|
72
|
+
*/
|
|
73
|
+
// eslint-disable-next-line max-params -- Small, centralized helper; keeping parameters explicit is clearer than introducing an options object here.
|
|
74
|
+
function resolvePattern(nestedPattern, nestedFieldName, flatPattern, flatFieldName, defaultPattern) {
|
|
75
|
+
const effective = typeof nestedPattern === "string"
|
|
76
|
+
? { value: nestedPattern, field: nestedFieldName }
|
|
77
|
+
: typeof flatPattern === "string"
|
|
78
|
+
? { value: flatPattern, field: flatFieldName }
|
|
79
|
+
: null;
|
|
80
|
+
if (!effective) {
|
|
81
|
+
return defaultPattern;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
return new RegExp(effective.value);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
optionErrors.push(buildInvalidRegexError(effective.field, effective.value));
|
|
88
|
+
return defaultPattern;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve an example string, preferring nested over flat configuration,
|
|
93
|
+
* and falling back to the provided default when necessary.
|
|
94
|
+
*
|
|
95
|
+
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
96
|
+
* @req REQ-EXAMPLE-MESSAGES
|
|
97
|
+
* @req REQ-BACKWARD-COMPAT
|
|
98
|
+
*/
|
|
99
|
+
function resolveExample(nestedExample, flatExample, defaultExample) {
|
|
100
|
+
if (typeof nestedExample === "string" && nestedExample.trim()) {
|
|
101
|
+
return nestedExample;
|
|
102
|
+
}
|
|
103
|
+
if (typeof flatExample === "string" && flatExample.trim()) {
|
|
104
|
+
return flatExample;
|
|
105
|
+
}
|
|
106
|
+
return defaultExample;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve user options into concrete, validated configuration.
|
|
110
|
+
* Falls back to existing defaults when options are not provided or invalid.
|
|
111
|
+
*/
|
|
112
|
+
function resolveOptions(rawOptions) {
|
|
113
|
+
optionErrors = [];
|
|
114
|
+
const user = normalizeUserOptions(rawOptions);
|
|
115
|
+
const nestedStoryPattern = user?.story?.pattern;
|
|
116
|
+
const flatStoryPattern = user?.storyPathPattern;
|
|
117
|
+
const nestedStoryExample = user?.story?.example;
|
|
118
|
+
const flatStoryExample = user?.storyPathExample;
|
|
119
|
+
const nestedReqPattern = user?.req?.pattern;
|
|
120
|
+
const flatReqPattern = user?.requirementIdPattern;
|
|
121
|
+
const nestedReqExample = user?.req?.example;
|
|
122
|
+
const flatReqExample = user?.requirementIdExample;
|
|
123
|
+
const storyPattern = resolvePattern(nestedStoryPattern, "story.pattern", flatStoryPattern, "storyPathPattern", getDefaultStoryPattern());
|
|
124
|
+
const reqPattern = resolvePattern(nestedReqPattern, "req.pattern", flatReqPattern, "requirementIdPattern", getDefaultReqPattern());
|
|
125
|
+
const storyExample = resolveExample(nestedStoryExample, flatStoryExample, getDefaultStoryExample());
|
|
126
|
+
const reqExample = resolveExample(nestedReqExample, flatReqExample, getDefaultReqExample());
|
|
127
|
+
resolvedDefaults = {
|
|
128
|
+
storyPattern,
|
|
129
|
+
storyExample,
|
|
130
|
+
reqPattern,
|
|
131
|
+
reqExample,
|
|
132
|
+
};
|
|
133
|
+
return resolvedDefaults;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Build the JSON schema for rule options.
|
|
137
|
+
*/
|
|
138
|
+
function getRuleSchema() {
|
|
139
|
+
return [
|
|
140
|
+
{
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
story: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
pattern: { type: "string" },
|
|
147
|
+
example: { type: "string" },
|
|
148
|
+
},
|
|
149
|
+
additionalProperties: false,
|
|
150
|
+
},
|
|
151
|
+
req: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
pattern: { type: "string" },
|
|
155
|
+
example: { type: "string" },
|
|
156
|
+
},
|
|
157
|
+
additionalProperties: false,
|
|
158
|
+
},
|
|
159
|
+
storyPathPattern: { type: "string" },
|
|
160
|
+
storyPathExample: { type: "string" },
|
|
161
|
+
requirementIdPattern: { type: "string" },
|
|
162
|
+
requirementIdExample: { type: "string" },
|
|
163
|
+
},
|
|
164
|
+
additionalProperties: false,
|
|
165
|
+
},
|
|
166
|
+
];
|
|
167
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ResolvedAnnotationOptions } from "./valid-annotation-options";
|
|
2
|
+
/**
|
|
3
|
+
* Shared constants and helpers for annotation-format validation.
|
|
4
|
+
*
|
|
5
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
6
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
7
|
+
* @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
|
|
8
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Constant to represent the "tag not found" index when searching
|
|
12
|
+
* for @story or @req within a comment.
|
|
13
|
+
*
|
|
14
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
15
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
16
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
17
|
+
* @req REQ-AUTOFIX-PRESERVE - Avoid risky text replacements when the annotation tag cannot be located
|
|
18
|
+
*/
|
|
19
|
+
export declare const TAG_NOT_FOUND_INDEX = -1;
|
|
20
|
+
export declare const STORY_EXAMPLE_PATH = "docs/stories/005.0-DEV-EXAMPLE.story.md";
|
|
21
|
+
/**
|
|
22
|
+
* Collapse internal whitespace in an annotation value so that multi-line
|
|
23
|
+
* annotations are treated as a single logical value.
|
|
24
|
+
*
|
|
25
|
+
* Example:
|
|
26
|
+
* "docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md" across
|
|
27
|
+
* multiple lines will be collapsed before validation.
|
|
28
|
+
*
|
|
29
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
30
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
31
|
+
* @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
|
|
32
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
33
|
+
*/
|
|
34
|
+
export declare function collapseAnnotationValue(value: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Attempt a minimal, safe auto-fix for common @story path suffix issues.
|
|
37
|
+
*
|
|
38
|
+
* Only handles:
|
|
39
|
+
* - missing ".md"
|
|
40
|
+
* - missing ".story.md"
|
|
41
|
+
* and skips any paths with traversal segments (e.g. "..").
|
|
42
|
+
*
|
|
43
|
+
* Returns the fixed path when safe, or null if no fix should be applied.
|
|
44
|
+
*
|
|
45
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
46
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
47
|
+
* @req REQ-AUTOFIX-SAFE - Auto-fix must be conservative and never broaden the referenced path
|
|
48
|
+
* @req REQ-AUTOFIX-PRESERVE - Preserve surrounding formatting when normalizing story path suffixes
|
|
49
|
+
*/
|
|
50
|
+
export declare function getFixedStoryPath(original: string): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Build a detailed error message for invalid @story annotations.
|
|
53
|
+
*
|
|
54
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
55
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
56
|
+
* @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
|
|
57
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildStoryErrorMessage(kind: "missing" | "invalid", value: string | null, options: ResolvedAnnotationOptions): string;
|
|
60
|
+
/**
|
|
61
|
+
* Build a detailed error message for invalid @req annotations.
|
|
62
|
+
*
|
|
63
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
64
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
65
|
+
* @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
|
|
66
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildReqErrorMessage(kind: "missing" | "invalid", value: string | null, options: ResolvedAnnotationOptions): string;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.STORY_EXAMPLE_PATH = exports.TAG_NOT_FOUND_INDEX = void 0;
|
|
4
|
+
exports.collapseAnnotationValue = collapseAnnotationValue;
|
|
5
|
+
exports.getFixedStoryPath = getFixedStoryPath;
|
|
6
|
+
exports.buildStoryErrorMessage = buildStoryErrorMessage;
|
|
7
|
+
exports.buildReqErrorMessage = buildReqErrorMessage;
|
|
8
|
+
const valid_annotation_options_1 = require("./valid-annotation-options");
|
|
9
|
+
/**
|
|
10
|
+
* Shared constants and helpers for annotation-format validation.
|
|
11
|
+
*
|
|
12
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
13
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
14
|
+
* @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
|
|
15
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Constant to represent the "tag not found" index when searching
|
|
19
|
+
* for @story or @req within a comment.
|
|
20
|
+
*
|
|
21
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
22
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
23
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
24
|
+
* @req REQ-AUTOFIX-PRESERVE - Avoid risky text replacements when the annotation tag cannot be located
|
|
25
|
+
*/
|
|
26
|
+
exports.TAG_NOT_FOUND_INDEX = -1;
|
|
27
|
+
exports.STORY_EXAMPLE_PATH = "docs/stories/005.0-DEV-EXAMPLE.story.md";
|
|
28
|
+
/**
|
|
29
|
+
* Collapse internal whitespace in an annotation value so that multi-line
|
|
30
|
+
* annotations are treated as a single logical value.
|
|
31
|
+
*
|
|
32
|
+
* Example:
|
|
33
|
+
* "docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md" across
|
|
34
|
+
* multiple lines will be collapsed before validation.
|
|
35
|
+
*
|
|
36
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
37
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
38
|
+
* @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
|
|
39
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
40
|
+
*/
|
|
41
|
+
function collapseAnnotationValue(value) {
|
|
42
|
+
return value.replace(/\s+/g, "");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Attempt a minimal, safe auto-fix for common @story path suffix issues.
|
|
46
|
+
*
|
|
47
|
+
* Only handles:
|
|
48
|
+
* - missing ".md"
|
|
49
|
+
* - missing ".story.md"
|
|
50
|
+
* and skips any paths with traversal segments (e.g. "..").
|
|
51
|
+
*
|
|
52
|
+
* Returns the fixed path when safe, or null if no fix should be applied.
|
|
53
|
+
*
|
|
54
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
55
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
56
|
+
* @req REQ-AUTOFIX-SAFE - Auto-fix must be conservative and never broaden the referenced path
|
|
57
|
+
* @req REQ-AUTOFIX-PRESERVE - Preserve surrounding formatting when normalizing story path suffixes
|
|
58
|
+
*/
|
|
59
|
+
function getFixedStoryPath(original) {
|
|
60
|
+
if (original.includes("..")) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (/\.story\.md$/.test(original)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (/\.story$/.test(original)) {
|
|
67
|
+
return `${original}.md`;
|
|
68
|
+
}
|
|
69
|
+
if (/\.md$/.test(original)) {
|
|
70
|
+
return original.replace(/\.md$/, ".story.md");
|
|
71
|
+
}
|
|
72
|
+
return `${original}.story.md`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build a detailed error message for invalid @story annotations.
|
|
76
|
+
*
|
|
77
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
78
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
79
|
+
* @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
|
|
80
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
81
|
+
*/
|
|
82
|
+
function buildStoryErrorMessage(kind, value, options) {
|
|
83
|
+
const example = options.storyExample || exports.STORY_EXAMPLE_PATH;
|
|
84
|
+
if (kind === "missing") {
|
|
85
|
+
return `Missing story path for @story annotation. Expected a path like "${example}".`;
|
|
86
|
+
}
|
|
87
|
+
return `Invalid story path "${value ?? ""}" for @story annotation. Expected a path like "${example}".`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build a detailed error message for invalid @req annotations.
|
|
91
|
+
*
|
|
92
|
+
* @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
|
|
93
|
+
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
94
|
+
* @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
|
|
95
|
+
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
96
|
+
*/
|
|
97
|
+
function buildReqErrorMessage(kind, value, options) {
|
|
98
|
+
const example = options.reqExample || (0, valid_annotation_options_1.getDefaultReqExample)();
|
|
99
|
+
if (kind === "missing") {
|
|
100
|
+
return `Missing requirement ID for @req annotation. Expected an identifier like "${example}".`;
|
|
101
|
+
}
|
|
102
|
+
return `Invalid requirement ID "${value ?? ""}" for @req annotation. Expected an identifier like "${example}" (uppercase letters, numbers, and dashes only).`;
|
|
103
|
+
}
|