eslint-plugin-traceability 1.11.0 → 1.11.2
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 +3 -4
- package/README.md +1 -1
- package/lib/src/index.d.ts +12 -1
- package/lib/src/index.js +43 -6
- package/lib/src/maintenance/commands.js +2 -3
- package/lib/src/maintenance/flags.js +111 -25
- package/lib/src/maintenance/update.js +1 -14
- package/lib/src/rules/helpers/require-story-core.d.ts +67 -0
- package/lib/src/rules/helpers/require-story-core.js +142 -23
- package/lib/src/rules/helpers/require-story-helpers.d.ts +9 -88
- package/lib/src/rules/helpers/require-story-helpers.js +118 -166
- package/lib/src/rules/helpers/require-story-io.js +51 -31
- package/lib/src/rules/helpers/valid-annotation-format-internal.d.ts +12 -13
- package/lib/src/rules/helpers/valid-annotation-format-internal.js +21 -16
- package/lib/src/rules/helpers/valid-annotation-format-validators.d.ts +29 -3
- package/lib/src/rules/helpers/valid-annotation-format-validators.js +29 -3
- package/lib/src/rules/helpers/valid-annotation-options.d.ts +3 -0
- package/lib/src/rules/helpers/valid-annotation-options.js +64 -21
- package/lib/src/rules/helpers/valid-annotation-utils.d.ts +3 -3
- package/lib/src/rules/helpers/valid-annotation-utils.js +10 -10
- package/lib/src/rules/helpers/valid-req-reference-helpers.d.ts +11 -0
- package/lib/src/rules/helpers/valid-req-reference-helpers.js +362 -0
- package/lib/src/rules/prefer-implements-annotation.js +7 -7
- package/lib/src/rules/require-story-annotation.d.ts +2 -0
- package/lib/src/rules/require-story-annotation.js +1 -1
- package/lib/src/rules/valid-req-reference.d.ts +4 -0
- package/lib/src/rules/valid-req-reference.js +5 -349
- package/lib/src/rules/valid-story-reference.d.ts +1 -1
- package/lib/src/rules/valid-story-reference.js +17 -10
- package/lib/src/utils/annotation-checker.js +31 -7
- package/lib/src/utils/branch-annotation-helpers.d.ts +2 -2
- package/lib/src/utils/branch-annotation-helpers.js +4 -4
- package/lib/src/utils/reqAnnotationDetection.js +36 -22
- package/lib/tests/cli-error-handling.test.js +2 -1
- package/lib/tests/config/eslint-config-validation.test.d.ts +8 -0
- package/lib/tests/config/eslint-config-validation.test.js +81 -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/fixtures/stale/example.js +1 -1
- package/lib/tests/fixtures/update/example.js +1 -1
- package/lib/tests/integration/cli-integration.test.js +9 -1
- package/lib/tests/integration/dogfooding-validation.test.d.ts +1 -0
- package/lib/tests/integration/dogfooding-validation.test.js +94 -0
- package/lib/tests/maintenance/batch.test.js +1 -0
- package/lib/tests/maintenance/cli.test.js +38 -0
- package/lib/tests/maintenance/detect-isolated.test.js +6 -5
- 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.js +18 -0
- package/lib/tests/perf/require-branch-annotation-large-file.test.d.ts +1 -0
- package/lib/tests/perf/require-branch-annotation-large-file.test.js +67 -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 +12 -1
- package/lib/tests/rules/auto-fix-behavior-008.test.js +16 -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 +34 -0
- package/lib/tests/rules/require-story-core-edgecases.test.js +1 -0
- package/lib/tests/rules/require-story-core.autofix.test.js +1 -0
- package/lib/tests/rules/require-story-core.test.js +1 -0
- package/lib/tests/rules/require-story-helpers-edgecases.test.d.ts +1 -0
- package/lib/tests/rules/require-story-helpers-edgecases.test.js +1 -0
- package/lib/tests/rules/require-story-helpers.test.js +4 -3
- 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-annotation-format-internal.test.d.ts +8 -0
- package/lib/tests/rules/valid-annotation-format-internal.test.js +47 -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/package.json +2 -2
- package/user-docs/api-reference.md +115 -8
- package/user-docs/examples.md +1 -1
- package/user-docs/migration-guide.md +35 -2
|
@@ -1,9 +1,19 @@
|
|
|
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
|
};
|
|
5
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
14
|
const valid_story_reference_1 = __importDefault(require("../../src/rules/valid-story-reference"));
|
|
15
|
+
const use_at_your_own_risk_1 = require("eslint/use-at-your-own-risk");
|
|
16
|
+
const index_1 = __importDefault(require("../../src/index"));
|
|
7
17
|
/** @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md */
|
|
8
18
|
describe("ESLint Configuration Setup (Story 002.0-DEV-ESLINT-CONFIG)", () => {
|
|
9
19
|
it("[REQ-RULE-OPTIONS] rule meta.schema defines expected properties", () => {
|
|
@@ -16,4 +26,75 @@ describe("ESLint Configuration Setup (Story 002.0-DEV-ESLINT-CONFIG)", () => {
|
|
|
16
26
|
const schema = valid_story_reference_1.default.meta.schema[0];
|
|
17
27
|
expect(schema.additionalProperties).toBe(false);
|
|
18
28
|
});
|
|
29
|
+
it("[REQ-CONFIG-VALIDATION] ESLint throws on unknown rule option", async () => {
|
|
30
|
+
const eslint = new use_at_your_own_risk_1.FlatESLint({
|
|
31
|
+
overrideConfig: [
|
|
32
|
+
{
|
|
33
|
+
plugins: {
|
|
34
|
+
traceability: index_1.default,
|
|
35
|
+
},
|
|
36
|
+
rules: {
|
|
37
|
+
"traceability/valid-story-reference": [
|
|
38
|
+
"error",
|
|
39
|
+
{
|
|
40
|
+
storyDirectories: ["stories"],
|
|
41
|
+
allowAbsolutePaths: false,
|
|
42
|
+
requireStoryExtension: true,
|
|
43
|
+
unknownOptionKey: true,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
overrideConfigFile: true,
|
|
50
|
+
ignore: false,
|
|
51
|
+
});
|
|
52
|
+
let caughtError;
|
|
53
|
+
try {
|
|
54
|
+
await eslint.lintText("const x = 1;");
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
caughtError = err;
|
|
58
|
+
}
|
|
59
|
+
expect(caughtError).toBeInstanceOf(Error);
|
|
60
|
+
const message = String(caughtError.message || caughtError);
|
|
61
|
+
expect(message).toContain("traceability/valid-story-reference");
|
|
62
|
+
expect(message.toLowerCase()).toContain("additional");
|
|
63
|
+
expect(message.toLowerCase()).toContain("unexpected property");
|
|
64
|
+
expect(message).toContain("unknownOptionKey");
|
|
65
|
+
});
|
|
66
|
+
it("[REQ-CONFIG-VALIDATION] ESLint throws on invalid option type", async () => {
|
|
67
|
+
const eslint = new use_at_your_own_risk_1.FlatESLint({
|
|
68
|
+
overrideConfig: [
|
|
69
|
+
{
|
|
70
|
+
plugins: {
|
|
71
|
+
traceability: index_1.default,
|
|
72
|
+
},
|
|
73
|
+
rules: {
|
|
74
|
+
"traceability/valid-story-reference": [
|
|
75
|
+
"error",
|
|
76
|
+
{
|
|
77
|
+
// storyDirectories must be an array, not a string
|
|
78
|
+
storyDirectories: "not-an-array",
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
overrideConfigFile: true,
|
|
85
|
+
ignore: false,
|
|
86
|
+
});
|
|
87
|
+
let caughtError;
|
|
88
|
+
try {
|
|
89
|
+
await eslint.lintText("const y = 2;");
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
caughtError = err;
|
|
93
|
+
}
|
|
94
|
+
expect(caughtError).toBeInstanceOf(Error);
|
|
95
|
+
const message = String(caughtError.message || caughtError);
|
|
96
|
+
expect(message).toContain("traceability/valid-story-reference");
|
|
97
|
+
expect(message).toContain("not-an-array");
|
|
98
|
+
expect(message.toLowerCase()).toContain("array");
|
|
99
|
+
});
|
|
19
100
|
});
|
|
@@ -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");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
* Dogfooding validation integration tests
|
|
38
|
+
* @supports docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md REQ-DOGFOODING-TEST REQ-DOGFOODING-CI
|
|
39
|
+
*/
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const child_process_1 = require("child_process");
|
|
42
|
+
/**
|
|
43
|
+
* @supports docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md REQ-DOGFOODING-TEST
|
|
44
|
+
*/
|
|
45
|
+
function getTsConfigFromEslintConfig(eslintConfig) {
|
|
46
|
+
const configs = Array.isArray(eslintConfig) ? eslintConfig : [eslintConfig];
|
|
47
|
+
return configs.find((config) => {
|
|
48
|
+
if (!config || !config.files)
|
|
49
|
+
return false;
|
|
50
|
+
const files = config.files;
|
|
51
|
+
return files.includes("**/*.ts") && files.includes("**/*.tsx");
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
describe("Dogfooding Validation (Story 023.0-MAINT-DOGFOODING-VALIDATION)", () => {
|
|
55
|
+
it("[REQ-DOGFOODING-TEST] should have traceability/require-story-annotation enabled for TS sources", () => {
|
|
56
|
+
/**
|
|
57
|
+
* @supports docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md REQ-DOGFOODING-TEST
|
|
58
|
+
*/
|
|
59
|
+
// Require the project's eslint.config.js and find the TS-specific config
|
|
60
|
+
// that applies to *.ts and *.tsx files.
|
|
61
|
+
const eslintConfig = require("../../eslint.config.js");
|
|
62
|
+
const tsConfig = getTsConfigFromEslintConfig(eslintConfig);
|
|
63
|
+
expect(tsConfig).toBeDefined();
|
|
64
|
+
const rules = tsConfig.rules || {};
|
|
65
|
+
const ruleEntry = rules["traceability/require-story-annotation"];
|
|
66
|
+
expect(ruleEntry).toBeDefined();
|
|
67
|
+
const severity = Array.isArray(ruleEntry) && ruleEntry.length > 0
|
|
68
|
+
? ruleEntry[0]
|
|
69
|
+
: ruleEntry;
|
|
70
|
+
expect(severity).toBe("error");
|
|
71
|
+
});
|
|
72
|
+
it("[REQ-DOGFOODING-CI] should run traceability/require-story-annotation via ESLint CLI on TS sources", () => {
|
|
73
|
+
/**
|
|
74
|
+
* @supports docs/stories/023.0-MAINT-DOGFOODING-VALIDATION.story.md REQ-DOGFOODING-CI
|
|
75
|
+
*/
|
|
76
|
+
const eslintBin = path.resolve(__dirname, "../../node_modules/.bin/eslint");
|
|
77
|
+
const configPath = path.resolve(__dirname, "../../eslint.config.js");
|
|
78
|
+
const tsSnippet = `
|
|
79
|
+
const x: number = 42;
|
|
80
|
+
export function foo() {
|
|
81
|
+
return x;
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
const result = (0, child_process_1.spawnSync)(process.platform === "win32" ? `${eslintBin}.cmd` : eslintBin, ["--config", configPath, "--stdin", "--stdin-filename", "src/dogfood.ts"], {
|
|
85
|
+
encoding: "utf8",
|
|
86
|
+
input: tsSnippet,
|
|
87
|
+
});
|
|
88
|
+
// The snippet intentionally lacks @story annotations, so the rule should
|
|
89
|
+
// report an error for the generated `src/dogfood.ts` virtual file.
|
|
90
|
+
expect(result.status).not.toBe(0);
|
|
91
|
+
expect(result.stdout).toContain("error");
|
|
92
|
+
expect(result.stdout).toContain("src/dogfood.ts");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -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"));
|
|
@@ -57,6 +58,26 @@ describe("Maintenance CLI (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
|
57
58
|
temp.cleanup();
|
|
58
59
|
}
|
|
59
60
|
});
|
|
61
|
+
it("[REQ-MAINT-VERIFY] verify exits with code 1 and prints guidance when annotations are stale or invalid", () => {
|
|
62
|
+
const temp = (0, temp_dir_helpers_1.createTempDir)("maint-cli-");
|
|
63
|
+
const dir = temp.dir;
|
|
64
|
+
process.chdir(dir);
|
|
65
|
+
const tsContent = `/**\n * @story missing.story.md\n */`;
|
|
66
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, "file.ts"), tsContent, "utf8");
|
|
67
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
68
|
+
const code = (0, cli_1.runMaintenanceCli)(["node", "traceability-maint", "verify"]);
|
|
69
|
+
try {
|
|
70
|
+
expect(code).toBe(1);
|
|
71
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
72
|
+
const message = String(logSpy.mock.calls[0][0]);
|
|
73
|
+
expect(message).toContain("Stale or invalid traceability annotations detected under");
|
|
74
|
+
expect(message).toContain("Run 'traceability-maint detect' or 'traceability-maint report' for details.");
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
logSpy.mockRestore();
|
|
78
|
+
temp.cleanup();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
60
81
|
it("[REQ-MAINT-REPORT] report prints human-readable summary and exits 0", () => {
|
|
61
82
|
const temp = (0, temp_dir_helpers_1.createTempDir)("maint-cli-");
|
|
62
83
|
const dir = temp.dir;
|
|
@@ -76,6 +97,23 @@ describe("Maintenance CLI (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
|
|
|
76
97
|
temp.cleanup();
|
|
77
98
|
}
|
|
78
99
|
});
|
|
100
|
+
it("[REQ-MAINT-REPORT] report prints 'nothing to report' when no stale annotations exist", () => {
|
|
101
|
+
const temp = (0, temp_dir_helpers_1.createTempDir)("maint-cli-");
|
|
102
|
+
const dir = temp.dir;
|
|
103
|
+
process.chdir(dir);
|
|
104
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
105
|
+
const code = (0, cli_1.runMaintenanceCli)(["node", "traceability-maint", "report"]);
|
|
106
|
+
try {
|
|
107
|
+
expect(code).toBe(0);
|
|
108
|
+
expect(logSpy).toHaveBeenCalled();
|
|
109
|
+
const allMessages = logSpy.mock.calls.flat().join("\n");
|
|
110
|
+
expect(allMessages).toContain("No stale @story annotations found. Nothing to report.");
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
logSpy.mockRestore();
|
|
114
|
+
temp.cleanup();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
79
117
|
it("[REQ-MAINT-UPDATE] update performs replacements and exits 0", () => {
|
|
80
118
|
const temp = (0, temp_dir_helpers_1.createTempDir)("maint-cli-");
|
|
81
119
|
const dir = temp.dir;
|
|
@@ -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"));
|
|
@@ -56,26 +57,26 @@ describe("detectStaleAnnotations isolated (Story 009.0-DEV-MAINTENANCE-TOOLS)",
|
|
|
56
57
|
const filePath2 = path.join(nestedDir, "file2.ts");
|
|
57
58
|
const content1 = `
|
|
58
59
|
/**
|
|
59
|
-
* @story
|
|
60
|
+
* @story docs/stories/non-existent-story.story.md
|
|
60
61
|
*/
|
|
61
62
|
`;
|
|
62
63
|
fs.writeFileSync(filePath1, content1, "utf8");
|
|
63
64
|
const content2 = `
|
|
64
65
|
/**
|
|
65
|
-
* @story
|
|
66
|
+
* @story docs/stories/another-non-existent.story.md
|
|
66
67
|
*/
|
|
67
68
|
`;
|
|
68
69
|
fs.writeFileSync(filePath2, content2, "utf8");
|
|
69
70
|
const result = (0, detect_1.detectStaleAnnotations)(tmpDir);
|
|
70
71
|
expect(result).toHaveLength(2);
|
|
71
|
-
expect(result).toContain("
|
|
72
|
-
expect(result).toContain("
|
|
72
|
+
expect(result).toContain("docs/stories/non-existent-story.story.md");
|
|
73
|
+
expect(result).toContain("docs/stories/another-non-existent.story.md");
|
|
73
74
|
}
|
|
74
75
|
finally {
|
|
75
76
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
76
77
|
}
|
|
77
78
|
});
|
|
78
|
-
it("[REQ-MAINT-DETECT]
|
|
79
|
+
it("[REQ-MAINT-DETECT] handles permission denied errors by returning an empty result", () => {
|
|
79
80
|
const tmpDir2 = fs.mkdtempSync(path.join(os.tmpdir(), "tmp-perm-"));
|
|
80
81
|
const dir = path.join(tmpDir2, "subdir");
|
|
81
82
|
fs.mkdirSync(dir);
|
|
@@ -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"));
|
|
@@ -127,4 +127,22 @@ describe("Maintenance CLI on large workspaces (Story 009.0-DEV-MAINTENANCE-TOOLS
|
|
|
127
127
|
expect(typeof payload.report).toBe("string");
|
|
128
128
|
logSpy.mockRestore();
|
|
129
129
|
});
|
|
130
|
+
it("[REQ-MAINT-VERIFY] verify completes within a generous time budget and reports stale annotations", () => {
|
|
131
|
+
const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
|
|
132
|
+
const start = perf_hooks_1.performance.now();
|
|
133
|
+
const exitCode = (0, cli_1.runMaintenanceCli)([
|
|
134
|
+
"node",
|
|
135
|
+
"traceability-maint",
|
|
136
|
+
"verify",
|
|
137
|
+
"--root",
|
|
138
|
+
workspace.root,
|
|
139
|
+
]);
|
|
140
|
+
const durationMs = perf_hooks_1.performance.now() - start;
|
|
141
|
+
expect(exitCode).toBe(1);
|
|
142
|
+
expect(durationMs).toBeLessThan(5000);
|
|
143
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
144
|
+
const message = String(logSpy.mock.calls[0][0]);
|
|
145
|
+
expect(message).toContain("Stale or invalid traceability annotations detected under");
|
|
146
|
+
logSpy.mockRestore();
|
|
147
|
+
});
|
|
130
148
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Performance tests for require-branch-annotation on large nested-branch files.
|
|
8
|
+
*
|
|
9
|
+
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-PERFORMANCE-OPTIMIZATION REQ-NESTED-HANDLING
|
|
10
|
+
*/
|
|
11
|
+
const eslint_1 = require("eslint");
|
|
12
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
13
|
+
const require_branch_annotation_1 = __importDefault(require("../../src/rules/require-branch-annotation"));
|
|
14
|
+
/**
|
|
15
|
+
* Build a large source file containing many nested branch structures
|
|
16
|
+
* (if-statements within if-statements) to exercise the rule at scale.
|
|
17
|
+
*
|
|
18
|
+
* The generated code intentionally omits annotations so that the rule
|
|
19
|
+
* produces diagnostics for both outer and inner branches.
|
|
20
|
+
*/
|
|
21
|
+
function buildLargeNestedBranchSource(functionCount, nestingDepth) {
|
|
22
|
+
const lines = [];
|
|
23
|
+
for (let i = 0; i < functionCount; i += 1) {
|
|
24
|
+
lines.push(`function fn_${i}() {`);
|
|
25
|
+
lines.push(" let x = 0;");
|
|
26
|
+
// Create a staircase of nested if-statements.
|
|
27
|
+
for (let depth = 0; depth < nestingDepth; depth += 1) {
|
|
28
|
+
const indent = " ".repeat(depth + 1);
|
|
29
|
+
lines.push(`${indent}if (x > ${depth}) {`);
|
|
30
|
+
}
|
|
31
|
+
const innerIndent = " ".repeat(nestingDepth + 1);
|
|
32
|
+
lines.push(`${innerIndent}if (x % 2 === 0) {`);
|
|
33
|
+
lines.push(`${innerIndent} x++;`);
|
|
34
|
+
lines.push(`${innerIndent}} else {`);
|
|
35
|
+
lines.push(`${innerIndent} x--;`);
|
|
36
|
+
lines.push(`${innerIndent}}`);
|
|
37
|
+
// Close all nested if blocks.
|
|
38
|
+
for (let depth = nestingDepth - 1; depth >= 0; depth -= 1) {
|
|
39
|
+
const indent = " ".repeat(depth + 1);
|
|
40
|
+
lines.push(`${indent}}`);
|
|
41
|
+
}
|
|
42
|
+
lines.push("}");
|
|
43
|
+
}
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
describe("require-branch-annotation performance on large nested-branch files (Story 004.0-DEV-BRANCH-ANNOTATIONS)", () => {
|
|
47
|
+
const ruleName = "traceability/require-branch-annotation";
|
|
48
|
+
it("[REQ-PERFORMANCE-OPTIMIZATION] analyzes a large nested-branch file within a generous time budget", () => {
|
|
49
|
+
const linter = new eslint_1.Linter({ configType: "eslintrc" });
|
|
50
|
+
linter.defineRule(ruleName, require_branch_annotation_1.default);
|
|
51
|
+
// 200 functions each with several nested branches gives us
|
|
52
|
+
// a substantial number of branch nodes without being extreme.
|
|
53
|
+
const source = buildLargeNestedBranchSource(200, 3);
|
|
54
|
+
const start = perf_hooks_1.performance.now();
|
|
55
|
+
const messages = linter.verify(source, {
|
|
56
|
+
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
57
|
+
rules: {
|
|
58
|
+
[ruleName]: "error",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const durationMs = perf_hooks_1.performance.now() - start;
|
|
62
|
+
// Sanity check: we expect diagnostics for many branches.
|
|
63
|
+
expect(messages.length).toBeGreaterThan(0);
|
|
64
|
+
// Guardrail: keep analysis comfortably under ~5 seconds on CI hardware.
|
|
65
|
+
expect(durationMs).toBeLessThan(5000);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -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
|
|
@@ -2,4 +2,5 @@
|
|
|
2
2
|
* Tests for: docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
3
3
|
* @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
4
4
|
* @req REQ-ERROR-HANDLING - Gracefully handles plugin loading errors and missing dependencies
|
|
5
|
+
* @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-ERROR-HANDLING
|
|
5
6
|
*/
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Tests for: docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
4
4
|
* @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
5
5
|
* @req REQ-ERROR-HANDLING - Gracefully handles plugin loading errors and missing dependencies
|
|
6
|
+
* @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-ERROR-HANDLING
|
|
6
7
|
*/
|
|
7
8
|
describe("Traceability ESLint Plugin Error Handling (Story 001.0-DEV-PLUGIN-SETUP)", () => {
|
|
8
9
|
beforeEach(() => {
|
|
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
/**
|
|
37
37
|
* Tests for: docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
38
38
|
* @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
|
|
39
|
-
* @
|
|
39
|
+
* @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-PLUGIN-STRUCTURE REQ-NPM-PACKAGE
|
|
40
40
|
*/
|
|
41
41
|
const index_1 = __importStar(require("../src/index"));
|
|
42
42
|
describe("Traceability ESLint Plugin (Story 001.0-DEV-PLUGIN-SETUP)", () => {
|
|
@@ -48,4 +48,15 @@ describe("Traceability ESLint Plugin (Story 001.0-DEV-PLUGIN-SETUP)", () => {
|
|
|
48
48
|
expect(index_1.default.rules).toBe(index_1.rules);
|
|
49
49
|
expect(index_1.default.configs).toBe(index_1.configs);
|
|
50
50
|
});
|
|
51
|
+
it("[REQ-PLUGIN-STRUCTURE][REQ-NPM-PACKAGE] plugin exposes meta with name, namespace, and version", () => {
|
|
52
|
+
// Arrange
|
|
53
|
+
const pkg = require("../package.json");
|
|
54
|
+
// Act
|
|
55
|
+
const meta = index_1.default.meta;
|
|
56
|
+
// Assert
|
|
57
|
+
expect(meta).toBeDefined();
|
|
58
|
+
expect(meta.name).toBe(pkg.name);
|
|
59
|
+
expect(meta.version).toBe(pkg.version);
|
|
60
|
+
expect(meta.namespace).toBe("traceability");
|
|
61
|
+
});
|
|
51
62
|
});
|
|
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
8
8
|
* @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
9
9
|
* @req REQ-AUTOFIX-MISSING - Verify ESLint --fix automatically adds missing @story annotations to functions
|
|
10
10
|
* @req REQ-AUTOFIX-FORMAT - Verify ESLint --fix corrects simple annotation format issues for @story annotations
|
|
11
|
+
* @supports docs/stories/008.0-DEV-AUTO-FIX.story.md REQ-AUTOFIX-MISSING REQ-AUTOFIX-FORMAT
|
|
11
12
|
*/
|
|
12
13
|
const eslint_1 = require("eslint");
|
|
13
14
|
const require_story_annotation_1 = __importDefault(require("../../src/rules/require-story-annotation"));
|
|
@@ -177,6 +178,21 @@ describe("Auto-fix behavior (Story 008.0-DEV-AUTO-FIX)", () => {
|
|
|
177
178
|
},
|
|
178
179
|
],
|
|
179
180
|
},
|
|
181
|
+
{
|
|
182
|
+
name: "[REQ-AUTOFIX-SELECTIVE] does not apply suffix fix when autoFix is false",
|
|
183
|
+
code: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story`,
|
|
184
|
+
output: null,
|
|
185
|
+
options: [
|
|
186
|
+
{
|
|
187
|
+
autoFix: false,
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
errors: [
|
|
191
|
+
{
|
|
192
|
+
messageId: "invalidStoryFormat",
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
},
|
|
180
196
|
],
|
|
181
197
|
});
|
|
182
198
|
});
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
/**
|
|
7
7
|
* Tests for: docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
8
8
|
* @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
|
|
9
|
+
* @supports docs/stories/007.0-DEV-ERROR-REPORTING.story.md REQ-ERROR-SPECIFIC REQ-ERROR-SUGGESTION REQ-ERROR-CONTEXT REQ-ERROR-LOCATION
|
|
9
10
|
* @req REQ-ERROR-SPECIFIC - Specific details about what annotation is missing or invalid
|
|
10
11
|
* @req REQ-ERROR-SUGGESTION - Suggest concrete steps to fix the issue
|
|
11
12
|
* @req REQ-ERROR-CONTEXT - Include relevant context in error messages
|
|
@@ -33,6 +33,14 @@ describe("prefer-implements-annotation rule (Story 010.3-DEV-MIGRATE-TO-SUPPORTS
|
|
|
33
33
|
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with @supports only is ignored",
|
|
34
34
|
code: `/**\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction alreadyImplements() {}`,
|
|
35
35
|
},
|
|
36
|
+
{
|
|
37
|
+
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with @story and @supports but no @req is ignored",
|
|
38
|
+
code: `/**\n * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction storyAndSupportsNoReq() {}`,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "[REQ-BACKWARD-COMP-VALIDATION] comment with @req and @supports but no @story is ignored",
|
|
42
|
+
code: `/**\n * @req REQ-ANNOTATION-REQUIRED\n * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED\n */\nfunction reqAndSupportsNoStory() {}`,
|
|
43
|
+
},
|
|
36
44
|
],
|
|
37
45
|
invalid: [
|
|
38
46
|
{
|