@prover-coder-ai/eslint-plugin-suggest-members 0.0.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/.jscpd.json +16 -0
- package/README.md +104 -0
- package/biome.json +37 -0
- package/docs/rules/no-loop-over-enums.md +29 -0
- package/docs/rules/suggest-exports.md +15 -0
- package/docs/rules/suggest-imports.md +15 -0
- package/docs/rules/suggest-members.md +15 -0
- package/docs/rules/suggest-missing-names.md +13 -0
- package/docs/rules/suggest-module-paths.md +15 -0
- package/eslint.config.mts +265 -0
- package/eslint.effect-ts-check.config.mjs +220 -0
- package/linter.config.json +32 -0
- package/package.json +79 -0
- package/scripts/checkFunctionalCore.ts +488 -0
- package/src/core/axioms.ts +23 -0
- package/src/core/effects/index.ts +39 -0
- package/src/core/formatting/messages.ts +315 -0
- package/src/core/index.ts +71 -0
- package/src/core/plugin-meta.ts +12 -0
- package/src/core/similarity/composite.ts +69 -0
- package/src/core/similarity/helpers.ts +34 -0
- package/src/core/similarity/index.ts +10 -0
- package/src/core/similarity/jaro-winkler.ts +25 -0
- package/src/core/similarity/jaro.ts +99 -0
- package/src/core/suggestion/engine.ts +35 -0
- package/src/core/types/domain.ts +28 -0
- package/src/core/types/eslint-nodes.ts +62 -0
- package/src/core/types/validation.ts +185 -0
- package/src/core/validation/candidates.ts +29 -0
- package/src/core/validation/module-path-utils.ts +33 -0
- package/src/core/validation/node-builtin-exports.ts +46 -0
- package/src/core/validators/index.ts +14 -0
- package/src/core/validators/node-predicates.ts +92 -0
- package/src/index.ts +56 -0
- package/src/rules/index.ts +25 -0
- package/src/rules/suggest-exports/index.ts +121 -0
- package/src/rules/suggest-imports/index.ts +25 -0
- package/src/rules/suggest-members/index.ts +154 -0
- package/src/rules/suggest-missing-names/index.ts +116 -0
- package/src/rules/suggest-module-paths/index.ts +101 -0
- package/src/shell/effects/errors.ts +80 -0
- package/src/shell/services/filesystem.ts +136 -0
- package/src/shell/services/typescript-compiler-effects.ts +85 -0
- package/src/shell/services/typescript-compiler-helpers.ts +89 -0
- package/src/shell/services/typescript-compiler-module-effects.ts +296 -0
- package/src/shell/services/typescript-compiler.ts +112 -0
- package/src/shell/services/typescript-effect-utils.ts +123 -0
- package/src/shell/shared/effect-utils.ts +18 -0
- package/src/shell/shared/import-validation-base.ts +181 -0
- package/src/shell/shared/import-validation-rule-factory.ts +116 -0
- package/src/shell/shared/validation-helpers.ts +94 -0
- package/src/shell/shared/validation-runner.ts +45 -0
- package/src/shell/validation/export-validation-effect.ts +54 -0
- package/src/shell/validation/import-validation-effect.ts +49 -0
- package/src/shell/validation/local-export-validation-effect.ts +10 -0
- package/src/shell/validation/member-validation-effect.ts +307 -0
- package/src/shell/validation/missing-name-validation-base.ts +153 -0
- package/src/shell/validation/missing-name-validation-effect.ts +10 -0
- package/src/shell/validation/missing-name-validators.ts +52 -0
- package/src/shell/validation/module-path-index.ts +144 -0
- package/src/shell/validation/module-validation-effect.ts +220 -0
- package/src/shell/validation/suggestion-signatures.ts +63 -0
- package/src/shell/validation/validation-base-effect.ts +165 -0
- package/tests/core/message-formatting.test.ts +121 -0
- package/tests/core/suggestion-engine.test.ts +34 -0
- package/tests/fixtures/consumer.ts +1 -0
- package/tests/fixtures/module-paths/alpha.ts +1 -0
- package/tests/fixtures/module-paths/beta.ts +1 -0
- package/tests/fixtures/modules/exports.ts +9 -0
- package/tests/plugin-signature.test.ts +69 -0
- package/tests/rules/suggest-imports-exports.test.ts +91 -0
- package/tests/rules/suggest-members.test.ts +98 -0
- package/tests/rules/suggest-missing-names.test.ts +35 -0
- package/tests/rules/suggest-module-paths.test.ts +54 -0
- package/tests/utils/rule-tester.ts +41 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +22 -0
- package/types/eslint-plugins.d.ts +15 -0
- package/vite.config.ts +33 -0
- package/vitest.config.ts +87 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { suggestExportsRule } from "../../src/rules/suggest-exports/index.js"
|
|
2
|
+
import { suggestImportsRule } from "../../src/rules/suggest-imports/index.js"
|
|
3
|
+
import { createRuleTester, resolveFixtureImportPath, resolveFixturePath } from "../utils/rule-tester.js"
|
|
4
|
+
|
|
5
|
+
const ruleTester = createRuleTester()
|
|
6
|
+
const filename = resolveFixturePath("consumer.ts")
|
|
7
|
+
|
|
8
|
+
const validImportsCode = `
|
|
9
|
+
import { useState, useEffect } from "./modules/exports"
|
|
10
|
+
useState()
|
|
11
|
+
useEffect()
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
const invalidImportCode = `
|
|
15
|
+
import { useStae } from "./modules/exports"
|
|
16
|
+
useStae()
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
const exportsModulePath = resolveFixtureImportPath("modules/exports.ts")
|
|
20
|
+
|
|
21
|
+
const invalidImportMessage =
|
|
22
|
+
`Export 'useStae' does not exist on type 'typeof import("${exportsModulePath}")'. Did you mean:\n` +
|
|
23
|
+
" - useState(): number\n" +
|
|
24
|
+
" - useMemo(): number\n" +
|
|
25
|
+
" - useEffect(): void\n" +
|
|
26
|
+
" - useCallback(): string"
|
|
27
|
+
|
|
28
|
+
ruleTester.run("suggest-imports", suggestImportsRule, {
|
|
29
|
+
valid: [
|
|
30
|
+
{
|
|
31
|
+
filename,
|
|
32
|
+
code: validImportsCode
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
invalid: [
|
|
36
|
+
{
|
|
37
|
+
filename,
|
|
38
|
+
code: invalidImportCode,
|
|
39
|
+
errors: [{ messageId: "suggestImports", data: { message: invalidImportMessage } }]
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const validExportModuleCode = `
|
|
45
|
+
export { useState } from "./modules/exports"
|
|
46
|
+
`
|
|
47
|
+
|
|
48
|
+
const validExportLocalCode = `
|
|
49
|
+
const formatGreeting = () => "ok"
|
|
50
|
+
export { formatGreeting }
|
|
51
|
+
`
|
|
52
|
+
|
|
53
|
+
const invalidExportModuleCode = `
|
|
54
|
+
export { useStae } from "./modules/exports"
|
|
55
|
+
`
|
|
56
|
+
|
|
57
|
+
const invalidExportLocalCode = `
|
|
58
|
+
const formatGreeting = () => "ok"
|
|
59
|
+
export { formatGree1ting }
|
|
60
|
+
`
|
|
61
|
+
|
|
62
|
+
const invalidExportModuleMessage = invalidImportMessage
|
|
63
|
+
|
|
64
|
+
const invalidExportLocalMessage = "Cannot find name 'formatGree1ting'. Did you mean:\n" +
|
|
65
|
+
" - formatGreeting(): string\n" +
|
|
66
|
+
" - FormData: typeof undici.FormData"
|
|
67
|
+
|
|
68
|
+
ruleTester.run("suggest-exports", suggestExportsRule, {
|
|
69
|
+
valid: [
|
|
70
|
+
{
|
|
71
|
+
filename,
|
|
72
|
+
code: validExportModuleCode
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
filename,
|
|
76
|
+
code: validExportLocalCode
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
invalid: [
|
|
80
|
+
{
|
|
81
|
+
filename,
|
|
82
|
+
code: invalidExportModuleCode,
|
|
83
|
+
errors: [{ messageId: "suggestExports", data: { message: invalidExportModuleMessage } }]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
filename,
|
|
87
|
+
code: invalidExportLocalCode,
|
|
88
|
+
errors: [{ messageId: "suggestExports", data: { message: invalidExportLocalMessage } }]
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { suggestMembersRule } from "../../src/rules/suggest-members/index.js"
|
|
2
|
+
import { createRuleTester, resolveFixturePath } from "../utils/rule-tester.js"
|
|
3
|
+
|
|
4
|
+
const ruleTester = createRuleTester()
|
|
5
|
+
const filename = resolveFixturePath("consumer.ts")
|
|
6
|
+
const memberAccessMessage =
|
|
7
|
+
"Property 'nmae' does not exist on type '{ name: string; count: number; }'. Did you mean:\n" +
|
|
8
|
+
" - name: string"
|
|
9
|
+
const memberOptionalAccessMessage = "Property 'nmae' does not exist on type '{ name: string; }'. Did you mean:\n" +
|
|
10
|
+
" - name: string"
|
|
11
|
+
const memberPatternMessage = "Property 'na1me' does not exist on type 'Named'. Did you mean:\n" +
|
|
12
|
+
" - name: string"
|
|
13
|
+
const memberLiteralMessage = "Property 'kin1d' does not exist on type 'Named'. Did you mean:\n" +
|
|
14
|
+
" - kind: \"named\""
|
|
15
|
+
|
|
16
|
+
ruleTester.run("suggest-members", suggestMembersRule, {
|
|
17
|
+
valid: [
|
|
18
|
+
{
|
|
19
|
+
filename,
|
|
20
|
+
code: `
|
|
21
|
+
const obj = { name: "ok", count: 1 }
|
|
22
|
+
obj.name
|
|
23
|
+
obj.count
|
|
24
|
+
`
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
filename,
|
|
28
|
+
code: `
|
|
29
|
+
const obj = { name: "ok" }
|
|
30
|
+
obj["name"]
|
|
31
|
+
`
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
filename,
|
|
35
|
+
code: `
|
|
36
|
+
const obj = { name: "ok" }
|
|
37
|
+
obj?.name
|
|
38
|
+
`
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
invalid: [
|
|
42
|
+
{
|
|
43
|
+
filename,
|
|
44
|
+
code: `
|
|
45
|
+
const obj = { name: "ok", count: 1 }
|
|
46
|
+
obj.nmae
|
|
47
|
+
`,
|
|
48
|
+
errors: [{
|
|
49
|
+
messageId: "suggestMembers",
|
|
50
|
+
data: {
|
|
51
|
+
message: memberAccessMessage
|
|
52
|
+
}
|
|
53
|
+
}]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
filename,
|
|
57
|
+
code: `
|
|
58
|
+
const obj = { name: "ok" }
|
|
59
|
+
obj?.nmae
|
|
60
|
+
`,
|
|
61
|
+
errors: [{
|
|
62
|
+
messageId: "suggestMembers",
|
|
63
|
+
data: {
|
|
64
|
+
message: memberOptionalAccessMessage
|
|
65
|
+
}
|
|
66
|
+
}]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
filename,
|
|
70
|
+
code: `
|
|
71
|
+
type Named = { readonly name: string }
|
|
72
|
+
const variant: Named = { name: "ok" }
|
|
73
|
+
const { na1me } = variant
|
|
74
|
+
void na1me
|
|
75
|
+
`,
|
|
76
|
+
errors: [{
|
|
77
|
+
messageId: "suggestMembers",
|
|
78
|
+
data: {
|
|
79
|
+
message: memberPatternMessage
|
|
80
|
+
}
|
|
81
|
+
}]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
filename,
|
|
85
|
+
code: `
|
|
86
|
+
type Named = { readonly kind: "named"; readonly name: string }
|
|
87
|
+
const variant: Named = { kin1d: "named", name: "ok" }
|
|
88
|
+
void variant
|
|
89
|
+
`,
|
|
90
|
+
errors: [{
|
|
91
|
+
messageId: "suggestMembers",
|
|
92
|
+
data: {
|
|
93
|
+
message: memberLiteralMessage
|
|
94
|
+
}
|
|
95
|
+
}]
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { suggestMissingNamesRule } from "../../src/rules/suggest-missing-names/index.js"
|
|
2
|
+
import { createRuleTester, resolveFixturePath } from "../utils/rule-tester.js"
|
|
3
|
+
|
|
4
|
+
const ruleTester = createRuleTester()
|
|
5
|
+
const filename = resolveFixturePath("consumer.ts")
|
|
6
|
+
const missingNameMessage = "Cannot find name 'formatGreeting'. Did you mean:\n" +
|
|
7
|
+
" - formatGree1ting(name: string): string\n" +
|
|
8
|
+
" - FormData: typeof undici.FormData"
|
|
9
|
+
|
|
10
|
+
ruleTester.run("suggest-missing-names", suggestMissingNamesRule, {
|
|
11
|
+
valid: [
|
|
12
|
+
{
|
|
13
|
+
filename,
|
|
14
|
+
code: `
|
|
15
|
+
const formatGreeting = (name: string) => name
|
|
16
|
+
formatGreeting("ok")
|
|
17
|
+
`
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
invalid: [
|
|
21
|
+
{
|
|
22
|
+
filename,
|
|
23
|
+
code: `
|
|
24
|
+
const formatGree1ting = (name: string) => name
|
|
25
|
+
formatGreeting("ok")
|
|
26
|
+
`,
|
|
27
|
+
errors: [{
|
|
28
|
+
messageId: "suggestMissingNames",
|
|
29
|
+
data: {
|
|
30
|
+
message: missingNameMessage
|
|
31
|
+
}
|
|
32
|
+
}]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { suggestModulePathsRule } from "../../src/rules/suggest-module-paths/index.js"
|
|
2
|
+
import { createRuleTester, resolveFixturePath } from "../utils/rule-tester.js"
|
|
3
|
+
|
|
4
|
+
const ruleTester = createRuleTester()
|
|
5
|
+
const filename = resolveFixturePath("consumer.ts")
|
|
6
|
+
const localModulePathMessage = "Cannot find module \"./module-paths/alhpa\". Did you mean:\n" +
|
|
7
|
+
" - ./module-paths/alpha\n" +
|
|
8
|
+
" - ./module-paths/beta"
|
|
9
|
+
const externalModulePathMessage =
|
|
10
|
+
"Cannot find module 'eff1ect' or its corresponding type declarations. Did you mean:\n" +
|
|
11
|
+
" - effect\n" +
|
|
12
|
+
" - @effect/cli\n" +
|
|
13
|
+
" - @effect/rpc\n" +
|
|
14
|
+
" - @effect/sql"
|
|
15
|
+
|
|
16
|
+
ruleTester.run("suggest-module-paths", suggestModulePathsRule, {
|
|
17
|
+
valid: [
|
|
18
|
+
{
|
|
19
|
+
filename,
|
|
20
|
+
code: `
|
|
21
|
+
import { alpha } from "./module-paths/alpha"
|
|
22
|
+
alpha
|
|
23
|
+
`
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
invalid: [
|
|
27
|
+
{
|
|
28
|
+
filename,
|
|
29
|
+
code: `
|
|
30
|
+
import { alpha } from "./module-paths/alhpa"
|
|
31
|
+
alpha
|
|
32
|
+
`,
|
|
33
|
+
errors: [{
|
|
34
|
+
messageId: "suggestModulePaths",
|
|
35
|
+
data: {
|
|
36
|
+
message: localModulePathMessage
|
|
37
|
+
}
|
|
38
|
+
}]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
filename,
|
|
42
|
+
code: `
|
|
43
|
+
import { pipe } from "eff1ect"
|
|
44
|
+
pipe
|
|
45
|
+
`,
|
|
46
|
+
errors: [{
|
|
47
|
+
messageId: "suggestModulePaths",
|
|
48
|
+
data: {
|
|
49
|
+
message: externalModulePathMessage
|
|
50
|
+
}
|
|
51
|
+
}]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { fileURLToPath } from "node:url"
|
|
3
|
+
|
|
4
|
+
import * as tsParser from "@typescript-eslint/parser"
|
|
5
|
+
import { RuleTester } from "@typescript-eslint/rule-tester"
|
|
6
|
+
import * as vitest from "vitest"
|
|
7
|
+
|
|
8
|
+
RuleTester.afterAll = vitest.afterAll
|
|
9
|
+
RuleTester.it = vitest.it
|
|
10
|
+
RuleTester.itOnly = vitest.it.only
|
|
11
|
+
RuleTester.describe = vitest.describe
|
|
12
|
+
|
|
13
|
+
export const tsconfigRootDir = path.join(
|
|
14
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
15
|
+
"..",
|
|
16
|
+
".."
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
export const createRuleTester = () =>
|
|
20
|
+
new RuleTester({
|
|
21
|
+
languageOptions: {
|
|
22
|
+
parser: tsParser,
|
|
23
|
+
parserOptions: {
|
|
24
|
+
projectService: {
|
|
25
|
+
allowDefaultProject: ["*.ts*"],
|
|
26
|
+
defaultProject: "tsconfig.json"
|
|
27
|
+
},
|
|
28
|
+
tsconfigRootDir
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export const resolveFixturePath = (relativePath: string): string =>
|
|
34
|
+
path.join(tsconfigRootDir, "tests", "fixtures", relativePath)
|
|
35
|
+
|
|
36
|
+
const normalizePath = (value: string): string => value.replaceAll("\\", "/")
|
|
37
|
+
|
|
38
|
+
const stripExtension = (value: string): string => value.replace(/\.[^/.]+$/, "")
|
|
39
|
+
|
|
40
|
+
export const resolveFixtureImportPath = (relativePath: string): string =>
|
|
41
|
+
stripExtension(normalizePath(resolveFixturePath(relativePath)))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "lib",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"types": ["node"]
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*.ts"],
|
|
12
|
+
"exclude": ["tests", "dist", "node_modules"]
|
|
13
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": ".",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"types": ["vitest"],
|
|
7
|
+
"baseUrl": ".",
|
|
8
|
+
"paths": {
|
|
9
|
+
"@/*": ["src/*"]
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"include": [
|
|
13
|
+
"src/**/*",
|
|
14
|
+
"tests/**/*",
|
|
15
|
+
"scripts/**/*",
|
|
16
|
+
"types/**/*.d.ts",
|
|
17
|
+
"vite.config.ts",
|
|
18
|
+
"vitest.config.ts",
|
|
19
|
+
"eslint.config.mts"
|
|
20
|
+
],
|
|
21
|
+
"exclude": ["dist", "node_modules"]
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare module "eslint-plugin-sort-destructure-keys" {
|
|
2
|
+
import type { ESLint } from "eslint";
|
|
3
|
+
|
|
4
|
+
const plugin: ESLint.Plugin;
|
|
5
|
+
export default plugin;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
declare module "@eslint-community/eslint-plugin-eslint-comments/configs" {
|
|
9
|
+
import type { Linter } from "eslint";
|
|
10
|
+
|
|
11
|
+
const configs: {
|
|
12
|
+
readonly recommended: Linter.Config;
|
|
13
|
+
};
|
|
14
|
+
export default configs;
|
|
15
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineConfig } from "vite"
|
|
2
|
+
import tsconfigPaths from "vite-tsconfig-paths"
|
|
3
|
+
|
|
4
|
+
const baseUrl = new URL(".", import.meta.url)
|
|
5
|
+
const normalizePath = (value: string): string =>
|
|
6
|
+
value.startsWith("/") && value.includes(":") ? value.slice(1) : value
|
|
7
|
+
const resolvePath = (relative: string): string =>
|
|
8
|
+
normalizePath(decodeURIComponent(new URL(relative, baseUrl).pathname))
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
plugins: [tsconfigPaths()],
|
|
12
|
+
publicDir: false,
|
|
13
|
+
resolve: {
|
|
14
|
+
alias: {
|
|
15
|
+
"@": resolvePath("src")
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
build: {
|
|
19
|
+
target: "node20",
|
|
20
|
+
outDir: "dist",
|
|
21
|
+
sourcemap: true,
|
|
22
|
+
ssr: "src/rules/main.ts",
|
|
23
|
+
rollupOptions: {
|
|
24
|
+
output: {
|
|
25
|
+
format: "es",
|
|
26
|
+
entryFileNames: "main.js"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
ssr: {
|
|
31
|
+
target: "node"
|
|
32
|
+
}
|
|
33
|
+
})
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// CHANGE: Migrate from Jest to Vitest with mathematical equivalence
|
|
2
|
+
// WHY: Faster execution, native ESM, Effect integration via @effect/vitest
|
|
3
|
+
// QUOTE(ТЗ): "Проект использует Effect + функциональную парадигму"
|
|
4
|
+
// REF: Migration from jest.config.mjs
|
|
5
|
+
// PURITY: SHELL (configuration only)
|
|
6
|
+
// INVARIANT: ∀ test: behavior_jest ≡ behavior_vitest
|
|
7
|
+
// EFFECT: Effect<TestReport, never, TestEnvironment>
|
|
8
|
+
// COMPLEXITY: O(n) test execution where n = |test_files|
|
|
9
|
+
|
|
10
|
+
import tsconfigPaths from "vite-tsconfig-paths"
|
|
11
|
+
import { defineConfig } from "vitest/config"
|
|
12
|
+
|
|
13
|
+
const baseUrl = new URL(".", import.meta.url)
|
|
14
|
+
const normalizePath = (value: string): string =>
|
|
15
|
+
value.startsWith("/") && value.includes(":") ? value.slice(1) : value
|
|
16
|
+
const resolvePath = (relative: string): string =>
|
|
17
|
+
normalizePath(decodeURIComponent(new URL(relative, baseUrl).pathname))
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
plugins: [tsconfigPaths()], // Resolves @/* paths from tsconfig
|
|
21
|
+
test: {
|
|
22
|
+
// CHANGE: Native ESM support without experimental flags
|
|
23
|
+
// WHY: Vitest designed for ESM, no need for --experimental-vm-modules
|
|
24
|
+
// INVARIANT: Deterministic test execution without side effects
|
|
25
|
+
globals: false, // IMPORTANT: Use explicit imports for type safety
|
|
26
|
+
environment: "node",
|
|
27
|
+
testTimeout: 15000,
|
|
28
|
+
|
|
29
|
+
// CHANGE: Match Jest's test file patterns
|
|
30
|
+
// INVARIANT: Same test discovery as Jest
|
|
31
|
+
include: ["tests/**/*.{test,spec}.ts"],
|
|
32
|
+
exclude: ["node_modules", "dist", "dist-test"],
|
|
33
|
+
|
|
34
|
+
// CHANGE: Coverage with 100% threshold for CORE (same as Jest)
|
|
35
|
+
// WHY: CORE must maintain mathematical guarantees via complete coverage
|
|
36
|
+
// INVARIANT: coverage_vitest ≥ coverage_jest ∧ ∀ f ∈ CORE: coverage(f) = 100%
|
|
37
|
+
coverage: {
|
|
38
|
+
provider: "v8", // Faster than babel (istanbul), native V8 coverage
|
|
39
|
+
reporter: ["text", "json", "html"],
|
|
40
|
+
include: ["src/**/*.ts"],
|
|
41
|
+
exclude: [
|
|
42
|
+
"src/**/*.test.ts",
|
|
43
|
+
"src/**/*.spec.ts",
|
|
44
|
+
"src/**/__tests__/**",
|
|
45
|
+
"scripts/**/*.ts"
|
|
46
|
+
],
|
|
47
|
+
// CHANGE: Maintain exact same thresholds as Jest
|
|
48
|
+
// WHY: Enforce 100% coverage for CORE, 10% minimum for SHELL
|
|
49
|
+
// INVARIANT: ∀ f ∈ src/core/**/*.ts: all_metrics(f) = 100%
|
|
50
|
+
// NOTE: Vitest v8 provider collects coverage for all matched files by default
|
|
51
|
+
thresholds: {
|
|
52
|
+
"src/core/**/*.ts": {
|
|
53
|
+
branches: 100,
|
|
54
|
+
functions: 100,
|
|
55
|
+
lines: 100,
|
|
56
|
+
statements: 100
|
|
57
|
+
},
|
|
58
|
+
global: {
|
|
59
|
+
branches: 10,
|
|
60
|
+
functions: 10,
|
|
61
|
+
lines: 10,
|
|
62
|
+
statements: 10
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// CHANGE: Faster test execution via thread pooling
|
|
68
|
+
// WHY: Vitest uses worker threads by default (faster than Jest's processes)
|
|
69
|
+
// COMPLEXITY: O(n/k) where n = tests, k = worker_count
|
|
70
|
+
// NOTE: Vitest runs tests in parallel by default, no additional config needed
|
|
71
|
+
|
|
72
|
+
// CHANGE: Clear mocks between tests (Jest equivalence)
|
|
73
|
+
// WHY: Prevent test contamination, ensure test independence
|
|
74
|
+
// INVARIANT: ∀ test_i, test_j: independent(test_i, test_j) ⇒ no_shared_state
|
|
75
|
+
clearMocks: true,
|
|
76
|
+
mockReset: true,
|
|
77
|
+
restoreMocks: true
|
|
78
|
+
// CHANGE: Disable globals to enforce explicit imports
|
|
79
|
+
// WHY: Type safety, explicit dependencies, functional purity
|
|
80
|
+
// NOTE: Tests must import { describe, it, expect } from "vitest"
|
|
81
|
+
},
|
|
82
|
+
resolve: {
|
|
83
|
+
alias: {
|
|
84
|
+
"@": resolvePath("src")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
})
|