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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper utilities for valid-story-reference rule.
|
|
3
|
+
*
|
|
4
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
5
|
+
* @req REQ-PROJECT-BOUNDARY - Ensure resolved candidate paths remain within the project root
|
|
6
|
+
* @req REQ-SECURITY-VALIDATION - Prevent path traversal and absolute path usage
|
|
7
|
+
*/
|
|
8
|
+
export interface ReportInvalidPathArgs {
|
|
9
|
+
storyPath: string;
|
|
10
|
+
commentNode: any;
|
|
11
|
+
context: any;
|
|
12
|
+
}
|
|
13
|
+
export type ReportInvalidPathFn = (args: ReportInvalidPathArgs) => void;
|
|
14
|
+
export interface HandleBoundaryOptions {
|
|
15
|
+
storyPath: string;
|
|
16
|
+
commentNode: any;
|
|
17
|
+
context: any;
|
|
18
|
+
cwd: string;
|
|
19
|
+
candidates: string[];
|
|
20
|
+
existenceResult: {
|
|
21
|
+
status: "exists" | "missing" | "fs-error" | null;
|
|
22
|
+
matchedPath?: string | null;
|
|
23
|
+
} | null;
|
|
24
|
+
reportInvalidPath: ReportInvalidPathFn;
|
|
25
|
+
}
|
|
26
|
+
export interface SecurityValidationOptions {
|
|
27
|
+
storyPath: string;
|
|
28
|
+
commentNode: any;
|
|
29
|
+
context: any;
|
|
30
|
+
cwd: string;
|
|
31
|
+
allowAbsolute: boolean;
|
|
32
|
+
reportInvalidPath: ReportInvalidPathFn;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Analyze candidate paths against the project boundary, returning whether any
|
|
36
|
+
* are within the project and whether any are outside.
|
|
37
|
+
*
|
|
38
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
39
|
+
* @req REQ-PROJECT-BOUNDARY - Validate files are within project boundaries
|
|
40
|
+
* @req REQ-CONFIGURABLE-PATHS - Respect configured storyDirectories while enforcing project boundaries
|
|
41
|
+
*/
|
|
42
|
+
export declare function analyzeCandidateBoundaries(candidates: string[], cwd: string): {
|
|
43
|
+
hasInProjectCandidate: boolean;
|
|
44
|
+
hasOutOfProjectCandidate: boolean;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Determine whether any candidate or matched path crosses the project
|
|
48
|
+
* boundary, and report an invalid path if so.
|
|
49
|
+
*
|
|
50
|
+
* This centralizes project-boundary invalidation logic used during
|
|
51
|
+
* existence checks, so the decision of *when* to call the invalid-path
|
|
52
|
+
* reporter is not duplicated.
|
|
53
|
+
*
|
|
54
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
55
|
+
* @req REQ-PROJECT-BOUNDARY - Ensure resolved candidate paths remain within the project root
|
|
56
|
+
* @req REQ-CONFIGURABLE-PATHS - Respect configured storyDirectories while enforcing project boundaries
|
|
57
|
+
*/
|
|
58
|
+
export declare function handleProjectBoundaryForExistence({ storyPath, commentNode, context, cwd, candidates, existenceResult, reportInvalidPath, }: HandleBoundaryOptions): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Perform security-related validations on the story path, including
|
|
61
|
+
* absolute-path usage and path traversal checks. Report invalid paths
|
|
62
|
+
* when necessary and indicate whether further processing should continue.
|
|
63
|
+
*
|
|
64
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
65
|
+
* @req REQ-SECURITY-VALIDATION - Prevent path traversal and absolute path usage
|
|
66
|
+
*/
|
|
67
|
+
export declare function performSecurityValidations({ storyPath, commentNode, context, cwd, allowAbsolute, reportInvalidPath, }: SecurityValidationOptions): boolean;
|
|
@@ -0,0 +1,92 @@
|
|
|
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.analyzeCandidateBoundaries = analyzeCandidateBoundaries;
|
|
7
|
+
exports.handleProjectBoundaryForExistence = handleProjectBoundaryForExistence;
|
|
8
|
+
exports.performSecurityValidations = performSecurityValidations;
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const storyReferenceUtils_1 = require("../../utils/storyReferenceUtils");
|
|
11
|
+
/**
|
|
12
|
+
* Analyze candidate paths against the project boundary, returning whether any
|
|
13
|
+
* are within the project and whether any are outside.
|
|
14
|
+
*
|
|
15
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
16
|
+
* @req REQ-PROJECT-BOUNDARY - Validate files are within project boundaries
|
|
17
|
+
* @req REQ-CONFIGURABLE-PATHS - Respect configured storyDirectories while enforcing project boundaries
|
|
18
|
+
*/
|
|
19
|
+
function analyzeCandidateBoundaries(candidates, cwd) {
|
|
20
|
+
let hasInProjectCandidate = false;
|
|
21
|
+
let hasOutOfProjectCandidate = false;
|
|
22
|
+
for (const candidate of candidates) {
|
|
23
|
+
const boundary = (0, storyReferenceUtils_1.enforceProjectBoundary)(candidate, cwd);
|
|
24
|
+
if (boundary.isWithinProject) {
|
|
25
|
+
hasInProjectCandidate = true;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
hasOutOfProjectCandidate = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { hasInProjectCandidate, hasOutOfProjectCandidate };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Determine whether any candidate or matched path crosses the project
|
|
35
|
+
* boundary, and report an invalid path if so.
|
|
36
|
+
*
|
|
37
|
+
* This centralizes project-boundary invalidation logic used during
|
|
38
|
+
* existence checks, so the decision of *when* to call the invalid-path
|
|
39
|
+
* reporter is not duplicated.
|
|
40
|
+
*
|
|
41
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
42
|
+
* @req REQ-PROJECT-BOUNDARY - Ensure resolved candidate paths remain within the project root
|
|
43
|
+
* @req REQ-CONFIGURABLE-PATHS - Respect configured storyDirectories while enforcing project boundaries
|
|
44
|
+
*/
|
|
45
|
+
function handleProjectBoundaryForExistence({ storyPath, commentNode, context, cwd, candidates, existenceResult, reportInvalidPath, }) {
|
|
46
|
+
if (candidates.length > 0) {
|
|
47
|
+
const { hasInProjectCandidate, hasOutOfProjectCandidate } = analyzeCandidateBoundaries(candidates, cwd);
|
|
48
|
+
if (hasOutOfProjectCandidate && !hasInProjectCandidate) {
|
|
49
|
+
reportInvalidPath({ storyPath, commentNode, context });
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (existenceResult &&
|
|
54
|
+
existenceResult.status === "exists" &&
|
|
55
|
+
existenceResult.matchedPath) {
|
|
56
|
+
const boundary = (0, storyReferenceUtils_1.enforceProjectBoundary)(existenceResult.matchedPath, cwd);
|
|
57
|
+
if (!boundary.isWithinProject) {
|
|
58
|
+
reportInvalidPath({ storyPath, commentNode, context });
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Perform security-related validations on the story path, including
|
|
66
|
+
* absolute-path usage and path traversal checks. Report invalid paths
|
|
67
|
+
* when necessary and indicate whether further processing should continue.
|
|
68
|
+
*
|
|
69
|
+
* @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
|
|
70
|
+
* @req REQ-SECURITY-VALIDATION - Prevent path traversal and absolute path usage
|
|
71
|
+
*/
|
|
72
|
+
function performSecurityValidations({ storyPath, commentNode, context, cwd, allowAbsolute, reportInvalidPath, }) {
|
|
73
|
+
// Absolute path check
|
|
74
|
+
if (path_1.default.isAbsolute(storyPath)) {
|
|
75
|
+
if (!allowAbsolute) {
|
|
76
|
+
reportInvalidPath({ storyPath, commentNode, context });
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
// When absolute paths are allowed, we still enforce extension and
|
|
80
|
+
// project-boundary checks via the existence phase.
|
|
81
|
+
}
|
|
82
|
+
// Path traversal check
|
|
83
|
+
const containsTraversal = storyPath.includes("..") || /\\|\//.test(storyPath);
|
|
84
|
+
if (containsTraversal) {
|
|
85
|
+
const full = path_1.default.resolve(cwd, path_1.default.normalize(storyPath));
|
|
86
|
+
if (!full.startsWith(cwd + path_1.default.sep)) {
|
|
87
|
+
reportInvalidPath({ storyPath, commentNode, context });
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
@@ -64,24 +64,19 @@ const rule = {
|
|
|
64
64
|
const scope = opts.scope || require_story_helpers_1.DEFAULT_SCOPE;
|
|
65
65
|
const exportPriority = opts.exportPriority || "all";
|
|
66
66
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
67
|
+
* Optional debug logging for troubleshooting this rule.
|
|
68
|
+
* Developers can temporarily uncomment the block below to log when the rule
|
|
69
|
+
* is activated for a given file during ESLint runs.
|
|
69
70
|
*
|
|
70
71
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
71
72
|
* @req REQ-DEBUG-LOG
|
|
72
73
|
*/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*/
|
|
80
|
-
if (debugEnabled) {
|
|
81
|
-
console.debug("require-story-annotation:create", typeof context.getFilename === "function"
|
|
82
|
-
? context.getFilename()
|
|
83
|
-
: "<unknown>");
|
|
84
|
-
}
|
|
74
|
+
// console.debug(
|
|
75
|
+
// "require-story-annotation:create",
|
|
76
|
+
// typeof context.getFilename === "function"
|
|
77
|
+
// ? context.getFilename()
|
|
78
|
+
// : "<unknown>",
|
|
79
|
+
// );
|
|
85
80
|
// Local closure that binds configured scope and export priority to the helper.
|
|
86
81
|
const should = (node) => (0, require_story_helpers_1.shouldProcessNode)(node, scope, exportPriority);
|
|
87
82
|
// Delegate visitor construction to helper to keep this file concise.
|