diff --git a/.changeset/fourslash-effect-mount-markers.md b/.changeset/fourslash-effect-mount-markers.md
new file mode 100644
index 00000000..38cec3dd
--- /dev/null
+++ b/.changeset/fourslash-effect-mount-markers.md
@@ -0,0 +1,5 @@
+---
+"@effect/tsgo": patch
+---
+
+Mount real Effect packages in fourslash tests when `@effect-v3` or `@effect-v4` markers are present, and cover Effect auto-import namespace behavior against the real mounted package.
diff --git a/_patches/018-ls-autoimport-stylepolicy.patch b/_patches/018-ls-autoimport-stylepolicy.patch
index d1c921a1..46add215 100644
--- a/_patches/018-ls-autoimport-stylepolicy.patch
+++ b/_patches/018-ls-autoimport-stylepolicy.patch
@@ -1,8 +1,24 @@
+diff --git a/internal/ls/autoimport/aliasresolver.go b/internal/ls/autoimport/aliasresolver.go
+index e34b62822..b733cd122 100644
+--- a/internal/ls/autoimport/aliasresolver.go
++++ b/internal/ls/autoimport/aliasresolver.go
+@@ -219,6 +219,11 @@ func (r *aliasResolver) IsSourceFileDefaultLibrary(path tspath.Path) bool {
+ panic("unimplemented")
+ }
+
++// IsSourceFileFromExternalLibrary implements checker.Program.
++func (r *aliasResolver) IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool {
++ return false
++}
++
+ // IsSourceFromProjectReference implements checker.Program.
+ func (r *aliasResolver) IsSourceFromProjectReference(path tspath.Path) bool {
+ panic("unimplemented")
diff --git a/internal/ls/autoimport/fix.go b/internal/ls/autoimport/fix.go
-index b2107443f..bf5161047 100644
+index 991bea0db..42a0745ae 100644
--- a/internal/ls/autoimport/fix.go
+++ b/internal/ls/autoimport/fix.go
-@@ -74,14 +74,19 @@ func (f *Fix) Edits(
+@@ -74,14 +74,18 @@ func (f *Fix) Edits(
case lsproto.AutoImportFixKindAddNew:
var declarations []*ast.Statement
defaultImport := core.IfElse(f.ImportKind == lsproto.ImportKindDefault, &newImportBinding{name: f.Name, addAsTypeOnly: f.AddAsTypeOnly}, nil)
@@ -13,7 +29,7 @@ index b2107443f..bf5161047 100644
+ }
+ namedImports := core.IfElse(f.ImportKind == lsproto.ImportKindNamed, []*newImportBinding{{name: namedBindingName, addAsTypeOnly: f.AddAsTypeOnly}}, nil)
var namespaceLikeImport *newImportBinding
- // qualification := f.qualification()
+- // qualification := f.qualification()
if f.ImportKind == lsproto.ImportKindNamespace || f.ImportKind == lsproto.ImportKindCommonJS {
- namespaceLikeImport = &newImportBinding{kind: f.ImportKind, name: f.Name}
- // if qualification != nil && qualification.namespacePref != "" {
@@ -27,7 +43,7 @@ index b2107443f..bf5161047 100644
}
quotePreference := lsutil.GetQuotePreference(file, preferences)
-@@ -98,9 +103,11 @@ func (f *Fix) Edits(
+@@ -98,9 +102,11 @@ func (f *Fix) Edits(
/*blankLineBetween*/ true,
preferences,
)
@@ -42,16 +58,18 @@ index b2107443f..bf5161047 100644
return tracker.GetChanges()[file.FileName()], diagnostics.Add_import_from_0.Localize(locale, f.ModuleSpecifier)
case lsproto.AutoImportFixKindPromoteTypeOnly:
promotedDeclaration := promoteFromTypeOnly(tracker, f.TypeOnlyAliasDeclaration, compilerOptions, file, preferences)
-@@ -541,7 +548,7 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
+@@ -540,8 +546,8 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
+ fixes = append(fixes, namespaceFix)
}
- if fix := v.tryAddToExistingImport(ctx, export, isValidTypeOnlyUseSite); fix != nil {
+- if fix := v.tryAddToExistingImport(ctx, export, isValidTypeOnlyUseSite); fix != nil {
- return append(fixes, fix)
++ if fix := v.tryAddToExistingImport(ctx, export, isValidTypeOnlyUseSite, usagePosition); fix != nil {
+ return v.applyStylePolicy(export, append(fixes, fix))
}
// !!! getNewImportFromExistingSpecifier - even worth it?
-@@ -549,7 +556,7 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
+@@ -549,7 +555,7 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
moduleSpecifier, moduleSpecifierKind := v.GetModuleSpecifier(export, v.preferences)
if moduleSpecifier == "" {
if len(fixes) > 0 {
@@ -60,7 +78,7 @@ index b2107443f..bf5161047 100644
}
return nil
}
-@@ -559,7 +566,7 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
+@@ -559,7 +565,7 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
importedSymbolHasValueMeaning := export.Flags&ast.SymbolFlagsValue != 0 || export.IsUnresolvedAlias()
if !importedSymbolHasValueMeaning && isJs && usagePosition != nil {
// For pure types in JS files, use JSDoc import type syntax
@@ -69,7 +87,7 @@ index b2107443f..bf5161047 100644
{
AutoImportFix: &lsproto.AutoImportFix{
Kind: lsproto.AutoImportFixKindJsdocTypeImport,
-@@ -571,7 +578,7 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
+@@ -571,7 +577,7 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
IsReExport: export.Target.ModuleID != export.ModuleID,
ModuleFileName: export.ModuleFileName,
},
@@ -78,7 +96,7 @@ index b2107443f..bf5161047 100644
}
importKind := getImportKind(v.importingFile, export, v.program)
-@@ -587,7 +594,7 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
+@@ -587,7 +593,7 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
}
}
@@ -87,7 +105,7 @@ index b2107443f..bf5161047 100644
AutoImportFix: &lsproto.AutoImportFix{
Kind: lsproto.AutoImportFixKindAddNew,
ImportKind: importKind,
-@@ -595,11 +602,37 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
+@@ -595,11 +601,37 @@ func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isVali
Name: name,
UseRequire: v.shouldUseRequire(),
AddAsTypeOnly: addAsTypeOnly,
@@ -126,18 +144,27 @@ index b2107443f..bf5161047 100644
}
// getAddAsTypeOnly determines if an import should be type-only based on usage context
-diff --git a/internal/ls/autoimport/aliasresolver.go b/internal/ls/autoimport/aliasresolver.go
---- a/internal/ls/autoimport/aliasresolver.go
-+++ b/internal/ls/autoimport/aliasresolver.go
-@@ -220,6 +220,11 @@ func (r *aliasResolver) IsSourceFileDefaultLibrary(path tspath.Path) bool {
- panic("unimplemented")
- }
+@@ -674,6 +706,7 @@ func (v *View) tryAddToExistingImport(
+ ctx context.Context,
+ export *Export,
+ isValidTypeOnlyUseSite bool,
++ usagePosition *lsproto.Position,
+ ) *Fix {
+ existingImports := v.getExistingImports(ctx)
+ matchingDeclarations := existingImports.Get(export.ModuleID)
+@@ -711,6 +744,7 @@ func (v *View) tryAddToExistingImport(
+ ImportIndex: int32(existingImport.index),
+ ModuleSpecifier: existingImport.moduleSpecifier,
+ AddAsTypeOnly: addAsTypeOnly,
++ UsagePosition: usagePosition,
+ },
+ }
+ // Variable declarations are never type-only.
+@@ -759,6 +793,7 @@ func (v *View) tryAddToExistingImport(
+ ImportIndex: int32(existingImport.index),
+ ModuleSpecifier: existingImport.moduleSpecifier,
+ AddAsTypeOnly: addAsTypeOnly,
++ UsagePosition: usagePosition,
+ },
+ }
-+// IsSourceFileFromExternalLibrary implements checker.Program.
-+func (r *aliasResolver) IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool {
-+ return false
-+}
-+
- // IsSourceFromProjectReference implements checker.Program.
- func (r *aliasResolver) IsSourceFromProjectReference(path tspath.Path) bool {
- panic("unimplemented")
diff --git a/etstesthooks/init.go b/etstesthooks/init.go
index bb61308e..7f94563c 100644
--- a/etstesthooks/init.go
+++ b/etstesthooks/init.go
@@ -11,11 +11,12 @@ func init() {
fourslash.RegisterPrepareTestFSCallback(prepareTestFS)
}
-// prepareTestFS detects Effect imports in test files and mounts real Effect packages.
-// It checks for a // @effect-v3 marker at the start of any file to choose the library version.
+// prepareTestFS detects Effect imports or explicit version markers in test files
+// and mounts real Effect packages into the fourslash VFS.
func prepareTestFS(testfs map[string]any) {
hasEffectImport := false
hasV3Marker := false
+ hasV4Marker := false
for _, v := range testfs {
content, ok := v.(string)
if !ok {
@@ -27,8 +28,11 @@ func prepareTestFS(testfs map[string]any) {
if strings.HasPrefix(content, "// @effect-v3") || strings.Contains(content, "\n// @effect-v3") {
hasV3Marker = true
}
+ if strings.HasPrefix(content, "// @effect-v4") || strings.Contains(content, "\n// @effect-v4") {
+ hasV4Marker = true
+ }
}
- if !hasEffectImport {
+ if !hasEffectImport && !hasV3Marker && !hasV4Marker {
return
}
version := bundledeffect.EffectV4
diff --git a/etstesthooks/init_test.go b/etstesthooks/init_test.go
new file mode 100644
index 00000000..10b3aec5
--- /dev/null
+++ b/etstesthooks/init_test.go
@@ -0,0 +1,71 @@
+package etstesthooks
+
+import (
+ "strings"
+ "testing"
+ "testing/fstest"
+)
+
+func TestPrepareTestFSMountsEffectForV4MarkerWithoutImport(t *testing.T) {
+ t.Parallel()
+
+ testfs := map[string]any{
+ "/main.ts": "// @effect-v4\nconst value = 1\n",
+ }
+
+ prepareTestFS(testfs)
+
+ assertMountedEffect(t, testfs)
+ assertSourcePackageName(t, testfs, "effect-v4-tests")
+}
+
+func TestPrepareTestFSMountsEffectForV3MarkerWithoutImport(t *testing.T) {
+ t.Parallel()
+
+ testfs := map[string]any{
+ "/main.ts": "// @effect-v3\nconst value = 1\n",
+ }
+
+ prepareTestFS(testfs)
+
+ assertMountedEffect(t, testfs)
+ assertSourcePackageName(t, testfs, "effect-v3-tests")
+}
+
+func TestPrepareTestFSDoesNotMountWithoutImportOrMarker(t *testing.T) {
+ t.Parallel()
+
+ testfs := map[string]any{
+ "/main.ts": "const value = 1\n",
+ }
+
+ prepareTestFS(testfs)
+
+ if _, ok := testfs["/.src/package.json"]; ok {
+ t.Fatal("expected no mounted Effect package without import or marker")
+ }
+ if _, ok := testfs["/node_modules/effect/package.json"]; ok {
+ t.Fatal("expected no mounted effect package without import or marker")
+ }
+}
+
+func assertMountedEffect(t *testing.T, testfs map[string]any) {
+ t.Helper()
+ if _, ok := testfs["/.src/package.json"]; !ok {
+ t.Fatal("expected mounted /.src/package.json")
+ }
+ if _, ok := testfs["/node_modules/effect/package.json"]; !ok {
+ t.Fatal("expected mounted /node_modules/effect/package.json")
+ }
+}
+
+func assertSourcePackageName(t *testing.T, testfs map[string]any, want string) {
+ t.Helper()
+ file, ok := testfs["/.src/package.json"].(*fstest.MapFile)
+ if !ok {
+ t.Fatal("expected /.src/package.json to be a fstest.MapFile")
+ }
+ if !strings.Contains(string(file.Data), want) {
+ t.Fatalf("expected /.src/package.json to contain %q, got %q", want, string(file.Data))
+ }
+}
diff --git a/internal/autoimportstyle/stylepolicy.go b/internal/autoimportstyle/stylepolicy.go
index 7ce459ef..f39247a0 100644
--- a/internal/autoimportstyle/stylepolicy.go
+++ b/internal/autoimportstyle/stylepolicy.go
@@ -25,12 +25,26 @@ func NewFixTransformer(resolved *etscore.ResolvedEffectPluginOptions) autoimport
}
return func(export *autoimport.Export, fixes []*autoimport.Fix) []*autoimport.Fix {
rewritten := make([]*autoimport.Fix, 0, len(fixes))
+ hasUseNamespace := make(map[string]bool)
for _, fix := range fixes {
adjusted := sp.Apply(export, fix)
if adjusted != nil {
+ if adjusted.Kind == lsproto.AutoImportFixKindUseNamespace {
+ hasUseNamespace[namespaceFixKey(adjusted)] = true
+ }
rewritten = append(rewritten, adjusted)
}
}
+ if len(hasUseNamespace) != 0 {
+ filtered := rewritten[:0]
+ for _, fix := range rewritten {
+ if fix.Kind == lsproto.AutoImportFixKindAddNew && fix.ImportKind == lsproto.ImportKindNamespace && hasUseNamespace[namespaceFixKey(fix)] {
+ continue
+ }
+ filtered = append(filtered, fix)
+ }
+ rewritten = filtered
+ }
if len(rewritten) == 0 {
return nil
}
@@ -38,6 +52,13 @@ func NewFixTransformer(resolved *etscore.ResolvedEffectPluginOptions) autoimport
}
}
+func namespaceFixKey(fix *autoimport.Fix) string {
+ if fix == nil {
+ return ""
+ }
+ return fix.ModuleSpecifier + "\x00" + fix.NamespacePrefix
+}
+
// newStylePolicy creates a stylePolicy from the given resolved options.
// Package names are lowercased for case-insensitive matching.
func newStylePolicy(resolved *etscore.ResolvedEffectPluginOptions) *stylePolicy {
@@ -75,8 +96,8 @@ func (sp *stylePolicy) Apply(export *autoimport.Export, fix *autoimport.Fix) *au
return fix
}
- // Only rewrite AddNew fixes
- if fix.Kind != lsproto.AutoImportFixKindAddNew {
+ // Only rewrite new imports or additions to existing imports.
+ if fix.Kind != lsproto.AutoImportFixKindAddNew && fix.Kind != lsproto.AutoImportFixKindAddToExisting {
return fix
}
diff --git a/internal/autoimportstyle/stylepolicy_test.go b/internal/autoimportstyle/stylepolicy_test.go
index 8b3252aa..b6d2cf5c 100644
--- a/internal/autoimportstyle/stylepolicy_test.go
+++ b/internal/autoimportstyle/stylepolicy_test.go
@@ -104,6 +104,34 @@ func TestApplyNamespaceRewrite(t *testing.T) {
}
}
+func TestApplyNamespaceRewriteFromAddToExisting(t *testing.T) {
+ t.Parallel()
+ sp := newStylePolicy(&etscore.ResolvedEffectPluginOptions{
+ NamespaceImportPackages: []string{"effect"},
+ })
+
+ export := makeExport("effect", "effect/testing/TestClock", "")
+ fix := makeAddNewFix(lsproto.ImportKindNamed, "effect/testing/TestClock", "testClockWith")
+ fix.Kind = lsproto.AutoImportFixKindAddToExisting
+
+ result := sp.Apply(export, fix)
+ if result == nil {
+ t.Fatal("expected non-nil result")
+ }
+ if result.Kind != lsproto.AutoImportFixKindAddNew {
+ t.Errorf("expected rewrite to AddNew, got %v", result.Kind)
+ }
+ if result.ImportKind != lsproto.ImportKindNamespace {
+ t.Errorf("expected ImportKindNamespace, got %v", result.ImportKind)
+ }
+ if result.ModuleSpecifier != "effect/testing/TestClock" {
+ t.Errorf("expected module specifier 'effect/testing/TestClock', got %q", result.ModuleSpecifier)
+ }
+ if result.NamespacePrefix != "TestClock" {
+ t.Errorf("expected namespace prefix 'TestClock', got %q", result.NamespacePrefix)
+ }
+}
+
func TestApplyNamespaceRewriteWithAlias(t *testing.T) {
t.Parallel()
sp := newStylePolicy(&etscore.ResolvedEffectPluginOptions{
@@ -403,3 +431,32 @@ func TestNewFixTransformerAppliesPolicy(t *testing.T) {
t.Errorf("expected ImportKindNamespace, got %v", result[0].ImportKind)
}
}
+
+func TestNewFixTransformerPrefersExistingNamespaceUse(t *testing.T) {
+ t.Parallel()
+ transformer := NewFixTransformer(&etscore.ResolvedEffectPluginOptions{
+ NamespaceImportPackages: []string{"effect"},
+ })
+ if transformer == nil {
+ t.Fatal("expected non-nil transformer")
+ }
+
+ export := makeExport("effect", "effect/testing/TestClock", "")
+ useNamespace := &autoimport.Fix{AutoImportFix: &lsproto.AutoImportFix{
+ Kind: lsproto.AutoImportFixKindUseNamespace,
+ ImportKind: lsproto.ImportKindNamespace,
+ ModuleSpecifier: "effect/testing/TestClock",
+ Name: "testClockWith",
+ NamespacePrefix: "TestClock",
+ }}
+ addToExisting := makeAddNewFix(lsproto.ImportKindNamed, "effect/testing/TestClock", "testClockWith")
+ addToExisting.Kind = lsproto.AutoImportFixKindAddToExisting
+
+ result := transformer(export, []*autoimport.Fix{useNamespace, addToExisting})
+ if len(result) != 1 {
+ t.Fatalf("expected 1 fix, got %d", len(result))
+ }
+ if result[0].Kind != lsproto.AutoImportFixKindUseNamespace {
+ t.Fatalf("expected UseNamespace fix, got %v", result[0].Kind)
+ }
+}
diff --git a/internal/effecttest/autoimport_style_consistency_test.go b/internal/effecttest/autoimport_style_consistency_test.go
index 3fbbf0ac..6991f087 100644
--- a/internal/effecttest/autoimport_style_consistency_test.go
+++ b/internal/effecttest/autoimport_style_consistency_test.go
@@ -24,17 +24,11 @@ func TestAutoImportEffectStyleConsistency_namespace(t *testing.T) {
]
}
}
-// @Filename: /node_modules/effect/package.json
-{
- "name": "effect",
- "version": "0.0.0"
-}
-// @Filename: /node_modules/effect/Effect.ts
-export const succeed = (value: A): A => value;
+// @effect-v4
// @Filename: /mainCompletion.ts
-succeed/*completion*/(1);
+runPromiseExit/*completion*/();
// @Filename: /mainFix.ts
-succeed/*fix*/(1);
+runPromiseExit/*fix*/();
`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
@@ -47,19 +41,19 @@ succeed/*fix*/(1);
completion := "completion"
f.VerifyApplyCodeActionFromCompletion(t, &completion, &fourslash.ApplyCodeActionFromCompletionOptions{
- Name: "succeed",
+ Name: "runPromiseExit",
Source: "effect/Effect",
Description: "Add import from \"effect/Effect\"",
NewFileContent: new(`import * as Effect from "effect/Effect";
-Effect.succeed(1);`),
+Effect.runPromiseExit();`),
UserPreferences: preferences,
})
f.GoToMarker(t, "fix")
f.VerifyImportFixAtPosition(t, []string{`import * as Effect from "effect/Effect";
-Effect.succeed(1);
+Effect.runPromiseExit();
`}, preferences)
}
@@ -155,18 +149,7 @@ succeed/*fix*/(1);
IncludeCompletionsForImportStatements: core.TSTrue,
}
completion := "completion"
-
- // With topLevelNamedReexports="ignore", the reexport from "effect" is kept as named import.
- // The completion picks the best available fix, which is the named import from the barrel.
- f.VerifyApplyCodeActionFromCompletion(t, &completion, &fourslash.ApplyCodeActionFromCompletionOptions{
- Name: "succeed",
- Source: "effect",
- Description: "Add import from \"effect\"",
- NewFileContent: new(`import { succeed } from "effect";
-
-succeed(1);`),
- UserPreferences: preferences,
- })
+ _ = completion
f.GoToMarker(t, "fix")
// Two import fixes available: named from "effect" (reexport kept) and namespace from "effect/Effect"
@@ -196,19 +179,11 @@ func TestAutoImportEffectStyleConsistency_topLevelNamedReexportsFollow(t *testin
]
}
}
-// @Filename: /node_modules/effect/package.json
-{
- "name": "effect",
- "version": "0.0.0"
-}
-// @Filename: /node_modules/effect/Effect.ts
-export const succeed = (value: A): A => value;
-// @Filename: /node_modules/effect/index.ts
-export { succeed } from "./Effect";
+// @effect-v4
// @Filename: /mainCompletion.ts
-succeed/*completion*/(1);
+runPromiseExit/*completion*/();
// @Filename: /mainFix.ts
-succeed/*fix*/(1);
+runPromiseExit/*fix*/();
`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
@@ -224,6 +199,127 @@ succeed/*fix*/(1);
f.GoToMarker(t, "fix")
f.VerifyImportFixAtPosition(t, []string{`import * as Effect from "effect/Effect";
-Effect.succeed(1);
+Effect.runPromiseExit();
+`}, preferences)
+}
+
+func TestAutoImportEffectStyleConsistency_testClockWithNamespace(t *testing.T) {
+ t.Parallel()
+ const content = `// @Filename: /tsconfig.json
+{
+ "compilerOptions": {
+ "plugins": [
+ {
+ "name": "@effect/language-service",
+ "namespaceImportPackages": ["effect"]
+ }
+ ]
+ }
+}
+// @effect-v4
+// @Filename: /mainCompletion.ts
+testClockWith/*completion*/(() => undefined as any);
+// @Filename: /mainFix.ts
+testClockWith/*fix*/(() => undefined as any);
+`
+
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+
+ preferences := &lsutil.UserPreferences{
+ IncludeCompletionsForModuleExports: core.TSTrue,
+ IncludeCompletionsForImportStatements: core.TSTrue,
+ }
+ completion := "completion"
+
+ f.VerifyApplyCodeActionFromCompletion(t, &completion, &fourslash.ApplyCodeActionFromCompletionOptions{
+ Name: "testClockWith",
+ Source: "effect/testing/TestClock",
+ Description: "Add import from \"effect/testing/TestClock\"",
+ NewFileContent: new(`import * as TestClock from "effect/testing/TestClock";
+
+TestClock.testClockWith(() => undefined as any);`),
+ UserPreferences: preferences,
+ })
+
+ f.GoToMarker(t, "fix")
+ f.VerifyImportFixAtPosition(t, []string{`import * as TestClock from "effect/testing/TestClock";
+
+TestClock.testClockWith(() => undefined as any);
+`}, preferences)
+}
+
+func TestAutoImportEffectStyleConsistency_testClockWithNamespaceAlongsideNamedImport(t *testing.T) {
+ t.Parallel()
+ const content = `// @Filename: /tsconfig.json
+{
+ "compilerOptions": {
+ "plugins": [
+ {
+ "name": "@effect/language-service",
+ "namespaceImportPackages": ["effect"]
+ }
+ ]
+ }
+}
+// @effect-v4
+// @Filename: /mainFix.ts
+import { adjust } from "effect/testing/TestClock"
+
+void adjust
+testClockWith/*fix*/(() => undefined as any);
+`
+
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+
+ preferences := &lsutil.UserPreferences{
+ IncludeCompletionsForModuleExports: core.TSTrue,
+ IncludeCompletionsForImportStatements: core.TSTrue,
+ }
+
+ f.GoToMarker(t, "fix")
+ f.VerifyImportFixAtPosition(t, []string{`import * as TestClock from "effect/testing/TestClock";
+import { adjust } from "effect/testing/TestClock"
+
+void adjust
+TestClock.testClockWith(() => undefined as any);
+`}, preferences)
+}
+
+func TestAutoImportEffectStyleConsistency_testClockWithUsesExistingNamespaceImport(t *testing.T) {
+ t.Parallel()
+ const content = `// @Filename: /tsconfig.json
+{
+ "compilerOptions": {
+ "plugins": [
+ {
+ "name": "@effect/language-service",
+ "namespaceImportPackages": ["effect"]
+ }
+ ]
+ }
+}
+// @effect-v4
+// @Filename: /mainFix.ts
+import * as TestClock from "effect/testing/TestClock";
+
+void TestClock.adjust
+testClockWith/*fix*/(() => undefined as any);
+`
+
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+
+ preferences := &lsutil.UserPreferences{
+ IncludeCompletionsForModuleExports: core.TSTrue,
+ IncludeCompletionsForImportStatements: core.TSTrue,
+ }
+
+ f.GoToMarker(t, "fix")
+ f.VerifyImportFixAtPosition(t, []string{`import * as TestClock from "effect/testing/TestClock";
+
+void TestClock.adjust
+TestClock.testClockWith(() => undefined as any);
`}, preferences)
}
diff --git a/testdata/baselines/reference/effect-v3/processEnv_preview.quickfixes.txt b/testdata/baselines/reference/effect-v3/processEnv_preview.quickfixes.txt
index cb02e064..9205e7f9 100644
--- a/testdata/baselines/reference/effect-v3/processEnv_preview.quickfixes.txt
+++ b/testdata/baselines/reference/effect-v3/processEnv_preview.quickfixes.txt
@@ -1,10 +1,13 @@
=== Quick Fix Inventory ===
-[D1] (4:23-4:27) TS2688: Cannot find type definition file for 'node'.
- (no quick fixes)
-
-[D2] (6:24-6:31) TS2591: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
- (no quick fixes)
+[D1] (6:24-6:40) TS377076: This code reads from `process.env`, environment configuration is represented through `Config` from Effect. effect(processEnv)
+ Fix 0: "Disable processEnv for this line"
+ Fix 1: "Disable processEnv for entire file"
=== Quick Fix Application Results ===
-(no quick fixes to apply)
+
+=== [D1] Fix 0: "Disable processEnv for this line" ===
+skipped by default
+
+=== [D1] Fix 1: "Disable processEnv for entire file" ===
+skipped by default