diff --git a/.changeset/three-buses-share.md b/.changeset/three-buses-share.md new file mode 100644 index 00000000..a80eedc7 --- /dev/null +++ b/.changeset/three-buses-share.md @@ -0,0 +1,7 @@ +--- +"@effect/tsgo": minor +--- + +Add data-first and data-last piping flow normalization so data-first Effect and Layer APIs contribute the same flow structure as their pipeable forms. + +This also extracts the shared bundled Effect test VFS helper into `internal/bundledeffect` and updates the affected flow and diagnostics baselines. diff --git a/.golangci.yml b/.golangci.yml index b6dd6c25..351b8285 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -77,6 +77,9 @@ linters: - path: internal/effecttest linters: - forbidigo + - path: internal/bundledeffect + linters: + - forbidigo - path: _tools linters: - forbidigo diff --git a/README.md b/README.md index 2620450d..92a9b259 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,8 @@ Each release of `effect-tsgo` is built against a specific upstream `tsgo` commit "quickinfo": true, // Controls Effect completions. (default: true) "completions": true, + // Enables additional debug-only Effect language service output. (default: false) + "debug": false, // Controls Effect goto references support. (default: true) "goto": true, // Controls Effect rename helpers. (default: true) diff --git a/etscore/options.go b/etscore/options.go index 9873e830..31889f4f 100644 --- a/etscore/options.go +++ b/etscore/options.go @@ -34,6 +34,9 @@ type EffectPluginOptions struct { // Completions enables Effect completions in the language service. Completions bool `json:"completions,omitzero" schema_description:"Controls Effect completions." schema_default:"true"` + // Debug enables extra debugging-oriented language service output. + Debug bool `json:"debug,omitzero" schema_description:"Enables additional debug-only Effect language service output." schema_default:"false"` + // Goto enables Effect goto/definition helpers in the language service. Goto bool `json:"goto,omitzero" schema_description:"Controls Effect goto references support." schema_default:"true"` @@ -218,6 +221,13 @@ func (e *EffectPluginOptions) GetCompletionsEnabled() bool { return e.Completions } +func (e *EffectPluginOptions) GetDebugEnabled() bool { + if e == nil { + return false + } + return e.Debug +} + // GetIncludeSuggestionsInTsc returns whether suggestion diagnostics should appear in tsc output. // Returns true (include suggestions) when the receiver is nil. func (e *EffectPluginOptions) GetIncludeSuggestionsInTsc() bool { diff --git a/etscore/options_parser.go b/etscore/options_parser.go index 0c3f149a..3067770d 100644 --- a/etscore/options_parser.go +++ b/etscore/options_parser.go @@ -99,6 +99,13 @@ func ParseFromPlugins(value any) *EffectPluginOptions { } } + // Parse debug (default: false) + if val, exists := getPluginValue("debug"); exists { + if b, ok := val.(bool); ok { + result.Debug = b + } + } + // Parse goto (default: true) if val, exists := getPluginValue("goto"); exists { if b, ok := val.(bool); ok { diff --git a/etscore/options_test.go b/etscore/options_test.go index c1a09b1a..7c95759e 100644 --- a/etscore/options_test.go +++ b/etscore/options_test.go @@ -40,6 +40,23 @@ func TestParseFromPlugins_ExitCodeDefaults(t *testing.T) { if opts.IgnoreEffectWarningsInTscExitCode { t.Error("expected IgnoreEffectWarningsInTscExitCode to default to false") } + if opts.GetDebugEnabled() { + t.Error("expected Debug to default to false") + } +} + +func TestParseFromPlugins_DebugExplicitTrue(t *testing.T) { + plugins := makePlugins(makePluginMap( + "name", etscore.EffectPluginName, + "debug", true, + )) + opts := etscore.ParseFromPlugins(plugins) + if opts == nil { + t.Fatal("expected non-nil options") + } + if !opts.GetDebugEnabled() { + t.Error("expected Debug to be true") + } } func TestParseFromPlugins_Overrides(t *testing.T) { diff --git a/etslshooks/document_symbols.go b/etslshooks/document_symbols.go index 0ca432f4..4189dfca 100644 --- a/etslshooks/document_symbols.go +++ b/etslshooks/document_symbols.go @@ -3,6 +3,7 @@ package etslshooks import ( "context" "slices" + "strconv" "strings" "github.com/effect-ts/tsgo/internal/typeparser" @@ -16,7 +17,8 @@ import ( ) func afterDocumentSymbols(ctx context.Context, sf *ast.SourceFile, symbols []*lsproto.DocumentSymbol, program *compiler.Program, langService *ls.LanguageService) []*lsproto.DocumentSymbol { - if program.Options().Effect == nil { + effectConfig := program.Options().Effect + if effectConfig == nil { return symbols } @@ -28,11 +30,15 @@ func afterDocumentSymbols(ctx context.Context, sf *ast.SourceFile, symbols []*ls serviceChildren := collectServiceDocumentSymbols(tp, c, sf, langService) errorChildren := collectErrorDocumentSymbols(tp, c, sf, langService) schemaChildren := collectSchemaDocumentSymbols(tp, c, sf, langService) - if len(layerChildren) == 0 && len(serviceChildren) == 0 && len(errorChildren) == 0 && len(schemaChildren) == 0 { + var flowChildren []*lsproto.DocumentSymbol + if effectConfig.GetDebugEnabled() { + flowChildren = collectFlowDocumentSymbols(tp, c, sf, langService) + } + if len(layerChildren) == 0 && len(serviceChildren) == 0 && len(errorChildren) == 0 && len(schemaChildren) == 0 && len(flowChildren) == 0 { return symbols } - effectChildren := make([]*lsproto.DocumentSymbol, 0, 4) + effectChildren := make([]*lsproto.DocumentSymbol, 0, 5) if len(layerChildren) > 0 { layers := newSyntheticNamespaceSymbol("Layers") layers.Children = &layerChildren @@ -53,6 +59,11 @@ func afterDocumentSymbols(ctx context.Context, sf *ast.SourceFile, symbols []*ls schemas.Children = &schemaChildren effectChildren = append(effectChildren, schemas) } + if len(flowChildren) > 0 { + flows := newSyntheticNamespaceSymbol("Flows") + flows.Children = &flowChildren + effectChildren = append(effectChildren, flows) + } effect := newSyntheticNamespaceSymbol("Effect") effect.Children = &effectChildren @@ -163,6 +174,58 @@ func collectSchemaDocumentSymbols(tp *typeparser.TypeParser, c *checker.Checker, return symbols } +func collectFlowDocumentSymbols(tp *typeparser.TypeParser, c *checker.Checker, sf *ast.SourceFile, langService *ls.LanguageService) []*lsproto.DocumentSymbol { + flows := tp.PipingFlows(sf, true) + if len(flows) == 0 { + return nil + } + + symbols := make([]*lsproto.DocumentSymbol, 0, len(flows)) + for i, flow := range flows { + if flow == nil || flow.Node == nil { + continue + } + + children := make([]*lsproto.DocumentSymbol, 0, len(flow.Transformations)+1) + if flow.Subject.Node != nil { + children = append(children, newNamedDocumentSymbol( + sf, + langService, + flow.Subject.Node, + debugFlowNodeText(sf, flow.Subject.Node), + typeToDetail(c, flow.Subject.OutType, flow.Subject.Node), + layerSymbolKind(flow.Subject.Node), + )) + } + for j, transformation := range flow.Transformations { + if transformation.Node == nil { + continue + } + children = append(children, newNamedDocumentSymbol( + sf, + langService, + transformation.Node, + strconv.Itoa(j)+": "+debugFlowTransformationText(sf, &transformation), + typeToDetail(c, transformation.OutType, transformation.Node), + lsproto.SymbolKindFunction, + )) + } + + flowSymbol := newNamedDocumentSymbol( + sf, + langService, + flow.Node, + "Flow "+strconv.Itoa(i), + nil, + lsproto.SymbolKindVariable, + ) + flowSymbol.Children = &children + symbols = append(symbols, flowSymbol) + } + + return symbols +} + func newSyntheticNamespaceSymbol(name string) *lsproto.DocumentSymbol { children := []*lsproto.DocumentSymbol{} zero := lsproto.Position{} @@ -181,30 +244,37 @@ func newSyntheticNamespaceSymbol(name string) *lsproto.DocumentSymbol { } } -func newEffectDocumentSymbol( - tp *typeparser.TypeParser, - c *checker.Checker, +func newNamedDocumentSymbol( sf *ast.SourceFile, langService *ls.LanguageService, node *ast.Node, - displayNode *ast.Node, - detail func(*typeparser.TypeParser, *checker.Checker, *ast.Node) *string, + name string, + detail *string, + kind lsproto.SymbolKind, ) *lsproto.DocumentSymbol { + children := []*lsproto.DocumentSymbol{} + if node == nil { + zero := lsproto.Position{} + return &lsproto.DocumentSymbol{ + Name: name, + Detail: detail, + Kind: kind, + Range: lsproto.Range{Start: zero, End: zero}, + SelectionRange: lsproto.Range{Start: zero, End: zero}, + Children: &children, + } + } + converters := ls.LanguageService_converters(langService) startPos := scanner.SkipTrivia(sf.Text(), node.Pos()) endPos := max(startPos, node.End()) start := converters.PositionToLineAndCharacter(sf, core.TextPos(startPos)) end := converters.PositionToLineAndCharacter(sf, core.TextPos(endPos)) - children := []*lsproto.DocumentSymbol{} - var symbolDetail *string - if detail != nil { - symbolDetail = detail(tp, c, node) - } return &lsproto.DocumentSymbol{ - Name: layerSymbolName(sf, displayNode), - Detail: symbolDetail, - Kind: layerSymbolKind(displayNode), + Name: name, + Detail: detail, + Kind: kind, Range: lsproto.Range{ Start: start, End: end, @@ -217,6 +287,58 @@ func newEffectDocumentSymbol( } } +func newEffectDocumentSymbol( + tp *typeparser.TypeParser, + c *checker.Checker, + sf *ast.SourceFile, + langService *ls.LanguageService, + node *ast.Node, + displayNode *ast.Node, + detail func(*typeparser.TypeParser, *checker.Checker, *ast.Node) *string, +) *lsproto.DocumentSymbol { + children := []*lsproto.DocumentSymbol{} + var symbolDetail *string + if detail != nil { + symbolDetail = detail(tp, c, node) + } + + symbol := newNamedDocumentSymbol(sf, langService, node, layerSymbolName(sf, displayNode), symbolDetail, layerSymbolKind(displayNode)) + symbol.Children = &children + return symbol +} + +func typeToDetail(c *checker.Checker, t *checker.Type, node *ast.Node) *string { + if c == nil || t == nil || node == nil { + return nil + } + detail := c.TypeToStringEx(t, node, checker.TypeFormatFlagsNoTruncation) + return &detail +} + +func debugFlowNodeText(sf *ast.SourceFile, node *ast.Node) string { + if node == nil { + return "" + } + text := strings.Join(strings.Fields(scanner.GetSourceTextOfNodeFromSourceFile(sf, node, false)), " ") + if text == "" { + return "" + } + if len(text) > 80 { + return text[:77] + "..." + } + return text +} + +func debugFlowTransformationText(sf *ast.SourceFile, transformation *typeparser.PipingFlowTransformation) string { + if transformation == nil { + return "" + } + if transformation.Callee != nil { + return debugFlowNodeText(sf, transformation.Callee) + } + return debugFlowNodeText(sf, transformation.Node) +} + func layerSymbolDetail(tp *typeparser.TypeParser, c *checker.Checker, node *ast.Node) *string { typeCheckNode, types := classificationTypes(tp, c, node) for _, t := range types { diff --git a/etstesthooks/doc.go b/etstesthooks/doc.go index b350a4dd..a6840a6e 100644 --- a/etstesthooks/doc.go +++ b/etstesthooks/doc.go @@ -1,10 +1,10 @@ -// Package etstesthooks provides automatic Effect package mounting for fourslash tests. -// -// This package registers a PrepareTestFSCallback that detects Effect imports in -// fourslash test files and automatically mounts the real Effect node_modules into -// the test VFS via effecttest.MountEffect. -// -// Import this package with a blank import in test files that use fourslash with Effect: -// -// import _ "github.com/effect-ts/tsgo/etstesthooks" -package etstesthooks +// Package etstesthooks provides automatic Effect package mounting for fourslash tests. +// +// This package registers a PrepareTestFSCallback that detects Effect imports in +// fourslash test files and automatically mounts the real Effect node_modules into +// the test VFS via vfstest.MountEffect. +// +// Import this package with a blank import in test files that use fourslash with Effect: +// +// import _ "github.com/effect-ts/tsgo/etstesthooks" +package etstesthooks diff --git a/etstesthooks/init.go b/etstesthooks/init.go index 564ab6e9..bb61308e 100644 --- a/etstesthooks/init.go +++ b/etstesthooks/init.go @@ -1,41 +1,41 @@ -package etstesthooks - -import ( - "strings" - - "github.com/effect-ts/tsgo/internal/effecttest" - "github.com/microsoft/typescript-go/shim/fourslash" -) - -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. -func prepareTestFS(testfs map[string]any) { - hasEffectImport := false - hasV3Marker := false - for _, v := range testfs { - content, ok := v.(string) - if !ok { - continue - } - if strings.Contains(content, `from "effect`) { - hasEffectImport = true - } - if strings.HasPrefix(content, "// @effect-v3") || strings.Contains(content, "\n// @effect-v3") { - hasV3Marker = true - } - } - if !hasEffectImport { - return - } - version := effecttest.EffectV4 - if hasV3Marker { - version = effecttest.EffectV3 - } - if err := effecttest.MountEffect(version, testfs); err != nil { - panic(err) - } -} +package etstesthooks + +import ( + "strings" + + "github.com/effect-ts/tsgo/internal/bundledeffect" + "github.com/microsoft/typescript-go/shim/fourslash" +) + +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. +func prepareTestFS(testfs map[string]any) { + hasEffectImport := false + hasV3Marker := false + for _, v := range testfs { + content, ok := v.(string) + if !ok { + continue + } + if strings.Contains(content, `from "effect`) { + hasEffectImport = true + } + if strings.HasPrefix(content, "// @effect-v3") || strings.Contains(content, "\n// @effect-v3") { + hasV3Marker = true + } + } + if !hasEffectImport { + return + } + version := bundledeffect.EffectV4 + if hasV3Marker { + version = bundledeffect.EffectV3 + } + if err := bundledeffect.MountEffect(version, testfs); err != nil { + panic(err) + } +} diff --git a/internal/bundledeffect/effect.go b/internal/bundledeffect/effect.go new file mode 100644 index 00000000..784770db --- /dev/null +++ b/internal/bundledeffect/effect.go @@ -0,0 +1,105 @@ +package bundledeffect + +import ( + "fmt" + "io/fs" + "maps" + "os" + pathpkg "path" + "path/filepath" + "runtime" + "sync" + "testing/fstest" +) + +type EffectVersion string + +const ( + EffectV3 EffectVersion = "effect-v3" + EffectV4 EffectVersion = "effect-v4" +) + +func EffectTsGoRootPath() string { + _, filename, _, ok := runtime.Caller(0) + if !ok { + panic("failed to get caller info for EffectTsGoRootPath") + } + return filepath.Dir(filepath.Dir(filepath.Dir(filename))) +} + +func PackagePath(version EffectVersion, packageName string) string { + return filepath.Join(EffectTsGoRootPath(), "testdata", "tests", string(version), "node_modules", filepath.FromSlash(packageName)) +} + +func EnsurePackageInstalled(version EffectVersion, packageName string) error { + path := PackagePath(version, packageName) + if _, err := os.Stat(path); os.IsNotExist(err) { + return fmt.Errorf("package not installed at %s", path) + } + return nil +} + +type cacheKey struct { + version EffectVersion + packageName string +} + +var ( + fsCacheMu sync.Mutex + fsCaches = map[cacheKey]func() map[string]any{} +) + +func packageFSCache(version EffectVersion, packageName string) func() map[string]any { + key := cacheKey{version: version, packageName: packageName} + fsCacheMu.Lock() + defer fsCacheMu.Unlock() + if loader, ok := fsCaches[key]; ok { + return loader + } + loader := sync.OnceValue(func() map[string]any { + packagePath := PackagePath(version, packageName) + testfs := make(map[string]any) + + packageFS := os.DirFS(packagePath) + err := fs.WalkDir(packageFS, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + content, err := fs.ReadFile(packageFS, path) + if err != nil { + return err + } + vfsPath := pathpkg.Join("/node_modules", packageName, path) + testfs[vfsPath] = &fstest.MapFile{Data: content} + return nil + }) + if err != nil { + panic(fmt.Sprintf("Failed to read package directory: %v", err)) + } + + return testfs + }) + fsCaches[key] = loader + return loader +} + +func MountEffect(version EffectVersion, testfs map[string]any) error { + packages := []string{"effect", "pure-rand", "@standard-schema/spec", "fast-check", "@types/node"} + for _, packageName := range packages { + if err := EnsurePackageInstalled(version, packageName); err != nil { + return err + } + maps.Copy(testfs, packageFSCache(version, packageName)()) + } + + packageJSONPath := filepath.Join(EffectTsGoRootPath(), "testdata", "tests", string(version), "package.json") + packageJSON, err := os.ReadFile(packageJSONPath) + if err != nil { + return err + } + testfs["/.src/package.json"] = &fstest.MapFile{Data: packageJSON} + return nil +} diff --git a/internal/effecttest/baseline.go b/internal/effecttest/baseline.go index 916ece94..d9483634 100644 --- a/internal/effecttest/baseline.go +++ b/internal/effecttest/baseline.go @@ -10,6 +10,7 @@ import ( "testing" "unicode/utf8" + "github.com/effect-ts/tsgo/internal/bundledeffect" "github.com/effect-ts/tsgo/internal/layergraph" "github.com/effect-ts/tsgo/internal/typeparser" "github.com/microsoft/typescript-go/shim/ast" @@ -23,7 +24,7 @@ import ( // TestDataPath returns the path to our testdata directory. func TestDataPath() string { - return filepath.Join(EffectTsGoRootPath(), "testdata") + return filepath.Join(bundledeffect.EffectTsGoRootPath(), "testdata") } // BaselineLocalPath returns the path to write local baselines. @@ -450,16 +451,16 @@ func runEffectBaseline(t *testing.T, fileName string, actual string, subfolder s if err != nil { if os.IsNotExist(err) { // New baseline — write both local and reference files. - if err := os.MkdirAll(localDir, 0755); err != nil { + if err := os.MkdirAll(localDir, 0o755); err != nil { t.Fatalf("Failed to create local baseline directory: %v", err) } - if err := os.WriteFile(localPath, []byte(actual), 0644); err != nil { + if err := os.WriteFile(localPath, []byte(actual), 0o644); err != nil { t.Fatalf("Failed to write local baseline: %v", err) } - if err := os.MkdirAll(referenceDir, 0755); err != nil { + if err := os.MkdirAll(referenceDir, 0o755); err != nil { t.Fatalf("Failed to create reference baseline directory: %v", err) } - if err := os.WriteFile(referencePath, []byte(actual), 0644); err != nil { + if err := os.WriteFile(referencePath, []byte(actual), 0o644); err != nil { t.Fatalf("Failed to write reference baseline: %v", err) } t.Logf("Created new baseline at %s", referencePath) @@ -475,10 +476,10 @@ func runEffectBaseline(t *testing.T, fileName string, actual string, subfolder s } // Mismatch — write local baseline so developers can inspect the diff. - if err := os.MkdirAll(localDir, 0755); err != nil { + if err := os.MkdirAll(localDir, 0o755); err != nil { t.Fatalf("Failed to create local baseline directory: %v", err) } - if err := os.WriteFile(localPath, []byte(actual), 0644); err != nil { + if err := os.WriteFile(localPath, []byte(actual), 0o644); err != nil { t.Fatalf("Failed to write local baseline: %v", err) } diff --git a/internal/effecttest/document_symbols_runner.go b/internal/effecttest/document_symbols_runner.go index 6195e931..26297ad8 100644 --- a/internal/effecttest/document_symbols_runner.go +++ b/internal/effecttest/document_symbols_runner.go @@ -9,6 +9,7 @@ import ( "testing" "testing/fstest" + "github.com/effect-ts/tsgo/internal/bundledeffect" "github.com/microsoft/typescript-go/shim/bundled" "github.com/microsoft/typescript-go/shim/diagnostics" "github.com/microsoft/typescript-go/shim/ls/lsconv" @@ -20,12 +21,12 @@ import ( ) // DocumentSymbolTestCasesDir returns the path to the Effect document-symbol test cases directory. -func DocumentSymbolTestCasesDir(version EffectVersion) string { - return filepath.Join(EffectTsGoRootPath(), "testdata", "tests", string(version)+"-document-symbols") +func DocumentSymbolTestCasesDir(version bundledeffect.EffectVersion) string { + return filepath.Join(bundledeffect.EffectTsGoRootPath(), "testdata", "tests", string(version)+"-document-symbols") } // DiscoverDocumentSymbolTestCases finds all .ts test files in the document-symbol test cases directory. -func DiscoverDocumentSymbolTestCases(version EffectVersion) ([]string, error) { +func DiscoverDocumentSymbolTestCases(version bundledeffect.EffectVersion) ([]string, error) { dir := DocumentSymbolTestCasesDir(version) entries, err := os.ReadDir(dir) if err != nil { @@ -45,7 +46,7 @@ func DiscoverDocumentSymbolTestCases(version EffectVersion) ([]string, error) { } // RunEffectDocumentSymbolsTest executes one document-symbol baseline test case. -func RunEffectDocumentSymbolsTest(t *testing.T, version EffectVersion, testFile string) { +func RunEffectDocumentSymbolsTest(t *testing.T, version bundledeffect.EffectVersion, testFile string) { AcquireProgram() defer ReleaseProgram() @@ -58,7 +59,7 @@ func RunEffectDocumentSymbolsTest(t *testing.T, version EffectVersion, testFile units := parseTestUnits(string(content), defaultFileName) testfs := make(map[string]any) - if err := MountEffect(version, testfs); err != nil { + if err := bundledeffect.MountEffect(version, testfs); err != nil { t.Fatal("Failed to mount Effect:", err) } @@ -147,7 +148,8 @@ func collectDocumentSymbolsForFile(t *testing.T, session *project.Session, fileN func collectHierarchicalDocumentSymbols(t *testing.T, langService interface { ProvideDocumentSymbols(ctx context.Context, documentURI lsproto.DocumentUri) (lsproto.DocumentSymbolResponse, error) -}, uri lsproto.DocumentUri) []*lsproto.DocumentSymbol { +}, uri lsproto.DocumentUri, +) []*lsproto.DocumentSymbol { t.Helper() caps := &lsproto.ClientCapabilities{ @@ -171,7 +173,8 @@ func collectHierarchicalDocumentSymbols(t *testing.T, langService interface { func collectFlatDocumentSymbols(t *testing.T, langService interface { ProvideDocumentSymbols(ctx context.Context, documentURI lsproto.DocumentUri) (lsproto.DocumentSymbolResponse, error) -}, uri lsproto.DocumentUri) []*lsproto.SymbolInformation { +}, uri lsproto.DocumentUri, +) []*lsproto.SymbolInformation { t.Helper() caps := &lsproto.ClientCapabilities{ diff --git a/internal/effecttest/document_symbols_runner_test.go b/internal/effecttest/document_symbols_runner_test.go index ace3826f..af66de55 100644 --- a/internal/effecttest/document_symbols_runner_test.go +++ b/internal/effecttest/document_symbols_runner_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/effect-ts/tsgo/internal/bundledeffect" "github.com/effect-ts/tsgo/internal/effecttest" _ "github.com/effect-ts/tsgo/etslshooks" @@ -12,11 +13,11 @@ import ( func TestEffectDocumentSymbols(t *testing.T) { t.Parallel() - if err := effecttest.EnsureEffectInstalled(effecttest.EffectV4); err != nil { + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV4, "effect"); err != nil { t.Skip("Effect not installed:", err) } - cases, err := effecttest.DiscoverDocumentSymbolTestCases(effecttest.EffectV4) + cases, err := effecttest.DiscoverDocumentSymbolTestCases(bundledeffect.EffectV4) if err != nil { t.Fatal("Failed to discover document symbol test cases:", err) } @@ -31,18 +32,18 @@ func TestEffectDocumentSymbols(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - effecttest.RunEffectDocumentSymbolsTest(t, effecttest.EffectV4, tc) + effecttest.RunEffectDocumentSymbolsTest(t, bundledeffect.EffectV4, tc) }) } } func TestEffectV3DocumentSymbols(t *testing.T) { t.Parallel() - if err := effecttest.EnsureEffectInstalled(effecttest.EffectV3); err != nil { + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV3, "effect"); err != nil { t.Skip("Effect V3 not installed:", err) } - cases, err := effecttest.DiscoverDocumentSymbolTestCases(effecttest.EffectV3) + cases, err := effecttest.DiscoverDocumentSymbolTestCases(bundledeffect.EffectV3) if err != nil { t.Fatal("Failed to discover V3 document symbol test cases:", err) } @@ -57,7 +58,7 @@ func TestEffectV3DocumentSymbols(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - effecttest.RunEffectDocumentSymbolsTest(t, effecttest.EffectV3, tc) + effecttest.RunEffectDocumentSymbolsTest(t, bundledeffect.EffectV3, tc) }) } } diff --git a/internal/effecttest/quickfix_runner.go b/internal/effecttest/quickfix_runner.go index 743551d6..e4ab3944 100644 --- a/internal/effecttest/quickfix_runner.go +++ b/internal/effecttest/quickfix_runner.go @@ -1,131 +1,132 @@ -package effecttest - -import ( - "fmt" - "os" - "strings" - "testing" - - "github.com/microsoft/typescript-go/shim/fourslash" - "github.com/microsoft/typescript-go/shim/ls/lsconv" - "github.com/microsoft/typescript-go/shim/tspath" -) - -// isDisableStyleFix returns true if the fix title matches the disable-style pattern: -// "Disable ... for this line" or "Disable ... for entire file". -func isDisableStyleFix(title string) bool { - return strings.HasPrefix(title, "Disable ") && - (strings.HasSuffix(title, " for this line") || strings.HasSuffix(title, " for entire file")) -} - -// RunEffectQuickFixTest executes a single Effect quick-fix baseline test case. -// It creates a fourslash test instance, collects quick-fix inventory and application -// results, and generates a *.quickfixes.txt baseline. -func RunEffectQuickFixTest(t *testing.T, version EffectVersion, testFile string) { - AcquireProgram() - defer ReleaseProgram() - - // Read the test file - content, err := os.ReadFile(testFile) - if err != nil { - t.Fatal("Failed to read test file:", err) - } - - // Parse test file into units (handles @filename directives) - defaultFileName := tspath.GetBaseFileName(testFile) - units := parseTestUnits(string(content), defaultFileName) - - // Build fourslash content with @filename directives - var sb strings.Builder - currentDirectory := "/.src" - - // Check if a tsconfig is provided - hasTsConfig := false - for _, unit := range units { - unitName := tspath.GetNormalizedAbsolutePath(unit.name, currentDirectory) - if strings.HasSuffix(strings.ToLower(unitName), "tsconfig.json") || strings.HasSuffix(strings.ToLower(unitName), "jsconfig.json") { - hasTsConfig = true - break - } - } - - // Inject default tsconfig if none was provided - if !hasTsConfig { - sb.WriteString("// @filename: ") - sb.WriteString(tspath.GetNormalizedAbsolutePath("tsconfig.json", currentDirectory)) - sb.WriteString("\n") - sb.WriteString(DefaultTsConfig) - sb.WriteString("\n") - } - - // Collect test file paths for later use - var testFileNames []string - - // Add all test units as fourslash files - for _, unit := range units { - unitName := tspath.GetNormalizedAbsolutePath(unit.name, currentDirectory) - sb.WriteString("// @filename: ") - sb.WriteString(unitName) - sb.WriteString("\n") - sb.WriteString(unit.content) - sb.WriteString("\n") - - // Track non-config files - if !strings.HasSuffix(strings.ToLower(unitName), "tsconfig.json") && !strings.HasSuffix(strings.ToLower(unitName), "jsconfig.json") { - testFileNames = append(testFileNames, unitName) - } - } - - // Create fourslash test instance - f, done := fourslash.NewFourslash(t, nil, sb.String()) - defer done() - - // Collect inventory and application results across all test files - var inventory []QuickFixInventoryEntry - var results []QuickFixApplicationResult - diagCounter := 0 - - for _, fileName := range testFileNames { - fileURI := lsconv.FileNameToDocumentURI(fileName) - - // Get quick-fix inventory for this file - fixes := f.GetQuickFixesForDiagnostics(t, fileURI) - - for _, fix := range fixes { - diagCounter++ - diagID := fmt.Sprintf("D%d", diagCounter) - - inventory = append(inventory, QuickFixInventoryEntry{ - ID: diagID, - FileURI: fileURI, - Diagnostic: fix.Diagnostic, - FixTitles: fix.FixTitles, - }) - - // Apply each quick fix and collect results - for fixIdx, fixTitle := range fix.FixTitles { - if isDisableStyleFix(fixTitle) { - results = append(results, QuickFixApplicationResult{ - DiagnosticID: diagID, - FixIndex: fixIdx, - FixTitle: fixTitle, - Skipped: true, - }) - } else { - result := f.ApplyQuickFix(t, fileURI, fix.Diagnostic, fixIdx) - results = append(results, QuickFixApplicationResult{ - DiagnosticID: diagID, - FixIndex: result.FixIndex, - FixTitle: result.FixTitle, - Changes: result.Changes, - }) - } - } - } - } - - // Generate baseline - baselineName := strings.TrimSuffix(tspath.GetBaseFileName(testFile), ".ts") - baselineSubfolder := string(version) - DoQuickFixBaseline(t, baselineName, baselineSubfolder, inventory, results) -} +package effecttest + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/effect-ts/tsgo/internal/bundledeffect" + "github.com/microsoft/typescript-go/shim/fourslash" + "github.com/microsoft/typescript-go/shim/ls/lsconv" + "github.com/microsoft/typescript-go/shim/tspath" +) + +// isDisableStyleFix returns true if the fix title matches the disable-style pattern: +// "Disable ... for this line" or "Disable ... for entire file". +func isDisableStyleFix(title string) bool { + return strings.HasPrefix(title, "Disable ") && + (strings.HasSuffix(title, " for this line") || strings.HasSuffix(title, " for entire file")) +} + +// RunEffectQuickFixTest executes a single Effect quick-fix baseline test case. +// It creates a fourslash test instance, collects quick-fix inventory and application +// results, and generates a *.quickfixes.txt baseline. +func RunEffectQuickFixTest(t *testing.T, version bundledeffect.EffectVersion, testFile string) { + AcquireProgram() + defer ReleaseProgram() + + // Read the test file + content, err := os.ReadFile(testFile) + if err != nil { + t.Fatal("Failed to read test file:", err) + } + + // Parse test file into units (handles @filename directives) + defaultFileName := tspath.GetBaseFileName(testFile) + units := parseTestUnits(string(content), defaultFileName) + + // Build fourslash content with @filename directives + var sb strings.Builder + currentDirectory := "/.src" + + // Check if a tsconfig is provided + hasTsConfig := false + for _, unit := range units { + unitName := tspath.GetNormalizedAbsolutePath(unit.name, currentDirectory) + if strings.HasSuffix(strings.ToLower(unitName), "tsconfig.json") || strings.HasSuffix(strings.ToLower(unitName), "jsconfig.json") { + hasTsConfig = true + break + } + } + + // Inject default tsconfig if none was provided + if !hasTsConfig { + sb.WriteString("// @filename: ") + sb.WriteString(tspath.GetNormalizedAbsolutePath("tsconfig.json", currentDirectory)) + sb.WriteString("\n") + sb.WriteString(DefaultTsConfig) + sb.WriteString("\n") + } + + // Collect test file paths for later use + var testFileNames []string + + // Add all test units as fourslash files + for _, unit := range units { + unitName := tspath.GetNormalizedAbsolutePath(unit.name, currentDirectory) + sb.WriteString("// @filename: ") + sb.WriteString(unitName) + sb.WriteString("\n") + sb.WriteString(unit.content) + sb.WriteString("\n") + + // Track non-config files + if !strings.HasSuffix(strings.ToLower(unitName), "tsconfig.json") && !strings.HasSuffix(strings.ToLower(unitName), "jsconfig.json") { + testFileNames = append(testFileNames, unitName) + } + } + + // Create fourslash test instance + f, done := fourslash.NewFourslash(t, nil, sb.String()) + defer done() + + // Collect inventory and application results across all test files + var inventory []QuickFixInventoryEntry + var results []QuickFixApplicationResult + diagCounter := 0 + + for _, fileName := range testFileNames { + fileURI := lsconv.FileNameToDocumentURI(fileName) + + // Get quick-fix inventory for this file + fixes := f.GetQuickFixesForDiagnostics(t, fileURI) + + for _, fix := range fixes { + diagCounter++ + diagID := fmt.Sprintf("D%d", diagCounter) + + inventory = append(inventory, QuickFixInventoryEntry{ + ID: diagID, + FileURI: fileURI, + Diagnostic: fix.Diagnostic, + FixTitles: fix.FixTitles, + }) + + // Apply each quick fix and collect results + for fixIdx, fixTitle := range fix.FixTitles { + if isDisableStyleFix(fixTitle) { + results = append(results, QuickFixApplicationResult{ + DiagnosticID: diagID, + FixIndex: fixIdx, + FixTitle: fixTitle, + Skipped: true, + }) + } else { + result := f.ApplyQuickFix(t, fileURI, fix.Diagnostic, fixIdx) + results = append(results, QuickFixApplicationResult{ + DiagnosticID: diagID, + FixIndex: result.FixIndex, + FixTitle: result.FixTitle, + Changes: result.Changes, + }) + } + } + } + } + + // Generate baseline + baselineName := strings.TrimSuffix(tspath.GetBaseFileName(testFile), ".ts") + baselineSubfolder := string(version) + DoQuickFixBaseline(t, baselineName, baselineSubfolder, inventory, results) +} diff --git a/internal/effecttest/quickfix_runner_test.go b/internal/effecttest/quickfix_runner_test.go index 94e367cb..f019c497 100644 --- a/internal/effecttest/quickfix_runner_test.go +++ b/internal/effecttest/quickfix_runner_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/effect-ts/tsgo/internal/bundledeffect" "github.com/effect-ts/tsgo/internal/effecttest" // Register fourslash VFS callback to mount Effect packages @@ -15,11 +16,11 @@ import ( func TestEffectQuickFixes(t *testing.T) { t.Parallel() - if err := effecttest.EnsureEffectInstalled(effecttest.EffectV4); err != nil { + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV4, "effect"); err != nil { t.Skip("Effect not installed:", err) } - cases, err := effecttest.DiscoverTestCases(effecttest.EffectV4) + cases, err := effecttest.DiscoverTestCases(bundledeffect.EffectV4) if err != nil { t.Fatal("Failed to discover test cases:", err) } @@ -34,18 +35,18 @@ func TestEffectQuickFixes(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - effecttest.RunEffectQuickFixTest(t, effecttest.EffectV4, tc) + effecttest.RunEffectQuickFixTest(t, bundledeffect.EffectV4, tc) }) } } func TestEffectV3QuickFixes(t *testing.T) { t.Parallel() - if err := effecttest.EnsureEffectInstalled(effecttest.EffectV3); err != nil { + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV3, "effect"); err != nil { t.Skip("Effect V3 not installed:", err) } - cases, err := effecttest.DiscoverTestCases(effecttest.EffectV3) + cases, err := effecttest.DiscoverTestCases(bundledeffect.EffectV3) if err != nil { t.Fatal("Failed to discover V3 test cases:", err) } @@ -60,7 +61,7 @@ func TestEffectV3QuickFixes(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - effecttest.RunEffectQuickFixTest(t, effecttest.EffectV3, tc) + effecttest.RunEffectQuickFixTest(t, bundledeffect.EffectV3, tc) }) } } diff --git a/internal/effecttest/refactor_runner.go b/internal/effecttest/refactor_runner.go index 24e1591b..96998581 100644 --- a/internal/effecttest/refactor_runner.go +++ b/internal/effecttest/refactor_runner.go @@ -1,250 +1,251 @@ -package effecttest - -import ( - "fmt" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" - "testing" - - "github.com/microsoft/typescript-go/shim/fourslash" - "github.com/microsoft/typescript-go/shim/ls/lsconv" - "github.com/microsoft/typescript-go/shim/lsp/lsproto" - "github.com/microsoft/typescript-go/shim/tspath" -) - -// RefactorTestCasesDir returns the path to the Effect refactor test cases directory. -func RefactorTestCasesDir(version EffectVersion) string { - return filepath.Join(EffectTsGoRootPath(), "testdata", "tests", string(version)+"-refactors") -} - -// DiscoverRefactorTestCases finds all .ts test files in the refactor test cases directory. -func DiscoverRefactorTestCases(version EffectVersion) ([]string, error) { - dir := RefactorTestCasesDir(version) - entries, err := os.ReadDir(dir) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, err - } - - var cases []string - for _, entry := range entries { - if !entry.IsDir() && filepath.Ext(entry.Name()) == ".ts" { - cases = append(cases, filepath.Join(dir, entry.Name())) - } - } - return cases, nil -} - -// refactorCommentPrefix matches lines starting with "// refactor:" -var refactorCommentPrefix = regexp.MustCompile(`^//\s*refactor:\s*`) - -// rangeRegex matches a single "L:C-L:C" range token (dash-separated start and end). -var rangeRegex = regexp.MustCompile(`^(\d+):(\d+)-(\d+):(\d+)$`) - -// pointRegex matches a single "L:C" point selection token (no dash). -var pointRegex = regexp.MustCompile(`^(\d+):(\d+)$`) - -// refactorCommentResult holds the parsed refactor comment data. -type refactorCommentResult struct { - found bool - ranges []lsproto.Range -} - -// parseRefactorComment scans the test file content for a first-line -// "// refactor: L:C-L:C,L:C-L:C,..." comment (1-based lines and columns). -// Multiple ranges are separated by commas. A single "L:C" denotes a point -// selection (zero-width range where start == end). The comment is NOT stripped -// from the source content. -func parseRefactorComment(content string) refactorCommentResult { - lines := strings.SplitN(content, "\n", 2) - firstLine := strings.TrimSpace(lines[0]) - - if !refactorCommentPrefix.MatchString(firstLine) { - return refactorCommentResult{found: false} - } - - // Strip the "// refactor:" prefix to get the ranges portion - payload := refactorCommentPrefix.ReplaceAllString(firstLine, "") - - // Split on "," to get individual range/point tokens - parts := strings.Split(payload, ",") - var ranges []lsproto.Range - for _, part := range parts { - part = strings.TrimSpace(part) - if part == "" { - continue - } - if m := rangeRegex.FindStringSubmatch(part); m != nil { - startLine, _ := strconv.Atoi(m[1]) - startCol, _ := strconv.Atoi(m[2]) - endLine, _ := strconv.Atoi(m[3]) - endCol, _ := strconv.Atoi(m[4]) - - ranges = append(ranges, lsproto.Range{ - Start: lsproto.Position{Line: uint32(startLine - 1), Character: uint32(startCol - 1)}, - End: lsproto.Position{Line: uint32(endLine - 1), Character: uint32(endCol - 1)}, - }) - } else if m := pointRegex.FindStringSubmatch(part); m != nil { - line, _ := strconv.Atoi(m[1]) - col, _ := strconv.Atoi(m[2]) - - pos := lsproto.Position{Line: uint32(line - 1), Character: uint32(col - 1)} - ranges = append(ranges, lsproto.Range{ - Start: pos, - End: pos, - }) - } - } - - if len(ranges) == 0 { - return refactorCommentResult{found: false} - } - - return refactorCommentResult{found: true, ranges: ranges} -} - -// RunEffectRefactorTest executes a single Effect refactor baseline test case. -// It creates a fourslash test instance, collects refactor inventory and application -// results for the selection specified by a "// refactor:" comment, and generates a *.refactors.txt baseline. -func RunEffectRefactorTest(t *testing.T, version EffectVersion, testFile string) { - AcquireProgram() - defer ReleaseProgram() - - // Read the test file - content, err := os.ReadFile(testFile) - if err != nil { - t.Fatal("Failed to read test file:", err) - } - - // Parse the "// refactor:" comment marker (comment is preserved in source) - rc := parseRefactorComment(string(content)) - sourceContent := string(content) - - // Parse test file into units (handles @filename directives) - defaultFileName := tspath.GetBaseFileName(testFile) - units := parseTestUnits(sourceContent, defaultFileName) - - // Build fourslash content with @filename directives - var sb strings.Builder - currentDirectory := "/.src" - - // Check if a tsconfig is provided - hasTsConfig := false - for _, unit := range units { - unitName := tspath.GetNormalizedAbsolutePath(unit.name, currentDirectory) - if strings.HasSuffix(strings.ToLower(unitName), "tsconfig.json") || strings.HasSuffix(strings.ToLower(unitName), "jsconfig.json") { - hasTsConfig = true - break - } - } - - // Inject default tsconfig if none was provided - if !hasTsConfig { - sb.WriteString("// @filename: ") - sb.WriteString(tspath.GetNormalizedAbsolutePath("tsconfig.json", currentDirectory)) - sb.WriteString("\n") - sb.WriteString(DefaultTsConfig) - sb.WriteString("\n") - } - - // Collect test file paths for later use - var testFileNames []string - - // Add all test units as fourslash files - for _, unit := range units { - unitName := tspath.GetNormalizedAbsolutePath(unit.name, currentDirectory) - sb.WriteString("// @filename: ") - sb.WriteString(unitName) - sb.WriteString("\n") - sb.WriteString(unit.content) - sb.WriteString("\n") - - // Track non-config files - if !strings.HasSuffix(strings.ToLower(unitName), "tsconfig.json") && !strings.HasSuffix(strings.ToLower(unitName), "jsconfig.json") { - testFileNames = append(testFileNames, unitName) - } - } - - // Create fourslash test instance - f, done := fourslash.NewFourslash(t, nil, sb.String()) - defer done() - - // Collect inventory and application results - var inventory []RefactorInventoryEntry - var results []RefactorApplicationResult - rangeCounter := 0 - - // If a "// refactor:" comment was found, test with each parsed selection range - if rc.found && len(testFileNames) > 0 { - fileURI := lsconv.FileNameToDocumentURI(testFileNames[0]) - - for _, selRange := range rc.ranges { - rangeCounter++ - rangeID := fmt.Sprintf("R%d", rangeCounter) - - actions := f.GetRefactorActionsForRange(t, fileURI, selRange) - - var titles []string - var kinds []string - for _, action := range actions { - titles = append(titles, action.Title) - kinds = append(kinds, action.Kind) - } - - rangeDesc := fmt.Sprintf("%d:%d-%d:%d", - selRange.Start.Line+1, - selRange.Start.Character+1, - selRange.End.Line+1, - selRange.End.Character+1, - ) - - inventory = append(inventory, RefactorInventoryEntry{ - ID: rangeID, - RangeText: rangeDesc, - ActionTitles: titles, - ActionKinds: kinds, - }) - - // Apply each refactor action and collect results - for actionIdx := range actions { - // Reset caret to beginning of file before each action to avoid - // stale caret positions from a previous action's apply+undo cycle. - f.GoToBOF(t) - result := f.ApplyRefactorAction(t, fileURI, selRange, actionIdx) - results = append(results, RefactorApplicationResult{ - RangeID: rangeID, - ActionIndex: result.ActionIndex, - ActionTitle: result.ActionTitle, - Changes: result.Changes, - }) - } - } - } - - // Also test that an empty selection (zero-width range) produces no refactors - if len(testFileNames) > 0 { - fileURI := lsconv.FileNameToDocumentURI(testFileNames[0]) - emptyRange := fourslash.RangeMarker{} - emptyActions := f.GetRefactorActionsForRange(t, fileURI, emptyRange.LSRange) - - rangeCounter++ - rangeID := fmt.Sprintf("R%d", rangeCounter) - inventory = append(inventory, RefactorInventoryEntry{ - ID: rangeID, - RangeText: "empty (0:0-0:0)", - ActionTitles: nil, - ActionKinds: nil, - }) - _ = emptyActions - } - - // Generate baseline - baselineName := strings.TrimSuffix(tspath.GetBaseFileName(testFile), ".ts") - baselineSubfolder := string(version) - DoRefactorBaseline(t, baselineName, baselineSubfolder, inventory, results) -} +package effecttest + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "testing" + + "github.com/effect-ts/tsgo/internal/bundledeffect" + "github.com/microsoft/typescript-go/shim/fourslash" + "github.com/microsoft/typescript-go/shim/ls/lsconv" + "github.com/microsoft/typescript-go/shim/lsp/lsproto" + "github.com/microsoft/typescript-go/shim/tspath" +) + +// RefactorTestCasesDir returns the path to the Effect refactor test cases directory. +func RefactorTestCasesDir(version bundledeffect.EffectVersion) string { + return filepath.Join(bundledeffect.EffectTsGoRootPath(), "testdata", "tests", string(version)+"-refactors") +} + +// DiscoverRefactorTestCases finds all .ts test files in the refactor test cases directory. +func DiscoverRefactorTestCases(version bundledeffect.EffectVersion) ([]string, error) { + dir := RefactorTestCasesDir(version) + entries, err := os.ReadDir(dir) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + + var cases []string + for _, entry := range entries { + if !entry.IsDir() && filepath.Ext(entry.Name()) == ".ts" { + cases = append(cases, filepath.Join(dir, entry.Name())) + } + } + return cases, nil +} + +// refactorCommentPrefix matches lines starting with "// refactor:" +var refactorCommentPrefix = regexp.MustCompile(`^//\s*refactor:\s*`) + +// rangeRegex matches a single "L:C-L:C" range token (dash-separated start and end). +var rangeRegex = regexp.MustCompile(`^(\d+):(\d+)-(\d+):(\d+)$`) + +// pointRegex matches a single "L:C" point selection token (no dash). +var pointRegex = regexp.MustCompile(`^(\d+):(\d+)$`) + +// refactorCommentResult holds the parsed refactor comment data. +type refactorCommentResult struct { + found bool + ranges []lsproto.Range +} + +// parseRefactorComment scans the test file content for a first-line +// "// refactor: L:C-L:C,L:C-L:C,..." comment (1-based lines and columns). +// Multiple ranges are separated by commas. A single "L:C" denotes a point +// selection (zero-width range where start == end). The comment is NOT stripped +// from the source content. +func parseRefactorComment(content string) refactorCommentResult { + lines := strings.SplitN(content, "\n", 2) + firstLine := strings.TrimSpace(lines[0]) + + if !refactorCommentPrefix.MatchString(firstLine) { + return refactorCommentResult{found: false} + } + + // Strip the "// refactor:" prefix to get the ranges portion + payload := refactorCommentPrefix.ReplaceAllString(firstLine, "") + + // Split on "," to get individual range/point tokens + parts := strings.Split(payload, ",") + var ranges []lsproto.Range + for _, part := range parts { + part = strings.TrimSpace(part) + if part == "" { + continue + } + if m := rangeRegex.FindStringSubmatch(part); m != nil { + startLine, _ := strconv.Atoi(m[1]) + startCol, _ := strconv.Atoi(m[2]) + endLine, _ := strconv.Atoi(m[3]) + endCol, _ := strconv.Atoi(m[4]) + + ranges = append(ranges, lsproto.Range{ + Start: lsproto.Position{Line: uint32(startLine - 1), Character: uint32(startCol - 1)}, + End: lsproto.Position{Line: uint32(endLine - 1), Character: uint32(endCol - 1)}, + }) + } else if m := pointRegex.FindStringSubmatch(part); m != nil { + line, _ := strconv.Atoi(m[1]) + col, _ := strconv.Atoi(m[2]) + + pos := lsproto.Position{Line: uint32(line - 1), Character: uint32(col - 1)} + ranges = append(ranges, lsproto.Range{ + Start: pos, + End: pos, + }) + } + } + + if len(ranges) == 0 { + return refactorCommentResult{found: false} + } + + return refactorCommentResult{found: true, ranges: ranges} +} + +// RunEffectRefactorTest executes a single Effect refactor baseline test case. +// It creates a fourslash test instance, collects refactor inventory and application +// results for the selection specified by a "// refactor:" comment, and generates a *.refactors.txt baseline. +func RunEffectRefactorTest(t *testing.T, version bundledeffect.EffectVersion, testFile string) { + AcquireProgram() + defer ReleaseProgram() + + // Read the test file + content, err := os.ReadFile(testFile) + if err != nil { + t.Fatal("Failed to read test file:", err) + } + + // Parse the "// refactor:" comment marker (comment is preserved in source) + rc := parseRefactorComment(string(content)) + sourceContent := string(content) + + // Parse test file into units (handles @filename directives) + defaultFileName := tspath.GetBaseFileName(testFile) + units := parseTestUnits(sourceContent, defaultFileName) + + // Build fourslash content with @filename directives + var sb strings.Builder + currentDirectory := "/.src" + + // Check if a tsconfig is provided + hasTsConfig := false + for _, unit := range units { + unitName := tspath.GetNormalizedAbsolutePath(unit.name, currentDirectory) + if strings.HasSuffix(strings.ToLower(unitName), "tsconfig.json") || strings.HasSuffix(strings.ToLower(unitName), "jsconfig.json") { + hasTsConfig = true + break + } + } + + // Inject default tsconfig if none was provided + if !hasTsConfig { + sb.WriteString("// @filename: ") + sb.WriteString(tspath.GetNormalizedAbsolutePath("tsconfig.json", currentDirectory)) + sb.WriteString("\n") + sb.WriteString(DefaultTsConfig) + sb.WriteString("\n") + } + + // Collect test file paths for later use + var testFileNames []string + + // Add all test units as fourslash files + for _, unit := range units { + unitName := tspath.GetNormalizedAbsolutePath(unit.name, currentDirectory) + sb.WriteString("// @filename: ") + sb.WriteString(unitName) + sb.WriteString("\n") + sb.WriteString(unit.content) + sb.WriteString("\n") + + // Track non-config files + if !strings.HasSuffix(strings.ToLower(unitName), "tsconfig.json") && !strings.HasSuffix(strings.ToLower(unitName), "jsconfig.json") { + testFileNames = append(testFileNames, unitName) + } + } + + // Create fourslash test instance + f, done := fourslash.NewFourslash(t, nil, sb.String()) + defer done() + + // Collect inventory and application results + var inventory []RefactorInventoryEntry + var results []RefactorApplicationResult + rangeCounter := 0 + + // If a "// refactor:" comment was found, test with each parsed selection range + if rc.found && len(testFileNames) > 0 { + fileURI := lsconv.FileNameToDocumentURI(testFileNames[0]) + + for _, selRange := range rc.ranges { + rangeCounter++ + rangeID := fmt.Sprintf("R%d", rangeCounter) + + actions := f.GetRefactorActionsForRange(t, fileURI, selRange) + + var titles []string + var kinds []string + for _, action := range actions { + titles = append(titles, action.Title) + kinds = append(kinds, action.Kind) + } + + rangeDesc := fmt.Sprintf("%d:%d-%d:%d", + selRange.Start.Line+1, + selRange.Start.Character+1, + selRange.End.Line+1, + selRange.End.Character+1, + ) + + inventory = append(inventory, RefactorInventoryEntry{ + ID: rangeID, + RangeText: rangeDesc, + ActionTitles: titles, + ActionKinds: kinds, + }) + + // Apply each refactor action and collect results + for actionIdx := range actions { + // Reset caret to beginning of file before each action to avoid + // stale caret positions from a previous action's apply+undo cycle. + f.GoToBOF(t) + result := f.ApplyRefactorAction(t, fileURI, selRange, actionIdx) + results = append(results, RefactorApplicationResult{ + RangeID: rangeID, + ActionIndex: result.ActionIndex, + ActionTitle: result.ActionTitle, + Changes: result.Changes, + }) + } + } + } + + // Also test that an empty selection (zero-width range) produces no refactors + if len(testFileNames) > 0 { + fileURI := lsconv.FileNameToDocumentURI(testFileNames[0]) + emptyRange := fourslash.RangeMarker{} + emptyActions := f.GetRefactorActionsForRange(t, fileURI, emptyRange.LSRange) + + rangeCounter++ + rangeID := fmt.Sprintf("R%d", rangeCounter) + inventory = append(inventory, RefactorInventoryEntry{ + ID: rangeID, + RangeText: "empty (0:0-0:0)", + ActionTitles: nil, + ActionKinds: nil, + }) + _ = emptyActions + } + + // Generate baseline + baselineName := strings.TrimSuffix(tspath.GetBaseFileName(testFile), ".ts") + baselineSubfolder := string(version) + DoRefactorBaseline(t, baselineName, baselineSubfolder, inventory, results) +} diff --git a/internal/effecttest/refactor_runner_test.go b/internal/effecttest/refactor_runner_test.go index 4187babe..ee194230 100644 --- a/internal/effecttest/refactor_runner_test.go +++ b/internal/effecttest/refactor_runner_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/effect-ts/tsgo/internal/bundledeffect" "github.com/effect-ts/tsgo/internal/effecttest" // Register fourslash VFS callback to mount Effect packages @@ -15,11 +16,11 @@ import ( func TestEffectRefactors(t *testing.T) { t.Parallel() - if err := effecttest.EnsureEffectInstalled(effecttest.EffectV4); err != nil { + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV4, "effect"); err != nil { t.Skip("Effect not installed:", err) } - cases, err := effecttest.DiscoverRefactorTestCases(effecttest.EffectV4) + cases, err := effecttest.DiscoverRefactorTestCases(bundledeffect.EffectV4) if err != nil { t.Fatal("Failed to discover refactor test cases:", err) } @@ -34,18 +35,18 @@ func TestEffectRefactors(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - effecttest.RunEffectRefactorTest(t, effecttest.EffectV4, tc) + effecttest.RunEffectRefactorTest(t, bundledeffect.EffectV4, tc) }) } } func TestEffectV3Refactors(t *testing.T) { t.Parallel() - if err := effecttest.EnsureEffectInstalled(effecttest.EffectV3); err != nil { + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV3, "effect"); err != nil { t.Skip("Effect V3 not installed:", err) } - cases, err := effecttest.DiscoverRefactorTestCases(effecttest.EffectV3) + cases, err := effecttest.DiscoverRefactorTestCases(bundledeffect.EffectV3) if err != nil { t.Fatal("Failed to discover V3 refactor test cases:", err) } @@ -60,7 +61,7 @@ func TestEffectV3Refactors(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - effecttest.RunEffectRefactorTest(t, effecttest.EffectV3, tc) + effecttest.RunEffectRefactorTest(t, bundledeffect.EffectV3, tc) }) } } diff --git a/internal/effecttest/runner.go b/internal/effecttest/runner.go index ffa33e09..301916cd 100644 --- a/internal/effecttest/runner.go +++ b/internal/effecttest/runner.go @@ -20,6 +20,7 @@ import ( "github.com/microsoft/typescript-go/shim/vfs" "github.com/microsoft/typescript-go/shim/vfs/vfstest" + "github.com/effect-ts/tsgo/internal/bundledeffect" "github.com/effect-ts/tsgo/internal/typeparser" // Import etscheckerhooks to register Effect diagnostic callbacks @@ -27,12 +28,12 @@ import ( ) // TestCasesDir returns the path to the Effect test cases directory for the given version. -func TestCasesDir(version EffectVersion) string { - return filepath.Join(EffectTsGoRootPath(), "testdata", "tests", string(version)) +func TestCasesDir(version bundledeffect.EffectVersion) string { + return filepath.Join(bundledeffect.EffectTsGoRootPath(), "testdata", "tests", string(version)) } // DiscoverTestCases finds all .ts test files in the test cases directory for the given version. -func DiscoverTestCases(version EffectVersion) ([]string, error) { +func DiscoverTestCases(version bundledeffect.EffectVersion) ([]string, error) { dir := TestCasesDir(version) entries, err := os.ReadDir(dir) if err != nil { @@ -48,8 +49,10 @@ func DiscoverTestCases(version EffectVersion) ([]string, error) { return cases, nil } -var lineDelimiter = regexp.MustCompile("\r?\n") -var optionRegex = regexp.MustCompile(`(?m)^\/{2}\s*@(\w+)\s*:\s*([^\r\n]*)`) +var ( + lineDelimiter = regexp.MustCompile("\r?\n") + optionRegex = regexp.MustCompile(`(?m)^\/{2}\s*@(\w+)\s*:\s*([^\r\n]*)`) +) // testUnit represents a single file within a multi-file test case. type testUnit struct { @@ -121,7 +124,7 @@ const DefaultTsConfig = `{ }` // RunEffectTest executes a single Effect diagnostic test case for the given version. -func RunEffectTest(t *testing.T, version EffectVersion, testFile string) { +func RunEffectTest(t *testing.T, version bundledeffect.EffectVersion, testFile string) { AcquireProgram() defer ReleaseProgram() @@ -139,7 +142,7 @@ func RunEffectTest(t *testing.T, version EffectVersion, testFile string) { testfs := make(map[string]any) // Mount Effect package - if err := MountEffect(version, testfs); err != nil { + if err := bundledeffect.MountEffect(version, testfs); err != nil { t.Fatal("Failed to mount Effect:", err) } diff --git a/internal/effecttest/runner_test.go b/internal/effecttest/runner_test.go index fbc0af9b..391be2a2 100644 --- a/internal/effecttest/runner_test.go +++ b/internal/effecttest/runner_test.go @@ -4,16 +4,18 @@ import ( "path/filepath" "strings" "testing" + + "github.com/effect-ts/tsgo/internal/bundledeffect" ) func TestEffectDiagnostics(t *testing.T) { t.Parallel() // Skip if Effect not installed - if err := EnsureEffectInstalled(EffectV4); err != nil { + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV4, "effect"); err != nil { t.Skip("Effect not installed:", err) } - cases, err := DiscoverTestCases(EffectV4) + cases, err := DiscoverTestCases(bundledeffect.EffectV4) if err != nil { t.Fatal("Failed to discover test cases:", err) } @@ -28,18 +30,18 @@ func TestEffectDiagnostics(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - RunEffectTest(t, EffectV4, tc) + RunEffectTest(t, bundledeffect.EffectV4, tc) }) } } func TestEffectV3Diagnostics(t *testing.T) { t.Parallel() - if err := EnsureEffectInstalled(EffectV3); err != nil { + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV3, "effect"); err != nil { t.Skip("Effect V3 not installed:", err) } - cases, err := DiscoverTestCases(EffectV3) + cases, err := DiscoverTestCases(bundledeffect.EffectV3) if err != nil { t.Fatal("Failed to discover V3 test cases:", err) } @@ -54,7 +56,7 @@ func TestEffectV3Diagnostics(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - RunEffectTest(t, EffectV3, tc) + RunEffectTest(t, bundledeffect.EffectV3, tc) }) } } diff --git a/internal/effecttest/vfs.go b/internal/effecttest/vfs.go index 9bad8719..a129670f 100644 --- a/internal/effecttest/vfs.go +++ b/internal/effecttest/vfs.go @@ -2,67 +2,16 @@ package effecttest import ( - "fmt" - "io/fs" - "maps" - "os" - pathpkg "path" - "path/filepath" "runtime" "strings" "sync" - "testing/fstest" + "github.com/effect-ts/tsgo/internal/bundledeffect" "github.com/microsoft/typescript-go/shim/ast" "github.com/microsoft/typescript-go/shim/bundled" "github.com/microsoft/typescript-go/shim/compiler" ) -// EffectVersion identifies an Effect major version for test infrastructure. -type EffectVersion string - -const ( - // EffectV3 targets the Effect V3 test workspace (testdata/tests/effect-v3). - EffectV3 EffectVersion = "effect-v3" - // EffectV4 targets the Effect V4 test workspace (testdata/tests/effect-v4). - EffectV4 EffectVersion = "effect-v4" -) - -// EffectTsGoRootPath returns the path to the @effect/tsgo repo root. -// This is determined relative to this source file's location. -func EffectTsGoRootPath() string { - _, filename, _, ok := runtime.Caller(0) - if !ok { - panic("failed to get caller info for EffectTsGoRootPath") - } - // This file is at internal/effecttest/vfs.go, so root is ../../ - return filepath.Dir(filepath.Dir(filepath.Dir(filename))) -} - -// EffectPackagePath returns the path to the Effect package in node_modules. -func EffectPackagePath(version EffectVersion) string { - return PackagePath(version, "effect") -} - -// PackagePath returns the path to a package in node_modules for the given version. -func PackagePath(version EffectVersion, packageName string) string { - return filepath.Join(EffectTsGoRootPath(), "testdata", "tests", string(version), "node_modules", filepath.FromSlash(packageName)) -} - -// EnsureEffectInstalled returns an error if Effect is not installed. -func EnsureEffectInstalled(version EffectVersion) error { - return EnsurePackageInstalled(version, "effect") -} - -// EnsurePackageInstalled returns an error if a package is not installed. -func EnsurePackageInstalled(version EffectVersion, packageName string) error { - path := PackagePath(version, packageName) - if _, err := os.Stat(path); os.IsNotExist(err) { - return fmt.Errorf("package not installed at %s", path) - } - return nil -} - // programSemaphore limits the number of concurrent TypeScript program // compilations to avoid OOM in memory-constrained environments. var programSemaphore = make(chan struct{}, maxConcurrentPrograms()) @@ -83,63 +32,10 @@ func ReleaseProgram() { runtime.GC() } -// cacheKey uniquely identifies a package cache entry by version and package name. -type cacheKey struct { - version EffectVersion - packageName string -} - -var ( - fsCacheMu sync.Mutex - fsCaches = map[cacheKey]func() map[string]any{} -) - -// packageFSCache returns a cached loader for a package's files. -// The loader is created once per (version, packageName) combination. -func packageFSCache(version EffectVersion, packageName string) func() map[string]any { - key := cacheKey{version: version, packageName: packageName} - fsCacheMu.Lock() - defer fsCacheMu.Unlock() - if loader, ok := fsCaches[key]; ok { - return loader - } - loader := sync.OnceValue(func() map[string]any { - packagePath := PackagePath(version, packageName) - testfs := make(map[string]any) - - // Walk the entire package directory and add all files. - packageFS := os.DirFS(packagePath) - err := fs.WalkDir(packageFS, ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - content, err := fs.ReadFile(packageFS, path) - if err != nil { - return err - } - vfsPath := pathpkg.Join("/node_modules", packageName, path) - testfs[vfsPath] = &fstest.MapFile{ - Data: content, - } - return nil - }) - if err != nil { - panic(fmt.Sprintf("Failed to read package directory: %v", err)) - } - - return testfs - }) - fsCaches[key] = loader - return loader -} - // astCacheKey combines EffectVersion and filename to avoid cross-version // collisions (V3 and V4 mount different package contents at the same VFS paths). type astCacheKey struct { - version EffectVersion + version bundledeffect.EffectVersion fileName string } @@ -160,7 +56,7 @@ var parsedLibCache sync.Map // map[string]*ast.SourceFile // are not cached. type cachingCompilerHost struct { compiler.CompilerHost - version EffectVersion + version bundledeffect.EffectVersion } func (h *cachingCompilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile { @@ -189,38 +85,3 @@ func (h *cachingCompilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *as } return h.CompilerHost.GetSourceFile(opts) } - -// MountEffect copies the Effect package files into the provided test filesystem. -// The Effect files are mounted at /node_modules/effect/ in the VFS. -func MountEffect(version EffectVersion, testfs map[string]any) error { - if err := EnsurePackageInstalled(version, "effect"); err != nil { - return err - } - if err := EnsurePackageInstalled(version, "pure-rand"); err != nil { - return err - } - if err := EnsurePackageInstalled(version, "@standard-schema/spec"); err != nil { - return err - } - if err := EnsurePackageInstalled(version, "fast-check"); err != nil { - return err - } - if err := EnsurePackageInstalled(version, "@types/node"); err != nil { - return err - } - - // Copy from cache into the test filesystem - maps.Copy(testfs, packageFSCache(version, "effect")()) - maps.Copy(testfs, packageFSCache(version, "pure-rand")()) - maps.Copy(testfs, packageFSCache(version, "@standard-schema/spec")()) - maps.Copy(testfs, packageFSCache(version, "fast-check")()) - maps.Copy(testfs, packageFSCache(version, "@types/node")()) - - packageJSONPath := filepath.Join(EffectTsGoRootPath(), "testdata", "tests", string(version), "package.json") - packageJSON, err := os.ReadFile(packageJSONPath) - if err != nil { - return err - } - testfs["/.src/package.json"] = &fstest.MapFile{Data: packageJSON} - return nil -} diff --git a/internal/refactors/wrap_with_effect_gen.go b/internal/refactors/wrap_with_effect_gen.go index f268a811..1bf265ff 100644 --- a/internal/refactors/wrap_with_effect_gen.go +++ b/internal/refactors/wrap_with_effect_gen.go @@ -7,6 +7,7 @@ import ( "github.com/microsoft/typescript-go/shim/astnav" "github.com/microsoft/typescript-go/shim/ls" "github.com/microsoft/typescript-go/shim/ls/change" + "github.com/microsoft/typescript-go/shim/lsp/lsproto" ) var WrapWithEffectGen = refactor.Refactor{ @@ -64,49 +65,13 @@ func runWrapWithEffectGen(ctx *refactor.Context) []ls.CodeAction { action := ctx.NewRefactorAction(refactor.RefactorAction{ Description: "Wrap with Effect.gen", Run: func(tracker *change.Tracker) { - // Build: Effect.gen(function*() { return yield* }) - clonedExpr := tracker.DeepCloneNode(matchedNode) - - // yield* - yieldExpr := tracker.NewYieldExpression( - tracker.NewToken(ast.KindAsteriskToken), - clonedExpr, - ) - - // return yield* - returnStmt := tracker.NewReturnStatement(yieldExpr) - - // { return yield* } - body := tracker.NewBlock( - tracker.NewNodeList([]*ast.Node{returnStmt}), - false, - ) - - // function*() { return yield* } - genFn := tracker.NewFunctionExpression( - nil, // modifiers - tracker.NewToken(ast.KindAsteriskToken), // asterisk (generator) - nil, // name - nil, // typeParameters - tracker.NewNodeList([]*ast.Node{}), // parameters (empty) - nil, // returnType - nil, // fullSignature - body, - ) - - // Effect.gen(...) - effectId := tracker.NewIdentifier(effectModuleName) - genAccess := tracker.NewPropertyAccessExpression( - effectId, nil, tracker.NewIdentifier("gen"), ast.NodeFlagsNone, - ) - effectGenCall := tracker.NewCallExpression( - genAccess, nil, nil, - tracker.NewNodeList([]*ast.Node{genFn}), - ast.NodeFlagsNone, - ) - - ast.SetParentInChildren(effectGenCall) - tracker.ReplaceNode(ctx.SourceFile, matchedNode, effectGenCall, nil) + start := astnav.GetStartOfNode(matchedNode, ctx.SourceFile, false) + textRange := ctx.SourceFile.Text()[start:matchedNode.End()] + wrapped := effectModuleName + ".gen(function*() { return yield* " + textRange + " })" + tracker.ReplaceRangeWithText(ctx.SourceFile, lsproto.Range{ + Start: ctx.BytePosToLSPPosition(start), + End: ctx.BytePosToLSPPosition(matchedNode.End()), + }, wrapped) }, }) if action == nil { diff --git a/internal/rules/rules_json_test.go b/internal/rules/rules_json_test.go index 963c04c2..20a86e3b 100644 --- a/internal/rules/rules_json_test.go +++ b/internal/rules/rules_json_test.go @@ -20,6 +20,7 @@ import ( "testing/fstest" "github.com/effect-ts/tsgo/etscore" + "github.com/effect-ts/tsgo/internal/bundledeffect" "github.com/effect-ts/tsgo/internal/effecttest" "github.com/effect-ts/tsgo/internal/fixables" "github.com/effect-ts/tsgo/internal/pluginoptions" @@ -217,8 +218,8 @@ func trimLeadingDirectives(sourceText string) (trimmed string, removedChars int) // findPreviewFile locates the preview fixture for a rule, checking v4 first then v3. // Returns the version, file path, and source text. -func findPreviewFile(root string, ruleName string) (effecttest.EffectVersion, string, string, error) { - for _, version := range []effecttest.EffectVersion{effecttest.EffectV4, effecttest.EffectV3} { +func findPreviewFile(root string, ruleName string) (bundledeffect.EffectVersion, string, string, error) { + for _, version := range []bundledeffect.EffectVersion{bundledeffect.EffectV4, bundledeffect.EffectV3} { filePath := filepath.Join(root, "testdata", "tests", string(version), ruleName+"_preview.ts") data, err := os.ReadFile(filePath) if err == nil { @@ -270,7 +271,7 @@ func buildTsConfigWithTestConfig(testConfig map[string]any) string { // the specified rule directly to collect diagnostics. We bypass the checker hooks // because preview files use "// @effect-diagnostics *:off" which causes the // hook to early-return. Instead, we run the rule directly via rule.Run(). -func evaluatePreview(t *testing.T, version effecttest.EffectVersion, sourceText string, r *rule.Rule) *previewPayload { +func evaluatePreview(t *testing.T, version bundledeffect.EffectVersion, sourceText string, r *rule.Rule) *previewPayload { t.Helper() effecttest.AcquireProgram() @@ -282,7 +283,7 @@ func evaluatePreview(t *testing.T, version effecttest.EffectVersion, sourceText // Create VFS testfs := make(map[string]any) - if err := effecttest.MountEffect(version, testfs); err != nil { + if err := bundledeffect.MountEffect(version, testfs); err != nil { t.Fatalf("mount effect for preview: %v", err) } diff --git a/internal/typeparser/data_first_signature.go b/internal/typeparser/data_first_signature.go new file mode 100644 index 00000000..60cb753b --- /dev/null +++ b/internal/typeparser/data_first_signature.go @@ -0,0 +1,154 @@ +package typeparser + +import ( + "strings" + + "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/checker" +) + +type ParsedDataFirstCallResult struct { + Node *ast.CallExpression + Callee *ast.Node + Subject *ast.Node + Args []*ast.Node + SubjectIndex int +} + +func (tp *TypeParser) ParseDataFirstCallAsPipeable(node *ast.Node) *ParsedDataFirstCallResult { + if tp == nil || tp.checker == nil || node == nil || node.Kind != ast.KindCallExpression { + return nil + } + + call := node.AsCallExpression() + if call == nil || call.Expression == nil || call.Arguments == nil || len(call.Arguments.Nodes) < 2 { + return nil + } + + for _, arg := range call.Arguments.Nodes { + if arg == nil || arg.Kind == ast.KindSpreadElement { + return nil + } + } + + c := tp.checker + resolved := c.GetResolvedSignature(node) + if resolved == nil || resolved.Declaration() == nil { + return nil + } + if len(resolved.Parameters()) != len(call.Arguments.Nodes) { + return nil + } + + resolvedSymbol := checker.Checker_getSymbolOfDeclaration(c, resolved.Declaration()) + if resolvedSymbol == nil { + return nil + } + + subjectIndexes := []int{0} + if len(call.Arguments.Nodes) == 2 { + last := len(call.Arguments.Nodes) - 1 + preferFirst := false + if params := resolved.Parameters(); len(params) > 0 { + preferFirst = isLikelySelfParameter(params[0]) + } + if preferFirst { + subjectIndexes = []int{0, last} + } else { + subjectIndexes = []int{last, 0} + } + } + + _, candidates := checker.GetResolvedSignatureForSignatureHelp(node, len(call.Arguments.Nodes)-1, c) + for _, subjectIndex := range subjectIndexes { + derived := derivePipeableSignatureFromDataFirst(c, resolved, subjectIndex) + if derived == nil { + continue + } + + for _, candidate := range candidates { + if candidate == nil || candidate.Declaration() == nil { + continue + } + candidateSymbol := checker.Checker_getSymbolOfDeclaration(c, candidate.Declaration()) + if candidateSymbol == nil || checker.Checker_getSymbolIfSameReference(c, resolvedSymbol, candidateSymbol) == nil { + continue + } + candidateReturn := c.GetReturnTypeOfSignature(candidate) + if candidateReturn == nil { + continue + } + returnedSigs := c.GetSignaturesOfType(candidateReturn, checker.SignatureKindCall) + hasUnaryReturnedCall := false + for _, returnedSig := range returnedSigs { + if returnedSig != nil && len(returnedSig.Parameters()) == 1 { + hasUnaryReturnedCall = true + break + } + } + if !hasUnaryReturnedCall { + continue + } + if !checker.Checker_isSignatureAssignableTo(c, candidate, derived, false) { + continue + } + + return &ParsedDataFirstCallResult{ + Node: call, + Callee: call.Expression, + Subject: call.Arguments.Nodes[subjectIndex], + Args: omitArgAt(call.Arguments.Nodes, subjectIndex), + SubjectIndex: subjectIndex, + } + } + } + + return nil +} + +func derivePipeableSignatureFromDataFirst(c *checker.Checker, sig *checker.Signature, subjectIndex int) *checker.Signature { + if c == nil || sig == nil { + return nil + } + params := sig.Parameters() + if subjectIndex < 0 || subjectIndex >= len(params) { + return nil + } + subject := params[subjectIndex] + if subject == nil { + return nil + } + + outerParams := make([]*ast.Symbol, 0, len(params)-1) + for i, param := range params { + if i == subjectIndex { + continue + } + outerParams = append(outerParams, param) + } + + innerFnType := checker.Checker_newFunctionType(c, nil, nil, []*ast.Symbol{subject}, c.GetReturnTypeOfSignature(sig)) + if innerFnType == nil { + return nil + } + return checker.Checker_newCallSignature(c, sig.TypeParameters(), sig.ThisParameter(), outerParams, innerFnType) +} + +func isLikelySelfParameter(sym *ast.Symbol) bool { + if sym == nil { + return false + } + name := strings.ToLower(sym.Name) + return name == "self" || strings.HasPrefix(name, "self") || name == "this" +} + +func omitArgAt(nodes []*ast.Node, index int) []*ast.Node { + result := make([]*ast.Node, 0, len(nodes)-1) + for i, node := range nodes { + if i == index { + continue + } + result = append(result, node) + } + return result +} diff --git a/internal/typeparser/data_first_signature_test.go b/internal/typeparser/data_first_signature_test.go new file mode 100644 index 00000000..0e1673d2 --- /dev/null +++ b/internal/typeparser/data_first_signature_test.go @@ -0,0 +1,324 @@ +package typeparser + +import ( + "strings" + "testing" + + "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/checker" +) + +func TestDerivedPipeableSignature_Provide(t *testing.T) { + t.Parallel() + + source := ` +import { Effect, Layer, ServiceMap } from "effect" + +class MyService extends ServiceMap.Service()("MyService", { + make: Effect.succeed({ value: 1 }) +}) { + static Default = Layer.effect(this, this.make) +} + +declare const program: Effect.Effect + +const provided = Effect.provide(program, MyService.Default, { local: true }) +` + + _, tp, sf, done := compileAndGetCheckerAndSourceFileWithEffectV4Internal(t, source) + defer done() + + call := findVariableInitializerCallByName(t, sf, "provided") + logDerivedPipeableSignatureComparison(t, tp, call.AsNode()) + result := tp.ParseDataFirstCallAsPipeable(call.AsNode()) + if result == nil { + t.Fatal("expected provide call to normalize via derived signature comparison") + } + if result.SubjectIndex != 0 { + t.Fatalf("expected provide subject index 0, got %d", result.SubjectIndex) + } +} + +func TestDerivedPipeableSignature_LayerSucceed(t *testing.T) { + t.Parallel() + + source := ` +import { Layer, ServiceMap } from "effect" + +class Service extends ServiceMap.Service()("Service") {} + +declare const make: { readonly value: 1 } + +const live = Layer.succeed(Service, make) +` + + _, tp, sf, done := compileAndGetCheckerAndSourceFileWithEffectV4Internal(t, source) + defer done() + + call := findVariableInitializerCallByName(t, sf, "live") + logDerivedPipeableSignatureComparison(t, tp, call.AsNode()) + result := tp.ParseDataFirstCallAsPipeable(call.AsNode()) + if result == nil { + t.Fatal("expected Layer.succeed call to normalize via derived signature comparison") + } + if result.SubjectIndex != 1 { + t.Fatalf("expected Layer.succeed subject index 1, got %d", result.SubjectIndex) + } +} + +func TestPipingFlows_DataFirstCalls(t *testing.T) { + t.Parallel() + + source := ` +import { Effect, Layer, ServiceMap } from "effect" + +class MyService extends ServiceMap.Service()("MyService", { + make: Effect.succeed({ value: 1 as const }) +}) { + static Default = Layer.effect(this, this.make) +} + +declare const program: Effect.Effect +declare const make: { readonly value: 1 } + +export const provided = Effect.provide(program, MyService.Default, { local: true }) +export const live = Layer.succeed(MyService, make) +` + + _, tp, sf, done := compileAndGetCheckerAndSourceFileWithEffectV4Internal(t, source) + defer done() + + flows := tp.PipingFlows(sf, false) + providedCall := findVariableInitializerCallByName(t, sf, "provided").AsNode() + liveCall := findVariableInitializerCallByName(t, sf, "live").AsNode() + + providedFlow := findFlowByNode(t, sf, flows, providedCall) + if strings.TrimSpace(nodeText(sf, providedFlow.Subject.Node)) != "program" { + t.Fatalf("provided subject = %q, want %q", strings.TrimSpace(nodeText(sf, providedFlow.Subject.Node)), "program") + } + assertSingleTransformation(t, sf, providedFlow, TransformationKindDataFirst, "Effect.provide", []string{"MyService.Default", "{ local: true }"}) + if got := stripWhitespace(ReconstructPipingFlow(sf, &providedFlow.Subject, ptrTransformations(providedFlow.Transformations))); got != stripWhitespace("Effect.provide(MyService.Default, { local: true })(program)") { + t.Fatalf("provided reconstructed flow = %q", got) + } + + liveFlow := findFlowByNode(t, sf, flows, liveCall) + if strings.TrimSpace(nodeText(sf, liveFlow.Subject.Node)) != "make" { + t.Fatalf("live subject = %q, want %q", strings.TrimSpace(nodeText(sf, liveFlow.Subject.Node)), "make") + } + assertSingleTransformation(t, sf, liveFlow, TransformationKindDataLast, "Layer.succeed", []string{"MyService"}) + if got := stripWhitespace(ReconstructPipingFlow(sf, &liveFlow.Subject, ptrTransformations(liveFlow.Transformations))); got != stripWhitespace("Layer.succeed(MyService)(make)") { + t.Fatalf("live reconstructed flow = %q", got) + } +} + +func TestParseDataFirstCallAsPipeable_CatchAllV3(t *testing.T) { + t.Parallel() + + source := ` +// @effect-v3 +import * as Effect from "effect/Effect" + +export const shouldReportDataFirst = Effect.catchAll( + Effect.never, + () => Effect.log("error") +) +` + + _, tp, sf, done := compileAndGetCheckerAndSourceFileWithEffectV3Internal(t, source) + defer done() + + call := findVariableInitializerCallByName(t, sf, "shouldReportDataFirst") + result := tp.ParseDataFirstCallAsPipeable(call.AsNode()) + if result == nil { + t.Fatal("expected data-first catchAll to normalize") + } + if strings.TrimSpace(nodeText(sf, result.Subject)) != "Effect.never" { + t.Fatalf("subject = %q, want %q", strings.TrimSpace(nodeText(sf, result.Subject)), "Effect.never") + } + if strings.TrimSpace(nodeText(sf, result.Callee)) != "Effect.catchAll" { + t.Fatalf("callee = %q, want %q", strings.TrimSpace(nodeText(sf, result.Callee)), "Effect.catchAll") + } + if result.SubjectIndex != 0 { + t.Fatalf("subject index = %d, want 0", result.SubjectIndex) + } + if len(result.Args) != 1 || stripWhitespace(nodeText(sf, result.Args[0])) != stripWhitespace("() => Effect.log(\"error\")") { + t.Fatalf("args = %q", strings.TrimSpace(nodeText(sf, result.Args[0]))) + } + + flows := tp.PipingFlows(sf, false) + flow := findFlowByNode(t, sf, flows, call.AsNode()) + if strings.TrimSpace(nodeText(sf, flow.Subject.Node)) != "Effect.never" { + t.Fatalf("flow subject = %q, want %q", strings.TrimSpace(nodeText(sf, flow.Subject.Node)), "Effect.never") + } + assertSingleTransformation(t, sf, flow, TransformationKindDataFirst, "Effect.catchAll", []string{"() => Effect.log(\"error\")"}) +} + +func logDerivedPipeableSignatureComparison(t *testing.T, tp *TypeParser, node *ast.Node) { + t.Helper() + if tp == nil || tp.checker == nil || node == nil { + return + } + + c := tp.checker + call := node.AsCallExpression() + if call == nil || len(call.Arguments.Nodes) < 2 { + return + } + + resolved := c.GetResolvedSignature(node) + if resolved == nil || resolved.Declaration() == nil { + t.Fatal("expected resolved data-first signature") + } + actualSymbol := checker.Checker_getSymbolOfDeclaration(c, resolved.Declaration()) + resolvedParamCount := len(resolved.Parameters()) + subjectIndexes := []int{0} + if len(call.Arguments.Nodes) > 1 { + last := len(call.Arguments.Nodes) - 1 + preferFirst := false + if params := resolved.Parameters(); len(params) > 0 { + preferFirst = isLikelySelfParameter(params[0]) + } + if preferFirst { + subjectIndexes = []int{0, last} + } else { + subjectIndexes = []int{last, 0} + } + } + + t.Logf("data-first resolved return type: %s", c.TypeToString(c.GetReturnTypeOfSignature(resolved))) + for _, subjectIndex := range subjectIndexes { + derived := derivePipeableSignatureFromDataFirst(c, resolved, subjectIndex) + if derived == nil { + t.Logf("subjectIndex=%d derived=nil", subjectIndex) + continue + } + derivedReturn := c.GetReturnTypeOfSignature(derived) + innerSigs := c.GetSignaturesOfType(derivedReturn, checker.SignatureKindCall) + innerReturns := make([]string, 0, len(innerSigs)) + for _, sig := range innerSigs { + if sig != nil { + innerReturns = append(innerReturns, c.TypeToString(c.GetReturnTypeOfSignature(sig))) + } + } + t.Logf("subjectIndex=%d derived return=%s typeArgs=%d innerReturns=%v", subjectIndex, c.TypeToString(derivedReturn), len(derived.TypeParameters()), innerReturns) + + resolvedOuter, candidates := checker.GetResolvedSignatureForSignatureHelp(node, resolvedParamCount-1, c) + _ = resolvedOuter + for i, candidate := range candidates { + if candidate == nil || candidate.Declaration() == nil { + continue + } + candidateSymbol := checker.Checker_getSymbolOfDeclaration(c, candidate.Declaration()) + if candidateSymbol == nil || checker.Checker_getSymbolIfSameReference(c, actualSymbol, candidateSymbol) == nil { + continue + } + candidateReturn := c.GetReturnTypeOfSignature(candidate) + returned := c.GetSignaturesOfType(candidateReturn, checker.SignatureKindCall) + returnedArity := make([]int, 0, len(returned)) + for _, rs := range returned { + if rs != nil { + returnedArity = append(returnedArity, len(rs.Parameters())) + } + } + t.Logf( + "derived=%s | candidate %d=%s | return=%s typeArgs=%d returnedArity=%v cand->derived=%v derived->cand=%v", + signatureString(c, derived), + i, + signatureString(c, candidate), + c.TypeToString(candidateReturn), + len(candidate.TypeParameters()), + returnedArity, + checker.Checker_isSignatureAssignableTo(c, candidate, derived, false), + checker.Checker_isSignatureAssignableTo(c, derived, candidate, false), + ) + } + } +} + +func signatureString(c *checker.Checker, sig *checker.Signature) string { + if c == nil || sig == nil { + return "" + } + return c.SignatureToStringEx(sig, nil, checker.TypeFormatFlagsWriteArrowStyleSignature) +} + +func findFlowByNode(t *testing.T, sf *ast.SourceFile, flows []*PipingFlow, node *ast.Node) *PipingFlow { + t.Helper() + for _, flow := range flows { + if flow != nil && flow.Node == node { + return flow + } + } + t.Fatalf("flow for node %q not found", nodeText(sf, node)) + return nil +} + +func assertSingleTransformation(t *testing.T, sf *ast.SourceFile, flow *PipingFlow, wantKind TransformationKind, wantCallee string, wantArgs []string) { + t.Helper() + if flow == nil { + t.Fatal("flow is nil") + } + if len(flow.Transformations) != 1 { + t.Fatalf("transformation count = %d, want 1", len(flow.Transformations)) + } + tr := flow.Transformations[0] + if tr.Kind != wantKind { + t.Fatalf("kind = %q, want %q", tr.Kind, wantKind) + } + if got := strings.TrimSpace(nodeText(sf, tr.Callee)); got != wantCallee { + t.Fatalf("callee = %q, want %q", got, wantCallee) + } + if len(tr.Args) != len(wantArgs) { + t.Fatalf("arg count = %d, want %d", len(tr.Args), len(wantArgs)) + } + for i, arg := range tr.Args { + if got := strings.TrimSpace(nodeText(sf, arg)); got != wantArgs[i] { + t.Fatalf("arg[%d] = %q, want %q", i, got, wantArgs[i]) + } + } +} + +func ptrTransformations(in []PipingFlowTransformation) []*PipingFlowTransformation { + result := make([]*PipingFlowTransformation, 0, len(in)) + for i := range in { + result = append(result, &in[i]) + } + return result +} + +func stripWhitespace(s string) string { + return strings.Join(strings.Fields(s), "") +} + +func findVariableInitializerCallByName(t *testing.T, sf *ast.SourceFile, name string) *ast.CallExpression { + t.Helper() + + var found *ast.CallExpression + var visit func(*ast.Node) + visit = func(node *ast.Node) { + if node == nil || found != nil { + return + } + if node.Kind == ast.KindVariableDeclaration { + decl := node.AsVariableDeclaration() + if decl != nil && decl.Name() != nil && decl.Name().Kind == ast.KindIdentifier { + if ident := decl.Name().AsIdentifier(); ident != nil && ident.Text == name && decl.Initializer != nil && decl.Initializer.Kind == ast.KindCallExpression { + found = decl.Initializer.AsCallExpression() + return + } + } + } + node.ForEachChild(func(child *ast.Node) bool { + visit(child) + return false + }) + } + + visit(sf.AsNode()) + if found == nil { + t.Fatalf("initializer call for variable %q not found", name) + } + return found +} diff --git a/internal/typeparser/helpers_test.go b/internal/typeparser/helpers_test.go index e7673467..c62fdafb 100644 --- a/internal/typeparser/helpers_test.go +++ b/internal/typeparser/helpers_test.go @@ -5,6 +5,7 @@ import ( "testing" "testing/fstest" + "github.com/effect-ts/tsgo/internal/bundledeffect" "github.com/microsoft/typescript-go/shim/ast" "github.com/microsoft/typescript-go/shim/bundled" "github.com/microsoft/typescript-go/shim/checker" @@ -111,6 +112,62 @@ func compileAndGetCheckerAndSourceFileInternal(t *testing.T, source string) (*ch return c, NewTypeParser(c.Program(), c), sf, done } +func compileAndGetCheckerAndSourceFileWithEffectV4Internal(t *testing.T, source string) (*checker.Checker, *TypeParser, *ast.SourceFile, func()) { + return compileAndGetCheckerAndSourceFileWithEffectVersionInternal(t, bundledeffect.EffectV4, source) +} + +func compileAndGetCheckerAndSourceFileWithEffectV3Internal(t *testing.T, source string) (*checker.Checker, *TypeParser, *ast.SourceFile, func()) { + return compileAndGetCheckerAndSourceFileWithEffectVersionInternal(t, bundledeffect.EffectV3, source) +} + +func compileAndGetCheckerAndSourceFileWithEffectVersionInternal(t *testing.T, version bundledeffect.EffectVersion, source string) (*checker.Checker, *TypeParser, *ast.SourceFile, func()) { + t.Helper() + + testfs := map[string]any{ + "/.src/test.ts": &fstest.MapFile{ + Data: []byte(source), + }, + } + if err := bundledeffect.MountEffect(version, testfs); err != nil { + t.Fatalf("failed to mount effect %s: %v", version, err) + } + + fs := vfstest.FromMap(testfs, true) + fs = bundled.WrapFS(fs) + + compilerOptions := &core.CompilerOptions{ + NewLine: core.NewLineKindLF, + SkipDefaultLibCheck: core.TSTrue, + NoErrorTruncation: core.TSTrue, + Target: core.ScriptTargetESNext, + Module: core.ModuleKindNodeNext, + ModuleResolution: core.ModuleResolutionKindNodeNext, + Strict: core.TSTrue, + } + + host := compiler.NewCompilerHost("/.src", fs, bundled.LibPath(), nil, nil) + program := compiler.NewProgram(compiler.ProgramOptions{ + Config: &tsoptions.ParsedCommandLine{ + ParsedConfig: &core.ParsedOptions{ + CompilerOptions: compilerOptions, + FileNames: []string{"/.src/test.ts"}, + }, + }, + Host: host, + SingleThreaded: core.TSTrue, + }) + + ctx := context.Background() + c, done := program.GetTypeChecker(ctx) + sf := program.GetSourceFile("/.src/test.ts") + if sf == nil { + done() + t.Fatal("Failed to get source file") + } + + return c, NewTypeParser(c.Program(), c), sf, done +} + func findIdentifierByText(t *testing.T, sf *ast.SourceFile, text string, occurrence int) *ast.Node { t.Helper() diff --git a/internal/typeparser/piping_flow.go b/internal/typeparser/piping_flow.go index 1197b311..e5ba7337 100644 --- a/internal/typeparser/piping_flow.go +++ b/internal/typeparser/piping_flow.go @@ -15,6 +15,8 @@ type TransformationKind string const ( TransformationKindPipe TransformationKind = "pipe" TransformationKindPipeable TransformationKind = "pipeable" + TransformationKindDataFirst TransformationKind = "dataFirst" + TransformationKindDataLast TransformationKind = "dataLast" TransformationKindCall TransformationKind = "call" TransformationKindEffectFn TransformationKind = "effectFn" TransformationKindEffectFnUntraced TransformationKind = "effectFnUntraced" @@ -311,6 +313,44 @@ func (tp *TypeParser) PipingFlows(sf *ast.SourceFile, includeEffectFn bool) []*P continue } + // Try single-arg call + if dataFirstResult := tp.ParseDataFirstCallAsPipeable(node); dataFirstResult != nil { + callOutType := tp.GetTypeAtLocation(node) + kind := TransformationKindDataFirst + if dataFirstResult.SubjectIndex != 0 { + kind = TransformationKindDataLast + } + transformation := PipingFlowTransformation{ + Kind: kind, + Node: node, + Callee: dataFirstResult.Callee, + Args: dataFirstResult.Args, + OutType: callOutType, + } + + if item.parentFlow != nil { + item.parentFlow.Transformations = append( + []PipingFlowTransformation{transformation}, + item.parentFlow.Transformations..., + ) + queue = append(queue, workItem{node: dataFirstResult.Subject, parentFlow: item.parentFlow}) + } else { + newFlow := &PipingFlow{ + Node: node, + Transformations: []PipingFlowTransformation{transformation}, + } + queue = append(queue, workItem{node: dataFirstResult.Subject, parentFlow: newFlow}) + } + + dataFirstResult.Callee.ForEachChild(enqueueChild) + for _, arg := range dataFirstResult.Args { + if arg != nil { + arg.ForEachChild(enqueueChild) + } + } + continue + } + // Try single-arg call if singleResult := parseSingleArgCall(node); singleResult != nil { var callOutType *checker.Type diff --git a/schema.json b/schema.json index 8286c4c1..5fc555a1 100644 --- a/schema.json +++ b/schema.json @@ -1083,6 +1083,11 @@ "description": "Controls Effect completions.", "type": "boolean" }, + "debug": { + "default": false, + "description": "Enables additional debug-only Effect language service output.", + "type": "boolean" + }, "diagnosticSeverity": { "$ref": "#/definitions/effectLanguageServicePluginDiagnosticSeverityDefinition", "default": {}, @@ -1664,6 +1669,11 @@ "default": "off", "description": "Detects 'any' or 'unknown' types in Effect error or requirements channels" }, + "asyncFunction": { + "$ref": "#/definitions/effectLanguageServicePluginSeverityDefinition", + "default": "off", + "description": "Warns when declaring async functions and suggests using Effect values and Effect.gen for async control flow" + }, "catchAllToMapError": { "$ref": "#/definitions/effectLanguageServicePluginSeverityDefinition", "default": "suggestion", @@ -1869,6 +1879,11 @@ "default": "warning", "description": "Warns against chaining Effect.provide calls which can cause service lifecycle issues" }, + "newPromise": { + "$ref": "#/definitions/effectLanguageServicePluginSeverityDefinition", + "default": "off", + "description": "Warns when constructing promises with new Promise instead of using Effect APIs" + }, "nodeBuiltinImport": { "$ref": "#/definitions/effectLanguageServicePluginSeverityDefinition", "default": "off", @@ -2047,6 +2062,11 @@ "description": "Controls Effect completions.", "type": "boolean" }, + "debug": { + "default": false, + "description": "Enables additional debug-only Effect language service output.", + "type": "boolean" + }, "diagnosticSeverity": { "$ref": "#/definitions/effectLanguageServicePluginDiagnosticSeverityDefinition", "default": {}, diff --git a/shim/checker/extra-shim.json b/shim/checker/extra-shim.json index ee6b589b..1601e608 100644 --- a/shim/checker/extra-shim.json +++ b/shim/checker/extra-shim.json @@ -1,8 +1,8 @@ -{ +{ "ExtraMethods": { - "Checker": ["isTypeAssignableTo", "isArrayType", "isReadonlyArrayType", "getIndexInfosOfType", "getTypeArguments", "getLiteralTypeFromProperty", "getSymbolIfSameReference", "getSymbolOfDeclaration"] + "Checker": ["isTypeAssignableTo", "isArrayType", "isReadonlyArrayType", "getIndexInfosOfType", "getTypeArguments", "getLiteralTypeFromProperty", "getSymbolIfSameReference", "getSymbolOfDeclaration", "isSignatureAssignableTo", "newFunctionType", "newCallSignature"] }, - "ExtraFields": { - "IndexInfo": ["keyType", "valueType"] - } -} + "ExtraFields": { + "IndexInfo": ["keyType", "valueType"] + } +} diff --git a/shim/checker/shim.go b/shim/checker/shim.go index 24639fe4..24878672 100644 --- a/shim/checker/shim.go +++ b/shim/checker/shim.go @@ -88,8 +88,14 @@ func Checker_isArrayType(recv *checker.Checker, t *checker.Type) bool func Checker_isReadonlyArrayType(recv *checker.Checker, t *checker.Type) bool //go:linkname Checker_getLiteralTypeFromProperty github.com/microsoft/typescript-go/internal/checker.(*Checker).getLiteralTypeFromProperty func Checker_getLiteralTypeFromProperty(recv *checker.Checker, prop *ast.Symbol, include checker.TypeFlags, includeNonPublic bool) *checker.Type +//go:linkname Checker_newFunctionType github.com/microsoft/typescript-go/internal/checker.(*Checker).newFunctionType +func Checker_newFunctionType(recv *checker.Checker, typeParameters []*checker.Type, thisParameter *ast.Symbol, parameters []*ast.Symbol, returnType *checker.Type) *checker.Type +//go:linkname Checker_newCallSignature github.com/microsoft/typescript-go/internal/checker.(*Checker).newCallSignature +func Checker_newCallSignature(recv *checker.Checker, typeParameters []*checker.Type, thisParameter *ast.Symbol, parameters []*ast.Symbol, returnType *checker.Type) *checker.Signature //go:linkname Checker_isTypeAssignableTo github.com/microsoft/typescript-go/internal/checker.(*Checker).isTypeAssignableTo func Checker_isTypeAssignableTo(recv *checker.Checker, source *checker.Type, target *checker.Type) bool +//go:linkname Checker_isSignatureAssignableTo github.com/microsoft/typescript-go/internal/checker.(*Checker).isSignatureAssignableTo +func Checker_isSignatureAssignableTo(recv *checker.Checker, source *checker.Signature, target *checker.Signature, ignoreReturnTypes bool) bool //go:linkname CompareTypes github.com/microsoft/typescript-go/internal/checker.CompareTypes func CompareTypes(t1 *checker.Type, t2 *checker.Type) int type CompositeSignature = checker.CompositeSignature diff --git a/testdata/baselines/reference/effect-v3/catchUnfailableEffect.errors.txt b/testdata/baselines/reference/effect-v3/catchUnfailableEffect.errors.txt index 6209c3c5..2e6a0dd7 100644 --- a/testdata/baselines/reference/effect-v3/catchUnfailableEffect.errors.txt +++ b/testdata/baselines/reference/effect-v3/catchUnfailableEffect.errors.txt @@ -4,9 +4,10 @@ Effect version: 3.19.19 /.src/catchUnfailableEffect.ts(6,3): suggestion TS377009: The previous Effect does not fail, so this error-handling branch will never run. effect(catchUnfailableEffect) /.src/catchUnfailableEffect.ts(15,3): suggestion TS377009: The previous Effect does not fail, so this error-handling branch will never run. effect(catchUnfailableEffect) /.src/catchUnfailableEffect.ts(37,3): suggestion TS377009: The previous Effect does not fail, so this error-handling branch will never run. effect(catchUnfailableEffect) +/.src/catchUnfailableEffect.ts(40,38): suggestion TS377009: The previous Effect does not fail, so this error-handling branch will never run. effect(catchUnfailableEffect) -==== /.src/catchUnfailableEffect.ts (3 errors) ==== +==== /.src/catchUnfailableEffect.ts (4 errors) ==== // @effect-v3 import * as Effect from "effect/Effect" import { pipe } from "effect/Function" @@ -52,3 +53,10 @@ Effect version: 3.19.19 !!! suggestion TS377009: The previous Effect does not fail, so this error-handling branch will never run. effect(catchUnfailableEffect) ) + export const shouldReportDataFirst = Effect.catchAll( + ~~~~~~~~~~~~~~~ +!!! suggestion TS377009: The previous Effect does not fail, so this error-handling branch will never run. effect(catchUnfailableEffect) + Effect.never, + () => Effect.log("error") // <- should report here + ) + diff --git a/testdata/baselines/reference/effect-v3/catchUnfailableEffect.pipings.txt b/testdata/baselines/reference/effect-v3/catchUnfailableEffect.pipings.txt index 7ea8912a..37bb19d3 100644 --- a/testdata/baselines/reference/effect-v3/catchUnfailableEffect.pipings.txt +++ b/testdata/baselines/reference/effect-v3/catchUnfailableEffect.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/catchUnfailableEffect.ts (14 flows) ==== +==== /.src/catchUnfailableEffect.ts (16 flows) ==== === Piping Flow === Location: 5:28 - 7:2 @@ -235,3 +235,31 @@ Transformations (1): callee: Effect.succeed args: (constant) outType: Effect + +=== Piping Flow === +Location: 40:37 - 43:2 +Node: Effect.catchAll(\n Effect.never,\n () => Effect.log("error") // <- should report here\n) +Node Kind: KindCallExpression + +Subject: Effect.never +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.catchAll + args: [() => Effect.log("error")] + outType: Effect + +=== Piping Flow === +Location: 42:8 - 42:28 +Node: Effect.log("error") +Node Kind: KindCallExpression + +Subject: "error" +Subject Type: "error" + +Transformations (1): + [0] kind: call + callee: Effect.log + args: (constant) + outType: Effect diff --git a/testdata/baselines/reference/effect-v3/catchUnfailableEffect.quickfixes.txt b/testdata/baselines/reference/effect-v3/catchUnfailableEffect.quickfixes.txt index aa6a108f..73dc8bcf 100644 --- a/testdata/baselines/reference/effect-v3/catchUnfailableEffect.quickfixes.txt +++ b/testdata/baselines/reference/effect-v3/catchUnfailableEffect.quickfixes.txt @@ -12,6 +12,10 @@ Fix 0: "Disable catchUnfailableEffect for this line" Fix 1: "Disable catchUnfailableEffect for entire file" +[D4] (40:38-40:53) TS377009: The previous Effect does not fail, so this error-handling branch will never run. effect(catchUnfailableEffect) + Fix 0: "Disable catchUnfailableEffect for this line" + Fix 1: "Disable catchUnfailableEffect for entire file" + === Quick Fix Application Results === === [D1] Fix 0: "Disable catchUnfailableEffect for this line" === @@ -31,3 +35,9 @@ skipped by default === [D3] Fix 1: "Disable catchUnfailableEffect for entire file" === skipped by default + +=== [D4] Fix 0: "Disable catchUnfailableEffect for this line" === +skipped by default + +=== [D4] Fix 1: "Disable catchUnfailableEffect for entire file" === +skipped by default diff --git a/testdata/baselines/reference/effect-v3/effectFnOpportunity_inferredLayer.pipings.txt b/testdata/baselines/reference/effect-v3/effectFnOpportunity_inferredLayer.pipings.txt index 1d7781bf..1fde68b3 100644 --- a/testdata/baselines/reference/effect-v3/effectFnOpportunity_inferredLayer.pipings.txt +++ b/testdata/baselines/reference/effect-v3/effectFnOpportunity_inferredLayer.pipings.txt @@ -61,18 +61,22 @@ Transformations (1): outType: Effect === Piping Flow === -Location: 17:58 - 21:3 -Node: Effect.gen(function*() {\n yield* Effect.log("log")\n // Log should be inferred with name MyService.log\n return { log: (what: string) => Effect.log(what) }\n}) +Location: 17:34 - 21:4 +Node: Layer.effect(MyService, Effect.gen(function*() {\n yield* Effect.log("log")\n // Log should be inferred with name MyService.log\n return { log: (what: string) => Effect.log(what) }\n})) Node Kind: KindCallExpression Subject: function*() {\n yield* Effect.log("log")\n // Log should be inferred with name MyService.log\n return { log: (what: string) => Effect.log(what) }\n} Subject Type: () => Generator>, { log: (what: string) => Effect; }, any> -Transformations (1): +Transformations (2): [0] kind: call callee: Effect.gen args: (constant) outType: Effect<{ log: (what: string) => Effect; }, never, never> + [1] kind: dataLast + callee: Layer.effect + args: [MyService] + outType: Layer === Piping Flow === Location: 18:11 - 18:29 diff --git a/testdata/baselines/reference/effect-v3/effectGenToFn.refactors.txt b/testdata/baselines/reference/effect-v3/effectGenToFn.refactors.txt index 48b5d448..6e3c0b85 100644 --- a/testdata/baselines/reference/effect-v3/effectGenToFn.refactors.txt +++ b/testdata/baselines/reference/effect-v3/effectGenToFn.refactors.txt @@ -160,13 +160,13 @@ export const program = () => }) export const programWithPipes = (fa: number, fb: number) => - Eff.gen(function*() { - return yield* Eff.gen(function*() { - const a = yield* Eff.succeed(fa) - const b = yield* Eff.succeed(fb) - return a + b - }).pipe(Eff.map(a => a + 1)) - }) + Eff.gen(function*() { return yield* Eff.gen(function*() { + const a = yield* Eff.succeed(fa) + const b = yield* Eff.succeed(fb) + return a + b + }).pipe( + Eff.map((a) => a + 1) + ) }) export function sampleReturns(arg1: A, arg2: B) { return Eff.gen(function*() { diff --git a/testdata/baselines/reference/effect-v3/effectGenToFn_mixedPipes.refactors.txt b/testdata/baselines/reference/effect-v3/effectGenToFn_mixedPipes.refactors.txt index 4089cb06..23eb6f65 100644 --- a/testdata/baselines/reference/effect-v3/effectGenToFn_mixedPipes.refactors.txt +++ b/testdata/baselines/reference/effect-v3/effectGenToFn_mixedPipes.refactors.txt @@ -39,12 +39,10 @@ import { pipe } from "effect/Function" const test = () => pipe( - Effect.gen(function*() { - return yield* Effect.gen(function*() { - const test = "test" - return yield* Effect.succeed(test) - }).pipe(Effect.asVoid) - }), + Effect.gen(function*() { return yield* Effect.gen(function*() { + const test = "test" + return yield* Effect.succeed(test) + }).pipe(Effect.asVoid) }), Effect.tapError(Effect.logError) ) diff --git a/testdata/baselines/reference/effect-v3/effectGenToFn_withPipes.refactors.txt b/testdata/baselines/reference/effect-v3/effectGenToFn_withPipes.refactors.txt index a8d0488b..eb3b2ee6 100644 --- a/testdata/baselines/reference/effect-v3/effectGenToFn_withPipes.refactors.txt +++ b/testdata/baselines/reference/effect-v3/effectGenToFn_withPipes.refactors.txt @@ -35,12 +35,13 @@ import * as Effect from "effect/Effect" import { pipe } from "effect/Function" const test = () => - Effect.gen(function*() { - return yield* pipe(Effect.gen(function*() { - const test = "test" - return yield* Effect.succeed(test) - }), Effect.tapError(Effect.logError)) - }) + Effect.gen(function*() { return yield* pipe( + Effect.gen(function*() { + const test = "test" + return yield* Effect.succeed(test) + }), + Effect.tapError(Effect.logError) + ) }) === [R1] Refactor 2: "Convert to Effect.fn(\"test\")" === diff --git a/testdata/baselines/reference/effect-v3/layerMergeAllWithDependencies.pipings.txt b/testdata/baselines/reference/effect-v3/layerMergeAllWithDependencies.pipings.txt index c05dfe0d..2a385e0d 100644 --- a/testdata/baselines/reference/effect-v3/layerMergeAllWithDependencies.pipings.txt +++ b/testdata/baselines/reference/effect-v3/layerMergeAllWithDependencies.pipings.txt @@ -1 +1,43 @@ -==== /.src/layerMergeAllWithDependencies.ts (0 flows) ==== +==== /.src/layerMergeAllWithDependencies.ts (3 flows) ==== + +=== Piping Flow === +Location: 11:10 - 11:36 +Node: Effect.as(FileSystem, {}) +Node Kind: KindCallExpression + +Subject: FileSystem +Subject Type: typeof FileSystem + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, FileSystem> + +=== Piping Flow === +Location: 14:10 - 14:62 +Node: Effect.as(Effect.zipRight(DbConnection, Cache), {}) +Node Kind: KindCallExpression + +Subject: Effect.zipRight(DbConnection, Cache) +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Cache | DbConnection> + +=== Piping Flow === +Location: 17:25 - 17:52 +Node: Layer.effect(Cache, Cache) +Node Kind: KindCallExpression + +Subject: Cache +Subject Type: typeof Cache + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [Cache] + outType: Layer diff --git a/testdata/baselines/reference/effect-v3/missedPipeableOpportunity.pipings.txt b/testdata/baselines/reference/effect-v3/missedPipeableOpportunity.pipings.txt index 9417334d..4d9e9aae 100644 --- a/testdata/baselines/reference/effect-v3/missedPipeableOpportunity.pipings.txt +++ b/testdata/baselines/reference/effect-v3/missedPipeableOpportunity.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/missedPipeableOpportunity.ts (12 flows) ==== +==== /.src/missedPipeableOpportunity.ts (17 flows) ==== === Piping Flow === Location: 5:30 - 5:52 @@ -14,6 +14,48 @@ Transformations (1): args: (constant) outType: Effect +=== Piping Flow === +Location: 6:45 - 6:73 +Node: Effect.map(e, (n) => n * 2) +Node Kind: KindCallExpression + +Subject: e +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [(n) => n * 2] + outType: Effect + +=== Piping Flow === +Location: 7:64 - 7:92 +Node: Effect.map(e, (m) => m * n) +Node Kind: KindCallExpression + +Subject: e +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [(m) => m * n] + outType: Effect + +=== Piping Flow === +Location: 8:47 - 8:79 +Node: Effect.map(e, (n) => String(n)) +Node Kind: KindCallExpression + +Subject: e +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [(n) => String(n)] + outType: Effect + === Piping Flow === Location: 8:68 - 8:78 Node: String(n) @@ -50,6 +92,20 @@ Transformations (3): args: (constant) outType: Effect +=== Piping Flow === +Location: 14:45 - 14:73 +Node: Effect.map(e, (n) => n * 3) +Node Kind: KindCallExpression + +Subject: e +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [(n) => n * 3] + outType: Effect + === Piping Flow === Location: 15:29 - 15:66 Node: toString(triple(double(addOne(10)))) @@ -130,6 +186,20 @@ Transformations (3): args: (constant) outType: Effect +=== Piping Flow === +Location: 26:49 - 26:79 +Node: Effect.map(e, (n) => n + 100) +Node Kind: KindCallExpression + +Subject: e +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [(n) => n + 100] + outType: Effect + === Piping Flow === Location: 27:29 - 27:67 Node: triple(addHundred(double(addOne(7)))) diff --git a/testdata/baselines/reference/effect-v3/missingEffectContext_lazy.pipings.txt b/testdata/baselines/reference/effect-v3/missingEffectContext_lazy.pipings.txt index 908fdbe5..df8c9256 100644 --- a/testdata/baselines/reference/effect-v3/missingEffectContext_lazy.pipings.txt +++ b/testdata/baselines/reference/effect-v3/missingEffectContext_lazy.pipings.txt @@ -1 +1,29 @@ -==== /.src/missingEffectContext_lazy.ts (0 flows) ==== +==== /.src/missingEffectContext_lazy.ts (2 flows) ==== + +=== Piping Flow === +Location: 12:9 - 12:54 +Node: Effect.onExit(evaluate(), () => Effect.void) +Node Kind: KindCallExpression + +Subject: evaluate() +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.onExit + args: [() => Effect.void] + outType: Effect + +=== Piping Flow === +Location: 20:9 - 20:54 +Node: Effect.onExit(evaluate(), () => Effect.void) +Node Kind: KindCallExpression + +Subject: evaluate() +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.onExit + args: [() => Effect.void] + outType: Effect diff --git a/testdata/baselines/reference/effect-v3/outdatedApi.pipings.txt b/testdata/baselines/reference/effect-v3/outdatedApi.pipings.txt index 88bc75e9..eca4a44f 100644 --- a/testdata/baselines/reference/effect-v3/outdatedApi.pipings.txt +++ b/testdata/baselines/reference/effect-v3/outdatedApi.pipings.txt @@ -29,18 +29,22 @@ Transformations (1): outType: Effect === Piping Flow === -Location: 13:43 - 13:61 -Node: Effect.fail("err") +Location: 13:26 - 13:89 +Node: Effect.catchAll(Effect.fail("err"), (_e) => Effect.succeed(0)) Node Kind: KindCallExpression Subject: "err" Subject Type: "err" -Transformations (1): +Transformations (2): [0] kind: call callee: Effect.fail args: (constant) outType: Effect + [1] kind: dataFirst + callee: Effect.catchAll + args: [(_e) => Effect.succeed(0)] + outType: Effect === Piping Flow === Location: 13:70 - 13:88 diff --git a/testdata/baselines/reference/effect-v3/pipeableToDatafirst.refactors.txt b/testdata/baselines/reference/effect-v3/pipeableToDatafirst.refactors.txt index 2e9de8fc..705cf3ae 100644 --- a/testdata/baselines/reference/effect-v3/pipeableToDatafirst.refactors.txt +++ b/testdata/baselines/reference/effect-v3/pipeableToDatafirst.refactors.txt @@ -117,7 +117,12 @@ const test3 = pipe( import * as T from "effect/Effect" import { pipe } from "effect/Function" -const test = T.gen(function*() { return yield* pipe(T.succeed("Hello"), T.flatMap(_ => T.log(_)), T.zipRight(T.succeed(42)), T.map(_ => _ * 2)) }) +const test = T.gen(function*() { return yield* pipe( + T.succeed("Hello"), + T.flatMap((_) => T.log(_)), + T.zipRight(T.succeed(42)), + T.map((_) => _ * 2) +) }) const noDataFirst = (value: string) => (eff: T.Effect) => pipe(eff, T.zipLeft(T.log(value))) @@ -241,7 +246,11 @@ const test = pipe( const noDataFirst = (value: string) => (eff: T.Effect) => pipe(eff, T.zipLeft(T.log(value))) -const test2 = T.gen(function*() { return yield* pipe(T.succeed("Hello"), T.flatMap(_ => T.log(_)), noDataFirst("42")) }) +const test2 = T.gen(function*() { return yield* pipe( + T.succeed("Hello"), + T.flatMap((_) => T.log(_)), + noDataFirst("42") +) }) const test3 = pipe( T.succeed("Hello"), @@ -359,5 +368,11 @@ const test2 = pipe( noDataFirst("42") ) -const test3 = T.gen(function*() { return yield* pipe(T.succeed("Hello"), T.flatMap(_ => T.log(_)), noDataFirst("a"), noDataFirst("b"), noDataFirst("c")) }) +const test3 = T.gen(function*() { return yield* pipe( + T.succeed("Hello"), + T.flatMap((_) => T.log(_)), + noDataFirst("a"), + noDataFirst("b"), + noDataFirst("c") +) }) diff --git a/testdata/baselines/reference/effect-v3/scopeInLayerEffect.pipings.txt b/testdata/baselines/reference/effect-v3/scopeInLayerEffect.pipings.txt index 2ebdce28..62488adb 100644 --- a/testdata/baselines/reference/effect-v3/scopeInLayerEffect.pipings.txt +++ b/testdata/baselines/reference/effect-v3/scopeInLayerEffect.pipings.txt @@ -113,18 +113,22 @@ Transformations (1): outType: Effect === Piping Flow === -Location: 30:13 - 34:5 -Node: Effect.gen(function*() {\n yield* Effect.addFinalizer(() => Effect.log("finalizer"))\n return { a: 1 }\n }) +Location: 29:31 - 35:2 +Node: Layer.scoped(\n MyService,\n Effect.gen(function*() {\n yield* Effect.addFinalizer(() => Effect.log("finalizer"))\n return { a: 1 }\n })\n) Node Kind: KindCallExpression Subject: function*() {\n yield* Effect.addFinalizer(() => Effect.log("finalizer"))\n return { a: 1 }\n } Subject Type: () => Generator>, { a: number; }, any> -Transformations (1): +Transformations (2): [0] kind: call callee: Effect.gen args: (constant) outType: Effect<{ a: number; }, never, Scope> + [1] kind: dataLast + callee: Layer.scoped + args: [MyService] + outType: Layer === Piping Flow === Location: 32:11 - 32:62 @@ -155,18 +159,22 @@ Transformations (1): outType: Effect === Piping Flow === -Location: 38:13 - 42:5 -Node: Effect.gen(function*() {\n yield* Effect.addFinalizer(() => Effect.log("finalizer"))\n return { a: 1 }\n }) +Location: 37:23 - 43:2 +Node: Layer.effect(\n MyService,\n Effect.gen(function*() {\n yield* Effect.addFinalizer(() => Effect.log("finalizer"))\n return { a: 1 }\n })\n) Node Kind: KindCallExpression Subject: function*() {\n yield* Effect.addFinalizer(() => Effect.log("finalizer"))\n return { a: 1 }\n } Subject Type: () => Generator>, { a: number; }, any> -Transformations (1): +Transformations (2): [0] kind: call callee: Effect.gen args: (constant) outType: Effect<{ a: number; }, never, Scope> + [1] kind: dataLast + callee: Layer.effect + args: [MyService] + outType: Layer === Piping Flow === Location: 40:11 - 40:62 @@ -197,18 +205,22 @@ Transformations (1): outType: Effect === Piping Flow === -Location: 46:13 - 51:5 -Node: Effect.gen(function*() {\n const _otherService = yield* OtherService\n yield* Effect.addFinalizer(() => Effect.log("finalizer"))\n return { a: 1 }\n }) +Location: 45:24 - 52:2 +Node: Layer.effect(\n MyService,\n Effect.gen(function*() {\n const _otherService = yield* OtherService\n yield* Effect.addFinalizer(() => Effect.log("finalizer"))\n return { a: 1 }\n })\n) Node Kind: KindCallExpression Subject: function*() {\n const _otherService = yield* OtherService\n yield* Effect.addFinalizer(() => Effect.log("finalizer"))\n return { a: 1 }\n } Subject Type: () => Generator> | YieldWrap>, { a: number; }, any> -Transformations (1): +Transformations (2): [0] kind: call callee: Effect.gen args: (constant) outType: Effect<{ a: number; }, never, OtherService | Scope> + [1] kind: dataLast + callee: Layer.effect + args: [MyService] + outType: Layer === Piping Flow === Location: 49:11 - 49:62 diff --git a/testdata/baselines/reference/effect-v3/scopeInLayerEffect_preview.pipings.txt b/testdata/baselines/reference/effect-v3/scopeInLayerEffect_preview.pipings.txt index ba13ca79..41d4fcf2 100644 --- a/testdata/baselines/reference/effect-v3/scopeInLayerEffect_preview.pipings.txt +++ b/testdata/baselines/reference/effect-v3/scopeInLayerEffect_preview.pipings.txt @@ -15,18 +15,22 @@ Transformations (1): outType: () => TagClass === Piping Flow === -Location: 8:43 - 11:3 -Node: Effect.gen(function*() {\n yield* Effect.addFinalizer(() => Effect.void)\n return { ok: true as const }\n}) +Location: 8:23 - 11:4 +Node: Layer.effect(Cache, Effect.gen(function*() {\n yield* Effect.addFinalizer(() => Effect.void)\n return { ok: true as const }\n})) Node Kind: KindCallExpression Subject: function*() {\n yield* Effect.addFinalizer(() => Effect.void)\n return { ok: true as const }\n} Subject Type: () => Generator>, { ok: true; }, any> -Transformations (1): +Transformations (2): [0] kind: call callee: Effect.gen args: (constant) outType: Effect<{ ok: true; }, never, Scope> + [1] kind: dataLast + callee: Layer.effect + args: [Cache] + outType: Layer === Piping Flow === Location: 9:9 - 9:48 diff --git a/testdata/baselines/reference/effect-v3/strictEffectProvide.pipings.txt b/testdata/baselines/reference/effect-v3/strictEffectProvide.pipings.txt index 3ec0b967..9efda820 100644 --- a/testdata/baselines/reference/effect-v3/strictEffectProvide.pipings.txt +++ b/testdata/baselines/reference/effect-v3/strictEffectProvide.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/strictEffectProvide.ts (5 flows) ==== +==== /.src/strictEffectProvide.ts (8 flows) ==== === Piping Flow === Location: 15:29 - 17:2 @@ -14,6 +14,20 @@ Transformations (1): args: [MyService1.Default] outType: Effect +=== Piping Flow === +Location: 20:29 - 20:77 +Node: Effect.provide(Effect.void, MyService1.Default) +Node Kind: KindCallExpression + +Subject: Effect.void +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.provide + args: [MyService1.Default] + outType: Effect + === Piping Flow === Location: 23:29 - 27:2 Node: Effect.void.pipe(\n Effect.map(() => 42),\n Effect.provide(MyService1.Default),\n Effect.map((n) => n + 1)\n) @@ -64,6 +78,20 @@ Transformations (1): args: [MyService1, new MyService1({ value: 1 })] outType: Effect +=== Piping Flow === +Location: 40:32 - 43:2 +Node: Layer.provide(\n MyService1.Default,\n MyService2.Default\n) +Node Kind: KindCallExpression + +Subject: MyService1.Default +Subject Type: Layer + +Transformations (1): + [0] kind: dataFirst + callee: Layer.provide + args: [MyService2.Default] + outType: Layer + === Piping Flow === Location: 46:32 - 49:3 Node: Effect.gen(function*() {\n const ctx = yield* Effect.context()\n return yield* Effect.provide(Effect.void, ctx)\n}) @@ -77,3 +105,17 @@ Transformations (1): callee: Effect.gen args: (constant) outType: Effect + +=== Piping Flow === +Location: 48:16 - 48:49 +Node: Effect.provide(Effect.void, ctx) +Node Kind: KindCallExpression + +Subject: Effect.void +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.provide + args: [ctx] + outType: Effect diff --git a/testdata/baselines/reference/effect-v3/togglePipeStyle.refactors.txt b/testdata/baselines/reference/effect-v3/togglePipeStyle.refactors.txt index 5ca7c1f0..0dfa4655 100644 --- a/testdata/baselines/reference/effect-v3/togglePipeStyle.refactors.txt +++ b/testdata/baselines/reference/effect-v3/togglePipeStyle.refactors.txt @@ -49,7 +49,7 @@ export const toPipeable = pipe(Effect.succeed(42), Effect.map((x) => x * 2)) import * as Effect from "effect/Effect" import { pipe } from "effect/Function" -export const toRegularPipe = Effect.gen(function*() { return yield* Effect.succeed(42).pipe(Effect.map(x => x * 2)) }) +export const toRegularPipe = Effect.gen(function*() { return yield* Effect.succeed(42).pipe(Effect.map((x) => x * 2)) }) export const toPipeable = pipe(Effect.succeed(42), Effect.map((x) => x * 2)) diff --git a/testdata/baselines/reference/effect-v3/wrapWithEffectGen.refactors.txt b/testdata/baselines/reference/effect-v3/wrapWithEffectGen.refactors.txt index 1f26c64b..7d3a5ce8 100644 --- a/testdata/baselines/reference/effect-v3/wrapWithEffectGen.refactors.txt +++ b/testdata/baselines/reference/effect-v3/wrapWithEffectGen.refactors.txt @@ -349,7 +349,11 @@ export const test2 = Effect.succeed(42).pipe( Effect.map((n) => n - 1) ) -export const test4 = Effect.gen(function*() { return yield* pipe(Effect.succeed(42), Effect.map(n => n + 1), Effect.map(n => n - 1)) }) +export const test4 = Effect.gen(function*() { return yield* pipe( + Effect.succeed(42), + Effect.map((n) => n + 1), + Effect.map((n) => n - 1) +) }) === [R6] Refactor 0: "Wrap with pipe" === diff --git a/testdata/baselines/reference/effect-v4/debugDocumentSymbolFlows.symbols.txt b/testdata/baselines/reference/effect-v4/debugDocumentSymbolFlows.symbols.txt new file mode 100644 index 00000000..89ab5aa1 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/debugDocumentSymbolFlows.symbols.txt @@ -0,0 +1,35 @@ +==== /test.ts ==== + +-- hierarchical[0] -- +(Package) Effect range=1:1-1:1 selection=1:1-1:1 + (Package) Flows range=1:1-1:1 selection=1:1-1:1 + (Variable) Flow 0 range=5:21-8:2 selection=5:21-8:2 + (Variable) start detail="Effect.Effect" range=5:21-5:26 selection=5:21-5:26 + (Function) 0: Effect.map detail="Effect.Effect" range=6:3-6:27 selection=6:3-6:27 + (Function) 1: Effect.flatMap detail="Effect.Effect" range=7:3-7:51 selection=7:3-7:51 + (Variable) Flow 1 range=7:25-7:50 selection=7:25-7:50 + (Variable) n detail="number" range=7:47-7:48 selection=7:47-7:48 + (Function) 0: String detail="string" range=7:40-7:49 selection=7:40-7:49 + (Function) 1: Effect.succeed detail="Effect.Effect" range=7:25-7:50 selection=7:25-7:50 +(Variable) Effect range=1:10-1:16 selection=1:10-1:16 +(Variable) start range=3:15-3:43 selection=3:15-3:20 +(Variable) flow range=5:14-8:2 selection=5:14-5:18 + (Function) Effect.map() callback range=6:14-6:26 selection=6:14-6:14 + (Function) Effect.flatMap() callback range=7:18-7:50 selection=7:18-7:18 + +-- flat[0] -- +(Package) Effect container="" range=1:1-1:1 +(Package) Flows container="Effect" range=1:1-1:1 +(Variable) Flow 0 container="Flows" range=5:21-8:2 +(Variable) start container="Flow 0" range=5:21-5:26 +(Function) 0: Effect.map container="Flow 0" range=6:3-6:27 +(Function) 1: Effect.flatMap container="Flow 0" range=7:3-7:51 +(Variable) Flow 1 container="Flows" range=7:25-7:50 +(Variable) n container="Flow 1" range=7:47-7:48 +(Function) 0: String container="Flow 1" range=7:40-7:49 +(Function) 1: Effect.succeed container="Flow 1" range=7:25-7:50 +(Variable) Effect container="" range=1:10-1:16 +(Variable) start container="" range=3:15-3:43 +(Variable) flow container="" range=5:14-8:2 +(Function) Effect.map() callback container="flow" range=6:14-6:26 +(Function) Effect.flatMap() callback container="flow" range=7:18-7:50 diff --git a/testdata/baselines/reference/effect-v4/effectFnOpportunity_inferredLayer.pipings.txt b/testdata/baselines/reference/effect-v4/effectFnOpportunity_inferredLayer.pipings.txt index c762350d..98d65c34 100644 --- a/testdata/baselines/reference/effect-v4/effectFnOpportunity_inferredLayer.pipings.txt +++ b/testdata/baselines/reference/effect-v4/effectFnOpportunity_inferredLayer.pipings.txt @@ -61,18 +61,22 @@ Transformations (1): outType: Effect === Piping Flow === -Location: 13:58 - 17:3 -Node: Effect.gen(function*() {\n yield* Effect.log("log")\n // Log should be inferred with name MyService.log\n return { log: (what: string) => Effect.log(what) }\n}) +Location: 13:34 - 17:4 +Node: Layer.effect(MyService, Effect.gen(function*() {\n yield* Effect.log("log")\n // Log should be inferred with name MyService.log\n return { log: (what: string) => Effect.log(what) }\n})) Node Kind: KindCallExpression Subject: function*() {\n yield* Effect.log("log")\n // Log should be inferred with name MyService.log\n return { log: (what: string) => Effect.log(what) }\n} Subject Type: () => Generator, { log: (what: string) => Effect; }, any> -Transformations (1): +Transformations (2): [0] kind: call callee: Effect.gen args: (constant) outType: Effect<{ log: (what: string) => Effect; }, never, never> + [1] kind: dataLast + callee: Layer.effect + args: [MyService] + outType: Layer === Piping Flow === Location: 14:9 - 14:27 diff --git a/testdata/baselines/reference/effect-v4/effectFnOpportunity_inferredLayerThis.pipings.txt b/testdata/baselines/reference/effect-v4/effectFnOpportunity_inferredLayerThis.pipings.txt index 1ee13b06..be676e7b 100644 --- a/testdata/baselines/reference/effect-v4/effectFnOpportunity_inferredLayerThis.pipings.txt +++ b/testdata/baselines/reference/effect-v4/effectFnOpportunity_inferredLayerThis.pipings.txt @@ -15,18 +15,22 @@ Transformations (1): outType: ServiceClass Effect; }> === Piping Flow === -Location: 6:38 - 9:7 -Node: Effect.gen(function*() {\n yield* Effect.log("log")\n return { log: (what: string) => Effect.log(what) }\n }) +Location: 6:19 - 9:8 +Node: Layer.effect(this, Effect.gen(function*() {\n yield* Effect.log("log")\n return { log: (what: string) => Effect.log(what) }\n })) Node Kind: KindCallExpression Subject: function*() {\n yield* Effect.log("log")\n return { log: (what: string) => Effect.log(what) }\n } Subject Type: () => Generator, { log: (what: string) => Effect; }, any> -Transformations (1): +Transformations (2): [0] kind: call callee: Effect.gen args: (constant) outType: Effect<{ log: (what: string) => Effect; }, never, never> + [1] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer === Piping Flow === Location: 7:15 - 7:33 diff --git a/testdata/baselines/reference/effect-v4/effectGenToFn.refactors.txt b/testdata/baselines/reference/effect-v4/effectGenToFn.refactors.txt index a6753177..bf409751 100644 --- a/testdata/baselines/reference/effect-v4/effectGenToFn.refactors.txt +++ b/testdata/baselines/reference/effect-v4/effectGenToFn.refactors.txt @@ -156,13 +156,13 @@ export const program = () => }) export const programWithPipes = (fa: number, fb: number) => - Eff.gen(function*() { - return yield* Eff.gen(function*() { - const a = yield* Eff.succeed(fa) - const b = yield* Eff.succeed(fb) - return a + b - }).pipe(Eff.map(a => a + 1)) - }) + Eff.gen(function*() { return yield* Eff.gen(function*() { + const a = yield* Eff.succeed(fa) + const b = yield* Eff.succeed(fb) + return a + b + }).pipe( + Eff.map((a) => a + 1) + ) }) export function sampleReturns(arg1: A, arg2: B) { return Eff.gen(function*() { diff --git a/testdata/baselines/reference/effect-v4/effectGenToFn_mixedPipes.refactors.txt b/testdata/baselines/reference/effect-v4/effectGenToFn_mixedPipes.refactors.txt index 64d55c36..c366b09d 100644 --- a/testdata/baselines/reference/effect-v4/effectGenToFn_mixedPipes.refactors.txt +++ b/testdata/baselines/reference/effect-v4/effectGenToFn_mixedPipes.refactors.txt @@ -37,12 +37,10 @@ import { pipe } from "effect/Function" const test = () => pipe( - Effect.gen(function*() { - return yield* Effect.gen(function*() { - const test = "test" - return yield* Effect.succeed(test) - }).pipe(Effect.asVoid) - }), + Effect.gen(function*() { return yield* Effect.gen(function*() { + const test = "test" + return yield* Effect.succeed(test) + }).pipe(Effect.asVoid) }), Effect.tapError(Effect.logError) ) diff --git a/testdata/baselines/reference/effect-v4/effectGenToFn_withPipes.refactors.txt b/testdata/baselines/reference/effect-v4/effectGenToFn_withPipes.refactors.txt index 355a1af0..3bcc3e1f 100644 --- a/testdata/baselines/reference/effect-v4/effectGenToFn_withPipes.refactors.txt +++ b/testdata/baselines/reference/effect-v4/effectGenToFn_withPipes.refactors.txt @@ -33,12 +33,13 @@ import * as Effect from "effect/Effect" import { pipe } from "effect/Function" const test = () => - Effect.gen(function*() { - return yield* pipe(Effect.gen(function*() { - const test = "test" - return yield* Effect.succeed(test) - }), Effect.tapError(Effect.logError)) - }) + Effect.gen(function*() { return yield* pipe( + Effect.gen(function*() { + const test = "test" + return yield* Effect.succeed(test) + }), + Effect.tapError(Effect.logError) + ) }) === [R1] Refactor 2: "Convert to Effect.fn(\"test\")" === diff --git a/testdata/baselines/reference/effect-v4/functionCalls.pipings.txt b/testdata/baselines/reference/effect-v4/functionCalls.pipings.txt index a85a4c65..5c59fe96 100644 --- a/testdata/baselines/reference/effect-v4/functionCalls.pipings.txt +++ b/testdata/baselines/reference/effect-v4/functionCalls.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/functionCalls.ts (3 flows) ==== +==== /.src/functionCalls.ts (4 flows) ==== === Piping Flow === Location: 5:23 - 10:2 @@ -22,6 +22,20 @@ Transformations (3): args: (constant) outType: number +=== Piping Flow === +Location: 13:53 - 13:104 +Node: Effect.tap(effect, (a) => Effect.log(`Got: ${a}`)) +Node Kind: KindCallExpression + +Subject: effect +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.tap + args: [(a) => Effect.log(`Got: ${a}`)] + outType: Effect + === Piping Flow === Location: 13:79 - 13:103 Node: Effect.log(`Got: ${a}`) diff --git a/testdata/baselines/reference/effect-v4/layerGraphDocumented.pipings.txt b/testdata/baselines/reference/effect-v4/layerGraphDocumented.pipings.txt index d1b1034c..156bbd2f 100644 --- a/testdata/baselines/reference/effect-v4/layerGraphDocumented.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerGraphDocumented.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/layerGraphDocumented.ts (4 flows) ==== +==== /.src/layerGraphDocumented.ts (10 flows) ==== === Piping Flow === Location: 7:8 - 7:27 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 9:19 - 9:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 16:8 - 16:27 Node: Effect.succeed({}) @@ -28,6 +42,80 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 18:19 - 18:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 25:8 - 25:45 +Node: Effect.as(FileSystem.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: FileSystem.asEffect() +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, FileSystem> + +=== Piping Flow === +Location: 27:19 - 27:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 34:8 - 34:81 +Node: Effect.as(Effect.andThen(DbConnection.asEffect(), Cache.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: DbConnection.asEffect() +Subject Type: Effect<{}, never, DbConnection> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [Cache.asEffect()] + outType: Effect<{}, never, Cache | DbConnection> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Cache | DbConnection> + +=== Piping Flow === +Location: 36:19 - 36:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Cache | DbConnection> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 42:25 - 42:79 Node: Cache.Default.pipe(Layer.provide(FileSystem.Default)) diff --git a/testdata/baselines/reference/effect-v4/layerGraphDuplicated.pipings.txt b/testdata/baselines/reference/effect-v4/layerGraphDuplicated.pipings.txt index 591be4e9..895cf24c 100644 --- a/testdata/baselines/reference/effect-v4/layerGraphDuplicated.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerGraphDuplicated.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/layerGraphDuplicated.ts (4 flows) ==== +==== /.src/layerGraphDuplicated.ts (16 flows) ==== === Piping Flow === Location: 4:8 - 4:27 @@ -14,6 +14,76 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 10:8 - 10:43 +Node: Effect.as(Database.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: Database.asEffect() +Subject Type: Effect<{}, never, Database> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Database> + +=== Piping Flow === +Location: 12:19 - 12:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Database> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 16:8 - 16:43 +Node: Effect.as(Database.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: Database.asEffect() +Subject Type: Effect<{}, never, Database> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Database> + +=== Piping Flow === +Location: 18:19 - 18:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Database> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 22:8 - 22:27 Node: Effect.succeed({}) @@ -28,6 +98,116 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 24:19 - 24:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 28:8 - 28:87 +Node: Effect.as(Effect.andThen(UserRepository.asEffect(), Analytics.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: UserRepository.asEffect() +Subject Type: Effect<{}, never, UserRepository> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [Analytics.asEffect()] + outType: Effect<{}, never, Analytics | UserRepository> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Analytics | UserRepository> + +=== Piping Flow === +Location: 30:19 - 30:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Analytics | UserRepository> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 34:8 - 34:89 +Node: Effect.as(Effect.andThen(EventsRepository.asEffect(), Analytics.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: EventsRepository.asEffect() +Subject Type: Effect<{}, never, EventsRepository> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [Analytics.asEffect()] + outType: Effect<{}, never, Analytics | EventsRepository> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Analytics | EventsRepository> + +=== Piping Flow === +Location: 36:19 - 36:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Analytics | EventsRepository> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 40:8 - 40:87 +Node: Effect.as(Effect.andThen(UserService.asEffect(), EventService.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: UserService.asEffect() +Subject Type: Effect<{}, never, UserService> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [EventService.asEffect()] + outType: Effect<{}, never, EventService | UserService> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, EventService | UserService> + +=== Piping Flow === +Location: 42:19 - 42:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, EventService | UserService> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 45:23 - 59:2 Node: pipe(\n Database.Default,\n Layer.provideMerge(UserRepository.Default),\n Layer.merge(Analytics.Default),\n Layer.provideMerge(UserService.Default),\n Layer.merge(\n pipe(\n Database.Default,\n Layer.provideMerge(EventsRepository.Default),\n Layer.merge(Analytics.Default),\n Layer.provideMerge(EventService.Default)\n )\n ),\n Layer.provideMerge(AppService.Default)\n) diff --git a/testdata/baselines/reference/effect-v4/layerGraphEffect.pipings.txt b/testdata/baselines/reference/effect-v4/layerGraphEffect.pipings.txt index caf3b0fc..60a26897 100644 --- a/testdata/baselines/reference/effect-v4/layerGraphEffect.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerGraphEffect.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/layerGraphEffect.ts (3 flows) ==== +==== /.src/layerGraphEffect.ts (4 flows) ==== === Piping Flow === Location: 4:8 - 6:5 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: "DatabaseContext"; }, never, never> +=== Piping Flow === +Location: 8:19 - 8:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: "DatabaseContext"; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 11:23 - 15:49 Node: Layer.effectDiscard(Effect.gen(function*() {\n const databaseContext = yield* DatabaseContext.asEffect()\n\n return yield* Effect.log(databaseContext.value)\n})).pipe(Layer.provide(DatabaseContext.Default)) diff --git a/testdata/baselines/reference/effect-v4/layerGraphFollowSymbols.pipings.txt b/testdata/baselines/reference/effect-v4/layerGraphFollowSymbols.pipings.txt index a7920693..371e8362 100644 --- a/testdata/baselines/reference/effect-v4/layerGraphFollowSymbols.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerGraphFollowSymbols.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/simple.ts (5 flows) ==== +==== /.src/simple.ts (11 flows) ==== === Piping Flow === Location: 4:8 - 4:27 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 9:8 - 9:27 Node: Effect.succeed({}) @@ -28,6 +42,80 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 11:19 - 11:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 14:8 - 14:45 +Node: Effect.as(FileSystem.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: FileSystem.asEffect() +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, FileSystem> + +=== Piping Flow === +Location: 16:19 - 16:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 19:8 - 19:81 +Node: Effect.as(Effect.andThen(DbConnection.asEffect(), Cache.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: DbConnection.asEffect() +Subject Type: Effect<{}, never, DbConnection> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [Cache.asEffect()] + outType: Effect<{}, never, Cache | DbConnection> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Cache | DbConnection> + +=== Piping Flow === +Location: 21:19 - 21:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Cache | DbConnection> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 25:28 - 25:86 Node: UserRepository.Default.pipe(Layer.provide(Cache.Default)) diff --git a/testdata/baselines/reference/effect-v4/layerGraphFollowSymbolsDeep.pipings.txt b/testdata/baselines/reference/effect-v4/layerGraphFollowSymbolsDeep.pipings.txt index cb5cfc02..2de960e3 100644 --- a/testdata/baselines/reference/effect-v4/layerGraphFollowSymbolsDeep.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerGraphFollowSymbolsDeep.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/base.ts (3 flows) ==== +==== /.src/base.ts (7 flows) ==== === Piping Flow === Location: 4:8 - 4:27 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 9:8 - 9:27 Node: Effect.succeed({}) @@ -28,6 +42,48 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 11:19 - 11:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 14:8 - 14:45 +Node: Effect.as(FileSystem.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: FileSystem.asEffect() +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, FileSystem> + +=== Piping Flow === +Location: 16:19 - 16:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 19:27 - 19:81 Node: Cache.Default.pipe(Layer.provide(FileSystem.Default)) diff --git a/testdata/baselines/reference/effect-v4/layerGraphFollowSymbolsOff.pipings.txt b/testdata/baselines/reference/effect-v4/layerGraphFollowSymbolsOff.pipings.txt index a2bd131f..160fb247 100644 --- a/testdata/baselines/reference/effect-v4/layerGraphFollowSymbolsOff.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerGraphFollowSymbolsOff.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/simple.ts (4 flows) ==== +==== /.src/simple.ts (10 flows) ==== === Piping Flow === Location: 4:8 - 4:27 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 9:8 - 9:27 Node: Effect.succeed({}) @@ -28,6 +42,80 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 11:19 - 11:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 14:8 - 14:45 +Node: Effect.as(FileSystem.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: FileSystem.asEffect() +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, FileSystem> + +=== Piping Flow === +Location: 16:19 - 16:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 19:8 - 19:81 +Node: Effect.as(Effect.andThen(DbConnection.asEffect(), Cache.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: DbConnection.asEffect() +Subject Type: Effect<{}, never, DbConnection> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [Cache.asEffect()] + outType: Effect<{}, never, Cache | DbConnection> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Cache | DbConnection> + +=== Piping Flow === +Location: 21:19 - 21:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Cache | DbConnection> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 23:28 - 23:86 Node: UserRepository.Default.pipe(Layer.provide(Cache.Default)) diff --git a/testdata/baselines/reference/effect-v4/layerGraphMultiple.pipings.txt b/testdata/baselines/reference/effect-v4/layerGraphMultiple.pipings.txt index 1034c83e..96788a7b 100644 --- a/testdata/baselines/reference/effect-v4/layerGraphMultiple.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerGraphMultiple.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/layerGraphMultiple.ts (3 flows) ==== +==== /.src/layerGraphMultiple.ts (15 flows) ==== === Piping Flow === Location: 12:8 - 12:27 @@ -14,6 +14,76 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 14:19 - 14:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 18:8 - 18:43 +Node: Effect.as(Database.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: Database.asEffect() +Subject Type: Effect<{}, never, Database> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Database> + +=== Piping Flow === +Location: 20:19 - 20:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Database> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 24:8 - 24:43 +Node: Effect.as(Database.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: Database.asEffect() +Subject Type: Effect<{}, never, Database> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Database> + +=== Piping Flow === +Location: 26:19 - 26:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Database> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 30:8 - 30:27 Node: Effect.succeed({}) @@ -28,6 +98,116 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 32:19 - 32:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 36:8 - 36:87 +Node: Effect.as(Effect.andThen(UserRepository.asEffect(), Analytics.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: UserRepository.asEffect() +Subject Type: Effect<{}, never, UserRepository> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [Analytics.asEffect()] + outType: Effect<{}, never, Analytics | UserRepository> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Analytics | UserRepository> + +=== Piping Flow === +Location: 38:19 - 38:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Analytics | UserRepository> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 42:8 - 42:89 +Node: Effect.as(Effect.andThen(EventsRepository.asEffect(), Analytics.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: EventsRepository.asEffect() +Subject Type: Effect<{}, never, EventsRepository> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [Analytics.asEffect()] + outType: Effect<{}, never, Analytics | EventsRepository> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Analytics | EventsRepository> + +=== Piping Flow === +Location: 44:19 - 44:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Analytics | EventsRepository> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 48:8 - 48:87 +Node: Effect.as(Effect.andThen(UserService.asEffect(), EventService.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: UserService.asEffect() +Subject Type: Effect<{}, never, UserService> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [EventService.asEffect()] + outType: Effect<{}, never, EventService | UserService> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, EventService | UserService> + +=== Piping Flow === +Location: 50:19 - 50:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, EventService | UserService> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 55:23 - 63:2 Node: pipe(\n DatabaseLive,\n Layer.provideMerge(UserRepository.Default),\n Layer.provideMerge(EventsRepository.Default),\n Layer.merge(Analytics.Default),\n Layer.provideMerge(UserService.Default),\n Layer.provideMerge(EventService.Default),\n Layer.provideMerge(AppService.Default)\n) diff --git a/testdata/baselines/reference/effect-v4/layerGraphSimple.pipings.txt b/testdata/baselines/reference/effect-v4/layerGraphSimple.pipings.txt index 86dc7e69..d74ecfe8 100644 --- a/testdata/baselines/reference/effect-v4/layerGraphSimple.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerGraphSimple.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/layerGraphSimple.ts (5 flows) ==== +==== /.src/layerGraphSimple.ts (11 flows) ==== === Piping Flow === Location: 12:8 - 12:27 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 14:19 - 14:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 17:8 - 17:27 Node: Effect.succeed({}) @@ -28,6 +42,80 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 19:19 - 19:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 22:8 - 22:45 +Node: Effect.as(FileSystem.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: FileSystem.asEffect() +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, FileSystem> + +=== Piping Flow === +Location: 24:19 - 24:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 27:8 - 27:81 +Node: Effect.as(Effect.andThen(DbConnection.asEffect(), Cache.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: DbConnection.asEffect() +Subject Type: Effect<{}, never, DbConnection> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [Cache.asEffect()] + outType: Effect<{}, never, Cache | DbConnection> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Cache | DbConnection> + +=== Piping Flow === +Location: 29:19 - 29:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Cache | DbConnection> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 33:28 - 33:86 Node: UserRepository.Default.pipe(Layer.provide(Cache.Default)) diff --git a/testdata/baselines/reference/effect-v4/layerGraphSpecialChars.pipings.txt b/testdata/baselines/reference/effect-v4/layerGraphSpecialChars.pipings.txt index b5209fbd..728de832 100644 --- a/testdata/baselines/reference/effect-v4/layerGraphSpecialChars.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerGraphSpecialChars.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/layerGraphSpecialChars.ts (1 flows) ==== +==== /.src/layerGraphSpecialChars.ts (2 flows) ==== === Piping Flow === Location: 11:32 - 11:48 @@ -13,3 +13,17 @@ Transformations (1): callee: Symbol.for args: (constant) outType: symbol + +=== Piping Flow === +Location: 21:30 - 21:100 +Node: Layer.succeed(WithSpecialChars, { value: "WithSpecialChars" } as any) +Node Kind: KindCallExpression + +Subject: { value: "WithSpecialChars" } as any +Subject Type: any + +Transformations (1): + [0] kind: dataLast + callee: Layer.succeed + args: [WithSpecialChars] + outType: LayerChars#!">, never, never> diff --git a/testdata/baselines/reference/effect-v4/layerMergeAllWithDependencies.pipings.txt b/testdata/baselines/reference/effect-v4/layerMergeAllWithDependencies.pipings.txt index 31736986..c4e56639 100644 --- a/testdata/baselines/reference/effect-v4/layerMergeAllWithDependencies.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerMergeAllWithDependencies.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/layerMergeAllWithDependencies.ts (2 flows) ==== +==== /.src/layerMergeAllWithDependencies.ts (9 flows) ==== === Piping Flow === Location: 4:8 - 4:27 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 9:8 - 9:27 Node: Effect.succeed({}) @@ -27,3 +41,91 @@ Transformations (1): callee: Effect.succeed args: (constant) outType: Effect<{}, never, never> + +=== Piping Flow === +Location: 11:19 - 11:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 14:8 - 14:45 +Node: Effect.as(FileSystem.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: FileSystem.asEffect() +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, FileSystem> + +=== Piping Flow === +Location: 16:19 - 16:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 19:8 - 19:81 +Node: Effect.as(Effect.andThen(DbConnection.asEffect(), Cache.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: DbConnection.asEffect() +Subject Type: Effect<{}, never, DbConnection> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [Cache.asEffect()] + outType: Effect<{}, never, Cache | DbConnection> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Cache | DbConnection> + +=== Piping Flow === +Location: 21:19 - 21:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Cache | DbConnection> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 24:25 - 24:63 +Node: Layer.effect(Cache, Cache.asEffect()) +Node Kind: KindCallExpression + +Subject: Cache.asEffect() +Subject Type: Effect<{}, never, Cache> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [Cache] + outType: Layer diff --git a/testdata/baselines/reference/effect-v4/layerMergeAllWithDependenciesNonPropertyAccess.pipings.txt b/testdata/baselines/reference/effect-v4/layerMergeAllWithDependenciesNonPropertyAccess.pipings.txt index 561e3ba8..4aa49319 100644 --- a/testdata/baselines/reference/effect-v4/layerMergeAllWithDependenciesNonPropertyAccess.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerMergeAllWithDependenciesNonPropertyAccess.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/layerMergeAllWithDependenciesNonPropertyAccess.ts (2 flows) ==== +==== /.src/layerMergeAllWithDependenciesNonPropertyAccess.ts (6 flows) ==== === Piping Flow === Location: 4:8 - 4:27 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 9:8 - 9:27 Node: Effect.succeed({}) @@ -27,3 +41,45 @@ Transformations (1): callee: Effect.succeed args: (constant) outType: Effect<{}, never, never> + +=== Piping Flow === +Location: 11:19 - 11:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 14:8 - 14:45 +Node: Effect.as(FileSystem.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: FileSystem.asEffect() +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, FileSystem> + +=== Piping Flow === +Location: 16:19 - 16:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer diff --git a/testdata/baselines/reference/effect-v4/layerMergeAllWithDependencies_preview.pipings.txt b/testdata/baselines/reference/effect-v4/layerMergeAllWithDependencies_preview.pipings.txt index c9abd853..0e3845a2 100644 --- a/testdata/baselines/reference/effect-v4/layerMergeAllWithDependencies_preview.pipings.txt +++ b/testdata/baselines/reference/effect-v4/layerMergeAllWithDependencies_preview.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/layerMergeAllWithDependencies_preview.ts (1 flows) ==== +==== /.src/layerMergeAllWithDependencies_preview.ts (4 flows) ==== === Piping Flow === Location: 5:53 - 5:72 @@ -13,3 +13,45 @@ Transformations (1): callee: Effect.succeed args: (constant) outType: Effect<{}, never, never> + +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 8:53 - 8:81 +Node: Effect.as(A.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: A.asEffect() +Subject Type: Effect<{}, never, A> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, A> + +=== Piping Flow === +Location: 9:19 - 9:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, A> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer diff --git a/testdata/baselines/reference/effect-v4/missedPipeableOpportunity.pipings.txt b/testdata/baselines/reference/effect-v4/missedPipeableOpportunity.pipings.txt index 9417334d..4d9e9aae 100644 --- a/testdata/baselines/reference/effect-v4/missedPipeableOpportunity.pipings.txt +++ b/testdata/baselines/reference/effect-v4/missedPipeableOpportunity.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/missedPipeableOpportunity.ts (12 flows) ==== +==== /.src/missedPipeableOpportunity.ts (17 flows) ==== === Piping Flow === Location: 5:30 - 5:52 @@ -14,6 +14,48 @@ Transformations (1): args: (constant) outType: Effect +=== Piping Flow === +Location: 6:45 - 6:73 +Node: Effect.map(e, (n) => n * 2) +Node Kind: KindCallExpression + +Subject: e +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [(n) => n * 2] + outType: Effect + +=== Piping Flow === +Location: 7:64 - 7:92 +Node: Effect.map(e, (m) => m * n) +Node Kind: KindCallExpression + +Subject: e +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [(m) => m * n] + outType: Effect + +=== Piping Flow === +Location: 8:47 - 8:79 +Node: Effect.map(e, (n) => String(n)) +Node Kind: KindCallExpression + +Subject: e +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [(n) => String(n)] + outType: Effect + === Piping Flow === Location: 8:68 - 8:78 Node: String(n) @@ -50,6 +92,20 @@ Transformations (3): args: (constant) outType: Effect +=== Piping Flow === +Location: 14:45 - 14:73 +Node: Effect.map(e, (n) => n * 3) +Node Kind: KindCallExpression + +Subject: e +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [(n) => n * 3] + outType: Effect + === Piping Flow === Location: 15:29 - 15:66 Node: toString(triple(double(addOne(10)))) @@ -130,6 +186,20 @@ Transformations (3): args: (constant) outType: Effect +=== Piping Flow === +Location: 26:49 - 26:79 +Node: Effect.map(e, (n) => n + 100) +Node Kind: KindCallExpression + +Subject: e +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [(n) => n + 100] + outType: Effect + === Piping Flow === Location: 27:29 - 27:67 Node: triple(addHundred(double(addOne(7)))) diff --git a/testdata/baselines/reference/effect-v4/missingEffectContext_sugg.pipings.txt b/testdata/baselines/reference/effect-v4/missingEffectContext_sugg.pipings.txt index 949e2a93..bbbeb371 100644 --- a/testdata/baselines/reference/effect-v4/missingEffectContext_sugg.pipings.txt +++ b/testdata/baselines/reference/effect-v4/missingEffectContext_sugg.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/missingEffectContext_sugg.ts (7 flows) ==== +==== /.src/missingEffectContext_sugg.ts (13 flows) ==== === Piping Flow === Location: 4:8 - 4:27 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 10:8 - 10:27 Node: Effect.succeed({}) @@ -28,6 +42,80 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 12:19 - 12:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 16:8 - 16:45 +Node: Effect.as(FileSystem.asEffect(), {}) +Node Kind: KindCallExpression + +Subject: FileSystem.asEffect() +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, FileSystem> + +=== Piping Flow === +Location: 18:19 - 18:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, FileSystem> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + +=== Piping Flow === +Location: 22:8 - 22:81 +Node: Effect.as(Effect.andThen(DbConnection.asEffect(), Cache.asEffect()), {}) +Node Kind: KindCallExpression + +Subject: DbConnection.asEffect() +Subject Type: Effect<{}, never, DbConnection> + +Transformations (2): + [0] kind: dataFirst + callee: Effect.andThen + args: [Cache.asEffect()] + outType: Effect<{}, never, Cache | DbConnection> + [1] kind: dataFirst + callee: Effect.as + args: [{}] + outType: Effect<{}, never, Cache | DbConnection> + +=== Piping Flow === +Location: 24:19 - 24:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, Cache | DbConnection> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 27:32 - 31:2 Node: UserRepository.Default.pipe(\n Layer.provide(Cache.Default),\n Layer.provide(FileSystem.Default),\n Layer.merge(DbConnection.Default)\n) diff --git a/testdata/baselines/reference/effect-v4/missingLayerContext.pipings.txt b/testdata/baselines/reference/effect-v4/missingLayerContext.pipings.txt index 30fc959f..f5ade51b 100644 --- a/testdata/baselines/reference/effect-v4/missingLayerContext.pipings.txt +++ b/testdata/baselines/reference/effect-v4/missingLayerContext.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/missingLayerContext.ts (5 flows) ==== +==== /.src/missingLayerContext.ts (8 flows) ==== === Piping Flow === Location: 4:8 - 4:37 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 10:8 - 10:37 Node: Effect.succeed({ value: 2 }) @@ -28,6 +42,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 12:19 - 12:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 16:8 - 16:37 Node: Effect.succeed({ value: 3 }) @@ -42,6 +70,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 18:19 - 18:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 25:2 - 28:26 Node: testFn(layerWithServices) diff --git a/testdata/baselines/reference/effect-v4/missingLayerContext_preview.pipings.txt b/testdata/baselines/reference/effect-v4/missingLayerContext_preview.pipings.txt index 3f979027..e293ebfc 100644 --- a/testdata/baselines/reference/effect-v4/missingLayerContext_preview.pipings.txt +++ b/testdata/baselines/reference/effect-v4/missingLayerContext_preview.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/missingLayerContext_preview.ts (1 flows) ==== +==== /.src/missingLayerContext_preview.ts (2 flows) ==== === Piping Flow === Location: 5:53 - 5:72 @@ -13,3 +13,17 @@ Transformations (1): callee: Effect.succeed args: (constant) outType: Effect<{}, never, never> + +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer diff --git a/testdata/baselines/reference/effect-v4/multipleEffectProvide.pipings.txt b/testdata/baselines/reference/effect-v4/multipleEffectProvide.pipings.txt index 3b8d35a0..9c3d42eb 100644 --- a/testdata/baselines/reference/effect-v4/multipleEffectProvide.pipings.txt +++ b/testdata/baselines/reference/effect-v4/multipleEffectProvide.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/multipleEffectProvide.ts (6 flows) ==== +==== /.src/multipleEffectProvide.ts (9 flows) ==== === Piping Flow === Location: 4:8 - 4:37 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 10:8 - 10:37 Node: Effect.succeed({ value: 2 }) @@ -28,6 +42,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 12:19 - 12:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 16:8 - 16:37 Node: Effect.succeed({ value: 3 }) @@ -42,6 +70,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 18:19 - 18:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 21:28 - 24:2 Node: Effect.void.pipe(\n Effect.provide(MyService1.Default),\n Effect.provide(MyService2.Default)\n) diff --git a/testdata/baselines/reference/effect-v4/multipleEffectProvide_effectFn.pipings.txt b/testdata/baselines/reference/effect-v4/multipleEffectProvide_effectFn.pipings.txt index ab781768..8da3ee00 100644 --- a/testdata/baselines/reference/effect-v4/multipleEffectProvide_effectFn.pipings.txt +++ b/testdata/baselines/reference/effect-v4/multipleEffectProvide_effectFn.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/multipleEffectProvide_effectFn.ts (7 flows) ==== +==== /.src/multipleEffectProvide_effectFn.ts (10 flows) ==== === Piping Flow === Location: 4:8 - 4:37 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 10:8 - 10:37 Node: Effect.succeed({ value: 2 }) @@ -28,6 +42,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 12:19 - 12:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 16:8 - 16:37 Node: Effect.succeed({ value: 3 }) @@ -42,6 +70,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 18:19 - 18:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 22:36 - 28:2 Node: Effect.fn(\n function*() {\n return yield* Effect.void\n },\n Effect.provide(MyService1.Default),\n Effect.provide(MyService2.Default)\n) diff --git a/testdata/baselines/reference/effect-v4/multipleEffectProvide_effectFnRegular.pipings.txt b/testdata/baselines/reference/effect-v4/multipleEffectProvide_effectFnRegular.pipings.txt index 959d0846..2f4f72b6 100644 --- a/testdata/baselines/reference/effect-v4/multipleEffectProvide_effectFnRegular.pipings.txt +++ b/testdata/baselines/reference/effect-v4/multipleEffectProvide_effectFnRegular.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/multipleEffectProvide_effectFnRegular.ts (6 flows) ==== +==== /.src/multipleEffectProvide_effectFnRegular.ts (9 flows) ==== === Piping Flow === Location: 4:8 - 4:37 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 10:8 - 10:37 Node: Effect.succeed({ value: 2 }) @@ -28,6 +42,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 12:19 - 12:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 16:8 - 16:37 Node: Effect.succeed({ value: 3 }) @@ -42,6 +70,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 18:19 - 18:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 22:43 - 26:2 Node: Effect.fn(\n () => Effect.void,\n Effect.provide(MyService1.Default),\n Effect.provide(MyService2.Default)\n) diff --git a/testdata/baselines/reference/effect-v4/multipleEffectProvide_preview.pipings.txt b/testdata/baselines/reference/effect-v4/multipleEffectProvide_preview.pipings.txt index 5c488b6f..97ba95b3 100644 --- a/testdata/baselines/reference/effect-v4/multipleEffectProvide_preview.pipings.txt +++ b/testdata/baselines/reference/effect-v4/multipleEffectProvide_preview.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/multipleEffectProvide_preview.ts (3 flows) ==== +==== /.src/multipleEffectProvide_preview.ts (5 flows) ==== === Piping Flow === Location: 5:53 - 5:72 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 8:53 - 8:72 Node: Effect.succeed({}) @@ -28,6 +42,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 9:19 - 9:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 11:23 - 11:94 Node: Effect.void.pipe(Effect.provide(A.Default), Effect.provide(B.Default)) diff --git a/testdata/baselines/reference/effect-v4/outdatedApi.pipings.txt b/testdata/baselines/reference/effect-v4/outdatedApi.pipings.txt index 7282f1dd..b85634e7 100644 --- a/testdata/baselines/reference/effect-v4/outdatedApi.pipings.txt +++ b/testdata/baselines/reference/effect-v4/outdatedApi.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/outdatedApi.ts (36 flows) ==== +==== /.src/outdatedApi.ts (37 flows) ==== === Piping Flow === Location: 6:25 - 8:3 @@ -339,18 +339,22 @@ Transformations (1): outType: Effect === Piping Flow === -Location: 67:33 - 67:50 -Node: Effect.succeed(1) +Location: 67:21 - 67:65 +Node: Effect.map(Effect.succeed(1), (n) => n + 1) Node Kind: KindCallExpression Subject: 1 Subject Type: 1 -Transformations (1): +Transformations (2): [0] kind: call callee: Effect.succeed args: (constant) outType: Effect + [1] kind: dataFirst + callee: Effect.map + args: [(n) => n + 1] + outType: Effect === Piping Flow === Location: 70:21 - 72:3 @@ -381,18 +385,22 @@ Transformations (1): outType: Effect === Piping Flow === -Location: 75:41 - 75:58 -Node: Effect.succeed(1) +Location: 75:25 - 75:89 +Node: Effect.flatMap(Effect.succeed(1), (n) => Effect.succeed(n + 1)) Node Kind: KindCallExpression Subject: 1 Subject Type: 1 -Transformations (1): +Transformations (2): [0] kind: call callee: Effect.succeed args: (constant) outType: Effect + [1] kind: dataFirst + callee: Effect.flatMap + args: [(n) => Effect.succeed(n + 1)] + outType: Effect === Piping Flow === Location: 75:66 - 75:88 @@ -408,6 +416,20 @@ Transformations (1): args: (constant) outType: Effect +=== Piping Flow === +Location: 81:22 - 81:75 +Node: Effect.bind(Effect.Do, "a", () => Effect.succeed(1)) +Node Kind: KindCallExpression + +Subject: Effect.Do +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataFirst + callee: Effect.bind + args: ["a", () => Effect.succeed(1)] + outType: Effect<{ a: number; }, never, never> + === Piping Flow === Location: 81:56 - 81:74 Node: Effect.succeed(1) diff --git a/testdata/baselines/reference/effect-v4/pipeableToDatafirst.refactors.txt b/testdata/baselines/reference/effect-v4/pipeableToDatafirst.refactors.txt index 6ad1d0b3..5398c0af 100644 --- a/testdata/baselines/reference/effect-v4/pipeableToDatafirst.refactors.txt +++ b/testdata/baselines/reference/effect-v4/pipeableToDatafirst.refactors.txt @@ -113,7 +113,12 @@ const test3 = pipe( import * as T from "effect/Effect" import { pipe } from "effect/Function" -const test = T.gen(function*() { return yield* pipe(T.succeed("Hello"), T.flatMap(_ => T.log(_)), T.andThen(T.succeed(42)), T.map(_ => _ * 2)) }) +const test = T.gen(function*() { return yield* pipe( + T.succeed("Hello"), + T.flatMap((_) => T.log(_)), + T.andThen(T.succeed(42)), + T.map((_) => _ * 2) +) }) const noDataFirst = (value: string) => (eff: T.Effect) => pipe(eff, T.andThen(T.log(value))) @@ -233,7 +238,11 @@ const test = pipe( const noDataFirst = (value: string) => (eff: T.Effect) => pipe(eff, T.andThen(T.log(value))) -const test2 = T.gen(function*() { return yield* pipe(T.succeed("Hello"), T.flatMap(_ => T.log(_)), noDataFirst("42")) }) +const test2 = T.gen(function*() { return yield* pipe( + T.succeed("Hello"), + T.flatMap((_) => T.log(_)), + noDataFirst("42") +) }) const test3 = pipe( T.succeed("Hello"), @@ -347,5 +356,11 @@ const test2 = pipe( noDataFirst("42") ) -const test3 = T.gen(function*() { return yield* pipe(T.succeed("Hello"), T.flatMap(_ => T.log(_)), noDataFirst("a"), noDataFirst("b"), noDataFirst("c")) }) +const test3 = T.gen(function*() { return yield* pipe( + T.succeed("Hello"), + T.flatMap((_) => T.log(_)), + noDataFirst("a"), + noDataFirst("b"), + noDataFirst("c") +) }) diff --git a/testdata/baselines/reference/effect-v4/simpleEffect.pipings.txt b/testdata/baselines/reference/effect-v4/simpleEffect.pipings.txt index 2635874c..c60e9457 100644 --- a/testdata/baselines/reference/effect-v4/simpleEffect.pipings.txt +++ b/testdata/baselines/reference/effect-v4/simpleEffect.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/simpleEffect.ts (9 flows) ==== +==== /.src/simpleEffect.ts (8 flows) ==== === Piping Flow === Location: 4:28 - 8:2 @@ -61,37 +61,31 @@ Location: 16:34 - 20:2 Node: Effect.map(Effect.succeed(1), (x) => x + 1).pipe(\n Effect.map((x) => x * 2),\n Effect.map((x) => x.toString()),\n Effect.map((x) => x.length > 0)\n) Node Kind: KindCallExpression -Subject: Effect.map(Effect.succeed(1), (x) => x + 1) -Subject Type: Effect +Subject: 1 +Subject Type: 1 -Transformations (3): - [0] kind: pipeable +Transformations (5): + [0] kind: call + callee: Effect.succeed + args: (constant) + outType: Effect + [1] kind: dataFirst + callee: Effect.map + args: [(x) => x + 1] + outType: Effect + [2] kind: pipeable callee: Effect.map args: [(x) => x * 2] outType: Effect - [1] kind: pipeable + [3] kind: pipeable callee: Effect.map args: [(x) => x.toString()] outType: Effect - [2] kind: pipeable + [4] kind: pipeable callee: Effect.map args: [(x) => x.length > 0] outType: Effect -=== Piping Flow === -Location: 16:46 - 16:63 -Node: Effect.succeed(1) -Node Kind: KindCallExpression - -Subject: 1 -Subject Type: 1 - -Transformations (1): - [0] kind: call - callee: Effect.succeed - args: (constant) - outType: Effect - === Piping Flow === Location: 22:39 - 24:2 Node: Effect.succeed(1).pipe(\n Effect.flatMap((x) => Effect.succeed(x + 1).pipe(Effect.map((x) => x * 2)))\n) diff --git a/testdata/baselines/reference/effect-v4/strictEffectProvide.pipings.txt b/testdata/baselines/reference/effect-v4/strictEffectProvide.pipings.txt index 04fc954d..e9c04083 100644 --- a/testdata/baselines/reference/effect-v4/strictEffectProvide.pipings.txt +++ b/testdata/baselines/reference/effect-v4/strictEffectProvide.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/strictEffectProvide.ts (8 flows) ==== +==== /.src/strictEffectProvide.ts (12 flows) ==== === Piping Flow === Location: 5:8 - 5:37 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 7:19 - 7:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 11:8 - 11:37 Node: Effect.succeed({ value: 2 }) @@ -28,6 +42,20 @@ Transformations (1): args: (constant) outType: Effect<{ value: number; }, never, never> +=== Piping Flow === +Location: 13:19 - 13:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{ value: number; }, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 17:29 - 19:2 Node: Effect.void.pipe(\n Effect.provide(MyService1.Default)\n) @@ -106,6 +134,20 @@ Transformations (1): args: (constant) outType: { value: number; } +=== Piping Flow === +Location: 42:32 - 45:2 +Node: Layer.provide(\n MyService1.Default,\n MyService2.Default\n) +Node Kind: KindCallExpression + +Subject: MyService1.Default +Subject Type: Layer + +Transformations (1): + [0] kind: dataFirst + callee: Layer.provide + args: [MyService2.Default] + outType: Layer + === Piping Flow === Location: 48:32 - 51:3 Node: Effect.gen(function*() {\n const ctx = yield* Effect.services()\n return yield* Effect.provide(Effect.void, ctx)\n}) @@ -119,3 +161,17 @@ Transformations (1): callee: Effect.gen args: (constant) outType: Effect + +=== Piping Flow === +Location: 50:16 - 50:49 +Node: Effect.provide(Effect.void, ctx) +Node Kind: KindCallExpression + +Subject: Effect.void +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.provide + args: [ctx] + outType: Effect diff --git a/testdata/baselines/reference/effect-v4/strictEffectProvide_preview.pipings.txt b/testdata/baselines/reference/effect-v4/strictEffectProvide_preview.pipings.txt index cb13978a..dcb15cd2 100644 --- a/testdata/baselines/reference/effect-v4/strictEffectProvide_preview.pipings.txt +++ b/testdata/baselines/reference/effect-v4/strictEffectProvide_preview.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/strictEffectProvide_preview.ts (2 flows) ==== +==== /.src/strictEffectProvide_preview.ts (3 flows) ==== === Piping Flow === Location: 5:68 - 5:87 @@ -14,6 +14,20 @@ Transformations (1): args: (constant) outType: Effect<{}, never, never> +=== Piping Flow === +Location: 6:19 - 6:49 +Node: Layer.effect(this, this.make) +Node Kind: KindCallExpression + +Subject: this.make +Subject Type: Effect<{}, never, never> + +Transformations (1): + [0] kind: dataLast + callee: Layer.effect + args: [this] + outType: Layer + === Piping Flow === Location: 8:23 - 8:72 Node: Effect.void.pipe(Effect.provide(Config.Default)) diff --git a/testdata/baselines/reference/effect-v4/togglePipeStyle.refactors.txt b/testdata/baselines/reference/effect-v4/togglePipeStyle.refactors.txt index 006acf7a..95806293 100644 --- a/testdata/baselines/reference/effect-v4/togglePipeStyle.refactors.txt +++ b/testdata/baselines/reference/effect-v4/togglePipeStyle.refactors.txt @@ -46,7 +46,7 @@ export const toPipeable = pipe(Effect.succeed(42), Effect.map((x) => x * 2)) import * as Effect from "effect/Effect" import { pipe } from "effect/Function" -export const toRegularPipe = Effect.gen(function*() { return yield* Effect.succeed(42).pipe(Effect.map(x => x * 2)) }) +export const toRegularPipe = Effect.gen(function*() { return yield* Effect.succeed(42).pipe(Effect.map((x) => x * 2)) }) export const toPipeable = pipe(Effect.succeed(42), Effect.map((x) => x * 2)) diff --git a/testdata/baselines/reference/effect-v4/wrapWithEffectGen.refactors.txt b/testdata/baselines/reference/effect-v4/wrapWithEffectGen.refactors.txt index 99e1fc81..392208fd 100644 --- a/testdata/baselines/reference/effect-v4/wrapWithEffectGen.refactors.txt +++ b/testdata/baselines/reference/effect-v4/wrapWithEffectGen.refactors.txt @@ -335,7 +335,11 @@ export const test2 = Effect.succeed(42).pipe( Effect.map((n) => n - 1) ) -export const test4 = Effect.gen(function*() { return yield* pipe(Effect.succeed(42), Effect.map(n => n + 1), Effect.map(n => n - 1)) }) +export const test4 = Effect.gen(function*() { return yield* pipe( + Effect.succeed(42), + Effect.map((n) => n + 1), + Effect.map((n) => n - 1) +) }) === [R6] Refactor 0: "Wrap with pipe" === diff --git a/testdata/baselines/reference/effect-v4/wrapWithEffectGen_nestedCallback.refactors.txt b/testdata/baselines/reference/effect-v4/wrapWithEffectGen_nestedCallback.refactors.txt new file mode 100644 index 00000000..02511fa6 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/wrapWithEffectGen_nestedCallback.refactors.txt @@ -0,0 +1,44 @@ +=== Refactor Inventory === + +[R1] selection: "3:21-3:22" + Refactor 0: "Wrap with pipe" (refactor.rewrite.effect.wrapWithPipe) + Refactor 1: "Wrap with Effect.gen" (refactor.rewrite.effect.wrapWithEffectGen) + +[R2] selection: "empty (0:0-0:0)" + (no refactors) + +=== Refactor Application Results === + +=== [R1] Refactor 0: "Wrap with pipe" === + +--- file:///.src/wrapWithEffectGen_nestedCallback.ts --- +// refactor: 3:21-3:22 +import * as Effect from "effect/Effect" + +export const task =pipe( )Effect.promise(() => { + if (Math.random() > 0.5) { + return Promise.resolve(1) + } + if (Math.random() > 0.2) { + return Promise.resolve(2) + } + return Promise.resolve(3) +}) + + +=== [R1] Refactor 1: "Wrap with Effect.gen" === + +--- file:///.src/wrapWithEffectGen_nestedCallback.ts --- +// refactor: 3:21-3:22 +import * as Effect from "effect/Effect" + +export const task = Effect.gen(function*() { return yield* Effect.promise(() => { + if (Math.random() > 0.5) { + return Promise.resolve(1) + } + if (Math.random() > 0.2) { + return Promise.resolve(2) + } + return Promise.resolve(3) +}) }) + diff --git a/testdata/baselines/reference/schema.json b/testdata/baselines/reference/schema.json index 794c572d..d193fa38 100644 --- a/testdata/baselines/reference/schema.json +++ b/testdata/baselines/reference/schema.json @@ -1083,6 +1083,11 @@ "description": "Controls Effect completions.", "type": "boolean" }, + "debug": { + "default": false, + "description": "Enables additional debug-only Effect language service output.", + "type": "boolean" + }, "diagnosticSeverity": { "$ref": "#/definitions/effectLanguageServicePluginDiagnosticSeverityDefinition", "default": {}, @@ -1664,6 +1669,11 @@ "default": "off", "description": "Detects 'any' or 'unknown' types in Effect error or requirements channels" }, + "asyncFunction": { + "$ref": "#/definitions/effectLanguageServicePluginSeverityDefinition", + "default": "off", + "description": "Warns when declaring async functions and suggests using Effect values and Effect.gen for async control flow" + }, "catchAllToMapError": { "$ref": "#/definitions/effectLanguageServicePluginSeverityDefinition", "default": "suggestion", @@ -1859,6 +1869,11 @@ "default": "warning", "description": "Warns against chaining Effect.provide calls which can cause service lifecycle issues" }, + "newPromise": { + "$ref": "#/definitions/effectLanguageServicePluginSeverityDefinition", + "default": "off", + "description": "Warns when constructing promises with new Promise instead of using Effect APIs" + }, "nodeBuiltinImport": { "$ref": "#/definitions/effectLanguageServicePluginSeverityDefinition", "default": "off", @@ -2027,6 +2042,11 @@ "description": "Controls Effect completions.", "type": "boolean" }, + "debug": { + "default": false, + "description": "Enables additional debug-only Effect language service output.", + "type": "boolean" + }, "diagnosticSeverity": { "$ref": "#/definitions/effectLanguageServicePluginDiagnosticSeverityDefinition", "default": {}, diff --git a/testdata/tests/effect-v3/catchUnfailableEffect.ts b/testdata/tests/effect-v3/catchUnfailableEffect.ts index 4181a672..f981fcc1 100644 --- a/testdata/tests/effect-v3/catchUnfailableEffect.ts +++ b/testdata/tests/effect-v3/catchUnfailableEffect.ts @@ -31,8 +31,13 @@ export const shouldNotTriggerThirdArgPipe = pipe( Effect.catchAll(() => Effect.succeed(42)) // <- should not report here ) -export const shouldTriggerThirdArgPipe = pipe( - Effect.succeed(42), - Effect.flatMap(() => Effect.void), - Effect.catchAll(() => Effect.succeed(42)) // <- should report here -) +export const shouldTriggerThirdArgPipe = pipe( + Effect.succeed(42), + Effect.flatMap(() => Effect.void), + Effect.catchAll(() => Effect.succeed(42)) // <- should report here +) + +export const shouldReportDataFirst = Effect.catchAll( + Effect.never, + () => Effect.log("error") // <- should report here +) diff --git a/testdata/tests/effect-v4-document-symbols/debugDocumentSymbolFlows.ts b/testdata/tests/effect-v4-document-symbols/debugDocumentSymbolFlows.ts new file mode 100644 index 00000000..b9eb21eb --- /dev/null +++ b/testdata/tests/effect-v4-document-symbols/debugDocumentSymbolFlows.ts @@ -0,0 +1,23 @@ +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "plugins": [ + { + "name": "@effect/language-service", + "debug": true, + "ignoreEffectErrorsInTscExitCode": true, + "skipDisabledOptimization": true + } + ] + } +} + +// @Filename: /test.ts +import { Effect } from "effect" + +declare const start: Effect.Effect + +export const flow = start.pipe( + Effect.map((n) => n + 1), + Effect.flatMap((n) => Effect.succeed(String(n))) +) diff --git a/testdata/tests/effect-v4-refactors/wrapWithEffectGen_nestedCallback.ts b/testdata/tests/effect-v4-refactors/wrapWithEffectGen_nestedCallback.ts new file mode 100644 index 00000000..ae9629ca --- /dev/null +++ b/testdata/tests/effect-v4-refactors/wrapWithEffectGen_nestedCallback.ts @@ -0,0 +1,12 @@ +// refactor: 3:21-3:22 +import * as Effect from "effect/Effect" + +export const task = Effect.promise(() => { + if (Math.random() > 0.5) { + return Promise.resolve(1) + } + if (Math.random() > 0.2) { + return Promise.resolve(2) + } + return Promise.resolve(3) +}) diff --git a/testdata/tests/effect-v4/catchUnfailableEffect.ts b/testdata/tests/effect-v4/catchUnfailableEffect.ts index 15c99818..20914742 100644 --- a/testdata/tests/effect-v4/catchUnfailableEffect.ts +++ b/testdata/tests/effect-v4/catchUnfailableEffect.ts @@ -34,14 +34,14 @@ export const shouldReportFn = Effect.fn(function*() { return yield* Effect.succeed(42) }, Effect.catchTag("MyError", () => Effect.void)) -// Chain where intermediate steps preserve never-fail -export const shouldReportThird = pipe( - Effect.succeed(42), - Effect.map((n) => n + 1), - Effect.catchTag("MyError", () => Effect.void) -) - -// === Should NOT trigger (E is not never) === +// Chain where intermediate steps preserve never-fail +export const shouldReportThird = pipe( + Effect.succeed(42), + Effect.map((n) => n + 1), + Effect.catchTag("MyError", () => Effect.void) +) + +// === Should NOT trigger (E is not never) === // Effect that can fail export const shouldNotReport = effectWithErrors.pipe(