eslint-plugin-traceability 1.6.4 → 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 +30 -15
- 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/require-story-visitors.js +5 -16
- 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/require-story-annotation.js +9 -14
- 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/maintenance/detect-isolated.test.js +68 -1
- package/lib/tests/maintenance/report.test.js +2 -2
- 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
|
@@ -36,6 +36,45 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.updateAnnotationReferences = updateAnnotationReferences;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const utils_1 = require("./utils");
|
|
39
|
+
/**
|
|
40
|
+
* Helper to process a single file for annotation reference updates
|
|
41
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
42
|
+
* @req REQ-MAINT-UPDATE
|
|
43
|
+
*/
|
|
44
|
+
function processFileForAnnotationUpdates(fullPath, regex, newPath, replacementCountRef) {
|
|
45
|
+
const stat = fs.statSync(fullPath);
|
|
46
|
+
/**
|
|
47
|
+
* Skip non-files in iteration
|
|
48
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
49
|
+
* @req REQ-MAINT-UPDATE
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* Skip entries that are not regular files (e.g., directories)
|
|
53
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
54
|
+
* @req REQ-MAINT-UPDATE
|
|
55
|
+
*/
|
|
56
|
+
if (!stat.isFile())
|
|
57
|
+
return;
|
|
58
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
59
|
+
const newContent = content.replace(regex,
|
|
60
|
+
/**
|
|
61
|
+
* Replacement callback to update annotation references
|
|
62
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
63
|
+
* @req REQ-MAINT-UPDATE
|
|
64
|
+
*/
|
|
65
|
+
(match, p1) => {
|
|
66
|
+
replacementCountRef.count++;
|
|
67
|
+
return `${p1}${newPath}`;
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Write file only if content changed
|
|
71
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
72
|
+
* @req REQ-MAINT-UPDATE
|
|
73
|
+
*/
|
|
74
|
+
if (newContent !== content) {
|
|
75
|
+
fs.writeFileSync(fullPath, newContent, "utf8");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
39
78
|
/**
|
|
40
79
|
* Update annotation references when story files are moved or renamed
|
|
41
80
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
@@ -54,7 +93,7 @@ function updateAnnotationReferences(codebasePath, oldPath, newPath) {
|
|
|
54
93
|
!fs.statSync(codebasePath).isDirectory()) {
|
|
55
94
|
return 0;
|
|
56
95
|
}
|
|
57
|
-
|
|
96
|
+
const replacementCountRef = { count: 0 };
|
|
58
97
|
const escapedOldPath = oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
59
98
|
const regex = new RegExp(`(@story\\s*)${escapedOldPath}`, "g");
|
|
60
99
|
const files = (0, utils_1.getAllFiles)(codebasePath);
|
|
@@ -69,38 +108,7 @@ function updateAnnotationReferences(codebasePath, oldPath, newPath) {
|
|
|
69
108
|
* @req REQ-MAINT-UPDATE
|
|
70
109
|
*/
|
|
71
110
|
for (const fullPath of files) {
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Skip non-files in iteration
|
|
75
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
76
|
-
* @req REQ-MAINT-UPDATE
|
|
77
|
-
*/
|
|
78
|
-
/**
|
|
79
|
-
* Skip entries that are not regular files (e.g., directories)
|
|
80
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
81
|
-
* @req REQ-MAINT-UPDATE
|
|
82
|
-
*/
|
|
83
|
-
if (!stat.isFile())
|
|
84
|
-
continue;
|
|
85
|
-
const content = fs.readFileSync(fullPath, "utf8");
|
|
86
|
-
const newContent = content.replace(regex,
|
|
87
|
-
/**
|
|
88
|
-
* Replacement callback to update annotation references
|
|
89
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
90
|
-
* @req REQ-MAINT-UPDATE
|
|
91
|
-
*/
|
|
92
|
-
(match, p1) => {
|
|
93
|
-
replacementCount++;
|
|
94
|
-
return `${p1}${newPath}`;
|
|
95
|
-
});
|
|
96
|
-
/**
|
|
97
|
-
* Write file only if content changed
|
|
98
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
99
|
-
* @req REQ-MAINT-UPDATE
|
|
100
|
-
*/
|
|
101
|
-
if (newContent !== content) {
|
|
102
|
-
fs.writeFileSync(fullPath, newContent, "utf8");
|
|
103
|
-
}
|
|
111
|
+
processFileForAnnotationUpdates(fullPath, regex, newPath, replacementCountRef);
|
|
104
112
|
}
|
|
105
|
-
return
|
|
113
|
+
return replacementCountRef.count;
|
|
106
114
|
}
|
|
@@ -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;
|
|
@@ -13,28 +13,17 @@ const require_story_helpers_1 = require("./require-story-helpers");
|
|
|
13
13
|
* @req REQ-BUILD-VISITORS-FNDECL - Provide visitor for FunctionDeclaration
|
|
14
14
|
*/
|
|
15
15
|
function buildFunctionDeclarationVisitor(context, sourceCode, options) {
|
|
16
|
-
/**
|
|
17
|
-
* Debug flag for optional visitor logging.
|
|
18
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
19
|
-
* @req REQ-DEBUG-LOG-TOGGLE - Allow opt-in debug logging via TRACEABILITY_DEBUG
|
|
20
|
-
*/
|
|
21
|
-
const debugEnabled = process.env.TRACEABILITY_DEBUG === "1";
|
|
22
16
|
/**
|
|
23
17
|
* Handle FunctionDeclaration nodes.
|
|
18
|
+
*
|
|
19
|
+
* Developers who need to troubleshoot this handler may temporarily add
|
|
20
|
+
* console.debug statements here, but by default no debug logging runs so that
|
|
21
|
+
* file paths and other details are not leaked during normal linting.
|
|
22
|
+
*
|
|
24
23
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
25
24
|
* @req REQ-ANNOTATION-REQUIRED - Report missing @story on function declarations
|
|
26
25
|
*/
|
|
27
26
|
function handleFunctionDeclaration(node) {
|
|
28
|
-
/**
|
|
29
|
-
* Debug logging for visitor entry
|
|
30
|
-
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
31
|
-
* @req REQ-DEBUG-LOG - Provide debug logging for visitor entry
|
|
32
|
-
*/
|
|
33
|
-
if (debugEnabled) {
|
|
34
|
-
console.debug("require-story-annotation:FunctionDeclaration", typeof context.getFilename === "function"
|
|
35
|
-
? context.getFilename()
|
|
36
|
-
: "<unknown>", node && node.id ? node.id.name : "<anonymous>");
|
|
37
|
-
}
|
|
38
27
|
if (!options.shouldProcessNode(node))
|
|
39
28
|
return;
|
|
40
29
|
const target = (0, require_story_helpers_1.resolveTargetNode)(sourceCode, node);
|
|
@@ -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;
|