eslint-plugin-traceability 1.10.1 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/README.md +3 -2
- package/lib/src/maintenance/cli.js +12 -12
- package/lib/src/maintenance/detect.js +19 -19
- package/lib/src/maintenance/flags.js +111 -25
- package/lib/src/rules/helpers/require-story-core.d.ts +55 -9
- package/lib/src/rules/helpers/require-story-core.js +85 -62
- package/lib/src/rules/helpers/require-story-helpers.d.ts +27 -48
- package/lib/src/rules/helpers/require-story-helpers.js +154 -116
- package/lib/src/rules/helpers/require-story-io.js +51 -31
- package/lib/src/rules/helpers/require-story-visitors.js +47 -6
- package/lib/src/rules/helpers/valid-annotation-format-validators.js +5 -1
- package/lib/src/rules/helpers/valid-annotation-options.d.ts +9 -0
- package/lib/src/rules/helpers/valid-annotation-options.js +67 -20
- package/lib/src/rules/helpers/valid-annotation-utils.js +31 -31
- package/lib/src/rules/helpers/valid-story-reference-helpers.js +19 -19
- package/lib/src/rules/prefer-implements-annotation.js +29 -1
- package/lib/src/rules/require-story-annotation.js +15 -0
- package/lib/src/rules/require-test-traceability.js +1 -6
- package/lib/src/utils/annotation-checker.js +32 -8
- package/lib/src/utils/reqAnnotationDetection.js +36 -22
- package/lib/tests/cli-error-handling.test.js +1 -0
- package/lib/tests/config/eslint-config-validation.test.d.ts +8 -0
- package/lib/tests/config/eslint-config-validation.test.js +8 -0
- package/lib/tests/config/flat-config-presets-integration.test.js +1 -3
- package/lib/tests/config/require-story-annotation-config.test.d.ts +9 -0
- package/lib/tests/config/require-story-annotation-config.test.js +9 -0
- package/lib/tests/integration/cli-integration.test.js +9 -1
- package/lib/tests/maintenance/batch.test.js +1 -0
- package/lib/tests/maintenance/cli.test.js +1 -0
- package/lib/tests/maintenance/detect-isolated.test.js +1 -0
- package/lib/tests/maintenance/detect.test.js +1 -0
- package/lib/tests/maintenance/index.test.js +1 -0
- package/lib/tests/maintenance/report.test.js +1 -0
- package/lib/tests/maintenance/update-isolated.test.js +1 -0
- package/lib/tests/maintenance/update.test.js +1 -0
- package/lib/tests/perf/maintenance-cli-large-workspace.test.d.ts +1 -0
- package/lib/tests/perf/maintenance-cli-large-workspace.test.js +130 -0
- package/lib/tests/perf/maintenance-large-workspace.test.d.ts +1 -0
- package/lib/tests/perf/maintenance-large-workspace.test.js +149 -0
- package/lib/tests/plugin-default-export-and-configs.test.js +2 -0
- package/lib/tests/plugin-setup-error.test.d.ts +1 -0
- package/lib/tests/plugin-setup-error.test.js +1 -0
- package/lib/tests/plugin-setup.test.js +1 -1
- package/lib/tests/rules/auto-fix-behavior-008.test.js +39 -0
- package/lib/tests/rules/error-reporting.test.js +1 -0
- package/lib/tests/rules/prefer-implements-annotation.test.js +8 -0
- package/lib/tests/rules/require-branch-annotation.test.js +2 -0
- package/lib/tests/rules/require-story-core-edgecases.test.js +1 -0
- package/lib/tests/rules/require-story-core.autofix.test.js +10 -3
- package/lib/tests/rules/require-story-core.test.js +14 -7
- package/lib/tests/rules/require-story-helpers-edgecases.test.d.ts +1 -0
- package/lib/tests/rules/require-story-helpers-edgecases.test.js +2 -1
- package/lib/tests/rules/require-story-helpers.test.js +18 -11
- package/lib/tests/rules/require-story-io-behavior.test.d.ts +1 -0
- package/lib/tests/rules/require-story-io-behavior.test.js +1 -0
- package/lib/tests/rules/require-story-io.edgecases.test.d.ts +1 -0
- package/lib/tests/rules/require-story-io.edgecases.test.js +1 -0
- package/lib/tests/rules/require-story-visitors-edgecases.test.d.ts +1 -0
- package/lib/tests/rules/require-story-visitors-edgecases.test.js +1 -0
- package/lib/tests/rules/valid-story-reference.test.js +2 -0
- package/lib/tests/utils/annotation-checker.test.js +2 -1
- package/lib/tests/utils/branch-annotation-helpers.test.js +2 -1
- package/lib/tests/utils/require-story-core-test-helpers.d.ts +1 -1
- package/lib/tests/utils/require-story-core-test-helpers.js +16 -16
- package/lib/tests/utils/temp-dir-helpers.js +1 -1
- package/package.json +9 -2
- package/user-docs/api-reference.md +123 -12
- package/user-docs/examples.md +41 -0
- package/user-docs/migration-guide.md +36 -3
|
@@ -45,6 +45,9 @@ const rule = {
|
|
|
45
45
|
uniqueItems: true,
|
|
46
46
|
},
|
|
47
47
|
exportPriority: { type: "string", enum: require_story_helpers_1.EXPORT_PRIORITY_VALUES },
|
|
48
|
+
annotationTemplate: { type: "string" },
|
|
49
|
+
methodAnnotationTemplate: { type: "string" },
|
|
50
|
+
autoFix: { type: "boolean" },
|
|
48
51
|
},
|
|
49
52
|
additionalProperties: false,
|
|
50
53
|
},
|
|
@@ -63,6 +66,15 @@ const rule = {
|
|
|
63
66
|
const opts = (context.options && context.options[0]) || {};
|
|
64
67
|
const scope = opts.scope || require_story_helpers_1.DEFAULT_SCOPE;
|
|
65
68
|
const exportPriority = opts.exportPriority || "all";
|
|
69
|
+
const annotationTemplate = typeof opts.annotationTemplate === "string" &&
|
|
70
|
+
opts.annotationTemplate.trim().length > 0
|
|
71
|
+
? opts.annotationTemplate.trim()
|
|
72
|
+
: undefined;
|
|
73
|
+
const methodAnnotationTemplate = typeof opts.methodAnnotationTemplate === "string" &&
|
|
74
|
+
opts.methodAnnotationTemplate.trim().length > 0
|
|
75
|
+
? opts.methodAnnotationTemplate.trim()
|
|
76
|
+
: undefined;
|
|
77
|
+
const autoFix = typeof opts.autoFix === "boolean" ? opts.autoFix : true;
|
|
66
78
|
/**
|
|
67
79
|
* Optional debug logging for troubleshooting this rule.
|
|
68
80
|
* Developers can temporarily uncomment the block below to log when the rule
|
|
@@ -84,6 +96,9 @@ const rule = {
|
|
|
84
96
|
shouldProcessNode: should,
|
|
85
97
|
scope,
|
|
86
98
|
exportPriority,
|
|
99
|
+
annotationTemplate,
|
|
100
|
+
methodAnnotationTemplate,
|
|
101
|
+
autoFix,
|
|
87
102
|
});
|
|
88
103
|
},
|
|
89
104
|
};
|
|
@@ -37,12 +37,7 @@ const rule = {
|
|
|
37
37
|
testFilePatterns: {
|
|
38
38
|
type: "array",
|
|
39
39
|
items: { type: "string" },
|
|
40
|
-
default: [
|
|
41
|
-
"**/tests/**/*.test.{js,ts}",
|
|
42
|
-
"**/tests/**/*.spec.{js,ts}",
|
|
43
|
-
"**/__tests__/**/*.{js,ts}",
|
|
44
|
-
"**/*.{test,spec}.{js,ts}",
|
|
45
|
-
],
|
|
40
|
+
default: ["/tests/", "/test/", "/__tests__", ".test.", ".spec."],
|
|
46
41
|
},
|
|
47
42
|
requireDescribeStory: {
|
|
48
43
|
type: "boolean",
|
|
@@ -76,7 +76,7 @@ function getFixTargetNode(node) {
|
|
|
76
76
|
* Returned function is a proper named function so no inline arrow is used.
|
|
77
77
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
78
78
|
* @req REQ-ANNOTATION-AUTOFIX - Provide autofix for missing @req annotation
|
|
79
|
-
* @
|
|
79
|
+
* @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-AUTOFIX REQ-ANNOTATION-REPORTING
|
|
80
80
|
*/
|
|
81
81
|
function createMissingReqFix(node) {
|
|
82
82
|
const target = getFixTargetNode(node);
|
|
@@ -90,6 +90,34 @@ function createMissingReqFix(node) {
|
|
|
90
90
|
return fixer.insertTextBefore(target, "/** @req <REQ-ID> */\n");
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the display name used when reporting a missing @req annotation.
|
|
95
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
96
|
+
* @req REQ-ANNOTATION-REPORTING - Use consistent naming when reporting missing @req
|
|
97
|
+
* @req REQ-ERROR-SPECIFIC - Derive a specific, human-readable name for the node
|
|
98
|
+
*/
|
|
99
|
+
function getReportedName(contextNode, parentNode) {
|
|
100
|
+
const rawName = (0, require_story_utils_1.getNodeName)(contextNode) ?? (0, require_story_utils_1.getNodeName)(parentNode);
|
|
101
|
+
return rawName ?? "(anonymous)";
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Determine the AST sub-node that should be used as the location for reporting.
|
|
105
|
+
* Prefers Identifier nodes (id or key) over the broader function-like node.
|
|
106
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
107
|
+
* @req REQ-ANNOTATION-REPORTING - Report missing @req on the most relevant node
|
|
108
|
+
* @req REQ-ERROR-SPECIFIC - Target the identifier when available for precise errors
|
|
109
|
+
*/
|
|
110
|
+
function getNameNodeForReqReport(node) {
|
|
111
|
+
const candidateId = node.id;
|
|
112
|
+
if (candidateId && candidateId.type === "Identifier") {
|
|
113
|
+
return candidateId;
|
|
114
|
+
}
|
|
115
|
+
const candidateKey = node.key;
|
|
116
|
+
if (candidateKey && candidateKey.type === "Identifier") {
|
|
117
|
+
return candidateKey;
|
|
118
|
+
}
|
|
119
|
+
return node;
|
|
120
|
+
}
|
|
93
121
|
/**
|
|
94
122
|
* Helper to report a missing @req annotation via the ESLint context API.
|
|
95
123
|
* Uses getNodeName to provide a readable name for the node.
|
|
@@ -102,13 +130,9 @@ function createMissingReqFix(node) {
|
|
|
102
130
|
* @req REQ-ERROR-CONTEXT - Include contextual hints to help understand the error
|
|
103
131
|
*/
|
|
104
132
|
function reportMissing(context, node, enableFix = true) {
|
|
105
|
-
const
|
|
106
|
-
const name =
|
|
107
|
-
const nameNode = (node
|
|
108
|
-
? node.id
|
|
109
|
-
: node && node.key && node.key.type === "Identifier"
|
|
110
|
-
? node.key
|
|
111
|
-
: node) ?? node;
|
|
133
|
+
const parentNode = node?.parent;
|
|
134
|
+
const name = getReportedName(node, parentNode);
|
|
135
|
+
const nameNode = getNameNodeForReqReport(node);
|
|
112
136
|
const reportOptions = {
|
|
113
137
|
node: nameNode,
|
|
114
138
|
messageId: "missingReq",
|
|
@@ -138,6 +138,38 @@ function fallbackTextBeforeHasReq(sourceCode, node) {
|
|
|
138
138
|
}
|
|
139
139
|
return false;
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Helper to combine advanced, location-based heuristics for requirement detection.
|
|
143
|
+
* Uses preceding lines, parent-chain comments, and fallback text windows to find @req/@supports.
|
|
144
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
145
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
146
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Use multiple heuristics to detect @req markers around the node
|
|
147
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Use multiple heuristics to detect @supports markers around the node
|
|
148
|
+
*/
|
|
149
|
+
function hasReqInAdvancedHeuristics(sourceCode, node) {
|
|
150
|
+
if (!sourceCode || !node) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return (linesBeforeHasReq(sourceCode, node) ||
|
|
154
|
+
parentChainHasReq(sourceCode, node) ||
|
|
155
|
+
fallbackTextBeforeHasReq(sourceCode, node));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Helper to check JSDoc and nearby comments for requirement annotations.
|
|
159
|
+
* Accepts both @req and @supports markers as evidence of requirement coverage.
|
|
160
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
161
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
162
|
+
* @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation in JSDoc/comments
|
|
163
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @supports as requirement coverage in JSDoc/comments
|
|
164
|
+
*/
|
|
165
|
+
function hasReqInJsdocOrComments(jsdoc, comments) {
|
|
166
|
+
if (jsdoc &&
|
|
167
|
+
typeof jsdoc.value === "string" &&
|
|
168
|
+
(jsdoc.value.includes("@req") || jsdoc.value.includes("@supports"))) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
return comments.some(commentContainsReq);
|
|
172
|
+
}
|
|
141
173
|
/**
|
|
142
174
|
* Helper to determine whether a JSDoc or any nearby comments contain a requirement annotation.
|
|
143
175
|
* Treats both @req and @supports annotations as evidence of requirement coverage.
|
|
@@ -151,30 +183,12 @@ function hasReqAnnotation(jsdoc, comments, context, node) {
|
|
|
151
183
|
const sourceCode = context && typeof context.getSourceCode === "function"
|
|
152
184
|
? context.getSourceCode()
|
|
153
185
|
: undefined;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// @req REQ-ANNOTATION-REQ-DETECTION - Use multiple heuristics to detect @req markers around the node
|
|
157
|
-
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Use multiple heuristics to detect @supports markers around the node
|
|
158
|
-
if (sourceCode && node) {
|
|
159
|
-
if (linesBeforeHasReq(sourceCode, node) ||
|
|
160
|
-
parentChainHasReq(sourceCode, node) ||
|
|
161
|
-
fallbackTextBeforeHasReq(sourceCode, node)) {
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
186
|
+
if (hasReqInAdvancedHeuristics(sourceCode, node)) {
|
|
187
|
+
return true;
|
|
164
188
|
}
|
|
165
189
|
}
|
|
166
190
|
catch {
|
|
167
|
-
//
|
|
168
|
-
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
169
|
-
// @req REQ-ANNOTATION-REQ-DETECTION - Fail gracefully when advanced detection heuristics throw
|
|
191
|
+
// swallow and fall through to simple checks
|
|
170
192
|
}
|
|
171
|
-
|
|
172
|
-
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
173
|
-
// @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
174
|
-
// @req REQ-ANNOTATION-REQ-DETECTION
|
|
175
|
-
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS
|
|
176
|
-
return ((jsdoc &&
|
|
177
|
-
typeof jsdoc.value === "string" &&
|
|
178
|
-
(jsdoc.value.includes("@req") || jsdoc.value.includes("@supports"))) ||
|
|
179
|
-
comments.some(commentContainsReq));
|
|
193
|
+
return hasReqInJsdocOrComments(jsdoc, comments);
|
|
180
194
|
}
|
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
* Tests for CLI error handling when plugin loading fails
|
|
8
8
|
* @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
9
9
|
* @req REQ-ERROR-HANDLING - Plugin CLI should exit with error on rule load failure
|
|
10
|
+
* @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-ERROR-HANDLING
|
|
10
11
|
*/
|
|
11
12
|
const child_process_1 = require("child_process");
|
|
12
13
|
const path_1 = __importDefault(require("path"));
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for ESLint config rule schemas.
|
|
4
|
+
*
|
|
5
|
+
* @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
|
|
6
|
+
* @req REQ-RULE-OPTIONS
|
|
7
|
+
* @req REQ-CONFIG-VALIDATION
|
|
8
|
+
* @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
|
|
9
|
+
*/
|
|
2
10
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
11
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
12
|
};
|
|
@@ -36,9 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
/**
|
|
37
37
|
* Tests for: docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
|
|
38
38
|
* @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
|
|
39
|
-
* @
|
|
40
|
-
* @req REQ-FLAT-CONFIG - Ensure presets work with ESLint v9 flat config
|
|
41
|
-
* @req REQ-PROJECT-INTEGRATION - Support seamless integration via documented preset usage
|
|
39
|
+
* @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md REQ-CONFIG-PRESETS REQ-FLAT-CONFIG REQ-PROJECT-INTEGRATION
|
|
42
40
|
*/
|
|
43
41
|
const use_at_your_own_risk_1 = require("eslint/use-at-your-own-risk");
|
|
44
42
|
const index_1 = __importStar(require("../../src/index"));
|
|
@@ -1 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the require-story-annotation rule schema configuration.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the ESLint rule options for require-story-annotation
|
|
5
|
+
* define the expected schema properties and constraints.
|
|
6
|
+
*
|
|
7
|
+
* @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
|
|
8
|
+
* @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md REQ-RULE-OPTIONS
|
|
9
|
+
*/
|
|
1
10
|
export {};
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for the require-story-annotation rule schema configuration.
|
|
4
|
+
*
|
|
5
|
+
* Verifies that the ESLint rule options for require-story-annotation
|
|
6
|
+
* define the expected schema properties and constraints.
|
|
7
|
+
*
|
|
8
|
+
* @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
|
|
9
|
+
* @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md REQ-RULE-OPTIONS
|
|
10
|
+
*/
|
|
2
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
13
|
};
|
|
@@ -3,6 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
/**
|
|
7
|
+
* Tests for CLI integration of the traceability plugin.
|
|
8
|
+
* Validates that the plugin registers correctly and enforces
|
|
9
|
+
* traceability-related rules when invoked via the ESLint CLI.
|
|
10
|
+
*
|
|
11
|
+
* @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-PLUGIN-STRUCTURE
|
|
12
|
+
* @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
13
|
+
*/
|
|
6
14
|
/**
|
|
7
15
|
* Tests for CLI integration functionality
|
|
8
16
|
* @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
@@ -10,7 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
18
|
*/
|
|
11
19
|
const child_process_1 = require("child_process");
|
|
12
20
|
const path_1 = __importDefault(require("path"));
|
|
13
|
-
describe("
|
|
21
|
+
describe("CLI Integration (Story 001.0-DEV-PLUGIN-SETUP)", () => {
|
|
14
22
|
const eslintPkgDir = path_1.default.dirname(require.resolve("eslint/package.json"));
|
|
15
23
|
const eslintCliPath = path_1.default.join(eslintPkgDir, "bin", "eslint.js");
|
|
16
24
|
const configPath = path_1.default.resolve(__dirname, "../../eslint.config.js");
|
|
@@ -38,6 +38,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
38
38
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
39
39
|
* @req REQ-MAINT-BATCH - Perform batch updates
|
|
40
40
|
* @req REQ-MAINT-VERIFY - Verify annotation references
|
|
41
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-BATCH REQ-MAINT-VERIFY
|
|
41
42
|
*/
|
|
42
43
|
const fs = __importStar(require("fs"));
|
|
43
44
|
const path = __importStar(require("path"));
|
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
11
11
|
* @req REQ-MAINT-REPORT - CLI reporting of stale annotations
|
|
12
12
|
* @req REQ-MAINT-UPDATE - CLI updating of annotation references
|
|
13
13
|
* @req REQ-MAINT-SAFE - Clear exit codes and non-destructive dry-run
|
|
14
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-VERIFY REQ-MAINT-REPORT REQ-MAINT-UPDATE REQ-MAINT-SAFE
|
|
14
15
|
*/
|
|
15
16
|
const fs_1 = __importDefault(require("fs"));
|
|
16
17
|
const path_1 = __importDefault(require("path"));
|
|
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
37
37
|
* Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
38
38
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
39
39
|
* @req REQ-MAINT-DETECT - Detect stale annotation references
|
|
40
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT
|
|
40
41
|
*/
|
|
41
42
|
const path = __importStar(require("path"));
|
|
42
43
|
const os = __importStar(require("os"));
|
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
* Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
8
8
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
9
9
|
* @req REQ-MAINT-DETECT - Detect stale annotation references
|
|
10
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT
|
|
10
11
|
*/
|
|
11
12
|
const fs_1 = __importDefault(require("fs"));
|
|
12
13
|
const path_1 = __importDefault(require("path"));
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
* Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
5
5
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
6
6
|
* @req REQ-MAINT-SAFE - Ensure all maintenance tools are exported correctly
|
|
7
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-SAFE REQ-MAINT-DETECT REQ-MAINT-UPDATE REQ-MAINT-BATCH REQ-MAINT-VERIFY REQ-MAINT-REPORT
|
|
7
8
|
*/
|
|
8
9
|
const maintenance_1 = require("../../src/maintenance");
|
|
9
10
|
describe("Maintenance Tools Index Exports (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
@@ -38,6 +38,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
38
38
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
39
39
|
* @req REQ-MAINT-REPORT - Generate maintenance report
|
|
40
40
|
* @req REQ-MAINT-SAFE - Ensure operations are safe and reversible
|
|
41
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT REQ-MAINT-SAFE
|
|
41
42
|
*/
|
|
42
43
|
const fs = __importStar(require("fs"));
|
|
43
44
|
const path = __importStar(require("path"));
|
|
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
37
37
|
* Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
38
38
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
39
39
|
* @req REQ-MAINT-UPDATE - Update annotation references
|
|
40
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
40
41
|
*/
|
|
41
42
|
const fs = __importStar(require("fs"));
|
|
42
43
|
const path = __importStar(require("path"));
|
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
* Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
8
8
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
9
9
|
* @req REQ-MAINT-UPDATE - Update annotation references
|
|
10
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
10
11
|
*/
|
|
11
12
|
const fs_1 = __importDefault(require("fs"));
|
|
12
13
|
const os_1 = __importDefault(require("os"));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
/**
|
|
37
|
+
* CLI-level performance tests for maintenance tools on large workspaces.
|
|
38
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-REPORT REQ-MAINT-SAFE
|
|
39
|
+
*/
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
44
|
+
const cli_1 = require("../../src/maintenance/cli");
|
|
45
|
+
function createCliLargeWorkspace() {
|
|
46
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "traceability-cli-large-"));
|
|
47
|
+
// Create a modestly sized workspace reusing the same shape as the core perf tests,
|
|
48
|
+
// but with fewer files to keep end-to-end CLI timing predictable.
|
|
49
|
+
for (let moduleIndex = 0; moduleIndex < 5; moduleIndex += 1) {
|
|
50
|
+
const moduleDir = path.join(root, `module-${moduleIndex.toString().padStart(3, "0")}`);
|
|
51
|
+
fs.mkdirSync(moduleDir);
|
|
52
|
+
for (let fileIndex = 0; fileIndex < 20; fileIndex += 1) {
|
|
53
|
+
const filePath = path.join(moduleDir, `file-${fileIndex.toString().padStart(3, "0")}.ts`);
|
|
54
|
+
const validStory = "cli-valid.story.md";
|
|
55
|
+
const staleStory = "cli-stale.story.md";
|
|
56
|
+
const content = `/**
|
|
57
|
+
* @story ${validStory}
|
|
58
|
+
* @story ${staleStory}
|
|
59
|
+
*/
|
|
60
|
+
export function cli_example_${moduleIndex}_${fileIndex}() {}
|
|
61
|
+
`;
|
|
62
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Create the valid story file so that only the stale entries are reported.
|
|
66
|
+
fs.writeFileSync(path.join(root, "cli-valid.story.md"), "# cli valid", "utf8");
|
|
67
|
+
return {
|
|
68
|
+
root,
|
|
69
|
+
cleanup: () => {
|
|
70
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
describe("Maintenance CLI on large workspaces (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
75
|
+
let workspace;
|
|
76
|
+
let originalCwd;
|
|
77
|
+
beforeAll(() => {
|
|
78
|
+
originalCwd = process.cwd();
|
|
79
|
+
workspace = createCliLargeWorkspace();
|
|
80
|
+
process.chdir(workspace.root);
|
|
81
|
+
});
|
|
82
|
+
afterAll(() => {
|
|
83
|
+
process.chdir(originalCwd);
|
|
84
|
+
workspace.cleanup();
|
|
85
|
+
});
|
|
86
|
+
it("[REQ-MAINT-DETECT] detect --json completes within a generous time budget and returns JSON payload", () => {
|
|
87
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
88
|
+
const start = perf_hooks_1.performance.now();
|
|
89
|
+
const exitCode = (0, cli_1.runMaintenanceCli)([
|
|
90
|
+
"node",
|
|
91
|
+
"traceability-maint",
|
|
92
|
+
"detect",
|
|
93
|
+
"--root",
|
|
94
|
+
workspace.root,
|
|
95
|
+
"--json",
|
|
96
|
+
]);
|
|
97
|
+
const durationMs = perf_hooks_1.performance.now() - start;
|
|
98
|
+
expect(exitCode === 0 || exitCode === 1).toBe(true);
|
|
99
|
+
expect(durationMs).toBeLessThan(5000);
|
|
100
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
101
|
+
const payloadRaw = String(logSpy.mock.calls[0][0]);
|
|
102
|
+
const payload = JSON.parse(payloadRaw);
|
|
103
|
+
expect(payload.root).toBe(workspace.root);
|
|
104
|
+
expect(Array.isArray(payload.stale)).toBe(true);
|
|
105
|
+
expect(payload.stale.length).toBeGreaterThan(0);
|
|
106
|
+
logSpy.mockRestore();
|
|
107
|
+
});
|
|
108
|
+
it("[REQ-MAINT-REPORT] report --format=json completes within a generous time budget", () => {
|
|
109
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
110
|
+
const start = perf_hooks_1.performance.now();
|
|
111
|
+
const exitCode = (0, cli_1.runMaintenanceCli)([
|
|
112
|
+
"node",
|
|
113
|
+
"traceability-maint",
|
|
114
|
+
"report",
|
|
115
|
+
"--root",
|
|
116
|
+
workspace.root,
|
|
117
|
+
"--format",
|
|
118
|
+
"json",
|
|
119
|
+
]);
|
|
120
|
+
const durationMs = perf_hooks_1.performance.now() - start;
|
|
121
|
+
expect(exitCode).toBe(0);
|
|
122
|
+
expect(durationMs).toBeLessThan(5000);
|
|
123
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
124
|
+
const payloadRaw = String(logSpy.mock.calls[0][0]);
|
|
125
|
+
const payload = JSON.parse(payloadRaw);
|
|
126
|
+
expect(payload.root).toBe(workspace.root);
|
|
127
|
+
expect(typeof payload.report).toBe("string");
|
|
128
|
+
logSpy.mockRestore();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
/**
|
|
37
|
+
* Performance and stress tests for maintenance tools on large workspaces.
|
|
38
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-VERIFY REQ-MAINT-REPORT REQ-MAINT-UPDATE REQ-MAINT-BATCH
|
|
39
|
+
*/
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
44
|
+
const detect_1 = require("../../src/maintenance/detect");
|
|
45
|
+
const batch_1 = require("../../src/maintenance/batch");
|
|
46
|
+
const report_1 = require("../../src/maintenance/report");
|
|
47
|
+
const update_1 = require("../../src/maintenance/update");
|
|
48
|
+
/**
|
|
49
|
+
* Shape of the synthetic large workspace:
|
|
50
|
+
* - 10 modules (module-000 .. module-009)
|
|
51
|
+
* - 50 files per module (file-000.ts .. file-049.ts)
|
|
52
|
+
* - Each file includes a mix of valid and stale @story references.
|
|
53
|
+
*/
|
|
54
|
+
function createLargeWorkspace() {
|
|
55
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "traceability-large-"));
|
|
56
|
+
// Create a pool of story files that will be considered "valid".
|
|
57
|
+
const validStories = [];
|
|
58
|
+
for (let i = 0; i < 250; i += 1) {
|
|
59
|
+
const storyName = `valid-story-${i.toString().padStart(4, "0")}.story.md`;
|
|
60
|
+
const storyPath = path.join(root, storyName);
|
|
61
|
+
fs.writeFileSync(storyPath, `# ${storyName}`, "utf8");
|
|
62
|
+
validStories.push(storyName);
|
|
63
|
+
}
|
|
64
|
+
let validIndex = 0;
|
|
65
|
+
let staleIndex = 0;
|
|
66
|
+
for (let moduleIndex = 0; moduleIndex < 10; moduleIndex += 1) {
|
|
67
|
+
const moduleDir = path.join(root, `module-${moduleIndex.toString().padStart(3, "0")}`);
|
|
68
|
+
fs.mkdirSync(moduleDir);
|
|
69
|
+
for (let fileIndex = 0; fileIndex < 50; fileIndex += 1) {
|
|
70
|
+
const filePath = path.join(moduleDir, `file-${fileIndex.toString().padStart(3, "0")}.ts`);
|
|
71
|
+
const validStory = validStories[validIndex % validStories.length] ??
|
|
72
|
+
"valid-story-0000.story.md";
|
|
73
|
+
validIndex += 1;
|
|
74
|
+
const staleStory = `stale-story-${staleIndex
|
|
75
|
+
.toString()
|
|
76
|
+
.padStart(4, "0")}.story.md`;
|
|
77
|
+
staleIndex += 1;
|
|
78
|
+
const content = `/**
|
|
79
|
+
* @story ${validStory}
|
|
80
|
+
* @story ${staleStory}
|
|
81
|
+
*/
|
|
82
|
+
export function example_${moduleIndex}_${fileIndex}() {}
|
|
83
|
+
`;
|
|
84
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
root,
|
|
89
|
+
cleanup: () => {
|
|
90
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
describe("Maintenance tools on large workspaces (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
95
|
+
let workspace;
|
|
96
|
+
beforeAll(() => {
|
|
97
|
+
workspace = createLargeWorkspace();
|
|
98
|
+
});
|
|
99
|
+
afterAll(() => {
|
|
100
|
+
workspace.cleanup();
|
|
101
|
+
});
|
|
102
|
+
it("[REQ-MAINT-DETECT] detectStaleAnnotations completes within a generous time budget", () => {
|
|
103
|
+
const start = perf_hooks_1.performance.now();
|
|
104
|
+
const stale = (0, detect_1.detectStaleAnnotations)(workspace.root);
|
|
105
|
+
const durationMs = perf_hooks_1.performance.now() - start;
|
|
106
|
+
// Sanity check: we expect at least some stale entries due to the generated stale-story-* references.
|
|
107
|
+
expect(stale.length).toBeGreaterThan(0);
|
|
108
|
+
// Guardrail: this operation should remain comfortably under ~5 seconds on CI hardware.
|
|
109
|
+
expect(durationMs).toBeLessThan(5000);
|
|
110
|
+
});
|
|
111
|
+
it("[REQ-MAINT-VERIFY] verifyAnnotations remains fast on large workspaces", () => {
|
|
112
|
+
const start = perf_hooks_1.performance.now();
|
|
113
|
+
const result = (0, batch_1.verifyAnnotations)(workspace.root);
|
|
114
|
+
const durationMs = perf_hooks_1.performance.now() - start;
|
|
115
|
+
// With both valid and stale references, verification should report false.
|
|
116
|
+
expect(result).toBe(false);
|
|
117
|
+
expect(durationMs).toBeLessThan(5000);
|
|
118
|
+
});
|
|
119
|
+
it("[REQ-MAINT-REPORT] generateMaintenanceReport produces output within a generous time budget", () => {
|
|
120
|
+
const start = perf_hooks_1.performance.now();
|
|
121
|
+
const report = (0, report_1.generateMaintenanceReport)(workspace.root);
|
|
122
|
+
const durationMs = perf_hooks_1.performance.now() - start;
|
|
123
|
+
expect(report).not.toBe("");
|
|
124
|
+
expect(durationMs).toBeLessThan(5000);
|
|
125
|
+
});
|
|
126
|
+
it("[REQ-MAINT-UPDATE] updateAnnotationReferences and batchUpdateAnnotations remain tractable", () => {
|
|
127
|
+
const exampleOldPath = "stale-story-0000.story.md";
|
|
128
|
+
const exampleNewPath = "updated-story-0000.story.md";
|
|
129
|
+
const singleStart = perf_hooks_1.performance.now();
|
|
130
|
+
const updatedCount = (0, update_1.updateAnnotationReferences)(workspace.root, exampleOldPath, exampleNewPath);
|
|
131
|
+
const singleDuration = perf_hooks_1.performance.now() - singleStart;
|
|
132
|
+
expect(updatedCount).toBeGreaterThan(0);
|
|
133
|
+
expect(singleDuration).toBeLessThan(5000);
|
|
134
|
+
const batchStart = perf_hooks_1.performance.now();
|
|
135
|
+
const totalUpdated = (0, batch_1.batchUpdateAnnotations)(workspace.root, [
|
|
136
|
+
{
|
|
137
|
+
oldPath: "stale-story-0001.story.md",
|
|
138
|
+
newPath: "updated-story-0001.story.md",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
oldPath: "stale-story-0002.story.md",
|
|
142
|
+
newPath: "updated-story-0002.story.md",
|
|
143
|
+
},
|
|
144
|
+
]);
|
|
145
|
+
const batchDuration = perf_hooks_1.performance.now() - batchStart;
|
|
146
|
+
expect(totalUpdated).toBeGreaterThanOrEqual(2);
|
|
147
|
+
expect(batchDuration).toBeLessThan(5000);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
/**
|
|
37
37
|
* Tests for: docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
38
|
+
* @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-PLUGIN-STRUCTURE REQ-RULE-REGISTRY REQ-CONFIG-SYSTEM
|
|
39
|
+
* @supports docs/stories/007.0-DEV-ERROR-REPORTING.story.md REQ-ERROR-SEVERITY
|
|
38
40
|
* @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
39
41
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
40
42
|
* @req REQ-PLUGIN-STRUCTURE - Validate plugin default export and configs in src/index.ts
|