@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.
Files changed (80) hide show
  1. package/.jscpd.json +16 -0
  2. package/README.md +104 -0
  3. package/biome.json +37 -0
  4. package/docs/rules/no-loop-over-enums.md +29 -0
  5. package/docs/rules/suggest-exports.md +15 -0
  6. package/docs/rules/suggest-imports.md +15 -0
  7. package/docs/rules/suggest-members.md +15 -0
  8. package/docs/rules/suggest-missing-names.md +13 -0
  9. package/docs/rules/suggest-module-paths.md +15 -0
  10. package/eslint.config.mts +265 -0
  11. package/eslint.effect-ts-check.config.mjs +220 -0
  12. package/linter.config.json +32 -0
  13. package/package.json +79 -0
  14. package/scripts/checkFunctionalCore.ts +488 -0
  15. package/src/core/axioms.ts +23 -0
  16. package/src/core/effects/index.ts +39 -0
  17. package/src/core/formatting/messages.ts +315 -0
  18. package/src/core/index.ts +71 -0
  19. package/src/core/plugin-meta.ts +12 -0
  20. package/src/core/similarity/composite.ts +69 -0
  21. package/src/core/similarity/helpers.ts +34 -0
  22. package/src/core/similarity/index.ts +10 -0
  23. package/src/core/similarity/jaro-winkler.ts +25 -0
  24. package/src/core/similarity/jaro.ts +99 -0
  25. package/src/core/suggestion/engine.ts +35 -0
  26. package/src/core/types/domain.ts +28 -0
  27. package/src/core/types/eslint-nodes.ts +62 -0
  28. package/src/core/types/validation.ts +185 -0
  29. package/src/core/validation/candidates.ts +29 -0
  30. package/src/core/validation/module-path-utils.ts +33 -0
  31. package/src/core/validation/node-builtin-exports.ts +46 -0
  32. package/src/core/validators/index.ts +14 -0
  33. package/src/core/validators/node-predicates.ts +92 -0
  34. package/src/index.ts +56 -0
  35. package/src/rules/index.ts +25 -0
  36. package/src/rules/suggest-exports/index.ts +121 -0
  37. package/src/rules/suggest-imports/index.ts +25 -0
  38. package/src/rules/suggest-members/index.ts +154 -0
  39. package/src/rules/suggest-missing-names/index.ts +116 -0
  40. package/src/rules/suggest-module-paths/index.ts +101 -0
  41. package/src/shell/effects/errors.ts +80 -0
  42. package/src/shell/services/filesystem.ts +136 -0
  43. package/src/shell/services/typescript-compiler-effects.ts +85 -0
  44. package/src/shell/services/typescript-compiler-helpers.ts +89 -0
  45. package/src/shell/services/typescript-compiler-module-effects.ts +296 -0
  46. package/src/shell/services/typescript-compiler.ts +112 -0
  47. package/src/shell/services/typescript-effect-utils.ts +123 -0
  48. package/src/shell/shared/effect-utils.ts +18 -0
  49. package/src/shell/shared/import-validation-base.ts +181 -0
  50. package/src/shell/shared/import-validation-rule-factory.ts +116 -0
  51. package/src/shell/shared/validation-helpers.ts +94 -0
  52. package/src/shell/shared/validation-runner.ts +45 -0
  53. package/src/shell/validation/export-validation-effect.ts +54 -0
  54. package/src/shell/validation/import-validation-effect.ts +49 -0
  55. package/src/shell/validation/local-export-validation-effect.ts +10 -0
  56. package/src/shell/validation/member-validation-effect.ts +307 -0
  57. package/src/shell/validation/missing-name-validation-base.ts +153 -0
  58. package/src/shell/validation/missing-name-validation-effect.ts +10 -0
  59. package/src/shell/validation/missing-name-validators.ts +52 -0
  60. package/src/shell/validation/module-path-index.ts +144 -0
  61. package/src/shell/validation/module-validation-effect.ts +220 -0
  62. package/src/shell/validation/suggestion-signatures.ts +63 -0
  63. package/src/shell/validation/validation-base-effect.ts +165 -0
  64. package/tests/core/message-formatting.test.ts +121 -0
  65. package/tests/core/suggestion-engine.test.ts +34 -0
  66. package/tests/fixtures/consumer.ts +1 -0
  67. package/tests/fixtures/module-paths/alpha.ts +1 -0
  68. package/tests/fixtures/module-paths/beta.ts +1 -0
  69. package/tests/fixtures/modules/exports.ts +9 -0
  70. package/tests/plugin-signature.test.ts +69 -0
  71. package/tests/rules/suggest-imports-exports.test.ts +91 -0
  72. package/tests/rules/suggest-members.test.ts +98 -0
  73. package/tests/rules/suggest-missing-names.test.ts +35 -0
  74. package/tests/rules/suggest-module-paths.test.ts +54 -0
  75. package/tests/utils/rule-tester.ts +41 -0
  76. package/tsconfig.build.json +13 -0
  77. package/tsconfig.json +22 -0
  78. package/types/eslint-plugins.d.ts +15 -0
  79. package/vite.config.ts +33 -0
  80. 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
+ })
@@ -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
+ })