diff --git a/.changeset/map-maperror-to-mapboth.md b/.changeset/map-maperror-to-mapboth.md new file mode 100644 index 00000000..442d4627 --- /dev/null +++ b/.changeset/map-maperror-to-mapboth.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": minor +--- + +Add `mapMapErrorToMapBoth` diagnostic (`TS377132`) to suggest using `Effect.mapBoth` instead of directly adjacent `Effect.map` and `Effect.mapError`. diff --git a/README.md b/README.md index b3778c5c..986dc0d0 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ Some diagnostics are off by default or have a default severity of suggestion, bu effectSucceedWithVoidSuggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0) flatMapConditionalToFilterOrFailSuggests Effect.filterOrFail or Effect.filterOrElse when Effect.flatMap conditionally passes its input through with Effect.succeed flatMapToMapSuggests using Effect.map instead of Effect.flatMap when the callback only wraps its result with Effect.succeed + mapMapErrorToMapBothSuggests using Effect.mapBoth instead of directly adjacent Effect.map and Effect.mapError mapSomeToAsSomeSuggests using Effect.asSome instead of Effect.map when the mapper only wraps the success value with Option.some matchEffectToMapBothSuggests Effect.mapBoth when Effect.matchEffect only transforms the failure and success channels matchEffectToMatchSuggests Effect.match or Effect.matchCause when both Effect.matchEffect handlers only return Effect.succeed diff --git a/_packages/tsgo/src/metadata.json b/_packages/tsgo/src/metadata.json index b4483bac..40aba11a 100644 --- a/_packages/tsgo/src/metadata.json +++ b/_packages/tsgo/src/metadata.json @@ -1961,6 +1961,30 @@ ] } }, + { + "name": "mapMapErrorToMapBoth", + "group": "style", + "description": "Suggests using Effect.mapBoth instead of directly adjacent Effect.map and Effect.mapError", + "defaultSeverity": "suggestion", + "fixable": false, + "supportedEffect": [ + "v3", + "v4" + ], + "codes": [ + 377132 + ], + "preview": { + "sourceText": "import { Effect } from \"effect\"\n\ndeclare const fetchCount: Effect.Effect\u003cnumber, string\u003e\n\nexport const preview = fetchCount.pipe(\n Effect.map((n) =\u003e n \u003e 0),\n Effect.mapError((s) =\u003e s.length)\n)\n", + "diagnostics": [ + { + "start": 160, + "end": 175, + "text": "`Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth)" + } + ] + } + }, { "name": "mapSomeToAsSome", "group": "style", diff --git a/docs/rules/map-map-error-to-map-both.md b/docs/rules/map-map-error-to-map-both.md new file mode 100644 index 00000000..89e4d0e8 --- /dev/null +++ b/docs/rules/map-map-error-to-map-both.md @@ -0,0 +1,68 @@ + + +# `mapMapErrorToMapBoth` + +Suggests using Effect.mapBoth instead of directly adjacent Effect.map and Effect.mapError + +| Property | Value | +| --- | --- | +| Category | Style | +| Default severity | `suggestion` | +| Fixable | No | +| Effect versions | v3, v4 | +| Diagnostic codes | `TS377132` | +| Language Service name | `mapMapErrorToMapBoth` | +| Oxlint name | `effecttsgo/map-map-error-to-map-both` | + +## Preview + +```ts +import { Effect } from "effect" + +declare const fetchCount: Effect.Effect + +export const preview = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.mapError((s) => s.length) +/** + ^^^^^^^^^^^^^^^ effecttsgo(map-map-error-to-map-both): `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. +*/ +) +``` + +## Language Service Configuration + +See the [Language Service setup guide](../../README.md#installation) for installation instructions. + +```jsonc +{ + "$schema": "./node_modules/@effect/tsgo/schema.json", + "compilerOptions": { + "plugins": [ + { + "name": "@effect/language-service", + "diagnosticSeverity": { + "mapMapErrorToMapBoth": "warning" + } + } + ] + } +} +``` + +## Oxlint Configuration + +See the [Oxlint setup guide](../README.md#oxlint-setup) for installation and patching instructions. + +```json +{ + "$schema": "./node_modules/@effect/tsgo/oxlint-schema.json", + "options": { + "typeAware": true + }, + "plugins": ["effecttsgo"], + "rules": { + "effecttsgo/map-map-error-to-map-both": "warn" + } +} +``` diff --git a/internal/diagnostics/effectDiagnosticMessages.json b/internal/diagnostics/effectDiagnosticMessages.json index 73c2c660..5d47df40 100644 --- a/internal/diagnostics/effectDiagnosticMessages.json +++ b/internal/diagnostics/effectDiagnosticMessages.json @@ -506,5 +506,9 @@ "This module reference imports `{0}`, which is obsolete in Effect v4. In Effect v4, Schema is provided directly by `Schema` from `effect` (or `effect/Schema`). effect(obsoleteSchemaImport)": { "category": "Warning", "code": 377128 + }, + "`Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth)": { + "category": "Suggestion", + "code": 377132 } } diff --git a/internal/rules/map_map_error_to_map_both.go b/internal/rules/map_map_error_to_map_both.go new file mode 100644 index 00000000..efcb1ea2 --- /dev/null +++ b/internal/rules/map_map_error_to_map_both.go @@ -0,0 +1,125 @@ +package rules + +import ( + "github.com/effect-ts/tsgo/etscore" + "github.com/effect-ts/tsgo/internal/rule" + "github.com/effect-ts/tsgo/internal/typeparser" + "github.com/microsoft/TypeScript/tsc/shim/ast" + "github.com/microsoft/TypeScript/tsc/shim/checker" + "github.com/microsoft/TypeScript/tsc/shim/core" + tsdiag "github.com/microsoft/TypeScript/tsc/shim/diagnostics" + "github.com/microsoft/TypeScript/tsc/shim/scanner" +) + +// MapMapErrorToMapBoth suggests using Effect.mapBoth instead of directly adjacent +// Effect.map and Effect.mapError in piping flows or nested data-first calls. +var MapMapErrorToMapBoth = rule.Rule{ + Name: "mapMapErrorToMapBoth", + Group: "style", + Description: "Suggests using Effect.mapBoth instead of directly adjacent Effect.map and Effect.mapError", + DefaultSeverity: etscore.SeveritySuggestion, + SupportedEffect: []string{"v3", "v4"}, + Codes: []int32{ + tsdiag.Effect_mapBoth_expresses_these_failure_and_success_transformations_more_directly_than_adjacent_Effect_map_and_Effect_mapError_effect_mapMapErrorToMapBoth.Code(), + }, + Run: func(ctx *rule.Context) []*ast.Diagnostic { + matches := AnalyzeMapMapErrorToMapBoth(ctx.TypeParser, ctx.Checker, ctx.SourceFile) + diags := make([]*ast.Diagnostic, len(matches)) + for i, match := range matches { + diags[i] = ctx.NewDiagnostic( + match.SourceFile, + match.Location, + tsdiag.Effect_mapBoth_expresses_these_failure_and_success_transformations_more_directly_than_adjacent_Effect_map_and_Effect_mapError_effect_mapMapErrorToMapBoth, + nil, + ) + } + return diags + }, +} + +// MapMapErrorToMapBothMatch holds match details for diagnostic emission. +type MapMapErrorToMapBothMatch struct { + SourceFile *ast.SourceFile + Location core.TextRange + Node *ast.Node + FirstCallee *ast.Node + SecondCallee *ast.Node +} + +// AnalyzeMapMapErrorToMapBoth finds directly adjacent Effect.map and Effect.mapError +// combinators in either order applied to an Effect receiver. +func AnalyzeMapMapErrorToMapBoth(tp *typeparser.TypeParser, _ *checker.Checker, sf *ast.SourceFile) []MapMapErrorToMapBothMatch { + if tp == nil || sf == nil { + return nil + } + + var matches []MapMapErrorToMapBothMatch + + flows := tp.PipingFlows(sf, false) + for _, flow := range flows { + sequences := flow.FindTransformationSequences( + func(transformation *typeparser.PipingFlowTransformation) bool { + return len(transformation.Args) > 0 && + (tp.IsNodeReferenceToEffectModuleApi(transformation.Callee, "map") || + tp.IsNodeReferenceToEffectModuleApi(transformation.Callee, "mapError")) + }, + func(transformation *typeparser.PipingFlowTransformation) bool { + return len(transformation.Args) > 0 && + (tp.IsNodeReferenceToEffectModuleApi(transformation.Callee, "map") || + tp.IsNodeReferenceToEffectModuleApi(transformation.Callee, "mapError")) + }, + ) + + nextAllowedIndex := 0 + for _, sequence := range sequences { + if sequence.Start < nextAllowedIndex { + continue + } + + first := &flow.Transformations[sequence.Start] + second := &flow.Transformations[sequence.Start+1] + + if first.Kind != second.Kind { + continue + } + if first.Kind != typeparser.TransformationKindPipe && + first.Kind != typeparser.TransformationKindPipeable && + first.Kind != typeparser.TransformationKindDataFirst { + continue + } + + isFirstMap := tp.IsNodeReferenceToEffectModuleApi(first.Callee, "map") + isFirstMapError := tp.IsNodeReferenceToEffectModuleApi(first.Callee, "mapError") + isSecondMap := tp.IsNodeReferenceToEffectModuleApi(second.Callee, "map") + isSecondMapError := tp.IsNodeReferenceToEffectModuleApi(second.Callee, "mapError") + isMapPair := (isFirstMap && isSecondMapError) || (isFirstMapError && isSecondMap) + + if !isMapPair { + continue + } + + inputType := flow.TransformationInputType(sequence.Start) + if inputType == nil { + if sequence.Start == 0 && flow.Subject.Node != nil { + inputType = tp.GetTypeAtLocation(flow.Subject.Node) + } else if inputNode := flow.TransformationInputNode(sequence.Start); inputNode != nil { + inputType = tp.GetTypeAtLocation(inputNode) + } + } + if inputType == nil || !tp.IsEffectType(inputType) || tp.IsEffectSubtype(inputType) { + continue + } + + nextAllowedIndex = sequence.Start + 2 + matches = append(matches, MapMapErrorToMapBothMatch{ + SourceFile: sf, + Location: scanner.GetErrorRangeForNode(sf, second.Callee), + Node: second.Callee, + FirstCallee: first.Callee, + SecondCallee: second.Callee, + }) + } + } + + return matches +} diff --git a/internal/rules/rules.go b/internal/rules/rules.go index ccaa3bee..f6b41912 100644 --- a/internal/rules/rules.go +++ b/internal/rules/rules.go @@ -119,4 +119,5 @@ var All = []rule.Rule{ AcquireReleaseDisposable, RaceFirstWithSleepToTimeout, RunOfExitToRunExit, + MapMapErrorToMapBoth, } diff --git a/shim/diagnostics/shim.go b/shim/diagnostics/shim.go index f93ae1fd..d677e9d4 100644 --- a/shim/diagnostics/shim.go +++ b/shim/diagnostics/shim.go @@ -716,6 +716,7 @@ var Effect_asSome_expresses_wrapping_the_success_value_in_Option_some_directly_e var Effect_forEach_expresses_this_effectful_array_mapping_more_directly_than_Effect_all_over_Array_map_effect_allOfMapToForEach = diagnostics.Effect_forEach_expresses_this_effectful_array_mapping_more_directly_than_Effect_all_over_Array_map_effect_allOfMapToForEach var Effect_fromOption_expresses_this_Option_to_Effect_conversion_more_directly_than_Option_match_or_an_Option_tag_conditional_effect_optionMatchToFromOption = diagnostics.Effect_fromOption_expresses_this_Option_to_Effect_conversion_more_directly_than_Option_match_or_an_Option_tag_conditional_effect_optionMatchToFromOption var Effect_mapBoth_expresses_these_failure_and_success_transformations_more_directly_than_Effect_matchEffect_effect_matchEffectToMapBoth = diagnostics.Effect_mapBoth_expresses_these_failure_and_success_transformations_more_directly_than_Effect_matchEffect_effect_matchEffectToMapBoth +var Effect_mapBoth_expresses_these_failure_and_success_transformations_more_directly_than_adjacent_Effect_map_and_Effect_mapError_effect_mapMapErrorToMapBoth = diagnostics.Effect_mapBoth_expresses_these_failure_and_success_transformations_more_directly_than_adjacent_Effect_map_and_Effect_mapError_effect_mapMapErrorToMapBoth var Effect_mapError_expresses_the_same_error_type_transformation_more_directly_than_Effect_0_followed_by_Effect_fail_effect_catchAllToMapError = diagnostics.Effect_mapError_expresses_the_same_error_type_transformation_more_directly_than_Effect_0_followed_by_Effect_fail_effect_catchAllToMapError var Effect_map_Effect_flatten_is_the_same_as_Effect_flatMap_that_expresses_the_same_steps_more_directly_effect_effectMapFlatten = diagnostics.Effect_map_Effect_flatten_is_the_same_as_Effect_flatMap_that_expresses_the_same_steps_more_directly_effect_effectMapFlatten var Effect_map_expresses_this_success_value_transformation_more_directly_than_Effect_flatMap_followed_by_Effect_succeed_effect_flatMapToMap = diagnostics.Effect_map_expresses_this_success_value_transformation_more_directly_than_Effect_flatMap_followed_by_Effect_succeed_effect_flatMapToMap diff --git a/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.errors.txt b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.errors.txt new file mode 100644 index 00000000..38ae9a7f --- /dev/null +++ b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.errors.txt @@ -0,0 +1,92 @@ +=== Metadata === +Effect version: 3.19.19 + +/.src/mapMapErrorToMapBoth.ts(13,3): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) +/.src/mapMapErrorToMapBoth.ts(19,3): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) +/.src/mapMapErrorToMapBoth.ts(26,3): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) +/.src/mapMapErrorToMapBoth.ts(30,37): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) +/.src/mapMapErrorToMapBoth.ts(36,37): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + + +==== /.src/mapMapErrorToMapBoth.ts (5 errors) ==== + // @effect-diagnostics mapMapErrorToMapBoth:suggestion + import { Data, Effect, pipe } from "effect" + + class TaskError extends Data.TaggedError("TaskError")<{ + readonly message: string + }> {} + + declare const fetchCount: Effect.Effect + + // BAD: pipe adjacent map then mapError + export const badPipeMapThenMapError = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) + ~~~~~~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + ) + + // BAD: pipe adjacent mapError then map + export const badPipeMapErrorThenMap = fetchCount.pipe( + Effect.mapError((message) => new TaskError({ message })), + Effect.map((n) => n > 0) + ~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + ) + + // BAD: function pipe style + export const badFunctionPipe = pipe( + fetchCount, + Effect.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) + ~~~~~~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + ) + + // BAD: nested data-first map(mapError(eff, g), f) + export const badNestedMapMapError = Effect.map( + ~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Effect.mapError(fetchCount, (message) => new TaskError({ message })), + (n) => n > 0 + ) + + // BAD: nested data-first mapError(map(eff, f), g) + export const badNestedMapErrorMap = Effect.mapError( + ~~~~~~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Effect.map(fetchCount, (n) => n > 0), + (message) => new TaskError({ message }) + ) + + // GOOD: separated by other step (silent) + export const goodSeparated = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.tap((b) => Effect.log(String(b))), + Effect.mapError((message) => new TaskError({ message })) + ) + + // GOOD: already using mapBoth (silent) + export const goodMapBoth = fetchCount.pipe( + Effect.mapBoth({ + onSuccess: (n) => n > 0, + onFailure: (message) => new TaskError({ message }) + }) + ) + + // GOOD: unrelated map functions (silent) + const unrelated = { + map: (f: (a: A) => B) => (self: Effect.Effect) => Effect.map(self, f), + mapError: (f: (e: E) => E2) => (self: Effect.Effect) => Effect.mapError(self, f) + } + + export const goodUnrelatedMap = fetchCount.pipe( + unrelated.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) + ) + + export const goodUnrelatedMapError = fetchCount.pipe( + Effect.map((n) => n > 0), + unrelated.mapError((message) => new TaskError({ message })) + ) + diff --git a/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.flows.mapMapErrorToMapBoth.mermaid b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.flows.mapMapErrorToMapBoth.mermaid new file mode 100644 index 00000000..1ca8a612 --- /dev/null +++ b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.flows.mapMapErrorToMapBoth.mermaid @@ -0,0 +1,198 @@ +flowchart TB + 0[/"type: #quot;TaskError#quot;
node: #quot;TaskError#quot;"/] + 1["type: new #lt;A extends Record#lt;string, any#gt; = #123;#125;#gt;#40;args: Equals#lt;A, #123;#125;#gt; extends true ? void : #123; readonly #91;P in keyof A as P extends #quot;_tag#quot; ? never : P#93;: A#91;P#93;; #125;#41; =#gt; YieldableError #amp; #123; readonly _tag: #quot;TaskError#quot;; #125; #amp; Readonly#lt;A#gt;
callee: Data.TaggedError
args: #91;#93;"] + 2[/"type: #lt;Tag extends string#gt;#40;tag: Tag#41; =#gt; new #lt;A extends Record#lt;string, any#gt; = #123;#125;#gt;#40;args: Equals#lt;A, #123;#125;#gt; extends true ? void : #123; readonly #91;P in keyof A as P extends #quot;_tag#quot; ? never : P#93;: A#91;P#93;; #125;#41; =#gt; YieldableError #amp; #123; readonly _tag: Tag; #125; #amp; Readonly#lt;A#gt;
node: Data.TaggedError"/] + 3[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 4["type: Effect#lt;boolean, string, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 5[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 6[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 7[/"type: boolean
node: n #gt; 0"/] + 8["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 9[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 10[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 11[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 12[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 13["type: Effect#lt;number, TaskError, never#gt;
callee: Effect.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 14[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 15[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 16[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 17["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 18[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 19[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 20[/"type: boolean
node: n #gt; 0"/] + 21[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 22["type: Effect#lt;boolean, string, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 23[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 24[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 25[/"type: boolean
node: n #gt; 0"/] + 26["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 27[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 28[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 29[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 30[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 31["type: Effect#lt;number, TaskError, never#gt;
callee:
args: #91;#93;"] + 32[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 33[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 34[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 35["type: Effect#lt;boolean, TaskError, never#gt;
callee:
args: #91;#93;"] + 36[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 37[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 38[/"type: boolean
node: n #gt; 0"/] + 39[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 40["type: Effect#lt;boolean, string, never#gt;
callee:
args: #91;#93;"] + 41[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 42[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 43[/"type: boolean
node: n #gt; 0"/] + 44["type: Effect#lt;boolean, TaskError, never#gt;
callee:
args: #91;#93;"] + 45[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 46[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 47[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 48[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 49["type: Effect#lt;boolean, string, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 50[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 51[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 52[/"type: boolean
node: n #gt; 0"/] + 53["type: Effect#lt;boolean, string, never#gt;
callee: Effect.tap
args: #91;#40;b#41; =#gt; Effect.log#40;String#40;b#41;#41;#93;"] + 54[/"type: #123; #lt;A, X#gt;#40;f: #40;a: NoInfer#lt;A#gt;#41; =#gt; X#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; #91;X#93; extends #91;Effect#lt;infer _A1, infer E1, infer R1#gt;#93; ? Effect#lt;A, E #124; E1, R #124; R1#gt; : #91;X#93; extends #91;PromiseLike#lt;infer _A1#gt;#93; ? Effect#lt;A, E #124; UnknownException, R#gt; : Effect#lt;A, E, R#gt;; #lt;A, X, E1, R1#gt;#40;f: #40;a: NoInfer#lt;A#gt;#41; =#gt; Effect#lt;X, E1, R1#gt;, options: #123; onlyEffect: true; #125;#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E #124; E1, R #124; R1#gt;; #lt;X#gt;#40;f: NotFunction#lt;X#gt;#41;: #lt;A, E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; #91;X#93; extends #91;Effect#lt;infer _A1, infer E1, infer R1#gt;#93; ? Effect#lt;A, E #124; E1, R #124; R1#gt; : #91;X#93; extends #91;PromiseLike#lt;infer _A1#gt;#93; ? Effect#lt;A, E #124; UnknownException, R#gt; : Effect#lt;A, E, R#gt;; #lt;X, E1, R1#gt;#40;f: Effect#lt;X, E1, R1#gt;, options: #123; onlyEffect: true; #125;#41;: #lt;A, E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E #124; E1, R #124; R1#gt;; #lt;A, E, R, X#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: NoInfer#lt;A#gt;#41; =#gt; X#41;: #91;X#93; extends #91;Effect#lt;infer _A1, infer E1, infer R1#gt;#93; ? Effect#lt;A, E #124; E1, R #124; R1#gt; : #91;X#93; extends #91;PromiseLike#lt;infer _A1#gt;#93; ? Effect#lt;A, E #124; UnknownException, R#gt; : Effect#lt;A, E, R#gt;; #lt;A, E, R, X, E1, R1#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: NoInfer#lt;A#gt;#41; =#gt; Effect#lt;X, E1, R1#gt;, options: #123; onlyEffect: true; #125;#41;: Effect#lt;A, E #124; E1, R #124; R1#gt;; #lt;A, E, R, X#gt;#40;self: Effect#lt;A, E, R#gt;, f: NotFunction#lt;X#gt;#41;: #91;X#93; extends #91;Effect#lt;infer _A1, infer E1, infer R1#gt;#93; ? Effect#lt;A, E #124; E1, R #124; R1#gt; : #91;X#93; extends #91;PromiseLike#lt;infer _A1#gt;#93; ? Effect#lt;A, E #124; UnknownException, R#gt; : Effect#lt;A, E, R#gt;; #lt;A, E, R, X, E1, R1#gt;#40;self: Effect#lt;A, E, R#gt;, f: Effect#lt;X, E1, R1#gt;, options: #123; onlyEffect: true; #125;#41;: Effect#lt;A, E #124; E1, R #124; R1#gt;; #125;
node: Effect.tap"/] + 55[["type: #40;b: boolean#41; =#gt; Effect#lt;void, never, never#gt;
node: #40;b#41; =#gt; Effect.log#40;String#40;b#41;#41;"]] + 56[/"type: boolean
node: b"/] + 57["type: string
callee: String
args: #91;#93;"] + 58[/"type: StringConstructor
node: String"/] + 59["type: Effect#lt;void, never, never#gt;
callee: Effect.log
args: #91;#93;"] + 60[/"type: #40;...message: readonly any#91;#93;#41; =#gt; Effect#lt;void, never, never#gt;
node: Effect.log"/] + 61["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 62[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 63[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 64[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 65[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 66["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.mapBoth
args: #91;#123;#92;n onSuccess: #40;n#41; =#gt; n #gt; 0,#92;n onFailure: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#92;n #125;#93;"] + 67[/"type: #123; #lt;E, E2, A, A2#gt;#40;options: #123; readonly onFailure: #40;e: E#41; =#gt; E2; readonly onSuccess: #40;a: A#41; =#gt; A2; #125;#41;: #lt;R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A2, E2, R#gt;; #lt;A, E, R, E2, A2#gt;#40;self: Effect#lt;A, E, R#gt;, options: #123; readonly onFailure: #40;e: E#41; =#gt; E2; readonly onSuccess: #40;a: A#41; =#gt; A2; #125;#41;: Effect#lt;A2, E2, R#gt;; #125;
node: Effect.mapBoth"/] + 68[/"type: #123; onSuccess: #40;n: number#41; =#gt; boolean; onFailure: #40;message: string#41; =#gt; TaskError; #125;
node: #123;#92;n onSuccess: #40;n#41; =#gt; n #gt; 0,#92;n onFailure: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#92;n #125;"/] + 69[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 70[/"type: boolean
node: n #gt; 0"/] + 71[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 72[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 73[/"type: #123; map: #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41; =#gt; #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; mapError: #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41; =#gt; #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #125;
node: #123;#92;n map: #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41; =#gt; #lt;E, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.map#40;self, f#41;,#92;n mapError: #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41; =#gt; #lt;A, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.mapError#40;self, f#41;#92;n#125;"/] + 74[["type: #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41; =#gt; #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;
node: #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41; =#gt; #lt;E, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.map#40;self, f#41;"]] + 75[["type: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;
node: #lt;E, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.map#40;self, f#41;"]] + 76[/"type: Effect#lt;A, E, R#gt;
node: self"/] + 77["type: Effect#lt;B, E, R#gt;
callee:
args: #91;#93;"] + 78[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 79[/"type: #40;a: A#41; =#gt; B
node: f"/] + 80[["type: #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41; =#gt; #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;
node: #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41; =#gt; #lt;A, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.mapError#40;self, f#41;"]] + 81[["type: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;
node: #lt;A, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.mapError#40;self, f#41;"]] + 82[/"type: Effect#lt;A, E, R#gt;
node: self"/] + 83["type: Effect#lt;A, E2, R#gt;
callee:
args: #91;#93;"] + 84[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 85[/"type: #40;e: E#41; =#gt; E2
node: f"/] + 86[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 87["type: Effect#lt;boolean, string, never#gt;
callee: unrelated.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 88[/"type: #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41; =#gt; #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;
node: unrelated.map"/] + 89[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 90[/"type: boolean
node: n #gt; 0"/] + 91["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 92[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 93[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 94[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 95[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 96["type: Effect#lt;boolean, string, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 97[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 98[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 99[/"type: boolean
node: n #gt; 0"/] + 100["type: Effect#lt;boolean, TaskError, never#gt;
callee: unrelated.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 101[/"type: #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41; =#gt; #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;
node: unrelated.mapError"/] + 102[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 103[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 0 -->|"kind: pipe"| 1 + 2 -->|"kind: transformCallee"| 1 + 5 -->|"kind: transformCallee"| 4 + 7 -->|"kind: potentialReturn"| 6 + 6 -->|"kind: transformArg"| 4 + 3 -->|"kind: pipe"| 4 + 9 -->|"kind: transformCallee"| 8 + 11 -->|"kind: potentialReturn"| 10 + 10 -->|"kind: transformArg"| 8 + 4 -->|"kind: pipe"| 8 + 14 -->|"kind: transformCallee"| 13 + 16 -->|"kind: potentialReturn"| 15 + 15 -->|"kind: transformArg"| 13 + 12 -->|"kind: pipe"| 13 + 18 -->|"kind: transformCallee"| 17 + 20 -->|"kind: potentialReturn"| 19 + 19 -->|"kind: transformArg"| 17 + 13 -->|"kind: pipe"| 17 + 23 -->|"kind: transformCallee"| 22 + 25 -->|"kind: potentialReturn"| 24 + 24 -->|"kind: transformArg"| 22 + 21 -->|"kind: pipe"| 22 + 27 -->|"kind: transformCallee"| 26 + 29 -->|"kind: potentialReturn"| 28 + 28 -->|"kind: transformArg"| 26 + 22 -->|"kind: pipe"| 26 + 30 -->|"kind: pipe"| 31 + 32 -->|"kind: transformCallee"| 30 + 34 -->|"kind: potentialReturn"| 33 + 33 -->|"kind: transformArg"| 30 + 31 -->|"kind: pipe"| 35 + 36 -->|"kind: transformCallee"| 30 + 38 -->|"kind: potentialReturn"| 37 + 37 -->|"kind: transformArg"| 30 + 39 -->|"kind: pipe"| 40 + 41 -->|"kind: transformCallee"| 39 + 43 -->|"kind: potentialReturn"| 42 + 42 -->|"kind: transformArg"| 39 + 40 -->|"kind: pipe"| 44 + 45 -->|"kind: transformCallee"| 39 + 47 -->|"kind: potentialReturn"| 46 + 46 -->|"kind: transformArg"| 39 + 50 -->|"kind: transformCallee"| 49 + 52 -->|"kind: potentialReturn"| 51 + 51 -->|"kind: transformArg"| 49 + 48 -->|"kind: pipe"| 49 + 54 -->|"kind: transformCallee"| 53 + 56 -->|"kind: pipe"| 57 + 58 -->|"kind: transformCallee"| 57 + 57 -->|"kind: pipe"| 59 + 60 -->|"kind: transformCallee"| 59 + 59 -->|"kind: potentialReturn"| 55 + 55 -->|"kind: transformArg"| 53 + 49 -->|"kind: pipe"| 53 + 62 -->|"kind: transformCallee"| 61 + 64 -->|"kind: potentialReturn"| 63 + 63 -->|"kind: transformArg"| 61 + 53 -->|"kind: pipe"| 61 + 67 -->|"kind: transformCallee"| 66 + 70 -->|"kind: potentialReturn"| 69 + 69 -->|"kind: usedBy"| 68 + 72 -->|"kind: potentialReturn"| 71 + 71 -->|"kind: usedBy"| 68 + 68 -->|"kind: transformArg"| 66 + 65 -->|"kind: pipe"| 66 + 76 -->|"kind: pipe"| 77 + 78 -->|"kind: transformCallee"| 76 + 79 -->|"kind: transformArg"| 76 + 77 -->|"kind: potentialReturn"| 75 + 75 -->|"kind: potentialReturn"| 74 + 74 -->|"kind: usedBy"| 73 + 82 -->|"kind: pipe"| 83 + 84 -->|"kind: transformCallee"| 82 + 85 -->|"kind: transformArg"| 82 + 83 -->|"kind: potentialReturn"| 81 + 81 -->|"kind: potentialReturn"| 80 + 80 -->|"kind: usedBy"| 73 + 88 -->|"kind: transformCallee"| 87 + 90 -->|"kind: potentialReturn"| 89 + 89 -->|"kind: transformArg"| 87 + 86 -->|"kind: pipe"| 87 + 92 -->|"kind: transformCallee"| 91 + 94 -->|"kind: potentialReturn"| 93 + 93 -->|"kind: transformArg"| 91 + 87 -->|"kind: pipe"| 91 + 97 -->|"kind: transformCallee"| 96 + 99 -->|"kind: potentialReturn"| 98 + 98 -->|"kind: transformArg"| 96 + 95 -->|"kind: pipe"| 96 + 101 -->|"kind: transformCallee"| 100 + 103 -->|"kind: potentialReturn"| 102 + 102 -->|"kind: transformArg"| 100 + 96 -->|"kind: pipe"| 100 \ No newline at end of file diff --git a/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.flows.txt b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.flows.txt new file mode 100644 index 00000000..de546937 --- /dev/null +++ b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.flows.txt @@ -0,0 +1 @@ +/.src/mapMapErrorToMapBoth.ts -> mapMapErrorToMapBoth.flows.mapMapErrorToMapBoth.mermaid diff --git a/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.layers.txt b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.layers.txt new file mode 100644 index 00000000..934955e6 --- /dev/null +++ b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.layers.txt @@ -0,0 +1 @@ +==== /.src/mapMapErrorToMapBoth.ts (0 layer exports) ==== diff --git a/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.pipings.txt b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.pipings.txt new file mode 100644 index 00000000..d34117b8 --- /dev/null +++ b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.pipings.txt @@ -0,0 +1,223 @@ +==== /.src/mapMapErrorToMapBoth.ts (13 flows) ==== + +=== Piping Flow === +Location: 4:24 - 4:54 +Node: Data.TaggedError("TaskError") +Node Kind: KindCallExpression + +Subject: "TaskError" +Subject Type: "TaskError" + +Transformations (1): + [0] kind: call + callee: Data.TaggedError + args: (constant) + outType: new = {}>(args: Equals extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & { readonly _tag: "TaskError"; } & Readonly + +=== Piping Flow === +Location: 11:38 - 14:2 +Node: fetchCount.pipe(\n Effect.map((n) => n > 0),\n Effect.mapError((message) => new TaskError({ message }))\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipeable + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipeable + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + +=== Piping Flow === +Location: 17:38 - 20:2 +Node: fetchCount.pipe(\n Effect.mapError((message) => new TaskError({ message })),\n Effect.map((n) => n > 0)\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipeable + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + [1] kind: pipeable + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + +=== Piping Flow === +Location: 23:31 - 27:2 +Node: pipe(\n fetchCount,\n Effect.map((n) => n > 0),\n Effect.mapError((message) => new TaskError({ message }))\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipe + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipe + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + +=== Piping Flow === +Location: 30:36 - 33:2 +Node: Effect.map(\n Effect.mapError(fetchCount, (message) => new TaskError({ message })),\n (n) => n > 0\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: dataFirst + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + [1] kind: dataFirst + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + +=== Piping Flow === +Location: 36:36 - 39:2 +Node: Effect.mapError(\n Effect.map(fetchCount, (n) => n > 0),\n (message) => new TaskError({ message })\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: dataFirst + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: dataFirst + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + +=== Piping Flow === +Location: 42:29 - 46:2 +Node: fetchCount.pipe(\n Effect.map((n) => n > 0),\n Effect.tap((b) => Effect.log(String(b))),\n Effect.mapError((message) => new TaskError({ message }))\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (3): + [0] kind: pipeable + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipeable + callee: Effect.tap + args: [(b) => Effect.log(String(b))] + outType: Effect + [2] kind: pipeable + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + +=== Piping Flow === +Location: 44:20 - 44:42 +Node: Effect.log(String(b)) +Node Kind: KindCallExpression + +Subject: b +Subject Type: boolean + +Transformations (2): + [0] kind: call + callee: String + args: (constant) + outType: string + [1] kind: call + callee: Effect.log + args: (constant) + outType: Effect + +=== Piping Flow === +Location: 49:27 - 54:2 +Node: fetchCount.pipe(\n Effect.mapBoth({\n onSuccess: (n) => n > 0,\n onFailure: (message) => new TaskError({ message })\n })\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (1): + [0] kind: pipeable + callee: Effect.mapBoth + args: [{\n onSuccess: (n) => n > 0,\n onFailure: (message) => new TaskError({ message })\n }] + outType: Effect + +=== Piping Flow === +Location: 58:73 - 58:93 +Node: Effect.map(self, f) +Node Kind: KindCallExpression + +Subject: self +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [f] + outType: Effect + +=== Piping Flow === +Location: 59:80 - 59:105 +Node: Effect.mapError(self, f) +Node Kind: KindCallExpression + +Subject: self +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.mapError + args: [f] + outType: Effect + +=== Piping Flow === +Location: 62:32 - 65:2 +Node: fetchCount.pipe(\n unrelated.map((n) => n > 0),\n Effect.mapError((message) => new TaskError({ message }))\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipeable + callee: unrelated.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipeable + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + +=== Piping Flow === +Location: 67:37 - 70:2 +Node: fetchCount.pipe(\n Effect.map((n) => n > 0),\n unrelated.mapError((message) => new TaskError({ message }))\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipeable + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipeable + callee: unrelated.mapError + args: [(message) => new TaskError({ message })] + outType: Effect diff --git a/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.quickfixes.txt b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.quickfixes.txt new file mode 100644 index 00000000..beffac6c --- /dev/null +++ b/testdata/baselines/reference/effect-v3/mapMapErrorToMapBoth.quickfixes.txt @@ -0,0 +1,53 @@ +=== Quick Fix Inventory === + +[D1] (13:3-13:18) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +[D2] (19:3-19:13) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +[D3] (26:3-26:18) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +[D4] (30:37-30:47) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +[D5] (36:37-36:52) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +=== Quick Fix Application Results === + +=== [D1] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D1] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default + +=== [D2] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D2] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default + +=== [D3] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D3] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default + +=== [D4] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D4] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default + +=== [D5] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D5] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.errors.txt b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.errors.txt new file mode 100644 index 00000000..a1b92d64 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.errors.txt @@ -0,0 +1,92 @@ +=== Metadata === +Effect version: 4.0.0 + +/.src/mapMapErrorToMapBoth.ts(13,3): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) +/.src/mapMapErrorToMapBoth.ts(19,3): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) +/.src/mapMapErrorToMapBoth.ts(26,3): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) +/.src/mapMapErrorToMapBoth.ts(30,37): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) +/.src/mapMapErrorToMapBoth.ts(36,37): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + + +==== /.src/mapMapErrorToMapBoth.ts (5 errors) ==== + // @effect-diagnostics mapMapErrorToMapBoth:suggestion + import { Data, Effect, pipe } from "effect" + + class TaskError extends Data.TaggedError("TaskError")<{ + readonly message: string + }> {} + + declare const fetchCount: Effect.Effect + + // BAD: pipe adjacent map then mapError + export const badPipeMapThenMapError = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) + ~~~~~~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + ) + + // BAD: pipe adjacent mapError then map + export const badPipeMapErrorThenMap = fetchCount.pipe( + Effect.mapError((message) => new TaskError({ message })), + Effect.map((n) => n > 0) + ~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + ) + + // BAD: function pipe style + export const badFunctionPipe = pipe( + fetchCount, + Effect.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) + ~~~~~~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + ) + + // BAD: nested data-first map(mapError(eff, g), f) + export const badNestedMapMapError = Effect.map( + ~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Effect.mapError(fetchCount, (message) => new TaskError({ message })), + (n) => n > 0 + ) + + // BAD: nested data-first mapError(map(eff, f), g) + export const badNestedMapErrorMap = Effect.mapError( + ~~~~~~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Effect.map(fetchCount, (n) => n > 0), + (message) => new TaskError({ message }) + ) + + // GOOD: separated by other step (silent) + export const goodSeparated = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.tap((b) => Effect.log(String(b))), + Effect.mapError((message) => new TaskError({ message })) + ) + + // GOOD: already using mapBoth (silent) + export const goodMapBoth = fetchCount.pipe( + Effect.mapBoth({ + onSuccess: (n) => n > 0, + onFailure: (message) => new TaskError({ message }) + }) + ) + + // GOOD: unrelated map functions (silent) + const unrelated = { + map: (f: (a: A) => B) => (self: Effect.Effect) => Effect.map(self, f), + mapError: (f: (e: E) => E2) => (self: Effect.Effect) => Effect.mapError(self, f) + } + + export const goodUnrelatedMap = fetchCount.pipe( + unrelated.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) + ) + + export const goodUnrelatedMapError = fetchCount.pipe( + Effect.map((n) => n > 0), + unrelated.mapError((message) => new TaskError({ message })) + ) + diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.flows.mapMapErrorToMapBoth.mermaid b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.flows.mapMapErrorToMapBoth.mermaid new file mode 100644 index 00000000..0ff0973b --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.flows.mapMapErrorToMapBoth.mermaid @@ -0,0 +1,198 @@ +flowchart TB + 0[/"type: #quot;TaskError#quot;
node: #quot;TaskError#quot;"/] + 1["type: new #lt;A extends Record#lt;string, any#gt; = #123;#125;#gt;#40;args: VoidIfEmpty#lt;#123; readonly #91;P in keyof A as P extends #quot;_tag#quot; ? never : P#93;: A#91;P#93;; #125;#gt;#41; =#gt; YieldableError #amp; #123; readonly _tag: #quot;TaskError#quot;; #125; #amp; Readonly#lt;A#gt;
callee: Data.TaggedError
args: #91;#93;"] + 2[/"type: #lt;Tag extends string#gt;#40;tag: Tag#41; =#gt; new #lt;A extends Record#lt;string, any#gt; = #123;#125;#gt;#40;args: VoidIfEmpty#lt;#123; readonly #91;P in keyof A as P extends #quot;_tag#quot; ? never : P#93;: A#91;P#93;; #125;#gt;#41; =#gt; YieldableError #amp; #123; readonly _tag: Tag; #125; #amp; Readonly#lt;A#gt;
node: Data.TaggedError"/] + 3[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 4["type: Effect#lt;boolean, string, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 5[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 6[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 7[/"type: boolean
node: n #gt; 0"/] + 8["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 9[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 10[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 11[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 12[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 13["type: Effect#lt;number, TaskError, never#gt;
callee: Effect.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 14[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 15[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 16[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 17["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 18[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 19[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 20[/"type: boolean
node: n #gt; 0"/] + 21[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 22["type: Effect#lt;boolean, string, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 23[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 24[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 25[/"type: boolean
node: n #gt; 0"/] + 26["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 27[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 28[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 29[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 30[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 31["type: Effect#lt;number, TaskError, never#gt;
callee:
args: #91;#93;"] + 32[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 33[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 34[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 35["type: Effect#lt;boolean, TaskError, never#gt;
callee:
args: #91;#93;"] + 36[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 37[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 38[/"type: boolean
node: n #gt; 0"/] + 39[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 40["type: Effect#lt;boolean, string, never#gt;
callee:
args: #91;#93;"] + 41[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 42[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 43[/"type: boolean
node: n #gt; 0"/] + 44["type: Effect#lt;boolean, TaskError, never#gt;
callee:
args: #91;#93;"] + 45[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 46[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 47[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 48[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 49["type: Effect#lt;boolean, string, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 50[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 51[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 52[/"type: boolean
node: n #gt; 0"/] + 53["type: Effect#lt;boolean, string, never#gt;
callee: Effect.tap
args: #91;#40;b#41; =#gt; Effect.log#40;String#40;b#41;#41;#93;"] + 54[/"type: #123; #lt;A, B, E2, R2#gt;#40;f: #40;a: NoInfer#lt;A#gt;#41; =#gt; Effect#lt;B, E2, R2#gt;#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E #124; E2, R #124; R2#gt;; #lt;B, E2, R2#gt;#40;f: Effect#lt;B, E2, R2#gt;#41;: #lt;A, E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E #124; E2, R #124; R2#gt;; #lt;A, E, R, B, E2, R2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: NoInfer#lt;A#gt;#41; =#gt; Effect#lt;B, E2, R2#gt;#41;: Effect#lt;A, E #124; E2, R #124; R2#gt;; #lt;A, E, R, B, E2, R2#gt;#40;self: Effect#lt;A, E, R#gt;, f: Effect#lt;B, E2, R2#gt;#41;: Effect#lt;A, E #124; E2, R #124; R2#gt;; #125;
node: Effect.tap"/] + 55[["type: #40;b: boolean#41; =#gt; Effect#lt;void, never, never#gt;
node: #40;b#41; =#gt; Effect.log#40;String#40;b#41;#41;"]] + 56[/"type: boolean
node: b"/] + 57["type: string
callee: String
args: #91;#93;"] + 58[/"type: StringConstructor
node: String"/] + 59["type: Effect#lt;void, never, never#gt;
callee: Effect.log
args: #91;#93;"] + 60[/"type: #40;...message: readonly any#91;#93;#41; =#gt; Effect#lt;void, never, never#gt;
node: Effect.log"/] + 61["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 62[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 63[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 64[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 65[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 66["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.mapBoth
args: #91;#123;#92;n onSuccess: #40;n#41; =#gt; n #gt; 0,#92;n onFailure: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#92;n #125;#93;"] + 67[/"type: #123; #lt;E, E2, A, A2#gt;#40;options: #123; readonly onFailure: #40;e: E#41; =#gt; E2; readonly onSuccess: #40;a: A#41; =#gt; A2; #125;#41;: #lt;R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A2, E2, R#gt;; #lt;A, E, R, E2, A2#gt;#40;self: Effect#lt;A, E, R#gt;, options: #123; readonly onFailure: #40;e: E#41; =#gt; E2; readonly onSuccess: #40;a: A#41; =#gt; A2; #125;#41;: Effect#lt;A2, E2, R#gt;; #125;
node: Effect.mapBoth"/] + 68[/"type: #123; onSuccess: #40;n: number#41; =#gt; boolean; onFailure: #40;message: string#41; =#gt; TaskError; #125;
node: #123;#92;n onSuccess: #40;n#41; =#gt; n #gt; 0,#92;n onFailure: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#92;n #125;"/] + 69[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 70[/"type: boolean
node: n #gt; 0"/] + 71[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 72[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 73[/"type: #123; map: #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41; =#gt; #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; mapError: #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41; =#gt; #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #125;
node: #123;#92;n map: #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41; =#gt; #lt;E, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.map#40;self, f#41;,#92;n mapError: #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41; =#gt; #lt;A, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.mapError#40;self, f#41;#92;n#125;"/] + 74[["type: #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41; =#gt; #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;
node: #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41; =#gt; #lt;E, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.map#40;self, f#41;"]] + 75[["type: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;
node: #lt;E, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.map#40;self, f#41;"]] + 76[/"type: Effect#lt;A, E, R#gt;
node: self"/] + 77["type: Effect#lt;B, E, R#gt;
callee:
args: #91;#93;"] + 78[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 79[/"type: #40;a: A#41; =#gt; B
node: f"/] + 80[["type: #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41; =#gt; #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;
node: #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41; =#gt; #lt;A, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.mapError#40;self, f#41;"]] + 81[["type: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;
node: #lt;A, R#gt;#40;self: Effect.Effect#lt;A, E, R#gt;#41; =#gt; Effect.mapError#40;self, f#41;"]] + 82[/"type: Effect#lt;A, E, R#gt;
node: self"/] + 83["type: Effect#lt;A, E2, R#gt;
callee:
args: #91;#93;"] + 84[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 85[/"type: #40;e: E#41; =#gt; E2
node: f"/] + 86[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 87["type: Effect#lt;boolean, string, never#gt;
callee: unrelated.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 88[/"type: #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41; =#gt; #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;
node: unrelated.map"/] + 89[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 90[/"type: boolean
node: n #gt; 0"/] + 91["type: Effect#lt;boolean, TaskError, never#gt;
callee: Effect.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 92[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 93[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 94[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 95[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 96["type: Effect#lt;boolean, string, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 97[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 98[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 99[/"type: boolean
node: n #gt; 0"/] + 100["type: Effect#lt;boolean, TaskError, never#gt;
callee: unrelated.mapError
args: #91;#40;message#41; =#gt; new TaskError#40;#123; message #125;#41;#93;"] + 101[/"type: #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41; =#gt; #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;
node: unrelated.mapError"/] + 102[["type: #40;message: string#41; =#gt; TaskError
node: #40;message#41; =#gt; new TaskError#40;#123; message #125;#41;"]] + 103[/"type: TaskError
node: new TaskError#40;#123; message #125;#41;"/] + 0 -->|"kind: pipe"| 1 + 2 -->|"kind: transformCallee"| 1 + 5 -->|"kind: transformCallee"| 4 + 7 -->|"kind: potentialReturn"| 6 + 6 -->|"kind: transformArg"| 4 + 3 -->|"kind: pipe"| 4 + 9 -->|"kind: transformCallee"| 8 + 11 -->|"kind: potentialReturn"| 10 + 10 -->|"kind: transformArg"| 8 + 4 -->|"kind: pipe"| 8 + 14 -->|"kind: transformCallee"| 13 + 16 -->|"kind: potentialReturn"| 15 + 15 -->|"kind: transformArg"| 13 + 12 -->|"kind: pipe"| 13 + 18 -->|"kind: transformCallee"| 17 + 20 -->|"kind: potentialReturn"| 19 + 19 -->|"kind: transformArg"| 17 + 13 -->|"kind: pipe"| 17 + 23 -->|"kind: transformCallee"| 22 + 25 -->|"kind: potentialReturn"| 24 + 24 -->|"kind: transformArg"| 22 + 21 -->|"kind: pipe"| 22 + 27 -->|"kind: transformCallee"| 26 + 29 -->|"kind: potentialReturn"| 28 + 28 -->|"kind: transformArg"| 26 + 22 -->|"kind: pipe"| 26 + 30 -->|"kind: pipe"| 31 + 32 -->|"kind: transformCallee"| 30 + 34 -->|"kind: potentialReturn"| 33 + 33 -->|"kind: transformArg"| 30 + 31 -->|"kind: pipe"| 35 + 36 -->|"kind: transformCallee"| 30 + 38 -->|"kind: potentialReturn"| 37 + 37 -->|"kind: transformArg"| 30 + 39 -->|"kind: pipe"| 40 + 41 -->|"kind: transformCallee"| 39 + 43 -->|"kind: potentialReturn"| 42 + 42 -->|"kind: transformArg"| 39 + 40 -->|"kind: pipe"| 44 + 45 -->|"kind: transformCallee"| 39 + 47 -->|"kind: potentialReturn"| 46 + 46 -->|"kind: transformArg"| 39 + 50 -->|"kind: transformCallee"| 49 + 52 -->|"kind: potentialReturn"| 51 + 51 -->|"kind: transformArg"| 49 + 48 -->|"kind: pipe"| 49 + 54 -->|"kind: transformCallee"| 53 + 56 -->|"kind: pipe"| 57 + 58 -->|"kind: transformCallee"| 57 + 57 -->|"kind: pipe"| 59 + 60 -->|"kind: transformCallee"| 59 + 59 -->|"kind: potentialReturn"| 55 + 55 -->|"kind: transformArg"| 53 + 49 -->|"kind: pipe"| 53 + 62 -->|"kind: transformCallee"| 61 + 64 -->|"kind: potentialReturn"| 63 + 63 -->|"kind: transformArg"| 61 + 53 -->|"kind: pipe"| 61 + 67 -->|"kind: transformCallee"| 66 + 70 -->|"kind: potentialReturn"| 69 + 69 -->|"kind: usedBy"| 68 + 72 -->|"kind: potentialReturn"| 71 + 71 -->|"kind: usedBy"| 68 + 68 -->|"kind: transformArg"| 66 + 65 -->|"kind: pipe"| 66 + 76 -->|"kind: pipe"| 77 + 78 -->|"kind: transformCallee"| 76 + 79 -->|"kind: transformArg"| 76 + 77 -->|"kind: potentialReturn"| 75 + 75 -->|"kind: potentialReturn"| 74 + 74 -->|"kind: usedBy"| 73 + 82 -->|"kind: pipe"| 83 + 84 -->|"kind: transformCallee"| 82 + 85 -->|"kind: transformArg"| 82 + 83 -->|"kind: potentialReturn"| 81 + 81 -->|"kind: potentialReturn"| 80 + 80 -->|"kind: usedBy"| 73 + 88 -->|"kind: transformCallee"| 87 + 90 -->|"kind: potentialReturn"| 89 + 89 -->|"kind: transformArg"| 87 + 86 -->|"kind: pipe"| 87 + 92 -->|"kind: transformCallee"| 91 + 94 -->|"kind: potentialReturn"| 93 + 93 -->|"kind: transformArg"| 91 + 87 -->|"kind: pipe"| 91 + 97 -->|"kind: transformCallee"| 96 + 99 -->|"kind: potentialReturn"| 98 + 98 -->|"kind: transformArg"| 96 + 95 -->|"kind: pipe"| 96 + 101 -->|"kind: transformCallee"| 100 + 103 -->|"kind: potentialReturn"| 102 + 102 -->|"kind: transformArg"| 100 + 96 -->|"kind: pipe"| 100 \ No newline at end of file diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.flows.txt b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.flows.txt new file mode 100644 index 00000000..de546937 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.flows.txt @@ -0,0 +1 @@ +/.src/mapMapErrorToMapBoth.ts -> mapMapErrorToMapBoth.flows.mapMapErrorToMapBoth.mermaid diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.layers.txt b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.layers.txt new file mode 100644 index 00000000..934955e6 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.layers.txt @@ -0,0 +1 @@ +==== /.src/mapMapErrorToMapBoth.ts (0 layer exports) ==== diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.pipings.txt b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.pipings.txt new file mode 100644 index 00000000..b3022e01 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.pipings.txt @@ -0,0 +1,223 @@ +==== /.src/mapMapErrorToMapBoth.ts (13 flows) ==== + +=== Piping Flow === +Location: 4:24 - 4:54 +Node: Data.TaggedError("TaskError") +Node Kind: KindCallExpression + +Subject: "TaskError" +Subject Type: "TaskError" + +Transformations (1): + [0] kind: call + callee: Data.TaggedError + args: (constant) + outType: new
= {}>(args: VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & { readonly _tag: "TaskError"; } & Readonly + +=== Piping Flow === +Location: 11:38 - 14:2 +Node: fetchCount.pipe(\n Effect.map((n) => n > 0),\n Effect.mapError((message) => new TaskError({ message }))\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipeable + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipeable + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + +=== Piping Flow === +Location: 17:38 - 20:2 +Node: fetchCount.pipe(\n Effect.mapError((message) => new TaskError({ message })),\n Effect.map((n) => n > 0)\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipeable + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + [1] kind: pipeable + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + +=== Piping Flow === +Location: 23:31 - 27:2 +Node: pipe(\n fetchCount,\n Effect.map((n) => n > 0),\n Effect.mapError((message) => new TaskError({ message }))\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipe + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipe + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + +=== Piping Flow === +Location: 30:36 - 33:2 +Node: Effect.map(\n Effect.mapError(fetchCount, (message) => new TaskError({ message })),\n (n) => n > 0\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: dataFirst + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + [1] kind: dataFirst + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + +=== Piping Flow === +Location: 36:36 - 39:2 +Node: Effect.mapError(\n Effect.map(fetchCount, (n) => n > 0),\n (message) => new TaskError({ message })\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: dataFirst + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: dataFirst + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + +=== Piping Flow === +Location: 42:29 - 46:2 +Node: fetchCount.pipe(\n Effect.map((n) => n > 0),\n Effect.tap((b) => Effect.log(String(b))),\n Effect.mapError((message) => new TaskError({ message }))\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (3): + [0] kind: pipeable + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipeable + callee: Effect.tap + args: [(b) => Effect.log(String(b))] + outType: Effect + [2] kind: pipeable + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + +=== Piping Flow === +Location: 44:20 - 44:42 +Node: Effect.log(String(b)) +Node Kind: KindCallExpression + +Subject: b +Subject Type: boolean + +Transformations (2): + [0] kind: call + callee: String + args: (constant) + outType: string + [1] kind: call + callee: Effect.log + args: (constant) + outType: Effect + +=== Piping Flow === +Location: 49:27 - 54:2 +Node: fetchCount.pipe(\n Effect.mapBoth({\n onSuccess: (n) => n > 0,\n onFailure: (message) => new TaskError({ message })\n })\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (1): + [0] kind: pipeable + callee: Effect.mapBoth + args: [{\n onSuccess: (n) => n > 0,\n onFailure: (message) => new TaskError({ message })\n }] + outType: Effect + +=== Piping Flow === +Location: 58:73 - 58:93 +Node: Effect.map(self, f) +Node Kind: KindCallExpression + +Subject: self +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.map + args: [f] + outType: Effect + +=== Piping Flow === +Location: 59:80 - 59:105 +Node: Effect.mapError(self, f) +Node Kind: KindCallExpression + +Subject: self +Subject Type: Effect + +Transformations (1): + [0] kind: dataFirst + callee: Effect.mapError + args: [f] + outType: Effect + +=== Piping Flow === +Location: 62:32 - 65:2 +Node: fetchCount.pipe(\n unrelated.map((n) => n > 0),\n Effect.mapError((message) => new TaskError({ message }))\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipeable + callee: unrelated.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipeable + callee: Effect.mapError + args: [(message) => new TaskError({ message })] + outType: Effect + +=== Piping Flow === +Location: 67:37 - 70:2 +Node: fetchCount.pipe(\n Effect.map((n) => n > 0),\n unrelated.mapError((message) => new TaskError({ message }))\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipeable + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipeable + callee: unrelated.mapError + args: [(message) => new TaskError({ message })] + outType: Effect diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.quickfixes.txt b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.quickfixes.txt new file mode 100644 index 00000000..beffac6c --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth.quickfixes.txt @@ -0,0 +1,53 @@ +=== Quick Fix Inventory === + +[D1] (13:3-13:18) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +[D2] (19:3-19:13) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +[D3] (26:3-26:18) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +[D4] (30:37-30:47) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +[D5] (36:37-36:52) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +=== Quick Fix Application Results === + +=== [D1] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D1] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default + +=== [D2] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D2] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default + +=== [D3] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D3] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default + +=== [D4] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D4] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default + +=== [D5] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D5] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.errors.txt b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.errors.txt new file mode 100644 index 00000000..6f298247 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.errors.txt @@ -0,0 +1,20 @@ +=== Metadata === +Effect version: 4.0.0 + +/.src/mapMapErrorToMapBoth_preview.ts(9,3): suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + + +==== /.src/mapMapErrorToMapBoth_preview.ts (1 errors) ==== + // @effect-diagnostics *:off + // @effect-diagnostics mapMapErrorToMapBoth:suggestion + import { Effect } from "effect" + + declare const fetchCount: Effect.Effect + + export const preview = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.mapError((s) => s.length) + ~~~~~~~~~~~~~~~ +!!! suggestion TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + ) + diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.flows.mapMapErrorToMapBoth_preview.mermaid b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.flows.mapMapErrorToMapBoth_preview.mermaid new file mode 100644 index 00000000..49dd202f --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.flows.mapMapErrorToMapBoth_preview.mermaid @@ -0,0 +1,18 @@ +flowchart TB + 0[/"type: Effect#lt;number, string, never#gt;
node: fetchCount"/] + 1["type: Effect#lt;boolean, string, never#gt;
callee: Effect.map
args: #91;#40;n#41; =#gt; n #gt; 0#93;"] + 2[/"type: #123; #lt;A, B#gt;#40;f: #40;a: A#41; =#gt; B#41;: #lt;E, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;B, E, R#gt;; #lt;A, E, R, B#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;a: A#41; =#gt; B#41;: Effect#lt;B, E, R#gt;; #125;
node: Effect.map"/] + 3[["type: #40;n: number#41; =#gt; boolean
node: #40;n#41; =#gt; n #gt; 0"]] + 4[/"type: boolean
node: n #gt; 0"/] + 5["type: Effect#lt;boolean, number, never#gt;
callee: Effect.mapError
args: #91;#40;s#41; =#gt; s.length#93;"] + 6[/"type: #123; #lt;E, E2#gt;#40;f: #40;e: E#41; =#gt; E2#41;: #lt;A, R#gt;#40;self: Effect#lt;A, E, R#gt;#41; =#gt; Effect#lt;A, E2, R#gt;; #lt;A, E, R, E2#gt;#40;self: Effect#lt;A, E, R#gt;, f: #40;e: E#41; =#gt; E2#41;: Effect#lt;A, E2, R#gt;; #125;
node: Effect.mapError"/] + 7[["type: #40;s: string#41; =#gt; number
node: #40;s#41; =#gt; s.length"]] + 8[/"type: number
node: s.length"/] + 2 -->|"kind: transformCallee"| 1 + 4 -->|"kind: potentialReturn"| 3 + 3 -->|"kind: transformArg"| 1 + 0 -->|"kind: pipe"| 1 + 6 -->|"kind: transformCallee"| 5 + 8 -->|"kind: potentialReturn"| 7 + 7 -->|"kind: transformArg"| 5 + 1 -->|"kind: pipe"| 5 \ No newline at end of file diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.flows.txt b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.flows.txt new file mode 100644 index 00000000..3ac09e76 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.flows.txt @@ -0,0 +1 @@ +/.src/mapMapErrorToMapBoth_preview.ts -> mapMapErrorToMapBoth_preview.flows.mapMapErrorToMapBoth_preview.mermaid diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.layers.txt b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.layers.txt new file mode 100644 index 00000000..3ef65e9f --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.layers.txt @@ -0,0 +1 @@ +==== /.src/mapMapErrorToMapBoth_preview.ts (0 layer exports) ==== diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.pipings.txt b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.pipings.txt new file mode 100644 index 00000000..f1724672 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.pipings.txt @@ -0,0 +1,19 @@ +==== /.src/mapMapErrorToMapBoth_preview.ts (1 flows) ==== + +=== Piping Flow === +Location: 7:23 - 10:2 +Node: fetchCount.pipe(\n Effect.map((n) => n > 0),\n Effect.mapError((s) => s.length)\n) +Node Kind: KindCallExpression + +Subject: fetchCount +Subject Type: Effect + +Transformations (2): + [0] kind: pipeable + callee: Effect.map + args: [(n) => n > 0] + outType: Effect + [1] kind: pipeable + callee: Effect.mapError + args: [(s) => s.length] + outType: Effect diff --git a/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.quickfixes.txt b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.quickfixes.txt new file mode 100644 index 00000000..cc14da01 --- /dev/null +++ b/testdata/baselines/reference/effect-v4/mapMapErrorToMapBoth_preview.quickfixes.txt @@ -0,0 +1,13 @@ +=== Quick Fix Inventory === + +[D1] (9:3-9:18) TS377132: `Effect.mapBoth` expresses these failure and success transformations more directly than adjacent `Effect.map` and `Effect.mapError`. effect(mapMapErrorToMapBoth) + Fix 0: "Disable mapMapErrorToMapBoth for this line" + Fix 1: "Disable mapMapErrorToMapBoth for entire file" + +=== Quick Fix Application Results === + +=== [D1] Fix 0: "Disable mapMapErrorToMapBoth for this line" === +skipped by default + +=== [D1] Fix 1: "Disable mapMapErrorToMapBoth for entire file" === +skipped by default diff --git a/testdata/tests/effect-v3/mapMapErrorToMapBoth.ts b/testdata/tests/effect-v3/mapMapErrorToMapBoth.ts new file mode 100644 index 00000000..551e4b18 --- /dev/null +++ b/testdata/tests/effect-v3/mapMapErrorToMapBoth.ts @@ -0,0 +1,70 @@ +// @effect-diagnostics mapMapErrorToMapBoth:suggestion +import { Data, Effect, pipe } from "effect" + +class TaskError extends Data.TaggedError("TaskError")<{ + readonly message: string +}> {} + +declare const fetchCount: Effect.Effect + +// BAD: pipe adjacent map then mapError +export const badPipeMapThenMapError = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) +) + +// BAD: pipe adjacent mapError then map +export const badPipeMapErrorThenMap = fetchCount.pipe( + Effect.mapError((message) => new TaskError({ message })), + Effect.map((n) => n > 0) +) + +// BAD: function pipe style +export const badFunctionPipe = pipe( + fetchCount, + Effect.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) +) + +// BAD: nested data-first map(mapError(eff, g), f) +export const badNestedMapMapError = Effect.map( + Effect.mapError(fetchCount, (message) => new TaskError({ message })), + (n) => n > 0 +) + +// BAD: nested data-first mapError(map(eff, f), g) +export const badNestedMapErrorMap = Effect.mapError( + Effect.map(fetchCount, (n) => n > 0), + (message) => new TaskError({ message }) +) + +// GOOD: separated by other step (silent) +export const goodSeparated = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.tap((b) => Effect.log(String(b))), + Effect.mapError((message) => new TaskError({ message })) +) + +// GOOD: already using mapBoth (silent) +export const goodMapBoth = fetchCount.pipe( + Effect.mapBoth({ + onSuccess: (n) => n > 0, + onFailure: (message) => new TaskError({ message }) + }) +) + +// GOOD: unrelated map functions (silent) +const unrelated = { + map: (f: (a: A) => B) => (self: Effect.Effect) => Effect.map(self, f), + mapError: (f: (e: E) => E2) => (self: Effect.Effect) => Effect.mapError(self, f) +} + +export const goodUnrelatedMap = fetchCount.pipe( + unrelated.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) +) + +export const goodUnrelatedMapError = fetchCount.pipe( + Effect.map((n) => n > 0), + unrelated.mapError((message) => new TaskError({ message })) +) diff --git a/testdata/tests/effect-v4/mapMapErrorToMapBoth.ts b/testdata/tests/effect-v4/mapMapErrorToMapBoth.ts new file mode 100644 index 00000000..551e4b18 --- /dev/null +++ b/testdata/tests/effect-v4/mapMapErrorToMapBoth.ts @@ -0,0 +1,70 @@ +// @effect-diagnostics mapMapErrorToMapBoth:suggestion +import { Data, Effect, pipe } from "effect" + +class TaskError extends Data.TaggedError("TaskError")<{ + readonly message: string +}> {} + +declare const fetchCount: Effect.Effect + +// BAD: pipe adjacent map then mapError +export const badPipeMapThenMapError = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) +) + +// BAD: pipe adjacent mapError then map +export const badPipeMapErrorThenMap = fetchCount.pipe( + Effect.mapError((message) => new TaskError({ message })), + Effect.map((n) => n > 0) +) + +// BAD: function pipe style +export const badFunctionPipe = pipe( + fetchCount, + Effect.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) +) + +// BAD: nested data-first map(mapError(eff, g), f) +export const badNestedMapMapError = Effect.map( + Effect.mapError(fetchCount, (message) => new TaskError({ message })), + (n) => n > 0 +) + +// BAD: nested data-first mapError(map(eff, f), g) +export const badNestedMapErrorMap = Effect.mapError( + Effect.map(fetchCount, (n) => n > 0), + (message) => new TaskError({ message }) +) + +// GOOD: separated by other step (silent) +export const goodSeparated = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.tap((b) => Effect.log(String(b))), + Effect.mapError((message) => new TaskError({ message })) +) + +// GOOD: already using mapBoth (silent) +export const goodMapBoth = fetchCount.pipe( + Effect.mapBoth({ + onSuccess: (n) => n > 0, + onFailure: (message) => new TaskError({ message }) + }) +) + +// GOOD: unrelated map functions (silent) +const unrelated = { + map: (f: (a: A) => B) => (self: Effect.Effect) => Effect.map(self, f), + mapError: (f: (e: E) => E2) => (self: Effect.Effect) => Effect.mapError(self, f) +} + +export const goodUnrelatedMap = fetchCount.pipe( + unrelated.map((n) => n > 0), + Effect.mapError((message) => new TaskError({ message })) +) + +export const goodUnrelatedMapError = fetchCount.pipe( + Effect.map((n) => n > 0), + unrelated.mapError((message) => new TaskError({ message })) +) diff --git a/testdata/tests/effect-v4/mapMapErrorToMapBoth_preview.ts b/testdata/tests/effect-v4/mapMapErrorToMapBoth_preview.ts new file mode 100644 index 00000000..90e2372a --- /dev/null +++ b/testdata/tests/effect-v4/mapMapErrorToMapBoth_preview.ts @@ -0,0 +1,10 @@ +// @effect-diagnostics *:off +// @effect-diagnostics mapMapErrorToMapBoth:suggestion +import { Effect } from "effect" + +declare const fetchCount: Effect.Effect + +export const preview = fetchCount.pipe( + Effect.map((n) => n > 0), + Effect.mapError((s) => s.length) +)