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.
Files changed (42) hide show
  1. package/README.md +38 -1
  2. package/lib/src/index.d.ts +28 -25
  3. package/lib/src/index.js +49 -31
  4. package/lib/src/maintenance/cli.d.ts +12 -0
  5. package/lib/src/maintenance/cli.js +279 -0
  6. package/lib/src/maintenance/detect.js +30 -15
  7. package/lib/src/maintenance/update.js +42 -34
  8. package/lib/src/maintenance/utils.js +30 -30
  9. package/lib/src/rules/helpers/require-story-io.js +51 -15
  10. package/lib/src/rules/helpers/require-story-visitors.js +5 -16
  11. package/lib/src/rules/helpers/valid-annotation-options.d.ts +118 -0
  12. package/lib/src/rules/helpers/valid-annotation-options.js +167 -0
  13. package/lib/src/rules/helpers/valid-annotation-utils.d.ts +68 -0
  14. package/lib/src/rules/helpers/valid-annotation-utils.js +103 -0
  15. package/lib/src/rules/helpers/valid-story-reference-helpers.d.ts +67 -0
  16. package/lib/src/rules/helpers/valid-story-reference-helpers.js +92 -0
  17. package/lib/src/rules/require-story-annotation.js +9 -14
  18. package/lib/src/rules/valid-annotation-format.js +168 -180
  19. package/lib/src/rules/valid-req-reference.js +139 -29
  20. package/lib/src/rules/valid-story-reference.d.ts +7 -0
  21. package/lib/src/rules/valid-story-reference.js +38 -80
  22. package/lib/src/utils/annotation-checker.js +2 -145
  23. package/lib/src/utils/branch-annotation-helpers.js +12 -3
  24. package/lib/src/utils/reqAnnotationDetection.d.ts +6 -0
  25. package/lib/src/utils/reqAnnotationDetection.js +152 -0
  26. package/lib/tests/maintenance/cli.test.d.ts +1 -0
  27. package/lib/tests/maintenance/cli.test.js +172 -0
  28. package/lib/tests/maintenance/detect-isolated.test.js +68 -1
  29. package/lib/tests/maintenance/report.test.js +2 -2
  30. package/lib/tests/rules/require-branch-annotation.test.js +3 -2
  31. package/lib/tests/rules/require-req-annotation.test.js +57 -68
  32. package/lib/tests/rules/require-story-annotation.test.js +13 -28
  33. package/lib/tests/rules/require-story-core-edgecases.test.js +3 -58
  34. package/lib/tests/rules/require-story-core.autofix.test.js +5 -41
  35. package/lib/tests/rules/valid-annotation-format.test.js +328 -51
  36. package/lib/tests/utils/annotation-checker.test.d.ts +23 -0
  37. package/lib/tests/utils/annotation-checker.test.js +24 -17
  38. package/lib/tests/utils/require-story-core-test-helpers.d.ts +10 -0
  39. package/lib/tests/utils/require-story-core-test-helpers.js +75 -0
  40. package/lib/tests/utils/ts-language-options.d.ts +22 -0
  41. package/lib/tests/utils/ts-language-options.js +27 -0
  42. 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
- * Environment-gated debug logging to avoid leaking file paths unless
68
- * explicitly enabled.
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
- const debugEnabled = process.env.TRACEABILITY_DEBUG === "1";
74
- /**
75
- * Debug log at the start of create to help diagnose rule activation in tests.
76
- *
77
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
78
- * @req REQ-DEBUG-LOG
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.