From 81c64ff0e115bbba28af172079cc7ba79c89978b Mon Sep 17 00:00:00 2001 From: Tyler Gibbs Date: Fri, 24 Jul 2026 00:40:37 -0500 Subject: [PATCH 1/7] add redundant layer merge fix for effect provide --- .changeset/tidy-layers-provide.md | 5 ++ README.md | 1 + .../__snapshots__/completions.test.ts.snap | 4 +- ...dundantLayerMergeAllInProvide.ts.codefixes | 9 +++ .../redundantLayerMergeAllInProvide.ts.output | 8 ++ ...rMergeAllInProvide_fix.from191to205.output | 31 ++++++++ ...rMergeAllInProvide_fix.from295to309.output | 31 ++++++++ ...rMergeAllInProvide_fix.from397to411.output | 31 ++++++++ .../redundantLayerMergeAllInProvide.ts | 30 ++++++++ .../__snapshots__/completions.test.ts.snap | 4 +- ...dundantLayerMergeAllInProvide.ts.codefixes | 12 +++ .../redundantLayerMergeAllInProvide.ts.output | 11 +++ ...rMergeAllInProvide_fix.from191to205.output | 35 +++++++++ ...rMergeAllInProvide_fix.from299to313.output | 35 +++++++++ ...rMergeAllInProvide_fix.from420to434.output | 35 +++++++++ ...rMergeAllInProvide_fix.from522to536.output | 35 +++++++++ ...ayerMergeAllInProvide_preview.ts.codefixes | 3 + ...ntLayerMergeAllInProvide_preview.ts.output | 2 + ...rMergeAllInProvide_fix.from193to207.output | 8 ++ .../redundantLayerMergeAllInProvide.ts | 34 +++++++++ ...redundantLayerMergeAllInProvide_preview.ts | 7 ++ packages/language-service/src/diagnostics.ts | 2 + .../redundantLayerMergeAllInProvide.ts | 74 +++++++++++++++++++ packages/language-service/src/metadata.json | 21 ++++++ schema.json | 12 +++ 25 files changed, 476 insertions(+), 4 deletions(-) create mode 100644 .changeset/tidy-layers-provide.md create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from295to309.output create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from397to411.output create mode 100644 packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from299to313.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from420to434.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from522to536.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.codefixes create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.redundantLayerMergeAllInProvide_fix.from193to207.output create mode 100644 packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts create mode 100644 packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide_preview.ts create mode 100644 packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts diff --git a/.changeset/tidy-layers-provide.md b/.changeset/tidy-layers-provide.md new file mode 100644 index 00000000..3974d328 --- /dev/null +++ b/.changeset/tidy-layers-provide.md @@ -0,0 +1,5 @@ +--- +"@effect/language-service": minor +--- + +add `redundantLayerMergeAllInProvide` to replace direct `Layer.mergeAll` arguments to `Effect.provide` with layer arrays. diff --git a/README.md b/README.md index 119e65a6..ab04a6f9 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ Some diagnostics are off by default or have a default severity of suggestion, bu missedPipeableOpportunity➖🔧Enforces the use of pipeable style for nested function calls✓✓ missingEffectServiceDependency➖Checks that Effect.Service dependencies satisfy all required layer inputs✓ nestedEffectGenYield➖Warns when yielding a nested bare Effect.gen inside an existing Effect generator context✓✓ + redundantLayerMergeAllInProvide💡🔧Replaces direct Layer.mergeAll arguments to Effect.provide with an array of layers✓✓ redundantSchemaTagIdentifier💡🔧Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest✓✓ schemaStructWithTag💡🔧Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field✓✓ schemaUnionOfLiterals➖🔧Simplifies Schema.Union of multiple Schema.Literal calls into single Schema.Literal✓ diff --git a/packages/harness-effect-v3/__snapshots__/completions.test.ts.snap b/packages/harness-effect-v3/__snapshots__/completions.test.ts.snap index 661bced5..275a3fb2 100644 --- a/packages/harness-effect-v3/__snapshots__/completions.test.ts.snap +++ b/packages/harness-effect-v3/__snapshots__/completions.test.ts.snap @@ -248,7 +248,7 @@ exports[`Completion effectDataClasses > effectDataClasses_directImportTaggedErro exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2:5 1`] = ` [ { - "insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,asyncFunction,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,cryptoRandomUUID,cryptoRandomUUIDInEffect,deterministicKeys,duplicatePackage,effectDoNotation,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapFlatten,effectMapVoid,effectSucceedWithVoid,extendsNativeError,flatMapToMap,floatingEffect,genericEffectServices,globalConsole,globalConsoleInEffect,globalDate,globalDateInEffect,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,globalFetchInEffect,globalRandom,globalRandomInEffect,globalTimers,globalTimersInEffect,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,lazyPromiseInEffectSync,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nestedEffectGenYield,newPromise,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,processEnv,processEnvInEffect,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryArrowBlock,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsafeEffectTypeAssertion,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", + "insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,asyncFunction,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,cryptoRandomUUID,cryptoRandomUUIDInEffect,deterministicKeys,duplicatePackage,effectDoNotation,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapFlatten,effectMapVoid,effectSucceedWithVoid,extendsNativeError,flatMapToMap,floatingEffect,genericEffectServices,globalConsole,globalConsoleInEffect,globalDate,globalDateInEffect,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,globalFetchInEffect,globalRandom,globalRandomInEffect,globalTimers,globalTimersInEffect,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,lazyPromiseInEffectSync,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nestedEffectGenYield,newPromise,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,processEnv,processEnvInEffect,redundantLayerMergeAllInProvide,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryArrowBlock,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsafeEffectTypeAssertion,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", "isSnippet": true, "kind": "string", "name": "@effect-diagnostics", @@ -259,7 +259,7 @@ exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2: "sortText": "11", }, { - "insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,asyncFunction,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,cryptoRandomUUID,cryptoRandomUUIDInEffect,deterministicKeys,duplicatePackage,effectDoNotation,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapFlatten,effectMapVoid,effectSucceedWithVoid,extendsNativeError,flatMapToMap,floatingEffect,genericEffectServices,globalConsole,globalConsoleInEffect,globalDate,globalDateInEffect,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,globalFetchInEffect,globalRandom,globalRandomInEffect,globalTimers,globalTimersInEffect,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,lazyPromiseInEffectSync,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nestedEffectGenYield,newPromise,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,processEnv,processEnvInEffect,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryArrowBlock,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsafeEffectTypeAssertion,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", + "insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,asyncFunction,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,cryptoRandomUUID,cryptoRandomUUIDInEffect,deterministicKeys,duplicatePackage,effectDoNotation,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapFlatten,effectMapVoid,effectSucceedWithVoid,extendsNativeError,flatMapToMap,floatingEffect,genericEffectServices,globalConsole,globalConsoleInEffect,globalDate,globalDateInEffect,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,globalFetchInEffect,globalRandom,globalRandomInEffect,globalTimers,globalTimersInEffect,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,lazyPromiseInEffectSync,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nestedEffectGenYield,newPromise,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,processEnv,processEnvInEffect,redundantLayerMergeAllInProvide,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryArrowBlock,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsafeEffectTypeAssertion,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", "isSnippet": true, "kind": "string", "name": "@effect-diagnostics-next-line", diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes new file mode 100644 index 00000000..1be11f99 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes @@ -0,0 +1,9 @@ +redundantLayerMergeAllInProvide_fix from 295 to 309 +redundantLayerMergeAllInProvide_skipNextLine from 295 to 309 +redundantLayerMergeAllInProvide_skipFile from 295 to 309 +redundantLayerMergeAllInProvide_fix from 191 to 205 +redundantLayerMergeAllInProvide_skipNextLine from 191 to 205 +redundantLayerMergeAllInProvide_skipFile from 191 to 205 +redundantLayerMergeAllInProvide_fix from 397 to 411 +redundantLayerMergeAllInProvide_skipNextLine from 397 to 411 +redundantLayerMergeAllInProvide_skipFile from 397 to 411 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output new file mode 100644 index 00000000..40c3f053 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output @@ -0,0 +1,8 @@ +Layer.mergeAll +8:17 - 8:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) + +Layer.mergeAll +13:2 - 13:16 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) + +Layer.mergeAll +17:17 - 17:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output new file mode 100644 index 00000000..31bf5139 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output @@ -0,0 +1,31 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 191 - 205 +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide([first, second]) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from295to309.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from295to309.output new file mode 100644 index 00000000..dddd65ec --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from295to309.output @@ -0,0 +1,31 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 295 - 309 +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + [first, second] +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from397to411.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from397to411.output new file mode 100644 index 00000000..f5ce64ab --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from397to411.output @@ -0,0 +1,31 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 397 - 411 +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide([...[first, second]]) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts new file mode 100644 index 00000000..9156a791 --- /dev/null +++ b/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts @@ -0,0 +1,30 @@ +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v4/__snapshots__/completions.test.ts.snap b/packages/harness-effect-v4/__snapshots__/completions.test.ts.snap index 6d9977c2..487094b3 100644 --- a/packages/harness-effect-v4/__snapshots__/completions.test.ts.snap +++ b/packages/harness-effect-v4/__snapshots__/completions.test.ts.snap @@ -109,7 +109,7 @@ exports[`Completion effectDataClasses > effectDataClasses.ts at 4:35 1`] = ` exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2:5 1`] = ` [ { - "insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,asyncFunction,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,cryptoRandomUUID,cryptoRandomUUIDInEffect,deterministicKeys,duplicatePackage,effectDoNotation,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapFlatten,effectMapVoid,effectSucceedWithVoid,extendsNativeError,flatMapToMap,floatingEffect,genericEffectServices,globalConsole,globalConsoleInEffect,globalDate,globalDateInEffect,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,globalFetchInEffect,globalRandom,globalRandomInEffect,globalTimers,globalTimersInEffect,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,lazyPromiseInEffectSync,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nestedEffectGenYield,newPromise,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,processEnv,processEnvInEffect,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryArrowBlock,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsafeEffectTypeAssertion,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", + "insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,asyncFunction,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,cryptoRandomUUID,cryptoRandomUUIDInEffect,deterministicKeys,duplicatePackage,effectDoNotation,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapFlatten,effectMapVoid,effectSucceedWithVoid,extendsNativeError,flatMapToMap,floatingEffect,genericEffectServices,globalConsole,globalConsoleInEffect,globalDate,globalDateInEffect,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,globalFetchInEffect,globalRandom,globalRandomInEffect,globalTimers,globalTimersInEffect,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,lazyPromiseInEffectSync,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nestedEffectGenYield,newPromise,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,processEnv,processEnvInEffect,redundantLayerMergeAllInProvide,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryArrowBlock,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsafeEffectTypeAssertion,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", "isSnippet": true, "kind": "string", "name": "@effect-diagnostics", @@ -120,7 +120,7 @@ exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2: "sortText": "11", }, { - "insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,asyncFunction,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,cryptoRandomUUID,cryptoRandomUUIDInEffect,deterministicKeys,duplicatePackage,effectDoNotation,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapFlatten,effectMapVoid,effectSucceedWithVoid,extendsNativeError,flatMapToMap,floatingEffect,genericEffectServices,globalConsole,globalConsoleInEffect,globalDate,globalDateInEffect,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,globalFetchInEffect,globalRandom,globalRandomInEffect,globalTimers,globalTimersInEffect,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,lazyPromiseInEffectSync,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nestedEffectGenYield,newPromise,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,processEnv,processEnvInEffect,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryArrowBlock,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsafeEffectTypeAssertion,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", + "insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,asyncFunction,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,cryptoRandomUUID,cryptoRandomUUIDInEffect,deterministicKeys,duplicatePackage,effectDoNotation,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapFlatten,effectMapVoid,effectSucceedWithVoid,extendsNativeError,flatMapToMap,floatingEffect,genericEffectServices,globalConsole,globalConsoleInEffect,globalDate,globalDateInEffect,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,globalFetchInEffect,globalRandom,globalRandomInEffect,globalTimers,globalTimersInEffect,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,lazyPromiseInEffectSync,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nestedEffectGenYield,newPromise,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,processEnv,processEnvInEffect,redundantLayerMergeAllInProvide,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryArrowBlock,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsafeEffectTypeAssertion,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", "isSnippet": true, "kind": "string", "name": "@effect-diagnostics-next-line", diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes new file mode 100644 index 00000000..38828172 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes @@ -0,0 +1,12 @@ +redundantLayerMergeAllInProvide_fix from 420 to 434 +redundantLayerMergeAllInProvide_skipNextLine from 420 to 434 +redundantLayerMergeAllInProvide_skipFile from 420 to 434 +redundantLayerMergeAllInProvide_fix from 191 to 205 +redundantLayerMergeAllInProvide_skipNextLine from 191 to 205 +redundantLayerMergeAllInProvide_skipFile from 191 to 205 +redundantLayerMergeAllInProvide_fix from 299 to 313 +redundantLayerMergeAllInProvide_skipNextLine from 299 to 313 +redundantLayerMergeAllInProvide_skipFile from 299 to 313 +redundantLayerMergeAllInProvide_fix from 522 to 536 +redundantLayerMergeAllInProvide_skipNextLine from 522 to 536 +redundantLayerMergeAllInProvide_skipFile from 522 to 536 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output new file mode 100644 index 00000000..c0be3f25 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output @@ -0,0 +1,11 @@ +Layer.mergeAll +8:17 - 8:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) + +Layer.mergeAll +12:17 - 12:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) + +Layer.mergeAll +17:2 - 17:16 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) + +Layer.mergeAll +21:17 - 21:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output new file mode 100644 index 00000000..94880eca --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output @@ -0,0 +1,35 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 191 - 205 +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide([first, second]) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second), { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from299to313.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from299to313.output new file mode 100644 index 00000000..ed2cdca6 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from299to313.output @@ -0,0 +1,35 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 299 - 313 +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide([first, second], { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from420to434.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from420to434.output new file mode 100644 index 00000000..d7079484 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from420to434.output @@ -0,0 +1,35 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 420 - 434 +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second), { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + [first, second] +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from522to536.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from522to536.output new file mode 100644 index 00000000..2716b898 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from522to536.output @@ -0,0 +1,35 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 522 - 536 +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second), { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide([...[first, second]]) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.codefixes new file mode 100644 index 00000000..a3c5192b --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.codefixes @@ -0,0 +1,3 @@ +redundantLayerMergeAllInProvide_fix from 193 to 207 +redundantLayerMergeAllInProvide_skipNextLine from 193 to 207 +redundantLayerMergeAllInProvide_skipFile from 193 to 207 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.output new file mode 100644 index 00000000..4106509b --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.output @@ -0,0 +1,2 @@ +Layer.mergeAll +6:17 - 6:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.redundantLayerMergeAllInProvide_fix.from193to207.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.redundantLayerMergeAllInProvide_fix.from193to207.output new file mode 100644 index 00000000..f6521863 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide_preview.ts.redundantLayerMergeAllInProvide_fix.from193to207.output @@ -0,0 +1,8 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 193 - 207 +// @effect-diagnostics *:off +// @effect-diagnostics redundantLayerMergeAllInProvide:suggestion +import { Effect, Layer } from "effect" + +export const preview = Effect.void.pipe( + Effect.provide([Layer.empty, Layer.empty]) +) diff --git a/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts new file mode 100644 index 00000000..f97fed32 --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts @@ -0,0 +1,34 @@ +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second), { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide_preview.ts b/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide_preview.ts new file mode 100644 index 00000000..fc7807c0 --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide_preview.ts @@ -0,0 +1,7 @@ +// @effect-diagnostics *:off +// @effect-diagnostics redundantLayerMergeAllInProvide:suggestion +import { Effect, Layer } from "effect" + +export const preview = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Layer.empty, Layer.empty)) +) diff --git a/packages/language-service/src/diagnostics.ts b/packages/language-service/src/diagnostics.ts index 28aaaa8c..fd80a91d 100644 --- a/packages/language-service/src/diagnostics.ts +++ b/packages/language-service/src/diagnostics.ts @@ -56,6 +56,7 @@ import { overriddenSchemaConstructor } from "./diagnostics/overriddenSchemaConst import { preferSchemaOverJson } from "./diagnostics/preferSchemaOverJson.js" import { processEnv } from "./diagnostics/processEnv.js" import { processEnvInEffect } from "./diagnostics/processEnvInEffect.js" +import { redundantLayerMergeAllInProvide } from "./diagnostics/redundantLayerMergeAllInProvide.js" import { redundantSchemaTagIdentifier } from "./diagnostics/redundantSchemaTagIdentifier.js" import { returnEffectInGen } from "./diagnostics/returnEffectInGen.js" import { runEffectInsideEffect } from "./diagnostics/runEffectInsideEffect.js" @@ -140,6 +141,7 @@ export const diagnostics = [ effectSucceedWithVoid, effectFnIife, effectFnOpportunity, + redundantLayerMergeAllInProvide, redundantSchemaTagIdentifier, schemaSyncInEffect, preferSchemaOverJson, diff --git a/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts new file mode 100644 index 00000000..60190d3b --- /dev/null +++ b/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts @@ -0,0 +1,74 @@ +import { pipe } from "effect/Function" +import type ts from "typescript" +import * as LSP from "../core/LSP.js" +import * as Nano from "../core/Nano.js" +import * as TypeParser from "../core/TypeParser.js" +import * as TypeScriptApi from "../core/TypeScriptApi.js" + +export const redundantLayerMergeAllInProvide = LSP.createDiagnostic({ + name: "redundantLayerMergeAllInProvide", + code: 77, + description: "Replaces direct Layer.mergeAll arguments to Effect.provide with an array of layers", + group: "style", + severity: "suggestion", + fixable: true, + supportedEffect: ["v3", "v4"], + apply: Nano.fn("redundantLayerMergeAllInProvide.apply")(function*(sourceFile, report) { + const ts = yield* Nano.service(TypeScriptApi.TypeScriptApi) + const typeParser = yield* Nano.service(TypeParser.TypeParser) + + const nodesToVisit: Array = [] + const appendNodeToVisit = (node: ts.Node) => { + nodesToVisit.push(node) + return undefined + } + ts.forEachChild(sourceFile, appendNodeToVisit) + + while (nodesToVisit.length > 0) { + const node = nodesToVisit.shift()! + ts.forEachChild(node, appendNodeToVisit) + + if (!ts.isCallExpression(node)) continue + + const isEffectProvide = yield* pipe( + typeParser.isNodeReferenceToEffectModuleApi("provide")(node.expression), + Nano.orUndefined + ) + if (!isEffectProvide) continue + + for (const argument of node.arguments) { + if (!ts.isCallExpression(argument)) continue + + const isLayerMergeAll = yield* pipe( + typeParser.isNodeReferenceToEffectLayerModuleApi("mergeAll")(argument.expression), + Nano.orUndefined + ) + if (!isLayerMergeAll) continue + + const startLine = sourceFile.getLineAndCharacterOfPosition(argument.getStart(sourceFile)).line + const endLine = sourceFile.getLineAndCharacterOfPosition(argument.getEnd()).line + + report({ + location: argument.expression, + messageText: + "`Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant.", + fixes: [{ + fixName: "redundantLayerMergeAllInProvide_fix", + description: "Replace Layer.mergeAll with an array", + apply: Nano.gen(function*() { + const changeTracker = yield* Nano.service(TypeScriptApi.ChangeTracker) + changeTracker.replaceNode( + sourceFile, + argument, + ts.factory.createArrayLiteralExpression( + argument.arguments, + startLine !== endLine + ) + ) + }) + }] + }) + } + } + }) +}) diff --git a/packages/language-service/src/metadata.json b/packages/language-service/src/metadata.json index 69f19809..1208a32e 100644 --- a/packages/language-service/src/metadata.json +++ b/packages/language-service/src/metadata.json @@ -1470,6 +1470,27 @@ ] } }, + { + "name": "redundantLayerMergeAllInProvide", + "group": "style", + "description": "Replaces direct Layer.mergeAll arguments to Effect.provide with an array of layers", + "defaultSeverity": "suggestion", + "fixable": true, + "supportedEffect": [ + "v3", + "v4" + ], + "preview": { + "sourceText": "import { Effect, Layer } from \"effect\"\n\nexport const preview = Effect.void.pipe(\n Effect.provide(Layer.mergeAll(Layer.empty, Layer.empty))\n)\n", + "diagnostics": [ + { + "start": 98, + "end": 112, + "text": "`Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide)" + } + ] + } + }, { "name": "redundantSchemaTagIdentifier", "group": "style", diff --git a/schema.json b/schema.json index 5f63b532..322cc958 100644 --- a/schema.json +++ b/schema.json @@ -2996,6 +2996,18 @@ "default": "off", "description": "Warns when reading process.env inside Effect generators instead of using Effect Config Default severity: off." }, + "redundantLayerMergeAllInProvide": { + "type": "string", + "enum": [ + "off", + "error", + "warning", + "message", + "suggestion" + ], + "default": "suggestion", + "description": "Replaces direct Layer.mergeAll arguments to Effect.provide with an array of layers Default severity: suggestion." + }, "redundantSchemaTagIdentifier": { "type": "string", "enum": [ From 79779a63efe380aef4f51e5c38b1949e82f389a3 Mon Sep 17 00:00:00 2001 From: Tyler Gibbs Date: Fri, 24 Jul 2026 09:33:00 -0500 Subject: [PATCH 2/7] fix redundant layer diagnostic node positions --- .../src/diagnostics/redundantLayerMergeAllInProvide.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts index 60190d3b..a80e24e8 100644 --- a/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts +++ b/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts @@ -45,8 +45,8 @@ export const redundantLayerMergeAllInProvide = LSP.createDiagnostic({ ) if (!isLayerMergeAll) continue - const startLine = sourceFile.getLineAndCharacterOfPosition(argument.getStart(sourceFile)).line - const endLine = sourceFile.getLineAndCharacterOfPosition(argument.getEnd()).line + const startLine = sourceFile.getLineAndCharacterOfPosition(ts.getTokenPosOfNode(argument, sourceFile)).line + const endLine = sourceFile.getLineAndCharacterOfPosition(argument.end).line report({ location: argument.expression, From 8d1a33f751adb58aaf7dfd9dabcbe32a2fefc4f1 Mon Sep 17 00:00:00 2001 From: Tyler Gibbs Date: Fri, 24 Jul 2026 10:14:29 -0500 Subject: [PATCH 3/7] fix redundant layer spread quick fixes --- ...dundantLayerMergeAllInProvide.ts.codefixes | 21 +++++----- .../redundantLayerMergeAllInProvide.ts.output | 9 +++-- ...MergeAllInProvide_fix.from231to245.output} | 7 +++- ...MergeAllInProvide_fix.from335to349.output} | 7 +++- ...MergeAllInProvide_fix.from437to451.output} | 9 ++++- ...MergeAllInProvide_fix.from550to564.output} | 11 ++--- .../redundantLayerMergeAllInProvide.ts | 5 +++ ...dundantLayerMergeAllInProvide.ts.codefixes | 27 +++++++------ .../redundantLayerMergeAllInProvide.ts.output | 11 +++-- ...MergeAllInProvide_fix.from231to245.output} | 7 +++- ...rMergeAllInProvide_fix.from339to353.output | 40 +++++++++++++++++++ ...MergeAllInProvide_fix.from460to474.output} | 7 +++- ...MergeAllInProvide_fix.from562to576.output} | 9 ++++- ...rMergeAllInProvide_fix.from675to689.output | 40 +++++++++++++++++++ .../redundantLayerMergeAllInProvide.ts | 5 +++ .../redundantLayerMergeAllInProvide.ts | 14 +++++-- 16 files changed, 184 insertions(+), 45 deletions(-) rename packages/harness-effect-v3/__snapshots__/diagnostics/{redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output => redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output} (83%) rename packages/harness-effect-v3/__snapshots__/diagnostics/{redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from295to309.output => redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from335to349.output} (83%) rename packages/harness-effect-v3/__snapshots__/diagnostics/{redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from397to411.output => redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from437to451.output} (79%) rename packages/{harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from299to313.output => harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output} (85%) rename packages/harness-effect-v4/__snapshots__/diagnostics/{redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output => redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output} (85%) create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from339to353.output rename packages/harness-effect-v4/__snapshots__/diagnostics/{redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from420to434.output => redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from460to474.output} (85%) rename packages/harness-effect-v4/__snapshots__/diagnostics/{redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from522to536.output => redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from562to576.output} (81%) create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from675to689.output diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes index 1be11f99..ba943841 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes @@ -1,9 +1,12 @@ -redundantLayerMergeAllInProvide_fix from 295 to 309 -redundantLayerMergeAllInProvide_skipNextLine from 295 to 309 -redundantLayerMergeAllInProvide_skipFile from 295 to 309 -redundantLayerMergeAllInProvide_fix from 191 to 205 -redundantLayerMergeAllInProvide_skipNextLine from 191 to 205 -redundantLayerMergeAllInProvide_skipFile from 191 to 205 -redundantLayerMergeAllInProvide_fix from 397 to 411 -redundantLayerMergeAllInProvide_skipNextLine from 397 to 411 -redundantLayerMergeAllInProvide_skipFile from 397 to 411 \ No newline at end of file +redundantLayerMergeAllInProvide_fix from 335 to 349 +redundantLayerMergeAllInProvide_skipNextLine from 335 to 349 +redundantLayerMergeAllInProvide_skipFile from 335 to 349 +redundantLayerMergeAllInProvide_fix from 231 to 245 +redundantLayerMergeAllInProvide_skipNextLine from 231 to 245 +redundantLayerMergeAllInProvide_skipFile from 231 to 245 +redundantLayerMergeAllInProvide_fix from 437 to 451 +redundantLayerMergeAllInProvide_skipNextLine from 437 to 451 +redundantLayerMergeAllInProvide_skipFile from 437 to 451 +redundantLayerMergeAllInProvide_fix from 550 to 564 +redundantLayerMergeAllInProvide_skipNextLine from 550 to 564 +redundantLayerMergeAllInProvide_skipFile from 550 to 564 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output index 40c3f053..92e2c26d 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output @@ -1,8 +1,11 @@ Layer.mergeAll -8:17 - 8:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) +9:17 - 9:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) Layer.mergeAll -13:2 - 13:16 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) +14:2 - 14:16 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) Layer.mergeAll -17:17 - 17:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) \ No newline at end of file +18:17 - 18:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) + +Layer.mergeAll +22:17 - 22:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output similarity index 83% rename from packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output rename to packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output index 31bf5139..8edbcb91 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output @@ -1,9 +1,10 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 191 - 205 +// code fix redundantLayerMergeAllInProvide_fix output for range 231 - 245 import { Effect, Layer } from "effect" const first = Layer.empty const second = Layer.empty const third = Layer.empty +const layers = [first, second] as const export const shouldReportPipeable = Effect.void.pipe( Effect.provide([first, second]) @@ -18,6 +19,10 @@ export const shouldReportSpread = Effect.void.pipe( Effect.provide(Layer.mergeAll(...[first, second])) ) +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + export const shouldNotReportTransformed = Effect.void.pipe( Effect.provide( Layer.mergeAll(first, second).pipe(Layer.provide(third)) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from295to309.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from335to349.output similarity index 83% rename from packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from295to309.output rename to packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from335to349.output index dddd65ec..ceda43df 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from295to309.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from335to349.output @@ -1,9 +1,10 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 295 - 309 +// code fix redundantLayerMergeAllInProvide_fix output for range 335 - 349 import { Effect, Layer } from "effect" const first = Layer.empty const second = Layer.empty const third = Layer.empty +const layers = [first, second] as const export const shouldReportPipeable = Effect.void.pipe( Effect.provide(Layer.mergeAll(first, second)) @@ -18,6 +19,10 @@ export const shouldReportSpread = Effect.void.pipe( Effect.provide(Layer.mergeAll(...[first, second])) ) +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + export const shouldNotReportTransformed = Effect.void.pipe( Effect.provide( Layer.mergeAll(first, second).pipe(Layer.provide(third)) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from397to411.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from437to451.output similarity index 79% rename from packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from397to411.output rename to packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from437to451.output index f5ce64ab..2badb84c 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from397to411.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from437to451.output @@ -1,9 +1,10 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 397 - 411 +// code fix redundantLayerMergeAllInProvide_fix output for range 437 - 451 import { Effect, Layer } from "effect" const first = Layer.empty const second = Layer.empty const third = Layer.empty +const layers = [first, second] as const export const shouldReportPipeable = Effect.void.pipe( Effect.provide(Layer.mergeAll(first, second)) @@ -15,7 +16,11 @@ export const shouldReportDataFirst = Effect.provide( ) export const shouldReportSpread = Effect.void.pipe( - Effect.provide([...[first, second]]) + Effect.provide([first, second]) +) + +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) ) export const shouldNotReportTransformed = Effect.void.pipe( diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from299to313.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output similarity index 85% rename from packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from299to313.output rename to packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output index ed2cdca6..d8b891e7 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from299to313.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output @@ -1,18 +1,15 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 299 - 313 +// code fix redundantLayerMergeAllInProvide_fix output for range 550 - 564 import { Effect, Layer } from "effect" const first = Layer.empty const second = Layer.empty const third = Layer.empty +const layers = [first, second] as const export const shouldReportPipeable = Effect.void.pipe( Effect.provide(Layer.mergeAll(first, second)) ) -export const shouldReportWithOptions = Effect.void.pipe( - Effect.provide([first, second], { local: true }) -) - export const shouldReportDataFirst = Effect.provide( Effect.void, Layer.mergeAll(first, second) @@ -22,6 +19,10 @@ export const shouldReportSpread = Effect.void.pipe( Effect.provide(Layer.mergeAll(...[first, second])) ) +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(layers) +) + export const shouldNotReportTransformed = Effect.void.pipe( Effect.provide( Layer.mergeAll(first, second).pipe(Layer.provide(third)) diff --git a/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts index 9156a791..c82d72b3 100644 --- a/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts +++ b/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts @@ -3,6 +3,7 @@ import { Effect, Layer } from "effect" const first = Layer.empty const second = Layer.empty const third = Layer.empty +const layers = [first, second] as const export const shouldReportPipeable = Effect.void.pipe( Effect.provide(Layer.mergeAll(first, second)) @@ -17,6 +18,10 @@ export const shouldReportSpread = Effect.void.pipe( Effect.provide(Layer.mergeAll(...[first, second])) ) +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + export const shouldNotReportTransformed = Effect.void.pipe( Effect.provide( Layer.mergeAll(first, second).pipe(Layer.provide(third)) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes index 38828172..ae01d550 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes @@ -1,12 +1,15 @@ -redundantLayerMergeAllInProvide_fix from 420 to 434 -redundantLayerMergeAllInProvide_skipNextLine from 420 to 434 -redundantLayerMergeAllInProvide_skipFile from 420 to 434 -redundantLayerMergeAllInProvide_fix from 191 to 205 -redundantLayerMergeAllInProvide_skipNextLine from 191 to 205 -redundantLayerMergeAllInProvide_skipFile from 191 to 205 -redundantLayerMergeAllInProvide_fix from 299 to 313 -redundantLayerMergeAllInProvide_skipNextLine from 299 to 313 -redundantLayerMergeAllInProvide_skipFile from 299 to 313 -redundantLayerMergeAllInProvide_fix from 522 to 536 -redundantLayerMergeAllInProvide_skipNextLine from 522 to 536 -redundantLayerMergeAllInProvide_skipFile from 522 to 536 \ No newline at end of file +redundantLayerMergeAllInProvide_fix from 460 to 474 +redundantLayerMergeAllInProvide_skipNextLine from 460 to 474 +redundantLayerMergeAllInProvide_skipFile from 460 to 474 +redundantLayerMergeAllInProvide_fix from 231 to 245 +redundantLayerMergeAllInProvide_skipNextLine from 231 to 245 +redundantLayerMergeAllInProvide_skipFile from 231 to 245 +redundantLayerMergeAllInProvide_fix from 339 to 353 +redundantLayerMergeAllInProvide_skipNextLine from 339 to 353 +redundantLayerMergeAllInProvide_skipFile from 339 to 353 +redundantLayerMergeAllInProvide_fix from 562 to 576 +redundantLayerMergeAllInProvide_skipNextLine from 562 to 576 +redundantLayerMergeAllInProvide_skipFile from 562 to 576 +redundantLayerMergeAllInProvide_fix from 675 to 689 +redundantLayerMergeAllInProvide_skipNextLine from 675 to 689 +redundantLayerMergeAllInProvide_skipFile from 675 to 689 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output index c0be3f25..9c078c58 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.output @@ -1,11 +1,14 @@ Layer.mergeAll -8:17 - 8:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) +9:17 - 9:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) Layer.mergeAll -12:17 - 12:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) +13:17 - 13:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) Layer.mergeAll -17:2 - 17:16 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) +18:2 - 18:16 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) Layer.mergeAll -21:17 - 21:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) \ No newline at end of file +22:17 - 22:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) + +Layer.mergeAll +26:17 - 26:31 | 2 | `Effect.provide` accepts an array of Layers directly, so this `Layer.mergeAll` call is redundant. effect(redundantLayerMergeAllInProvide) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output similarity index 85% rename from packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output rename to packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output index 94880eca..8ae2dd5a 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from191to205.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output @@ -1,9 +1,10 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 191 - 205 +// code fix redundantLayerMergeAllInProvide_fix output for range 231 - 245 import { Effect, Layer } from "effect" const first = Layer.empty const second = Layer.empty const third = Layer.empty +const layers = [first, second] as const export const shouldReportPipeable = Effect.void.pipe( Effect.provide([first, second]) @@ -22,6 +23,10 @@ export const shouldReportSpread = Effect.void.pipe( Effect.provide(Layer.mergeAll(...[first, second])) ) +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + export const shouldNotReportTransformed = Effect.void.pipe( Effect.provide( Layer.mergeAll(first, second).pipe(Layer.provide(third)) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from339to353.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from339to353.output new file mode 100644 index 00000000..91f5a9fa --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from339to353.output @@ -0,0 +1,40 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 339 - 353 +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty +const layers = [first, second] as const + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide([first, second], { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from420to434.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from460to474.output similarity index 85% rename from packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from420to434.output rename to packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from460to474.output index d7079484..8f9022a4 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from420to434.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from460to474.output @@ -1,9 +1,10 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 420 - 434 +// code fix redundantLayerMergeAllInProvide_fix output for range 460 - 474 import { Effect, Layer } from "effect" const first = Layer.empty const second = Layer.empty const third = Layer.empty +const layers = [first, second] as const export const shouldReportPipeable = Effect.void.pipe( Effect.provide(Layer.mergeAll(first, second)) @@ -22,6 +23,10 @@ export const shouldReportSpread = Effect.void.pipe( Effect.provide(Layer.mergeAll(...[first, second])) ) +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + export const shouldNotReportTransformed = Effect.void.pipe( Effect.provide( Layer.mergeAll(first, second).pipe(Layer.provide(third)) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from522to536.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from562to576.output similarity index 81% rename from packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from522to536.output rename to packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from562to576.output index 2716b898..b5116f5a 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from522to536.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from562to576.output @@ -1,9 +1,10 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 522 - 536 +// code fix redundantLayerMergeAllInProvide_fix output for range 562 - 576 import { Effect, Layer } from "effect" const first = Layer.empty const second = Layer.empty const third = Layer.empty +const layers = [first, second] as const export const shouldReportPipeable = Effect.void.pipe( Effect.provide(Layer.mergeAll(first, second)) @@ -19,7 +20,11 @@ export const shouldReportDataFirst = Effect.provide( ) export const shouldReportSpread = Effect.void.pipe( - Effect.provide([...[first, second]]) + Effect.provide([first, second]) +) + +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) ) export const shouldNotReportTransformed = Effect.void.pipe( diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from675to689.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from675to689.output new file mode 100644 index 00000000..2cd2b666 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from675to689.output @@ -0,0 +1,40 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 675 - 689 +import { Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty +const layers = [first, second] as const + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second), { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(layers) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) diff --git a/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts index f97fed32..eb9f1afb 100644 --- a/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts +++ b/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts @@ -3,6 +3,7 @@ import { Effect, Layer } from "effect" const first = Layer.empty const second = Layer.empty const third = Layer.empty +const layers = [first, second] as const export const shouldReportPipeable = Effect.void.pipe( Effect.provide(Layer.mergeAll(first, second)) @@ -21,6 +22,10 @@ export const shouldReportSpread = Effect.void.pipe( Effect.provide(Layer.mergeAll(...[first, second])) ) +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + export const shouldNotReportTransformed = Effect.void.pipe( Effect.provide( Layer.mergeAll(first, second).pipe(Layer.provide(third)) diff --git a/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts index a80e24e8..26c534c8 100644 --- a/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts +++ b/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts @@ -57,13 +57,19 @@ export const redundantLayerMergeAllInProvide = LSP.createDiagnostic({ description: "Replace Layer.mergeAll with an array", apply: Nano.gen(function*() { const changeTracker = yield* Nano.service(TypeScriptApi.ChangeTracker) - changeTracker.replaceNode( - sourceFile, - argument, - ts.factory.createArrayLiteralExpression( + const onlyArgument = argument.arguments.length === 1 + ? argument.arguments[0] + : undefined + const replacement = onlyArgument && ts.isSpreadElement(onlyArgument) + ? onlyArgument.expression + : ts.factory.createArrayLiteralExpression( argument.arguments, startLine !== endLine ) + changeTracker.replaceNode( + sourceFile, + argument, + replacement ) }) }] From 7be63f4e3bf11da834df3afbc28623b95f777574 Mon Sep 17 00:00:00 2001 From: Tyler Gibbs Date: Fri, 24 Jul 2026 10:38:04 -0500 Subject: [PATCH 4/7] preserve layer dependency diagnostics --- ...rMergeAllInProvide_fix.from231to245.output | 11 ++ ...rMergeAllInProvide_fix.from335to349.output | 11 ++ ...rMergeAllInProvide_fix.from437to451.output | 11 ++ ...rMergeAllInProvide_fix.from550to564.output | 11 ++ .../redundantLayerMergeAllInProvide.ts | 11 ++ ...dundantLayerMergeAllInProvide.ts.codefixes | 30 ++-- ...rMergeAllInProvide_fix.from240to254.output | 55 ++++++ ...rMergeAllInProvide_fix.from348to362.output | 55 ++++++ ...rMergeAllInProvide_fix.from469to483.output | 55 ++++++ ...rMergeAllInProvide_fix.from571to585.output | 55 ++++++ ...rMergeAllInProvide_fix.from684to698.output | 55 ++++++ .../redundantLayerMergeAllInProvide.ts | 17 +- .../diagnostics/layerMergeAllDependencies.ts | 69 +++++++ .../layerMergeAllWithDependencies.ts | 170 ++++++------------ .../redundantLayerMergeAllInProvide.ts | 2 + 15 files changed, 483 insertions(+), 135 deletions(-) create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from240to254.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from348to362.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from469to483.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from571to585.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from684to698.output create mode 100644 packages/language-service/src/diagnostics/layerMergeAllDependencies.ts diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output index 8edbcb91..2f26aeb3 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output @@ -34,3 +34,14 @@ const combined = Layer.mergeAll(first, second) export const shouldNotReportPrecomposed = Effect.void.pipe( Effect.provide(combined) ) + +class Dependency extends Effect.Service()("Dependency", { + succeed: {} +}) {} +class Consumer extends Effect.Service()("Consumer", { + effect: Effect.as(Dependency, {}) +}) {} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from335to349.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from335to349.output index ceda43df..0fe6ed5a 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from335to349.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from335to349.output @@ -34,3 +34,14 @@ const combined = Layer.mergeAll(first, second) export const shouldNotReportPrecomposed = Effect.void.pipe( Effect.provide(combined) ) + +class Dependency extends Effect.Service()("Dependency", { + succeed: {} +}) {} +class Consumer extends Effect.Service()("Consumer", { + effect: Effect.as(Dependency, {}) +}) {} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from437to451.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from437to451.output index 2badb84c..d148bbeb 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from437to451.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from437to451.output @@ -34,3 +34,14 @@ const combined = Layer.mergeAll(first, second) export const shouldNotReportPrecomposed = Effect.void.pipe( Effect.provide(combined) ) + +class Dependency extends Effect.Service()("Dependency", { + succeed: {} +}) {} +class Consumer extends Effect.Service()("Consumer", { + effect: Effect.as(Dependency, {}) +}) {} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output index d8b891e7..5871c628 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output @@ -34,3 +34,14 @@ const combined = Layer.mergeAll(first, second) export const shouldNotReportPrecomposed = Effect.void.pipe( Effect.provide(combined) ) + +class Dependency extends Effect.Service()("Dependency", { + succeed: {} +}) {} +class Consumer extends Effect.Service()("Consumer", { + effect: Effect.as(Dependency, {}) +}) {} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts index c82d72b3..db0a2e08 100644 --- a/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts +++ b/packages/harness-effect-v3/examples/diagnostics/redundantLayerMergeAllInProvide.ts @@ -33,3 +33,14 @@ const combined = Layer.mergeAll(first, second) export const shouldNotReportPrecomposed = Effect.void.pipe( Effect.provide(combined) ) + +class Dependency extends Effect.Service()("Dependency", { + succeed: {} +}) {} +class Consumer extends Effect.Service()("Consumer", { + effect: Effect.as(Dependency, {}) +}) {} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes index ae01d550..89c0325f 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.codefixes @@ -1,15 +1,15 @@ -redundantLayerMergeAllInProvide_fix from 460 to 474 -redundantLayerMergeAllInProvide_skipNextLine from 460 to 474 -redundantLayerMergeAllInProvide_skipFile from 460 to 474 -redundantLayerMergeAllInProvide_fix from 231 to 245 -redundantLayerMergeAllInProvide_skipNextLine from 231 to 245 -redundantLayerMergeAllInProvide_skipFile from 231 to 245 -redundantLayerMergeAllInProvide_fix from 339 to 353 -redundantLayerMergeAllInProvide_skipNextLine from 339 to 353 -redundantLayerMergeAllInProvide_skipFile from 339 to 353 -redundantLayerMergeAllInProvide_fix from 562 to 576 -redundantLayerMergeAllInProvide_skipNextLine from 562 to 576 -redundantLayerMergeAllInProvide_skipFile from 562 to 576 -redundantLayerMergeAllInProvide_fix from 675 to 689 -redundantLayerMergeAllInProvide_skipNextLine from 675 to 689 -redundantLayerMergeAllInProvide_skipFile from 675 to 689 \ No newline at end of file +redundantLayerMergeAllInProvide_fix from 469 to 483 +redundantLayerMergeAllInProvide_skipNextLine from 469 to 483 +redundantLayerMergeAllInProvide_skipFile from 469 to 483 +redundantLayerMergeAllInProvide_fix from 240 to 254 +redundantLayerMergeAllInProvide_skipNextLine from 240 to 254 +redundantLayerMergeAllInProvide_skipFile from 240 to 254 +redundantLayerMergeAllInProvide_fix from 348 to 362 +redundantLayerMergeAllInProvide_skipNextLine from 348 to 362 +redundantLayerMergeAllInProvide_skipFile from 348 to 362 +redundantLayerMergeAllInProvide_fix from 571 to 585 +redundantLayerMergeAllInProvide_skipNextLine from 571 to 585 +redundantLayerMergeAllInProvide_skipFile from 571 to 585 +redundantLayerMergeAllInProvide_fix from 684 to 698 +redundantLayerMergeAllInProvide_skipNextLine from 684 to 698 +redundantLayerMergeAllInProvide_skipFile from 684 to 698 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from240to254.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from240to254.output new file mode 100644 index 00000000..a491db04 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from240to254.output @@ -0,0 +1,55 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 240 - 254 +import { Context, Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty +const layers = [first, second] as const + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide([first, second]) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second), { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) + +class Dependency extends Context.Service()("Dependency", { + make: Effect.succeed({}) +}) { + static Default = Layer.effect(this, this.make) +} +class Consumer extends Context.Service()("Consumer", { + make: Effect.as(Dependency, {}) +}) { + static Default = Layer.effect(this, this.make) +} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from348to362.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from348to362.output new file mode 100644 index 00000000..346389a3 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from348to362.output @@ -0,0 +1,55 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 348 - 362 +import { Context, Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty +const layers = [first, second] as const + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide([first, second], { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) + +class Dependency extends Context.Service()("Dependency", { + make: Effect.succeed({}) +}) { + static Default = Layer.effect(this, this.make) +} +class Consumer extends Context.Service()("Consumer", { + make: Effect.as(Dependency, {}) +}) { + static Default = Layer.effect(this, this.make) +} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from469to483.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from469to483.output new file mode 100644 index 00000000..020c7741 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from469to483.output @@ -0,0 +1,55 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 469 - 483 +import { Context, Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty +const layers = [first, second] as const + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second), { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + [first, second] +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) + +class Dependency extends Context.Service()("Dependency", { + make: Effect.succeed({}) +}) { + static Default = Layer.effect(this, this.make) +} +class Consumer extends Context.Service()("Consumer", { + make: Effect.as(Dependency, {}) +}) { + static Default = Layer.effect(this, this.make) +} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from571to585.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from571to585.output new file mode 100644 index 00000000..1a03900b --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from571to585.output @@ -0,0 +1,55 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 571 - 585 +import { Context, Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty +const layers = [first, second] as const + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second), { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide([first, second]) +) + +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layers)) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) + +class Dependency extends Context.Service()("Dependency", { + make: Effect.succeed({}) +}) { + static Default = Layer.effect(this, this.make) +} +class Consumer extends Context.Service()("Consumer", { + make: Effect.as(Dependency, {}) +}) { + static Default = Layer.effect(this, this.make) +} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from684to698.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from684to698.output new file mode 100644 index 00000000..e59e8c75 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from684to698.output @@ -0,0 +1,55 @@ +// code fix redundantLayerMergeAllInProvide_fix output for range 684 - 698 +import { Context, Effect, Layer } from "effect" + +const first = Layer.empty +const second = Layer.empty +const third = Layer.empty +const layers = [first, second] as const + +export const shouldReportPipeable = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second)) +) + +export const shouldReportWithOptions = Effect.void.pipe( + Effect.provide(Layer.mergeAll(first, second), { local: true }) +) + +export const shouldReportDataFirst = Effect.provide( + Effect.void, + Layer.mergeAll(first, second) +) + +export const shouldReportSpread = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...[first, second])) +) + +export const shouldReportTupleSpread = Effect.void.pipe( + Effect.provide(layers) +) + +export const shouldNotReportTransformed = Effect.void.pipe( + Effect.provide( + Layer.mergeAll(first, second).pipe(Layer.provide(third)) + ) +) + +const combined = Layer.mergeAll(first, second) + +export const shouldNotReportPrecomposed = Effect.void.pipe( + Effect.provide(combined) +) + +class Dependency extends Context.Service()("Dependency", { + make: Effect.succeed({}) +}) { + static Default = Layer.effect(this, this.make) +} +class Consumer extends Context.Service()("Consumer", { + make: Effect.as(Dependency, {}) +}) { + static Default = Layer.effect(this, this.make) +} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts index eb9f1afb..aa2bf3b3 100644 --- a/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts +++ b/packages/harness-effect-v4/examples/diagnostics/redundantLayerMergeAllInProvide.ts @@ -1,4 +1,4 @@ -import { Effect, Layer } from "effect" +import { Context, Effect, Layer } from "effect" const first = Layer.empty const second = Layer.empty @@ -37,3 +37,18 @@ const combined = Layer.mergeAll(first, second) export const shouldNotReportPrecomposed = Effect.void.pipe( Effect.provide(combined) ) + +class Dependency extends Context.Service()("Dependency", { + make: Effect.succeed({}) +}) { + static Default = Layer.effect(this, this.make) +} +class Consumer extends Context.Service()("Consumer", { + make: Effect.as(Dependency, {}) +}) { + static Default = Layer.effect(this, this.make) +} + +export const shouldNotReportWithDependencies = Effect.void.pipe( + Effect.provide(Layer.mergeAll(Dependency.Default, Consumer.Default)) +) diff --git a/packages/language-service/src/diagnostics/layerMergeAllDependencies.ts b/packages/language-service/src/diagnostics/layerMergeAllDependencies.ts new file mode 100644 index 00000000..0404e095 --- /dev/null +++ b/packages/language-service/src/diagnostics/layerMergeAllDependencies.ts @@ -0,0 +1,69 @@ +import * as Option from "effect/Option" +import type ts from "typescript" +import * as Nano from "../core/Nano.js" +import * as TypeCheckerApi from "../core/TypeCheckerApi.js" +import * as TypeCheckerUtils from "../core/TypeCheckerUtils.js" +import * as TypeParser from "../core/TypeParser.js" +import * as TypeScriptApi from "../core/TypeScriptApi.js" + +export interface LayerMergeAllDependency { + readonly provider: ts.Expression + readonly providedTypes: string +} + +export const findLayerMergeAllDependencies = Nano.fn("findLayerMergeAllDependencies")(function*( + layerArgs: ReadonlyArray +) { + if (layerArgs.length <= 1) return [] + + const ts = yield* Nano.service(TypeScriptApi.TypeScriptApi) + const typeChecker = yield* Nano.service(TypeCheckerApi.TypeCheckerApi) + const typeCheckerUtils = yield* Nano.service(TypeCheckerUtils.TypeCheckerUtils) + const typeParser = yield* Nano.service(TypeParser.TypeParser) + const layerInfos: Array<{ + arg: ts.Expression + requirementsType: ts.Type + }> = [] + const actuallyProvidedMap = new Map() + + for (const arg of layerArgs) { + const argType = typeCheckerUtils.getTypeAtLocation(arg) + if (!argType) continue + + const layerTypeParsedOption = yield* Nano.option(typeParser.layerType(argType, arg)) + if (Option.isNone(layerTypeParsedOption)) continue + + const layerTypeParsed = layerTypeParsedOption.value + const providedMembers = typeCheckerUtils.unrollUnionMembers(layerTypeParsed.ROut) + + for (const providedType of providedMembers) { + if (providedType.flags & ts.TypeFlags.Never) continue + const isPassThrough = typeChecker.isTypeAssignableTo(providedType, layerTypeParsed.RIn) + if (!isPassThrough) { + actuallyProvidedMap.set(providedType, arg) + } + } + + layerInfos.push({ + arg, + requirementsType: layerTypeParsed.RIn + }) + } + + const providerToTypes = new Map>() + for (const layer of layerInfos) { + for (const [providedType, provider] of actuallyProvidedMap) { + if (provider === layer.arg) continue + if (!typeChecker.isTypeAssignableTo(providedType, layer.requirementsType)) continue + + const providedTypes = providerToTypes.get(provider) ?? new Set() + providedTypes.add(typeChecker.typeToString(providedType)) + providerToTypes.set(provider, providedTypes) + } + } + + return Array.from(providerToTypes, ([provider, providedTypes]) => ({ + provider, + providedTypes: Array.from(providedTypes).join(", ") + })) +}) diff --git a/packages/language-service/src/diagnostics/layerMergeAllWithDependencies.ts b/packages/language-service/src/diagnostics/layerMergeAllWithDependencies.ts index 0959b429..2cdae97e 100644 --- a/packages/language-service/src/diagnostics/layerMergeAllWithDependencies.ts +++ b/packages/language-service/src/diagnostics/layerMergeAllWithDependencies.ts @@ -1,13 +1,11 @@ import { pipe } from "effect/Function" -import * as Option from "effect/Option" import type ts from "typescript" import * as LSP from "../core/LSP.js" import * as Nano from "../core/Nano.js" -import * as TypeCheckerApi from "../core/TypeCheckerApi.js" -import * as TypeCheckerUtils from "../core/TypeCheckerUtils.js" import * as TypeParser from "../core/TypeParser.js" import * as TypeScriptApi from "../core/TypeScriptApi.js" import * as TypeScriptUtils from "../core/TypeScriptUtils.js" +import { findLayerMergeAllDependencies } from "./layerMergeAllDependencies.js" export const layerMergeAllWithDependencies = LSP.createDiagnostic({ name: "layerMergeAllWithDependencies", @@ -20,8 +18,6 @@ export const layerMergeAllWithDependencies = LSP.createDiagnostic({ supportedEffect: ["v3", "v4"], apply: Nano.fn("layerMergeAllWithDependencies.apply")(function*(sourceFile, report) { const ts = yield* Nano.service(TypeScriptApi.TypeScriptApi) - const typeChecker = yield* Nano.service(TypeCheckerApi.TypeCheckerApi) - const typeCheckerUtils = yield* Nano.service(TypeCheckerUtils.TypeCheckerUtils) const typeParser = yield* Nano.service(TypeParser.TypeParser) const tsUtils = yield* Nano.service(TypeScriptUtils.TypeScriptUtils) @@ -49,123 +45,59 @@ export const layerMergeAllWithDependencies = LSP.createDiagnostic({ ) if (checkLayerMergeAll) { - // Get all layer arguments const layerArgs = node.arguments - - if (layerArgs.length > 1) { - // Parse all layers to extract their ROut (provided) and RIn (required) types - const layerInfos: Array<{ - arg: ts.Expression - requirementsType: ts.Type - }> = [] - - // Map of actually provided types -> layer argument that provides it - const actuallyProvidedMap = new Map() - - for (const arg of layerArgs) { - const argType = typeCheckerUtils.getTypeAtLocation(arg) - if (!argType) continue - - const layerTypeParsedOption = yield* Nano.option(typeParser.layerType(argType, arg)) - if (Option.isNone(layerTypeParsedOption)) continue - - const layerTypeParsed = layerTypeParsedOption.value - - // Unroll union members for provided types (ROut) - const providedMembers = typeCheckerUtils.unrollUnionMembers(layerTypeParsed.ROut) - - // Filter out pass-through types: types that are both provided and required - // Add only actually provided types to the map - for (const providedType of providedMembers) { - if (providedType.flags & ts.TypeFlags.Never) continue - const isPassThrough = typeChecker.isTypeAssignableTo(providedType, layerTypeParsed.RIn) - if (!isPassThrough) { - actuallyProvidedMap.set(providedType, arg) - } - } - - layerInfos.push({ - arg, - requirementsType: layerTypeParsed.RIn - }) - } - - // Check for interdependencies - // Build a map of provider -> layers that depend on it - const providerToConsumers = new Map< - ts.Expression, - Array<{ consumer: ts.Expression; providedType: ts.Type }> - >() - - for (const layer of layerInfos) { - // Check if any of the actually provided types satisfy this layer's requirements - for (const [providedType, providerArg] of actuallyProvidedMap) { - // Skip if this is the same layer - if (providerArg === layer.arg) continue - - // Check if this provided type is assignable to the layer's requirements type - if (typeChecker.isTypeAssignableTo(providedType, layer.requirementsType)) { - const consumers = providerToConsumers.get(providerArg) || [] - consumers.push({ consumer: layer.arg, providedType }) - providerToConsumers.set(providerArg, consumers) - } - } - } - - // Report on providers that have consumers - for (const [providerArg, consumers] of providerToConsumers) { - const providedTypes = Array.from(new Set(consumers.map((c) => typeChecker.typeToString(c.providedType)))) - .join(", ") - - report({ - location: providerArg, - messageText: - `This layer provides ${providedTypes} which is required by another layer in the same Layer.mergeAll call. Layer.mergeAll creates layers in parallel, so dependencies between layers will not be satisfied. Consider moving this layer into a Layer.provideMerge after the Layer.mergeAll.`, - fixes: [{ - fixName: "layerMergeAllWithDependencies_fix", - description: "Move layer to Layer.provideMerge", - apply: Nano.gen(function*() { - const changeTracker = yield* Nano.service(TypeScriptApi.ChangeTracker) - - // Find the index of the provider argument - const providerIndex = layerArgs.indexOf(providerArg as ts.Expression) - if (providerIndex === -1) return - - // Step 1: Delete the provider argument from Layer.mergeAll - // Need to handle commas correctly - const providerArgNode = providerArg as ts.Expression - if (providerIndex === 0 && layerArgs.length > 1) { - // First argument - delete including the trailing comma - changeTracker.deleteRange(sourceFile, { - pos: providerArgNode.pos, - end: layerArgs[1].pos - }) - } else if (providerIndex > 0) { - // Not first argument - delete including the preceding comma - changeTracker.deleteRange(sourceFile, { - pos: layerArgs[providerIndex - 1].end, - end: providerArgNode.end - }) - } - - // Step 2: Insert .pipe(Layer.provideMerge(providerArg)) at the end - const provideMergeCall = ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(layerModuleIdentifier), - ts.factory.createIdentifier("provideMerge") - ), - undefined, - [providerArgNode] - ) - - changeTracker.insertNodeAt(sourceFile, node.end, provideMergeCall, { - prefix: ".pipe(" + const dependencies = yield* findLayerMergeAllDependencies(layerArgs) + for (const dependency of dependencies) { + const providerArg = dependency.provider + const providedTypes = dependency.providedTypes + report({ + location: providerArg, + messageText: + `This layer provides ${providedTypes} which is required by another layer in the same Layer.mergeAll call. Layer.mergeAll creates layers in parallel, so dependencies between layers will not be satisfied. Consider moving this layer into a Layer.provideMerge after the Layer.mergeAll.`, + fixes: [{ + fixName: "layerMergeAllWithDependencies_fix", + description: "Move layer to Layer.provideMerge", + apply: Nano.gen(function*() { + const changeTracker = yield* Nano.service(TypeScriptApi.ChangeTracker) + + // Find the index of the provider argument + const providerIndex = layerArgs.indexOf(providerArg as ts.Expression) + if (providerIndex === -1) return + + // Step 1: Delete the provider argument from Layer.mergeAll + // Need to handle commas correctly + const providerArgNode = providerArg as ts.Expression + if (providerIndex === 0 && layerArgs.length > 1) { + // First argument - delete including the trailing comma + changeTracker.deleteRange(sourceFile, { + pos: providerArgNode.pos, + end: layerArgs[1].pos + }) + } else if (providerIndex > 0) { + // Not first argument - delete including the preceding comma + changeTracker.deleteRange(sourceFile, { + pos: layerArgs[providerIndex - 1].end, + end: providerArgNode.end }) - changeTracker.insertText(sourceFile, node.end, ")") + } + + // Step 2: Insert .pipe(Layer.provideMerge(providerArg)) at the end + const provideMergeCall = ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(layerModuleIdentifier), + ts.factory.createIdentifier("provideMerge") + ), + undefined, + [providerArgNode] + ) + + changeTracker.insertNodeAt(sourceFile, node.end, provideMergeCall, { + prefix: ".pipe(" }) - }] - }) - } + changeTracker.insertText(sourceFile, node.end, ")") + }) + }] + }) } } } diff --git a/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts index 26c534c8..81ed61e7 100644 --- a/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts +++ b/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts @@ -4,6 +4,7 @@ import * as LSP from "../core/LSP.js" import * as Nano from "../core/Nano.js" import * as TypeParser from "../core/TypeParser.js" import * as TypeScriptApi from "../core/TypeScriptApi.js" +import { findLayerMergeAllDependencies } from "./layerMergeAllDependencies.js" export const redundantLayerMergeAllInProvide = LSP.createDiagnostic({ name: "redundantLayerMergeAllInProvide", @@ -44,6 +45,7 @@ export const redundantLayerMergeAllInProvide = LSP.createDiagnostic({ Nano.orUndefined ) if (!isLayerMergeAll) continue + if ((yield* findLayerMergeAllDependencies(argument.arguments)).length > 0) continue const startLine = sourceFile.getLineAndCharacterOfPosition(ts.getTokenPosOfNode(argument, sourceFile)).line const endLine = sourceFile.getLineAndCharacterOfPosition(argument.end).line From 10f1fc933d74ab7d99e6a102b7d7e0b69f20ef77 Mon Sep 17 00:00:00 2001 From: Tyler Gibbs Date: Fri, 24 Jul 2026 10:38:14 -0500 Subject: [PATCH 5/7] test: remove obsolete snapshot ranges --- ...rMergeAllInProvide_fix.from231to245.output | 40 ------------------- ...rMergeAllInProvide_fix.from339to353.output | 40 ------------------- ...rMergeAllInProvide_fix.from460to474.output | 40 ------------------- ...rMergeAllInProvide_fix.from562to576.output | 40 ------------------- ...rMergeAllInProvide_fix.from675to689.output | 40 ------------------- 5 files changed, 200 deletions(-) delete mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output delete mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from339to353.output delete mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from460to474.output delete mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from562to576.output delete mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from675to689.output diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output deleted file mode 100644 index 8ae2dd5a..00000000 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from231to245.output +++ /dev/null @@ -1,40 +0,0 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 231 - 245 -import { Effect, Layer } from "effect" - -const first = Layer.empty -const second = Layer.empty -const third = Layer.empty -const layers = [first, second] as const - -export const shouldReportPipeable = Effect.void.pipe( - Effect.provide([first, second]) -) - -export const shouldReportWithOptions = Effect.void.pipe( - Effect.provide(Layer.mergeAll(first, second), { local: true }) -) - -export const shouldReportDataFirst = Effect.provide( - Effect.void, - Layer.mergeAll(first, second) -) - -export const shouldReportSpread = Effect.void.pipe( - Effect.provide(Layer.mergeAll(...[first, second])) -) - -export const shouldReportTupleSpread = Effect.void.pipe( - Effect.provide(Layer.mergeAll(...layers)) -) - -export const shouldNotReportTransformed = Effect.void.pipe( - Effect.provide( - Layer.mergeAll(first, second).pipe(Layer.provide(third)) - ) -) - -const combined = Layer.mergeAll(first, second) - -export const shouldNotReportPrecomposed = Effect.void.pipe( - Effect.provide(combined) -) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from339to353.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from339to353.output deleted file mode 100644 index 91f5a9fa..00000000 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from339to353.output +++ /dev/null @@ -1,40 +0,0 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 339 - 353 -import { Effect, Layer } from "effect" - -const first = Layer.empty -const second = Layer.empty -const third = Layer.empty -const layers = [first, second] as const - -export const shouldReportPipeable = Effect.void.pipe( - Effect.provide(Layer.mergeAll(first, second)) -) - -export const shouldReportWithOptions = Effect.void.pipe( - Effect.provide([first, second], { local: true }) -) - -export const shouldReportDataFirst = Effect.provide( - Effect.void, - Layer.mergeAll(first, second) -) - -export const shouldReportSpread = Effect.void.pipe( - Effect.provide(Layer.mergeAll(...[first, second])) -) - -export const shouldReportTupleSpread = Effect.void.pipe( - Effect.provide(Layer.mergeAll(...layers)) -) - -export const shouldNotReportTransformed = Effect.void.pipe( - Effect.provide( - Layer.mergeAll(first, second).pipe(Layer.provide(third)) - ) -) - -const combined = Layer.mergeAll(first, second) - -export const shouldNotReportPrecomposed = Effect.void.pipe( - Effect.provide(combined) -) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from460to474.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from460to474.output deleted file mode 100644 index 8f9022a4..00000000 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from460to474.output +++ /dev/null @@ -1,40 +0,0 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 460 - 474 -import { Effect, Layer } from "effect" - -const first = Layer.empty -const second = Layer.empty -const third = Layer.empty -const layers = [first, second] as const - -export const shouldReportPipeable = Effect.void.pipe( - Effect.provide(Layer.mergeAll(first, second)) -) - -export const shouldReportWithOptions = Effect.void.pipe( - Effect.provide(Layer.mergeAll(first, second), { local: true }) -) - -export const shouldReportDataFirst = Effect.provide( - Effect.void, - [first, second] -) - -export const shouldReportSpread = Effect.void.pipe( - Effect.provide(Layer.mergeAll(...[first, second])) -) - -export const shouldReportTupleSpread = Effect.void.pipe( - Effect.provide(Layer.mergeAll(...layers)) -) - -export const shouldNotReportTransformed = Effect.void.pipe( - Effect.provide( - Layer.mergeAll(first, second).pipe(Layer.provide(third)) - ) -) - -const combined = Layer.mergeAll(first, second) - -export const shouldNotReportPrecomposed = Effect.void.pipe( - Effect.provide(combined) -) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from562to576.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from562to576.output deleted file mode 100644 index b5116f5a..00000000 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from562to576.output +++ /dev/null @@ -1,40 +0,0 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 562 - 576 -import { Effect, Layer } from "effect" - -const first = Layer.empty -const second = Layer.empty -const third = Layer.empty -const layers = [first, second] as const - -export const shouldReportPipeable = Effect.void.pipe( - Effect.provide(Layer.mergeAll(first, second)) -) - -export const shouldReportWithOptions = Effect.void.pipe( - Effect.provide(Layer.mergeAll(first, second), { local: true }) -) - -export const shouldReportDataFirst = Effect.provide( - Effect.void, - Layer.mergeAll(first, second) -) - -export const shouldReportSpread = Effect.void.pipe( - Effect.provide([first, second]) -) - -export const shouldReportTupleSpread = Effect.void.pipe( - Effect.provide(Layer.mergeAll(...layers)) -) - -export const shouldNotReportTransformed = Effect.void.pipe( - Effect.provide( - Layer.mergeAll(first, second).pipe(Layer.provide(third)) - ) -) - -const combined = Layer.mergeAll(first, second) - -export const shouldNotReportPrecomposed = Effect.void.pipe( - Effect.provide(combined) -) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from675to689.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from675to689.output deleted file mode 100644 index 2cd2b666..00000000 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from675to689.output +++ /dev/null @@ -1,40 +0,0 @@ -// code fix redundantLayerMergeAllInProvide_fix output for range 675 - 689 -import { Effect, Layer } from "effect" - -const first = Layer.empty -const second = Layer.empty -const third = Layer.empty -const layers = [first, second] as const - -export const shouldReportPipeable = Effect.void.pipe( - Effect.provide(Layer.mergeAll(first, second)) -) - -export const shouldReportWithOptions = Effect.void.pipe( - Effect.provide(Layer.mergeAll(first, second), { local: true }) -) - -export const shouldReportDataFirst = Effect.provide( - Effect.void, - Layer.mergeAll(first, second) -) - -export const shouldReportSpread = Effect.void.pipe( - Effect.provide(Layer.mergeAll(...[first, second])) -) - -export const shouldReportTupleSpread = Effect.void.pipe( - Effect.provide(layers) -) - -export const shouldNotReportTransformed = Effect.void.pipe( - Effect.provide( - Layer.mergeAll(first, second).pipe(Layer.provide(third)) - ) -) - -const combined = Layer.mergeAll(first, second) - -export const shouldNotReportPrecomposed = Effect.void.pipe( - Effect.provide(combined) -) From 280fbc70a215fa0c232d351fbc7a6846fd40a12c Mon Sep 17 00:00:00 2001 From: Tyler Gibbs Date: Fri, 24 Jul 2026 13:40:44 -0500 Subject: [PATCH 6/7] fix multiple provide detection for layer arrays --- .../multipleEffectProvide.ts.codefixes | 5 +- ...tipleEffectProvide_fix.from430to464.output | 9 +++ ...tipleEffectProvide_fix.from564to598.output | 9 +++ ...tipleEffectProvide_fix.from663to697.output | 9 +++ ...tipleEffectProvide_fix.from793to827.output | 9 +++ ...ipleEffectProvide_fix.from972to1028.output | 42 ++++++++++++++ .../multipleEffectProvide.ts.output | 5 +- .../diagnostics/multipleEffectProvide.ts | 9 +++ .../multipleEffectProvide.ts.codefixes | 5 +- ...pleEffectProvide_fix.from1339to1373.output | 9 +++ ...pleEffectProvide_fix.from1480to1536.output | 58 +++++++++++++++++++ ...tipleEffectProvide_fix.from592to626.output | 9 +++ ...tipleEffectProvide_fix.from726to760.output | 9 +++ ...tipleEffectProvide_fix.from819to853.output | 9 +++ ...tipleEffectProvide_fix.from949to983.output | 9 +++ .../multipleEffectProvide.ts.output | 5 +- .../diagnostics/multipleEffectProvide.ts | 9 +++ .../src/diagnostics/multipleEffectProvide.ts | 38 ++++++++---- 18 files changed, 241 insertions(+), 16 deletions(-) create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from972to1028.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1480to1536.output diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes index f1590cb2..b052d8c3 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes @@ -9,4 +9,7 @@ multipleEffectProvide_skipNextLine from 663 to 697 multipleEffectProvide_skipFile from 663 to 697 multipleEffectProvide_fix from 793 to 827 multipleEffectProvide_skipNextLine from 793 to 827 -multipleEffectProvide_skipFile from 793 to 827 \ No newline at end of file +multipleEffectProvide_skipFile from 793 to 827 +multipleEffectProvide_fix from 972 to 1028 +multipleEffectProvide_skipNextLine from 972 to 1028 +multipleEffectProvide_skipFile from 972 to 1028 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from430to464.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from430to464.output index e213e916..af0f31b5 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from430to464.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from430to464.output @@ -31,3 +31,12 @@ export const shouldReportSingle = Effect.void.pipe( Effect.provide(MyService2.Default), Effect.provide(MyService3.Default) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from564to598.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from564to598.output index ca40b355..48dc9f0f 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from564to598.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from564to598.output @@ -31,3 +31,12 @@ export const shouldReportSingle = Effect.void.pipe( Effect.provide(MyService2.Default), Effect.provide(MyService3.Default) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from663to697.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from663to697.output index 2d0b3497..068128cc 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from663to697.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from663to697.output @@ -31,3 +31,12 @@ export const shouldReportSingle = Effect.void.pipe( Effect.provide(MyService2.Default), Effect.provide(MyService3.Default) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from793to827.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from793to827.output index a054ba3d..6545230b 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from793to827.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from793to827.output @@ -30,3 +30,12 @@ export const shouldReportSeparately = Effect.void.pipe( export const shouldReportSingle = Effect.void.pipe( Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default, MyService3.Default)) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from972to1028.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from972to1028.output new file mode 100644 index 00000000..f94af31f --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from972to1028.output @@ -0,0 +1,42 @@ +// code fix multipleEffectProvide_fix output for range 972 - 1028 +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" + +class MyService1 extends Effect.Service()("MyService1", { + succeed: { value: 1 } +}) {} + +class MyService2 extends Effect.Service()("MyService2", { + succeed: { value: 2 } +}) {} + +class MyService3 extends Effect.Service()("MyService3", { + succeed: { value: 3 } +}) {} + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignoreLogged, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default, MyService3.Default)) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.output index 6d8a28b4..b7c75f35 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.output @@ -8,4 +8,7 @@ Effect.provide(MyService1.Default) 25:2 - 25:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) Effect.provide(MyService1.Default) -30:2 - 30:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) \ No newline at end of file +30:2 - 30:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) + +Effect.provide([MyService1.Default, MyService2.Default]) +36:2 - 36:58 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) \ No newline at end of file diff --git a/packages/harness-effect-v3/examples/diagnostics/multipleEffectProvide.ts b/packages/harness-effect-v3/examples/diagnostics/multipleEffectProvide.ts index e2d10fbf..bc4ddcf5 100644 --- a/packages/harness-effect-v3/examples/diagnostics/multipleEffectProvide.ts +++ b/packages/harness-effect-v3/examples/diagnostics/multipleEffectProvide.ts @@ -31,3 +31,12 @@ export const shouldReportSingle = Effect.void.pipe( Effect.provide(MyService2.Default), Effect.provide(MyService3.Default) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes index 2cb4e6bb..99c9920b 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes @@ -12,4 +12,7 @@ multipleEffectProvide_skipNextLine from 949 to 983 multipleEffectProvide_skipFile from 949 to 983 multipleEffectProvide_fix from 1339 to 1373 multipleEffectProvide_skipNextLine from 1339 to 1373 -multipleEffectProvide_skipFile from 1339 to 1373 \ No newline at end of file +multipleEffectProvide_skipFile from 1339 to 1373 +multipleEffectProvide_fix from 1480 to 1536 +multipleEffectProvide_skipNextLine from 1480 to 1536 +multipleEffectProvide_skipFile from 1480 to 1536 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1339to1373.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1339to1373.output index 0459591a..8e045a2a 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1339to1373.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1339to1373.output @@ -47,3 +47,12 @@ export const shouldReportAfterLocalProvide = Effect.void.pipe( Effect.provide(MyService1.Default, { local: true }), Effect.provide(Layer.mergeAll(MyService2.Default, MyService3.Default)) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1480to1536.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1480to1536.output new file mode 100644 index 00000000..ae46b590 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1480to1536.output @@ -0,0 +1,58 @@ +// code fix multipleEffectProvide_fix output for range 1480 - 1536 +import { Context, Effect, Layer } from "effect" + +class MyService1 extends Context.Service()("MyService1", { + make: Effect.succeed({ value: 1 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService2 extends Context.Service()("MyService2", { + make: Effect.succeed({ value: 2 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService3 extends Context.Service()("MyService3", { + make: Effect.succeed({ value: 3 }) +}) { + static Default = Layer.effect(this, this.make) +} + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignore, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default, { local: true }) +) + +export const shouldReportAfterLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default, { local: true }), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default, MyService3.Default)) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from592to626.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from592to626.output index 28d35e40..0ca38f8b 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from592to626.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from592to626.output @@ -47,3 +47,12 @@ export const shouldReportAfterLocalProvide = Effect.void.pipe( Effect.provide(MyService2.Default), Effect.provide(MyService3.Default) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from726to760.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from726to760.output index dbefcace..1110f9a0 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from726to760.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from726to760.output @@ -47,3 +47,12 @@ export const shouldReportAfterLocalProvide = Effect.void.pipe( Effect.provide(MyService2.Default), Effect.provide(MyService3.Default) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from819to853.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from819to853.output index feef817a..611627eb 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from819to853.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from819to853.output @@ -47,3 +47,12 @@ export const shouldReportAfterLocalProvide = Effect.void.pipe( Effect.provide(MyService2.Default), Effect.provide(MyService3.Default) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from949to983.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from949to983.output index fa879812..d768cc21 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from949to983.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from949to983.output @@ -46,3 +46,12 @@ export const shouldReportAfterLocalProvide = Effect.void.pipe( Effect.provide(MyService2.Default), Effect.provide(MyService3.Default) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.output index c1b2c826..99a7adb6 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.output @@ -11,4 +11,7 @@ Effect.provide(MyService1.Default) 35:2 - 35:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) Effect.provide(MyService2.Default) -47:2 - 47:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) \ No newline at end of file +47:2 - 47:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) + +Effect.provide([MyService1.Default, MyService2.Default]) +52:2 - 52:58 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) \ No newline at end of file diff --git a/packages/harness-effect-v4/examples/diagnostics/multipleEffectProvide.ts b/packages/harness-effect-v4/examples/diagnostics/multipleEffectProvide.ts index 76a00ff3..6cd19bcf 100644 --- a/packages/harness-effect-v4/examples/diagnostics/multipleEffectProvide.ts +++ b/packages/harness-effect-v4/examples/diagnostics/multipleEffectProvide.ts @@ -47,3 +47,12 @@ export const shouldReportAfterLocalProvide = Effect.void.pipe( Effect.provide(MyService2.Default), Effect.provide(MyService3.Default) ) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) diff --git a/packages/language-service/src/diagnostics/multipleEffectProvide.ts b/packages/language-service/src/diagnostics/multipleEffectProvide.ts index 26855b6e..c6197cc6 100644 --- a/packages/language-service/src/diagnostics/multipleEffectProvide.ts +++ b/packages/language-service/src/diagnostics/multipleEffectProvide.ts @@ -40,7 +40,12 @@ export const multipleEffectProvide = LSP.createDiagnostic({ for (const flow of flows) { let currentChunk = 0 - const previousLayers: Array> = [[]] + const previousLayers: Array< + Array<{ + layers: ReadonlyArray + node: ts.CallExpression + }> + > = [[]] // Look for consecutive Effect.provide transformations in the flow for (const transformation of flow.transformations) { @@ -77,19 +82,28 @@ export const multipleEffectProvide = LSP.createDiagnostic({ } const layer = transformation.args[0] - const type = typeCheckerUtils.getTypeAtLocation(layer) + const layers = ts.isArrayLiteralExpression(layer) + ? Array.from(layer.elements) + : [layer] const node = ts.findAncestor(transformation.callee, ts.isCallExpression) - // Check if the argument is a Layer type and we found the call expression - const isLayerType = type - ? yield* pipe( - typeParser.layerType(type, layer), - Nano.option - ) - : Option.none() + let allLayers = layers.length > 0 + for (const candidate of layers) { + const type = typeCheckerUtils.getTypeAtLocation(candidate) + const isLayerType = type + ? yield* pipe( + typeParser.layerType(type, candidate), + Nano.option + ) + : Option.none() + if (Option.isNone(isLayerType)) { + allLayers = false + break + } + } - if (Option.isSome(isLayerType) && node) { - previousLayers[currentChunk].push({ layer, node }) + if (allLayers && node) { + previousLayers[currentChunk].push({ layers, node }) } else { // Not a layer, breaks the chain currentChunk++ @@ -130,7 +144,7 @@ export const multipleEffectProvide = LSP.createDiagnostic({ ts.factory.createIdentifier("mergeAll") ), undefined, - chunk.map((c) => c.layer) + chunk.flatMap((c) => c.layers) )] ) changeTracker.insertNodeAt(sourceFile, ts.getTokenPosOfNode(chunk[0].node, sourceFile), newNode) From 19f3d37d043a6c5cd5fac7cfb43a18bb268c04d4 Mon Sep 17 00:00:00 2001 From: Tyler Gibbs Date: Fri, 24 Jul 2026 15:28:04 -0500 Subject: [PATCH 7/7] fix provide diagnostics for layer tuples --- .../multipleEffectProvide.ts.codefixes | 33 +++++---- ...pleEffectProvide_fix.from1095to1151.output | 56 +++++++++++++++ ...pleEffectProvide_fix.from1380to1406.output | 56 +++++++++++++++ ...tipleEffectProvide_fix.from553to587.output | 56 +++++++++++++++ ...tipleEffectProvide_fix.from687to721.output | 56 +++++++++++++++ ...tipleEffectProvide_fix.from786to820.output | 56 +++++++++++++++ ...tipleEffectProvide_fix.from916to950.output | 55 ++++++++++++++ .../multipleEffectProvide.ts.output | 13 ++-- ...rMergeAllInProvide_fix.from550to564.output | 2 +- .../strictEffectProvide.ts.codefixes | 18 ++--- .../diagnostics/strictEffectProvide.ts.output | 11 +-- .../diagnostics/multipleEffectProvide.ts | 14 ++++ .../diagnostics/strictEffectProvide.ts | 10 +++ .../multipleEffectProvide.ts.codefixes | 39 +++++----- ...pleEffectProvide_fix.from1072to1106.output | 71 ++++++++++++++++++ ...pleEffectProvide_fix.from1462to1496.output | 72 +++++++++++++++++++ ...pleEffectProvide_fix.from1603to1659.output | 72 +++++++++++++++++++ ...pleEffectProvide_fix.from1888to1914.output | 72 +++++++++++++++++++ ...tipleEffectProvide_fix.from715to749.output | 72 +++++++++++++++++++ ...tipleEffectProvide_fix.from849to883.output | 72 +++++++++++++++++++ ...tipleEffectProvide_fix.from942to976.output | 72 +++++++++++++++++++ .../multipleEffectProvide.ts.output | 15 ++-- ...rMergeAllInProvide_fix.from684to698.output | 2 +- .../strictEffectProvide.ts.codefixes | 18 ++--- .../diagnostics/strictEffectProvide.ts.output | 11 +-- .../diagnostics/multipleEffectProvide.ts | 14 ++++ .../diagnostics/strictEffectProvide.ts | 10 +++ .../diagnostics/effectProvideLayerArgument.ts | 58 +++++++++++++++ .../src/diagnostics/multipleEffectProvide.ts | 29 ++------ .../redundantLayerMergeAllInProvide.ts | 51 ++++++++++--- .../src/diagnostics/strictEffectProvide.ts | 9 +-- 31 files changed, 1087 insertions(+), 108 deletions(-) create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1095to1151.output create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1380to1406.output create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from553to587.output create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from687to721.output create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from786to820.output create mode 100644 packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from916to950.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1072to1106.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1462to1496.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1603to1659.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1888to1914.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from715to749.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from849to883.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from942to976.output create mode 100644 packages/language-service/src/diagnostics/effectProvideLayerArgument.ts diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes index b052d8c3..aaba74bd 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes @@ -1,15 +1,18 @@ -multipleEffectProvide_fix from 430 to 464 -multipleEffectProvide_skipNextLine from 430 to 464 -multipleEffectProvide_skipFile from 430 to 464 -multipleEffectProvide_fix from 564 to 598 -multipleEffectProvide_skipNextLine from 564 to 598 -multipleEffectProvide_skipFile from 564 to 598 -multipleEffectProvide_fix from 663 to 697 -multipleEffectProvide_skipNextLine from 663 to 697 -multipleEffectProvide_skipFile from 663 to 697 -multipleEffectProvide_fix from 793 to 827 -multipleEffectProvide_skipNextLine from 793 to 827 -multipleEffectProvide_skipFile from 793 to 827 -multipleEffectProvide_fix from 972 to 1028 -multipleEffectProvide_skipNextLine from 972 to 1028 -multipleEffectProvide_skipFile from 972 to 1028 \ No newline at end of file +multipleEffectProvide_fix from 553 to 587 +multipleEffectProvide_skipNextLine from 553 to 587 +multipleEffectProvide_skipFile from 553 to 587 +multipleEffectProvide_fix from 687 to 721 +multipleEffectProvide_skipNextLine from 687 to 721 +multipleEffectProvide_skipFile from 687 to 721 +multipleEffectProvide_fix from 786 to 820 +multipleEffectProvide_skipNextLine from 786 to 820 +multipleEffectProvide_skipFile from 786 to 820 +multipleEffectProvide_fix from 916 to 950 +multipleEffectProvide_skipNextLine from 916 to 950 +multipleEffectProvide_skipFile from 916 to 950 +multipleEffectProvide_fix from 1095 to 1151 +multipleEffectProvide_skipNextLine from 1095 to 1151 +multipleEffectProvide_skipFile from 1095 to 1151 +multipleEffectProvide_fix from 1380 to 1406 +multipleEffectProvide_skipNextLine from 1380 to 1406 +multipleEffectProvide_skipFile from 1380 to 1406 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1095to1151.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1095to1151.output new file mode 100644 index 00000000..12ae9545 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1095to1151.output @@ -0,0 +1,56 @@ +// code fix multipleEffectProvide_fix output for range 1095 - 1151 +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" + +class MyService1 extends Effect.Service()("MyService1", { + succeed: { value: 1 } +}) {} + +class MyService2 extends Effect.Service()("MyService2", { + succeed: { value: 2 } +}) {} + +class MyService3 extends Effect.Service()("MyService3", { + succeed: { value: 3 } +}) {} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignoreLogged, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default, MyService3.Default)) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1380to1406.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1380to1406.output new file mode 100644 index 00000000..4b7ac4bd --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1380to1406.output @@ -0,0 +1,56 @@ +// code fix multipleEffectProvide_fix output for range 1380 - 1406 +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" + +class MyService1 extends Effect.Service()("MyService1", { + succeed: { value: 1 } +}) {} + +class MyService2 extends Effect.Service()("MyService2", { + succeed: { value: 2 } +}) {} + +class MyService3 extends Effect.Service()("MyService3", { + succeed: { value: 3 } +}) {} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignoreLogged, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layerTuple, MyService3.Default)) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from553to587.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from553to587.output new file mode 100644 index 00000000..d16d4c70 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from553to587.output @@ -0,0 +1,56 @@ +// code fix multipleEffectProvide_fix output for range 553 - 587 +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" + +class MyService1 extends Effect.Service()("MyService1", { + succeed: { value: 1 } +}) {} + +class MyService2 extends Effect.Service()("MyService2", { + succeed: { value: 2 } +}) {} + +class MyService3 extends Effect.Service()("MyService3", { + succeed: { value: 3 } +}) {} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default)) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignoreLogged, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from687to721.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from687to721.output new file mode 100644 index 00000000..67aeb4b4 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from687to721.output @@ -0,0 +1,56 @@ +// code fix multipleEffectProvide_fix output for range 687 - 721 +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" + +class MyService1 extends Effect.Service()("MyService1", { + succeed: { value: 1 } +}) {} + +class MyService2 extends Effect.Service()("MyService2", { + succeed: { value: 2 } +}) {} + +class MyService3 extends Effect.Service()("MyService3", { + succeed: { value: 3 } +}) {} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default)), + Effect.ignoreLogged, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from786to820.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from786to820.output new file mode 100644 index 00000000..3632e268 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from786to820.output @@ -0,0 +1,56 @@ +// code fix multipleEffectProvide_fix output for range 786 - 820 +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" + +class MyService1 extends Effect.Service()("MyService1", { + succeed: { value: 1 } +}) {} + +class MyService2 extends Effect.Service()("MyService2", { + succeed: { value: 2 } +}) {} + +class MyService3 extends Effect.Service()("MyService3", { + succeed: { value: 3 } +}) {} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignoreLogged, + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default)) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from916to950.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from916to950.output new file mode 100644 index 00000000..c3c94520 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from916to950.output @@ -0,0 +1,55 @@ +// code fix multipleEffectProvide_fix output for range 916 - 950 +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" + +class MyService1 extends Effect.Service()("MyService1", { + succeed: { value: 1 } +}) {} + +class MyService2 extends Effect.Service()("MyService2", { + succeed: { value: 2 } +}) {} + +class MyService3 extends Effect.Service()("MyService3", { + succeed: { value: 3 } +}) {} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignoreLogged, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default, MyService3.Default)) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.output index b7c75f35..1efc1c07 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/multipleEffectProvide.ts.output @@ -1,14 +1,17 @@ -Effect.provide(MyService1.Default) -17:2 - 17:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) - Effect.provide(MyService1.Default) 22:2 - 22:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) Effect.provide(MyService1.Default) -25:2 - 25:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) +27:2 - 27:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) Effect.provide(MyService1.Default) 30:2 - 30:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) +Effect.provide(MyService1.Default) +35:2 - 35:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) + Effect.provide([MyService1.Default, MyService2.Default]) -36:2 - 36:58 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) \ No newline at end of file +41:2 - 41:58 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) + +Effect.provide(layerTuple) +50:2 - 50:28 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output index 5871c628..ff16080c 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from550to564.output @@ -20,7 +20,7 @@ export const shouldReportSpread = Effect.void.pipe( ) export const shouldReportTupleSpread = Effect.void.pipe( - Effect.provide(layers) + Effect.provide([layers[0], layers[1]]) ) export const shouldNotReportTransformed = Effect.void.pipe( diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/strictEffectProvide.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/strictEffectProvide.ts.codefixes index 598a2a25..e6f9971f 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/strictEffectProvide.ts.codefixes +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/strictEffectProvide.ts.codefixes @@ -1,8 +1,10 @@ -strictEffectProvide_skipNextLine from 569 to 616 -strictEffectProvide_skipFile from 569 to 616 -strictEffectProvide_skipNextLine from 427 to 461 -strictEffectProvide_skipFile from 427 to 461 -strictEffectProvide_skipNextLine from 735 to 769 -strictEffectProvide_skipFile from 735 to 769 -strictEffectProvide_skipNextLine from 893 to 963 -strictEffectProvide_skipFile from 893 to 963 \ No newline at end of file +strictEffectProvide_skipNextLine from 692 to 739 +strictEffectProvide_skipFile from 692 to 739 +strictEffectProvide_skipNextLine from 550 to 584 +strictEffectProvide_skipFile from 550 to 584 +strictEffectProvide_skipNextLine from 858 to 892 +strictEffectProvide_skipFile from 858 to 892 +strictEffectProvide_skipNextLine from 1016 to 1086 +strictEffectProvide_skipFile from 1016 to 1086 +strictEffectProvide_skipNextLine from 1223 to 1249 +strictEffectProvide_skipFile from 1223 to 1249 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/strictEffectProvide.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/strictEffectProvide.ts.output index b596f039..030bd078 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/strictEffectProvide.ts.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/strictEffectProvide.ts.output @@ -1,11 +1,14 @@ Effect.provide(MyService1.Default) -15:2 - 15:36 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) +20:2 - 20:36 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) Effect.provide(Effect.void, MyService1.Default) -19:29 - 19:76 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) +24:29 - 24:76 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) Effect.provide(MyService1.Default) -24:2 - 24:36 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) +29:2 - 29:36 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default)) -30:2 - 30:72 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) \ No newline at end of file +35:2 - 35:72 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) + +Effect.provide(layerTuple) +40:2 - 40:28 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) \ No newline at end of file diff --git a/packages/harness-effect-v3/examples/diagnostics/multipleEffectProvide.ts b/packages/harness-effect-v3/examples/diagnostics/multipleEffectProvide.ts index bc4ddcf5..e1432094 100644 --- a/packages/harness-effect-v3/examples/diagnostics/multipleEffectProvide.ts +++ b/packages/harness-effect-v3/examples/diagnostics/multipleEffectProvide.ts @@ -13,6 +13,11 @@ class MyService3 extends Effect.Service()("MyService3", { succeed: { value: 3 } }) {} +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + export const shouldReport = Effect.void.pipe( Effect.provide(MyService1.Default), Effect.provide(MyService2.Default) @@ -40,3 +45,12 @@ export const shouldReportAfterArrayRewrite = Effect.void.pipe( export const shouldNotReportSingleArray = Effect.void.pipe( Effect.provide([MyService1.Default, MyService2.Default]) ) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v3/examples/diagnostics/strictEffectProvide.ts b/packages/harness-effect-v3/examples/diagnostics/strictEffectProvide.ts index a087c127..997b509d 100644 --- a/packages/harness-effect-v3/examples/diagnostics/strictEffectProvide.ts +++ b/packages/harness-effect-v3/examples/diagnostics/strictEffectProvide.ts @@ -10,6 +10,11 @@ class MyService2 extends Effect.Service()("MyService2", { succeed: { value: 2 } }) {} +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + // Should report: Effect.provide with a Layer export const shouldReport1 = Effect.void.pipe( Effect.provide(MyService1.Default) @@ -30,6 +35,11 @@ export const shouldReport4 = Effect.void.pipe( Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default)) ) +// Should report: non-empty tuples are Layer collections accepted by Effect.provide +export const shouldReport5 = Effect.void.pipe( + Effect.provide(layerTuple) +) + // Should NOT report: providing a plain service (not a layer) export const shouldNotReport1 = Effect.void.pipe( Effect.provideService(MyService1, new MyService1({ value: 1 })) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes index 99c9920b..45f9cbc0 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.codefixes @@ -1,18 +1,21 @@ -multipleEffectProvide_fix from 592 to 626 -multipleEffectProvide_skipNextLine from 592 to 626 -multipleEffectProvide_skipFile from 592 to 626 -multipleEffectProvide_fix from 726 to 760 -multipleEffectProvide_skipNextLine from 726 to 760 -multipleEffectProvide_skipFile from 726 to 760 -multipleEffectProvide_fix from 819 to 853 -multipleEffectProvide_skipNextLine from 819 to 853 -multipleEffectProvide_skipFile from 819 to 853 -multipleEffectProvide_fix from 949 to 983 -multipleEffectProvide_skipNextLine from 949 to 983 -multipleEffectProvide_skipFile from 949 to 983 -multipleEffectProvide_fix from 1339 to 1373 -multipleEffectProvide_skipNextLine from 1339 to 1373 -multipleEffectProvide_skipFile from 1339 to 1373 -multipleEffectProvide_fix from 1480 to 1536 -multipleEffectProvide_skipNextLine from 1480 to 1536 -multipleEffectProvide_skipFile from 1480 to 1536 \ No newline at end of file +multipleEffectProvide_fix from 715 to 749 +multipleEffectProvide_skipNextLine from 715 to 749 +multipleEffectProvide_skipFile from 715 to 749 +multipleEffectProvide_fix from 849 to 883 +multipleEffectProvide_skipNextLine from 849 to 883 +multipleEffectProvide_skipFile from 849 to 883 +multipleEffectProvide_fix from 942 to 976 +multipleEffectProvide_skipNextLine from 942 to 976 +multipleEffectProvide_skipFile from 942 to 976 +multipleEffectProvide_fix from 1072 to 1106 +multipleEffectProvide_skipNextLine from 1072 to 1106 +multipleEffectProvide_skipFile from 1072 to 1106 +multipleEffectProvide_fix from 1462 to 1496 +multipleEffectProvide_skipNextLine from 1462 to 1496 +multipleEffectProvide_skipFile from 1462 to 1496 +multipleEffectProvide_fix from 1603 to 1659 +multipleEffectProvide_skipNextLine from 1603 to 1659 +multipleEffectProvide_skipFile from 1603 to 1659 +multipleEffectProvide_fix from 1888 to 1914 +multipleEffectProvide_skipNextLine from 1888 to 1914 +multipleEffectProvide_skipFile from 1888 to 1914 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1072to1106.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1072to1106.output new file mode 100644 index 00000000..da3db514 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1072to1106.output @@ -0,0 +1,71 @@ +// code fix multipleEffectProvide_fix output for range 1072 - 1106 +import { Context, Effect, Layer } from "effect" + +class MyService1 extends Context.Service()("MyService1", { + make: Effect.succeed({ value: 1 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService2 extends Context.Service()("MyService2", { + make: Effect.succeed({ value: 2 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService3 extends Context.Service()("MyService3", { + make: Effect.succeed({ value: 3 }) +}) { + static Default = Layer.effect(this, this.make) +} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignore, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default, MyService3.Default)) +) + +export const shouldNotReportLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default, { local: true }) +) + +export const shouldReportAfterLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default, { local: true }), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1462to1496.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1462to1496.output new file mode 100644 index 00000000..9bd5fb72 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1462to1496.output @@ -0,0 +1,72 @@ +// code fix multipleEffectProvide_fix output for range 1462 - 1496 +import { Context, Effect, Layer } from "effect" + +class MyService1 extends Context.Service()("MyService1", { + make: Effect.succeed({ value: 1 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService2 extends Context.Service()("MyService2", { + make: Effect.succeed({ value: 2 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService3 extends Context.Service()("MyService3", { + make: Effect.succeed({ value: 3 }) +}) { + static Default = Layer.effect(this, this.make) +} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignore, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default, { local: true }) +) + +export const shouldReportAfterLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default, { local: true }), + Effect.provide(Layer.mergeAll(MyService2.Default, MyService3.Default)) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1603to1659.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1603to1659.output new file mode 100644 index 00000000..73f51aa5 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1603to1659.output @@ -0,0 +1,72 @@ +// code fix multipleEffectProvide_fix output for range 1603 - 1659 +import { Context, Effect, Layer } from "effect" + +class MyService1 extends Context.Service()("MyService1", { + make: Effect.succeed({ value: 1 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService2 extends Context.Service()("MyService2", { + make: Effect.succeed({ value: 2 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService3 extends Context.Service()("MyService3", { + make: Effect.succeed({ value: 3 }) +}) { + static Default = Layer.effect(this, this.make) +} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignore, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default, { local: true }) +) + +export const shouldReportAfterLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default, { local: true }), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default, MyService3.Default)) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1888to1914.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1888to1914.output new file mode 100644 index 00000000..bd78b24b --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from1888to1914.output @@ -0,0 +1,72 @@ +// code fix multipleEffectProvide_fix output for range 1888 - 1914 +import { Context, Effect, Layer } from "effect" + +class MyService1 extends Context.Service()("MyService1", { + make: Effect.succeed({ value: 1 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService2 extends Context.Service()("MyService2", { + make: Effect.succeed({ value: 2 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService3 extends Context.Service()("MyService3", { + make: Effect.succeed({ value: 3 }) +}) { + static Default = Layer.effect(this, this.make) +} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignore, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default, { local: true }) +) + +export const shouldReportAfterLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default, { local: true }), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(Layer.mergeAll(...layerTuple, MyService3.Default)) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from715to749.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from715to749.output new file mode 100644 index 00000000..9cccb1e5 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from715to749.output @@ -0,0 +1,72 @@ +// code fix multipleEffectProvide_fix output for range 715 - 749 +import { Context, Effect, Layer } from "effect" + +class MyService1 extends Context.Service()("MyService1", { + make: Effect.succeed({ value: 1 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService2 extends Context.Service()("MyService2", { + make: Effect.succeed({ value: 2 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService3 extends Context.Service()("MyService3", { + make: Effect.succeed({ value: 3 }) +}) { + static Default = Layer.effect(this, this.make) +} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default)) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignore, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default, { local: true }) +) + +export const shouldReportAfterLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default, { local: true }), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from849to883.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from849to883.output new file mode 100644 index 00000000..53c02baf --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from849to883.output @@ -0,0 +1,72 @@ +// code fix multipleEffectProvide_fix output for range 849 - 883 +import { Context, Effect, Layer } from "effect" + +class MyService1 extends Context.Service()("MyService1", { + make: Effect.succeed({ value: 1 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService2 extends Context.Service()("MyService2", { + make: Effect.succeed({ value: 2 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService3 extends Context.Service()("MyService3", { + make: Effect.succeed({ value: 3 }) +}) { + static Default = Layer.effect(this, this.make) +} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default)), + Effect.ignore, + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default, { local: true }) +) + +export const shouldReportAfterLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default, { local: true }), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from942to976.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from942to976.output new file mode 100644 index 00000000..cff0623b --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.multipleEffectProvide_fix.from942to976.output @@ -0,0 +1,72 @@ +// code fix multipleEffectProvide_fix output for range 942 - 976 +import { Context, Effect, Layer } from "effect" + +class MyService1 extends Context.Service()("MyService1", { + make: Effect.succeed({ value: 1 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService2 extends Context.Service()("MyService2", { + make: Effect.succeed({ value: 2 }) +}) { + static Default = Layer.effect(this, this.make) +} + +class MyService3 extends Context.Service()("MyService3", { + make: Effect.succeed({ value: 3 }) +}) { + static Default = Layer.effect(this, this.make) +} + +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + +export const shouldReport = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default) +) + +export const shouldReportSeparately = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.ignore, + Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default)) +) + +export const shouldReportSingle = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default), + Effect.provide(MyService2.Default, { local: true }) +) + +export const shouldReportAfterLocalProvide = Effect.void.pipe( + Effect.provide(MyService1.Default, { local: true }), + Effect.provide(MyService2.Default), + Effect.provide(MyService3.Default) +) + +export const shouldReportAfterArrayRewrite = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleArray = Effect.void.pipe( + Effect.provide([MyService1.Default, MyService2.Default]) +) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.output index 99a7adb6..d0393aee 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/multipleEffectProvide.ts.output @@ -1,17 +1,20 @@ -Effect.provide(MyService1.Default) -22:2 - 22:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) - Effect.provide(MyService1.Default) 27:2 - 27:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) Effect.provide(MyService1.Default) -30:2 - 30:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) +32:2 - 32:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) Effect.provide(MyService1.Default) 35:2 - 35:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) +Effect.provide(MyService1.Default) +40:2 - 40:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) + Effect.provide(MyService2.Default) -47:2 - 47:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) +52:2 - 52:36 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) Effect.provide([MyService1.Default, MyService2.Default]) -52:2 - 52:58 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) \ No newline at end of file +57:2 - 57:58 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) + +Effect.provide(layerTuple) +66:2 - 66:28 | 0 | This expression chains multiple `Effect.provide` calls. Providing Layers in multiple calls in a chain can break service lifecycle behavior compared with a single combined provide with merged layers. effect(multipleEffectProvide) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from684to698.output b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from684to698.output index e59e8c75..f52053cf 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from684to698.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/redundantLayerMergeAllInProvide.ts.redundantLayerMergeAllInProvide_fix.from684to698.output @@ -24,7 +24,7 @@ export const shouldReportSpread = Effect.void.pipe( ) export const shouldReportTupleSpread = Effect.void.pipe( - Effect.provide(layers) + Effect.provide([layers[0], layers[1]]) ) export const shouldNotReportTransformed = Effect.void.pipe( diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/strictEffectProvide.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/strictEffectProvide.ts.codefixes index 19490e13..e9bcb334 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/strictEffectProvide.ts.codefixes +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/strictEffectProvide.ts.codefixes @@ -1,8 +1,10 @@ -strictEffectProvide_skipNextLine from 667 to 714 -strictEffectProvide_skipFile from 667 to 714 -strictEffectProvide_skipNextLine from 525 to 559 -strictEffectProvide_skipFile from 525 to 559 -strictEffectProvide_skipNextLine from 833 to 867 -strictEffectProvide_skipFile from 833 to 867 -strictEffectProvide_skipNextLine from 991 to 1061 -strictEffectProvide_skipFile from 991 to 1061 \ No newline at end of file +strictEffectProvide_skipNextLine from 790 to 837 +strictEffectProvide_skipFile from 790 to 837 +strictEffectProvide_skipNextLine from 648 to 682 +strictEffectProvide_skipFile from 648 to 682 +strictEffectProvide_skipNextLine from 956 to 990 +strictEffectProvide_skipFile from 956 to 990 +strictEffectProvide_skipNextLine from 1114 to 1184 +strictEffectProvide_skipFile from 1114 to 1184 +strictEffectProvide_skipNextLine from 1321 to 1347 +strictEffectProvide_skipFile from 1321 to 1347 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/strictEffectProvide.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/strictEffectProvide.ts.output index 200650dc..eac2ba2b 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/strictEffectProvide.ts.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/strictEffectProvide.ts.output @@ -1,11 +1,14 @@ Effect.provide(MyService1.Default) -18:2 - 18:36 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) +23:2 - 23:36 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) Effect.provide(Effect.void, MyService1.Default) -22:29 - 22:76 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) +27:29 - 27:76 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) Effect.provide(MyService1.Default) -27:2 - 27:36 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) +32:2 - 32:36 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default)) -33:2 - 33:72 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) \ No newline at end of file +38:2 - 38:72 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) + +Effect.provide(layerTuple) +43:2 - 43:28 | 0 | Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide) \ No newline at end of file diff --git a/packages/harness-effect-v4/examples/diagnostics/multipleEffectProvide.ts b/packages/harness-effect-v4/examples/diagnostics/multipleEffectProvide.ts index 6cd19bcf..e4e8bb17 100644 --- a/packages/harness-effect-v4/examples/diagnostics/multipleEffectProvide.ts +++ b/packages/harness-effect-v4/examples/diagnostics/multipleEffectProvide.ts @@ -18,6 +18,11 @@ class MyService3 extends Context.Service()("MyService3", { static Default = Layer.effect(this, this.make) } +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + export const shouldReport = Effect.void.pipe( Effect.provide(MyService1.Default), Effect.provide(MyService2.Default) @@ -56,3 +61,12 @@ export const shouldReportAfterArrayRewrite = Effect.void.pipe( export const shouldNotReportSingleArray = Effect.void.pipe( Effect.provide([MyService1.Default, MyService2.Default]) ) + +export const shouldReportAfterTupleRewrite = Effect.void.pipe( + Effect.provide(layerTuple), + Effect.provide(MyService3.Default) +) + +export const shouldNotReportSingleTuple = Effect.void.pipe( + Effect.provide(layerTuple) +) diff --git a/packages/harness-effect-v4/examples/diagnostics/strictEffectProvide.ts b/packages/harness-effect-v4/examples/diagnostics/strictEffectProvide.ts index ce522876..fb806b58 100644 --- a/packages/harness-effect-v4/examples/diagnostics/strictEffectProvide.ts +++ b/packages/harness-effect-v4/examples/diagnostics/strictEffectProvide.ts @@ -13,6 +13,11 @@ class MyService2 extends Context.Service()("MyService2", { static Default = Layer.effect(this, this.make) } +const layerTuple: [typeof MyService1.Default, typeof MyService2.Default] = [ + MyService1.Default, + MyService2.Default +] + // Should report: Effect.provide with a Layer export const shouldReport1 = Effect.void.pipe( Effect.provide(MyService1.Default) @@ -33,6 +38,11 @@ export const shouldReport4 = Effect.void.pipe( Effect.provide(Layer.mergeAll(MyService1.Default, MyService2.Default)) ) +// Should report: non-empty tuples are Layer collections accepted by Effect.provide +export const shouldReport5 = Effect.void.pipe( + Effect.provide(layerTuple) +) + // Should NOT report: providing a plain service (not a layer) export const shouldNotReport1 = Effect.void.pipe( Effect.provideService(MyService1, MyService1.of({ value: 1 })) diff --git a/packages/language-service/src/diagnostics/effectProvideLayerArgument.ts b/packages/language-service/src/diagnostics/effectProvideLayerArgument.ts new file mode 100644 index 00000000..330cc4e2 --- /dev/null +++ b/packages/language-service/src/diagnostics/effectProvideLayerArgument.ts @@ -0,0 +1,58 @@ +import { pipe } from "effect/Function" +import * as Option from "effect/Option" +import type ts from "typescript" +import * as Nano from "../core/Nano.js" +import * as TypeCheckerApi from "../core/TypeCheckerApi.js" +import * as TypeCheckerUtils from "../core/TypeCheckerUtils.js" +import * as TypeParser from "../core/TypeParser.js" +import * as TypeScriptApi from "../core/TypeScriptApi.js" + +export const parseEffectProvideLayerArgument = Nano.fn( + "effectProvideLayerArgument.parse" +)(function*(argument: ts.Expression) { + const ts = yield* Nano.service(TypeScriptApi.TypeScriptApi) + const typeChecker = yield* Nano.service(TypeCheckerApi.TypeCheckerApi) + const typeCheckerUtils = yield* Nano.service(TypeCheckerUtils.TypeCheckerUtils) + const typeParser = yield* Nano.service(TypeParser.TypeParser) + + if (ts.isArrayLiteralExpression(argument)) { + if (argument.elements.length === 0) { + return yield* TypeParser.typeParserIssue("Layer array is empty", undefined, argument) + } + for (const element of argument.elements) { + const type = typeCheckerUtils.getTypeAtLocation(element) + if (!type) { + return yield* TypeParser.typeParserIssue("Could not get Layer array element type", undefined, element) + } + yield* typeParser.layerType(type, element) + } + return Array.from(argument.elements) + } + + const argumentType = typeCheckerUtils.getTypeAtLocation(argument) + if (!argumentType) { + return yield* TypeParser.typeParserIssue("Could not get Effect.provide argument type", undefined, argument) + } + + const directLayer = yield* pipe( + typeParser.layerType(argumentType, argument), + Nano.option + ) + if (Option.isSome(directLayer)) { + return [argument] + } + + if (!typeChecker.isTupleType(argumentType)) { + return yield* TypeParser.typeParserIssue("Effect.provide argument is not a Layer tuple", argumentType, argument) + } + + const elementTypes = typeChecker.getTypeArguments(argumentType as ts.TypeReference) + if (elementTypes.length === 0) { + return yield* TypeParser.typeParserIssue("Layer tuple is empty", argumentType, argument) + } + for (const elementType of elementTypes) { + yield* typeParser.layerType(elementType, argument) + } + + return [ts.factory.createSpreadElement(argument)] +}) diff --git a/packages/language-service/src/diagnostics/multipleEffectProvide.ts b/packages/language-service/src/diagnostics/multipleEffectProvide.ts index c6197cc6..8255da2e 100644 --- a/packages/language-service/src/diagnostics/multipleEffectProvide.ts +++ b/packages/language-service/src/diagnostics/multipleEffectProvide.ts @@ -3,10 +3,10 @@ import * as Option from "effect/Option" import type ts from "typescript" import * as LSP from "../core/LSP.js" import * as Nano from "../core/Nano.js" -import * as TypeCheckerUtils from "../core/TypeCheckerUtils.js" import * as TypeParser from "../core/TypeParser.js" import * as TypeScriptApi from "../core/TypeScriptApi.js" import * as TypeScriptUtils from "../core/TypeScriptUtils.js" +import { parseEffectProvideLayerArgument } from "./effectProvideLayerArgument.js" export const multipleEffectProvide = LSP.createDiagnostic({ name: "multipleEffectProvide", @@ -19,7 +19,6 @@ export const multipleEffectProvide = LSP.createDiagnostic({ apply: Nano.fn("multipleEffectProvide.apply")(function*(sourceFile, report) { const ts = yield* Nano.service(TypeScriptApi.TypeScriptApi) const tsUtils = yield* Nano.service(TypeScriptUtils.TypeScriptUtils) - const typeCheckerUtils = yield* Nano.service(TypeCheckerUtils.TypeCheckerUtils) const typeParser = yield* Nano.service(TypeParser.TypeParser) const supportedEffect = typeParser.supportedEffect() @@ -82,28 +81,14 @@ export const multipleEffectProvide = LSP.createDiagnostic({ } const layer = transformation.args[0] - const layers = ts.isArrayLiteralExpression(layer) - ? Array.from(layer.elements) - : [layer] const node = ts.findAncestor(transformation.callee, ts.isCallExpression) + const layers = yield* pipe( + parseEffectProvideLayerArgument(layer), + Nano.option + ) - let allLayers = layers.length > 0 - for (const candidate of layers) { - const type = typeCheckerUtils.getTypeAtLocation(candidate) - const isLayerType = type - ? yield* pipe( - typeParser.layerType(type, candidate), - Nano.option - ) - : Option.none() - if (Option.isNone(isLayerType)) { - allLayers = false - break - } - } - - if (allLayers && node) { - previousLayers[currentChunk].push({ layers, node }) + if (Option.isSome(layers) && node) { + previousLayers[currentChunk].push({ layers: layers.value, node }) } else { // Not a layer, breaks the chain currentChunk++ diff --git a/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts b/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts index 81ed61e7..1cf74905 100644 --- a/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts +++ b/packages/language-service/src/diagnostics/redundantLayerMergeAllInProvide.ts @@ -2,6 +2,8 @@ import { pipe } from "effect/Function" import type ts from "typescript" import * as LSP from "../core/LSP.js" import * as Nano from "../core/Nano.js" +import * as TypeCheckerApi from "../core/TypeCheckerApi.js" +import * as TypeCheckerUtils from "../core/TypeCheckerUtils.js" import * as TypeParser from "../core/TypeParser.js" import * as TypeScriptApi from "../core/TypeScriptApi.js" import { findLayerMergeAllDependencies } from "./layerMergeAllDependencies.js" @@ -16,6 +18,8 @@ export const redundantLayerMergeAllInProvide = LSP.createDiagnostic({ supportedEffect: ["v3", "v4"], apply: Nano.fn("redundantLayerMergeAllInProvide.apply")(function*(sourceFile, report) { const ts = yield* Nano.service(TypeScriptApi.TypeScriptApi) + const typeChecker = yield* Nano.service(TypeCheckerApi.TypeCheckerApi) + const typeCheckerUtils = yield* Nano.service(TypeCheckerUtils.TypeCheckerUtils) const typeParser = yield* Nano.service(TypeParser.TypeParser) const nodesToVisit: Array = [] @@ -49,6 +53,44 @@ export const redundantLayerMergeAllInProvide = LSP.createDiagnostic({ const startLine = sourceFile.getLineAndCharacterOfPosition(ts.getTokenPosOfNode(argument, sourceFile)).line const endLine = sourceFile.getLineAndCharacterOfPosition(argument.end).line + const onlyArgument = argument.arguments.length === 1 + ? argument.arguments[0] + : undefined + let replacement: ts.Expression + if (onlyArgument && ts.isSpreadElement(onlyArgument)) { + const spreadExpression = onlyArgument.expression + const spreadType = typeCheckerUtils.getTypeAtLocation(spreadExpression) + if ( + spreadType && + typeChecker.isTupleType(spreadType) && + (spreadType as ts.TupleTypeReference).target.readonly + ) { + const tupleTarget = (spreadType as ts.TupleTypeReference).target + const elementTypes = typeChecker.getTypeArguments(spreadType as ts.TypeReference) + if ( + !ts.isIdentifier(spreadExpression) || + elementTypes.length === 0 || + tupleTarget.elementFlags.some((flag) => flag !== ts.ElementFlags.Required) + ) { + continue + } + replacement = ts.factory.createArrayLiteralExpression( + elementTypes.map((_, index) => + ts.factory.createElementAccessExpression( + ts.factory.createIdentifier(spreadExpression.text), + index + ) + ) + ) + } else { + replacement = spreadExpression + } + } else { + replacement = ts.factory.createArrayLiteralExpression( + argument.arguments, + startLine !== endLine + ) + } report({ location: argument.expression, @@ -59,15 +101,6 @@ export const redundantLayerMergeAllInProvide = LSP.createDiagnostic({ description: "Replace Layer.mergeAll with an array", apply: Nano.gen(function*() { const changeTracker = yield* Nano.service(TypeScriptApi.ChangeTracker) - const onlyArgument = argument.arguments.length === 1 - ? argument.arguments[0] - : undefined - const replacement = onlyArgument && ts.isSpreadElement(onlyArgument) - ? onlyArgument.expression - : ts.factory.createArrayLiteralExpression( - argument.arguments, - startLine !== endLine - ) changeTracker.replaceNode( sourceFile, argument, diff --git a/packages/language-service/src/diagnostics/strictEffectProvide.ts b/packages/language-service/src/diagnostics/strictEffectProvide.ts index 4e4eeba6..2feb11e5 100644 --- a/packages/language-service/src/diagnostics/strictEffectProvide.ts +++ b/packages/language-service/src/diagnostics/strictEffectProvide.ts @@ -3,9 +3,9 @@ import * as Option from "effect/Option" import type ts from "typescript" import * as LSP from "../core/LSP.js" import * as Nano from "../core/Nano.js" -import * as TypeCheckerUtils from "../core/TypeCheckerUtils.js" import * as TypeParser from "../core/TypeParser.js" import * as TypeScriptApi from "../core/TypeScriptApi.js" +import { parseEffectProvideLayerArgument } from "./effectProvideLayerArgument.js" export const strictEffectProvide = LSP.createDiagnostic({ name: "strictEffectProvide", @@ -17,7 +17,6 @@ export const strictEffectProvide = LSP.createDiagnostic({ supportedEffect: ["v3", "v4"], apply: Nano.fn("strictEffectProvide.apply")(function*(sourceFile, report) { const ts = yield* Nano.service(TypeScriptApi.TypeScriptApi) - const typeCheckerUtils = yield* Nano.service(TypeCheckerUtils.TypeCheckerUtils) const typeParser = yield* Nano.service(TypeParser.TypeParser) const parseEffectProvideWithLayerGen = Nano.fn("strictEffectProvide.parseEffectProvideWithLayer")( @@ -27,11 +26,7 @@ export const strictEffectProvide = LSP.createDiagnostic({ // Check if any argument is a Layer using firstSuccessOf return yield* Nano.firstSuccessOf( - node.arguments.map((arg) => { - const argType = typeCheckerUtils.getTypeAtLocation(arg) - if (!argType) return TypeParser.typeParserIssue("Could not get argument type") - return typeParser.layerType(argType, arg) - }) + node.arguments.map(parseEffectProvideLayerArgument) ) } )