@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,181 @@
1
+ // CHANGE: shared import/export validation base
2
+ // WHY: eliminate duplication between suggest-imports and suggest-exports
3
+ // QUOTE(TZ): n/a
4
+ // REF: AGENTS.md SHELL
5
+ // SOURCE: n/a
6
+ // PURITY: SHELL
7
+ // EFFECT: Effect-based validation
8
+ // INVARIANT: validation is deterministic for given inputs
9
+ // COMPLEXITY: O(1)/O(n)
10
+ import type { TSESTree } from "@typescript-eslint/utils"
11
+ import { ESLintUtils } from "@typescript-eslint/utils"
12
+ import type { RuleContext } from "@typescript-eslint/utils/ts-eslint"
13
+ import type { Layer } from "effect"
14
+ import { Effect, Exit } from "effect"
15
+
16
+ import type { FilesystemError, TypeScriptServiceError } from "../effects/errors.js"
17
+ import type { FilesystemServiceTag } from "../services/filesystem.js"
18
+ import type { TypeScriptCompilerServiceTag } from "../services/typescript-compiler.js"
19
+ import { makeTypeScriptCompilerServiceLayer } from "../services/typescript-compiler.js"
20
+ import { isValidImportIdentifier, tryValidationWithFallback } from "./validation-helpers.js"
21
+
22
+ export type ModuleSpecifier = TSESTree.ImportSpecifier | TSESTree.ExportSpecifier
23
+
24
+ export interface TypeScriptServiceLayerContext {
25
+ readonly layer: Layer.Layer<TypeScriptCompilerServiceTag>
26
+ readonly hasTypeScript: boolean
27
+ }
28
+
29
+ export interface ImportValidationConfig<TResult> {
30
+ readonly validateSpecifier: (
31
+ specifier: ModuleSpecifier,
32
+ importName: string,
33
+ modulePath: string,
34
+ containingFilePath: string
35
+ ) => Effect.Effect<TResult, TypeScriptServiceError, TypeScriptCompilerServiceTag>
36
+ readonly fallbackValidationEffect?: (
37
+ importName: string,
38
+ modulePath: string,
39
+ contextFilePath: string
40
+ ) => Effect.Effect<TResult, FilesystemError, FilesystemServiceTag>
41
+ readonly formatMessage: (result: TResult) => string
42
+ readonly messageId: string
43
+ readonly skipWhenTypeScriptAvailable?: boolean
44
+ }
45
+
46
+ interface ValidateModuleSpecifierParams<TResult> {
47
+ readonly importedNode: TSESTree.Node | undefined
48
+ readonly specifier: ModuleSpecifier
49
+ readonly modulePath: string
50
+ readonly config: ImportValidationConfig<TResult>
51
+ readonly context: RuleContext<string, ReadonlyArray<string>>
52
+ readonly tsService: TypeScriptServiceLayerContext
53
+ }
54
+
55
+ const validateModuleSpecifier = <TResult>({
56
+ config,
57
+ context,
58
+ importedNode,
59
+ modulePath,
60
+ specifier,
61
+ tsService
62
+ }: ValidateModuleSpecifierParams<TResult>): void => {
63
+ if (!importedNode) return
64
+ if (!isValidImportIdentifier(importedNode)) return
65
+
66
+ const imported = importedNode
67
+
68
+ executeImportValidation({
69
+ imported,
70
+ specifier,
71
+ modulePath,
72
+ config,
73
+ context,
74
+ containingFilePath: context.filename || "",
75
+ tsService
76
+ })
77
+ }
78
+
79
+ const makeSpecifierValidator = <TSpecifier extends ModuleSpecifier>(
80
+ getImportedNode: (specifier: TSpecifier) => TSESTree.Node | undefined
81
+ ) =>
82
+ <TResult>(
83
+ specifier: TSpecifier,
84
+ modulePath: string,
85
+ config: ImportValidationConfig<TResult>,
86
+ context: RuleContext<string, ReadonlyArray<string>>,
87
+ tsService: TypeScriptServiceLayerContext
88
+ ): void => {
89
+ validateModuleSpecifier({
90
+ importedNode: getImportedNode(specifier),
91
+ specifier,
92
+ modulePath,
93
+ config,
94
+ context,
95
+ tsService
96
+ })
97
+ }
98
+
99
+ export const validateImportSpecifierBase = makeSpecifierValidator<
100
+ TSESTree.ImportSpecifier
101
+ >((specifier) => specifier.imported)
102
+
103
+ export const validateExportSpecifierBase = makeSpecifierValidator<
104
+ TSESTree.ExportSpecifier
105
+ >((specifier) => specifier.local)
106
+
107
+ const executeImportValidation = <TResult>(params: {
108
+ readonly imported: TSESTree.Identifier
109
+ readonly specifier: ModuleSpecifier
110
+ readonly modulePath: string
111
+ readonly config: ImportValidationConfig<TResult>
112
+ readonly context: RuleContext<string, ReadonlyArray<string>>
113
+ readonly containingFilePath: string
114
+ readonly tsService: TypeScriptServiceLayerContext
115
+ }): void => {
116
+ const { config, containingFilePath, context, imported, modulePath, specifier, tsService } = params
117
+ const importName = imported.name
118
+
119
+ if (config.skipWhenTypeScriptAvailable === true && tsService.hasTypeScript) {
120
+ return
121
+ }
122
+
123
+ const validationEffect = Effect.provide(
124
+ config.validateSpecifier(specifier, importName, modulePath, containingFilePath),
125
+ tsService.layer
126
+ )
127
+
128
+ tryValidationWithFallback({
129
+ imported,
130
+ importName,
131
+ modulePath,
132
+ config,
133
+ context,
134
+ validationEffect
135
+ })
136
+ }
137
+
138
+ const emptyTypeScriptLayer = makeTypeScriptCompilerServiceLayer()
139
+
140
+ export const createTypeScriptServiceLayerForContext = (
141
+ context: RuleContext<string, ReadonlyArray<string>>
142
+ ): TypeScriptServiceLayerContext => {
143
+ const parseResult = getParserServicesForContext(context)
144
+
145
+ if (!parseResult) {
146
+ return {
147
+ layer: emptyTypeScriptLayer,
148
+ hasTypeScript: false
149
+ }
150
+ }
151
+
152
+ const program = parseResult.program
153
+ if (!program) {
154
+ return {
155
+ layer: emptyTypeScriptLayer,
156
+ hasTypeScript: false
157
+ }
158
+ }
159
+ const checker = program.getTypeChecker()
160
+
161
+ return {
162
+ layer: makeTypeScriptCompilerServiceLayer(checker, program),
163
+ hasTypeScript: true
164
+ }
165
+ }
166
+
167
+ export const getParserServicesForContext = (
168
+ context: RuleContext<string, ReadonlyArray<string>>
169
+ ): ReturnType<typeof ESLintUtils.getParserServices> | null =>
170
+ Exit.match(
171
+ Effect.runSyncExit(
172
+ Effect.try({
173
+ try: () => ESLintUtils.getParserServices(context, false),
174
+ catch: () => null
175
+ })
176
+ ),
177
+ {
178
+ onFailure: () => null,
179
+ onSuccess: (value) => value
180
+ }
181
+ )
@@ -0,0 +1,116 @@
1
+ // CHANGE: rule factory for import/export validations
2
+ // WHY: shared listener wiring
3
+ // QUOTE(TZ): n/a
4
+ // REF: AGENTS.md SHELL
5
+ // SOURCE: n/a
6
+ // PURITY: SHELL
7
+ // EFFECT: ESLint rule creation
8
+ // INVARIANT: listeners are deterministic
9
+ // COMPLEXITY: O(1)/O(n)
10
+ import type { TSESTree } from "@typescript-eslint/utils"
11
+ import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils"
12
+ import type { RuleContext, RuleListener, RuleModule } from "@typescript-eslint/utils/ts-eslint"
13
+
14
+ import { isTypeOnlyImport } from "../../core/validators/index.js"
15
+ import type { ImportValidationConfig, TypeScriptServiceLayerContext } from "./import-validation-base.js"
16
+ import {
17
+ createTypeScriptServiceLayerForContext,
18
+ validateExportSpecifierBase,
19
+ validateImportSpecifierBase
20
+ } from "./import-validation-base.js"
21
+
22
+ const createRule = <TResult>(
23
+ description: string,
24
+ messageId: string,
25
+ config: ImportValidationConfig<TResult>,
26
+ buildListener: (
27
+ context: RuleContext<string, ReadonlyArray<string>>,
28
+ config: ImportValidationConfig<TResult>
29
+ ) => RuleListener
30
+ ): RuleModule<string, ReadonlyArray<string>> =>
31
+ ESLintUtils.RuleCreator.withoutDocs({
32
+ meta: {
33
+ type: "problem",
34
+ docs: { description },
35
+ messages: { [messageId]: "{{message}}" },
36
+ schema: []
37
+ },
38
+ defaultOptions: [],
39
+ create(context) {
40
+ return buildListener(context, config)
41
+ }
42
+ })
43
+
44
+ const getModulePathFromImport = (node: TSESTree.ImportDeclaration): string | undefined =>
45
+ typeof node.source.value === "string" ? node.source.value : undefined
46
+
47
+ const getModulePathFromExport = (
48
+ node: TSESTree.ExportNamedDeclaration
49
+ ): string | undefined => typeof node.source?.value === "string" ? node.source.value : undefined
50
+
51
+ const buildImportListeners = <TResult>(
52
+ context: RuleContext<string, ReadonlyArray<string>>,
53
+ config: ImportValidationConfig<TResult>
54
+ ): RuleListener => {
55
+ const tsService = createTypeScriptServiceLayerForContext(context)
56
+
57
+ return {
58
+ ImportDeclaration(node: TSESTree.ImportDeclaration): void {
59
+ if (isTypeOnlyImport(node)) return
60
+ const modulePath = getModulePathFromImport(node)
61
+ if (!modulePath) return
62
+
63
+ for (const specifier of node.specifiers) {
64
+ if (specifier.type === AST_NODE_TYPES.ImportSpecifier) {
65
+ validateImportSpecifierBase(
66
+ specifier,
67
+ modulePath,
68
+ config,
69
+ context,
70
+ tsService
71
+ )
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ export const createExportValidationListener = <TResult>(
79
+ context: RuleContext<string, ReadonlyArray<string>>,
80
+ config: ImportValidationConfig<TResult>
81
+ ): RuleListener => {
82
+ const tsService: TypeScriptServiceLayerContext = createTypeScriptServiceLayerForContext(context)
83
+
84
+ return {
85
+ ExportNamedDeclaration(node: TSESTree.ExportNamedDeclaration): void {
86
+ const modulePath = getModulePathFromExport(node)
87
+ if (!modulePath) return
88
+
89
+ for (const specifier of node.specifiers) {
90
+ validateExportSpecifierBase(
91
+ specifier,
92
+ modulePath,
93
+ config,
94
+ context,
95
+ tsService
96
+ )
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ const makeValidationRule = <TResult>(
103
+ buildListener: (
104
+ context: RuleContext<string, ReadonlyArray<string>>,
105
+ config: ImportValidationConfig<TResult>
106
+ ) => RuleListener
107
+ ) =>
108
+ (
109
+ _ruleName: string,
110
+ description: string,
111
+ messageId: string,
112
+ config: ImportValidationConfig<TResult>
113
+ ): RuleModule<string, ReadonlyArray<string>> => createRule(description, messageId, config, buildListener)
114
+
115
+ export const createValidationRule = makeValidationRule(buildImportListeners)
116
+ export const createExportValidationRule = makeValidationRule(createExportValidationListener)
@@ -0,0 +1,94 @@
1
+ // CHANGE: validation helpers for import/export rules
2
+ // WHY: shared reporting + fallback handling
3
+ // QUOTE(TZ): n/a
4
+ // REF: AGENTS.md SHELL
5
+ // SOURCE: n/a
6
+ // PURITY: SHELL
7
+ // EFFECT: Effect.runSyncExit
8
+ // INVARIANT: reports only when message non-empty
9
+ // COMPLEXITY: O(1)/O(1)
10
+ import type { TSESTree } from "@typescript-eslint/utils"
11
+ import { AST_NODE_TYPES } from "@typescript-eslint/utils"
12
+ import type { RuleContext } from "@typescript-eslint/utils/ts-eslint"
13
+ import { Effect } from "effect"
14
+
15
+ import { shouldSkipIdentifier } from "../../core/validators/index.js"
16
+ import type { TypeScriptServiceError } from "../effects/errors.js"
17
+ import { makeFilesystemServiceLayer } from "../services/filesystem.js"
18
+ import type { ImportValidationConfig } from "./import-validation-base.js"
19
+ import { runEffect } from "./validation-runner.js"
20
+
21
+ interface BaseValidationParams<TResult> {
22
+ readonly imported: TSESTree.Identifier
23
+ readonly importName: string
24
+ readonly modulePath: string
25
+ readonly config: ImportValidationConfig<TResult>
26
+ readonly context: RuleContext<string, ReadonlyArray<string>>
27
+ }
28
+
29
+ export const isValidImportIdentifier = (
30
+ imported: TSESTree.Node
31
+ ): imported is TSESTree.Identifier => {
32
+ if (imported.type !== AST_NODE_TYPES.Identifier) return false
33
+ return !shouldSkipIdentifier(imported.name)
34
+ }
35
+
36
+ const reportValidationResult = <TResult>(
37
+ imported: TSESTree.Identifier,
38
+ config: ImportValidationConfig<TResult>,
39
+ context: RuleContext<string, ReadonlyArray<string>>,
40
+ result: TResult
41
+ ): void => {
42
+ const message = config.formatMessage(result)
43
+ if (message.length === 0) return
44
+
45
+ context.report({
46
+ node: imported,
47
+ messageId: config.messageId,
48
+ data: { message }
49
+ })
50
+ }
51
+
52
+ const tryFallbackValidationOnly = <TResult>(
53
+ params: BaseValidationParams<TResult>
54
+ ): void => {
55
+ const { config, context, importName, imported, modulePath } = params
56
+
57
+ if (!config.fallbackValidationEffect) return
58
+
59
+ const fallbackEffect = config.fallbackValidationEffect(
60
+ importName,
61
+ modulePath,
62
+ context.filename || ""
63
+ )
64
+
65
+ const result = runEffect(
66
+ Effect.provide(fallbackEffect, makeFilesystemServiceLayer())
67
+ )
68
+ if (!result) return
69
+
70
+ reportValidationResult(imported, config, context, result)
71
+ }
72
+
73
+ export const tryValidationWithFallback = <TResult>(
74
+ params: BaseValidationParams<TResult> & {
75
+ readonly validationEffect: Effect.Effect<TResult, TypeScriptServiceError>
76
+ }
77
+ ): void => {
78
+ const {
79
+ config,
80
+ context,
81
+ importName,
82
+ imported,
83
+ modulePath,
84
+ validationEffect
85
+ } = params
86
+
87
+ const result = runEffect(validationEffect)
88
+ if (!result) {
89
+ tryFallbackValidationOnly({ imported, importName, modulePath, config, context })
90
+ return
91
+ }
92
+
93
+ reportValidationResult(imported, config, context, result)
94
+ }
@@ -0,0 +1,45 @@
1
+ // CHANGE: shared validation runner
2
+ // WHY: execute Effect validations synchronously for ESLint
3
+ // QUOTE(TZ): n/a
4
+ // REF: AGENTS.md Effect
5
+ // SOURCE: n/a
6
+ // PURITY: SHELL
7
+ // EFFECT: Effect.runSyncExit
8
+ // INVARIANT: reports occur in same tick
9
+ // COMPLEXITY: O(1)/O(1)
10
+ import type { TSESTree } from "@typescript-eslint/utils"
11
+ import type { RuleContext } from "@typescript-eslint/utils/ts-eslint"
12
+ import { Effect, Exit } from "effect"
13
+
14
+ interface ValidationConfig<T extends { _tag: string }, E> {
15
+ readonly validationEffect: Effect.Effect<T, E>
16
+ readonly context: RuleContext<string, readonly []>
17
+ readonly reportNode: TSESTree.Node
18
+ readonly messageId: string
19
+ readonly formatMessage: (result: T) => string
20
+ readonly fallbackEffect?: Effect.Effect<T, E>
21
+ }
22
+
23
+ export const runEffect = <T, E>(effect: Effect.Effect<T, E>): T | null =>
24
+ Exit.match(Effect.runSyncExit(effect), {
25
+ onFailure: () => null,
26
+ onSuccess: (value) => value
27
+ })
28
+
29
+ export const runValidationEffect = <T extends { _tag: string }, E>(
30
+ config: ValidationConfig<T, E>
31
+ ): void => {
32
+ const { context, fallbackEffect, formatMessage, messageId, reportNode, validationEffect } = config
33
+
34
+ const result = runEffect(validationEffect) ?? (fallbackEffect ? runEffect(fallbackEffect) : null)
35
+
36
+ if (!result) return
37
+ if (result._tag === "Valid") return
38
+
39
+ const message = formatMessage(result)
40
+ context.report({
41
+ node: reportNode,
42
+ messageId,
43
+ data: { message }
44
+ })
45
+ }
@@ -0,0 +1,54 @@
1
+ // CHANGE: export validation effect
2
+ // WHY: suggest similar exports for re-exports
3
+ // QUOTE(TZ): n/a
4
+ // REF: AGENTS.md CORE↔SHELL
5
+ // SOURCE: n/a
6
+ // PURITY: SHELL
7
+ // EFFECT: Effect<ExportValidationResult, TypeScriptServiceError, TypeScriptCompilerServiceTag>
8
+ // INVARIANT: Valid | ExportNotFound
9
+ // COMPLEXITY: O(n log n)/O(n)
10
+ import { Match } from "effect"
11
+
12
+ import type { ExportValidationResult } from "../../core/index.js"
13
+ import { formatExportMessage, makeExportNotFoundResult, makeValidExportResult } from "../../core/index.js"
14
+ import type { BaseESLintNode } from "../../core/types/eslint-nodes.js"
15
+ import { baseValidationEffect, isValidExportCandidate } from "./validation-base-effect.js"
16
+
17
+ export const validateExportAccessEffect = (
18
+ node: BaseESLintNode,
19
+ exportName: string,
20
+ modulePath: string,
21
+ containingFilePath: string
22
+ ) => {
23
+ const config = {
24
+ makeValidResult: makeValidExportResult,
25
+ makeInvalidResult: makeExportNotFoundResult,
26
+ isValidCandidate: isValidExportCandidate
27
+ }
28
+
29
+ return baseValidationEffect(node, exportName, modulePath, containingFilePath, config)
30
+ }
31
+
32
+ export const formatExportValidationMessage = (
33
+ result: ExportValidationResult
34
+ ): string =>
35
+ Match.value(result).pipe(
36
+ Match.when({ _tag: "Valid" }, () => ""),
37
+ Match.when({ _tag: "ExportNotFound" }, (invalid) => {
38
+ if (invalid.suggestions.length === 0) {
39
+ return formatExportMessage(
40
+ invalid.exportName,
41
+ invalid.modulePath,
42
+ invalid.typeName,
43
+ invalid.suggestions
44
+ )
45
+ }
46
+ return formatExportMessage(
47
+ invalid.exportName,
48
+ invalid.modulePath,
49
+ invalid.typeName,
50
+ invalid.suggestions
51
+ )
52
+ }),
53
+ Match.exhaustive
54
+ )
@@ -0,0 +1,49 @@
1
+ // CHANGE: import validation effect
2
+ // WHY: suggest similar exports for named imports
3
+ // QUOTE(TZ): n/a
4
+ // REF: AGENTS.md CORE↔SHELL
5
+ // SOURCE: n/a
6
+ // PURITY: SHELL
7
+ // EFFECT: Effect<ImportValidationResult, TypeScriptServiceError, TypeScriptCompilerServiceTag>
8
+ // INVARIANT: Valid | ImportNotFound
9
+ // COMPLEXITY: O(n log n)/O(n)
10
+ import { Match } from "effect"
11
+
12
+ import type { ImportValidationResult } from "../../core/index.js"
13
+ import { formatImportMessage, makeImportNotFoundResult, makeValidImportResult } from "../../core/index.js"
14
+ import type { BaseESLintNode } from "../../core/types/eslint-nodes.js"
15
+ import { baseValidationEffect, isValidImportCandidate } from "./validation-base-effect.js"
16
+
17
+ export const validateImportSpecifierEffect = (
18
+ node: BaseESLintNode,
19
+ importName: string,
20
+ modulePath: string,
21
+ containingFilePath: string
22
+ ) => {
23
+ const config = {
24
+ makeValidResult: makeValidImportResult,
25
+ makeInvalidResult: makeImportNotFoundResult,
26
+ isValidCandidate: isValidImportCandidate
27
+ }
28
+
29
+ return baseValidationEffect(node, importName, modulePath, containingFilePath, config)
30
+ }
31
+
32
+ export const formatImportValidationMessage = (
33
+ result: ImportValidationResult
34
+ ): string =>
35
+ Match.value(result).pipe(
36
+ Match.when({ _tag: "Valid" }, () => ""),
37
+ Match.when({ _tag: "ImportNotFound" }, (invalid) => {
38
+ if (invalid.suggestions.length === 0) {
39
+ return `Variable "${invalid.importName}" is not defined.`
40
+ }
41
+ return formatImportMessage(
42
+ invalid.importName,
43
+ invalid.modulePath,
44
+ invalid.typeName,
45
+ invalid.suggestions
46
+ )
47
+ }),
48
+ Match.exhaustive
49
+ )
@@ -0,0 +1,10 @@
1
+ // CHANGE: local export validation (Effect)
2
+ // WHY: re-export shared validator for local export identifiers
3
+ // QUOTE(TZ): n/a
4
+ // REF: AGENTS.md CORE↔SHELL
5
+ // SOURCE: n/a
6
+ // PURITY: SHELL
7
+ // EFFECT: n/a
8
+ // INVARIANT: export-only module
9
+ // COMPLEXITY: O(1)/O(1)
10
+ export { formatLocalExportValidationMessage, validateLocalExportIdentifierEffect } from "./missing-name-validators.js"