Skip to content

Commit 57fcf35

Browse files
Add effectFnImplicitAny diagnostic (#692)
1 parent 0e16db0 commit 57fcf35

23 files changed

+338
-12
lines changed

.changeset/real-trains-glow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@effect/language-service": minor
3+
---
4+
5+
Add the `effectFnImplicitAny` diagnostic to mirror `noImplicitAny` for unannotated `Effect.fn` and `Effect.fnUntraced` callback parameters, and support `// @strict` in diagnostic example files so test fixtures can enable strict compiler options.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Some diagnostics are off by default or have a default severity of suggestion, bu
5656
<tr><td><code>anyUnknownInErrorContext</code></td><td></td><td></td><td>Detects 'any' or 'unknown' types in Effect error or requirements channels</td><td></td><td></td></tr>
5757
<tr><td><code>classSelfMismatch</code></td><td></td><td>🔧</td><td>Ensures Self type parameter matches the class name in Service/Tag/Schema classes</td><td></td><td></td></tr>
5858
<tr><td><code>duplicatePackage</code></td><td>⚠️</td><td></td><td>Detects when multiple versions of the same Effect package are loaded</td><td></td><td></td></tr>
59+
<tr><td><code>effectFnImplicitAny</code></td><td></td><td></td><td>Mirrors noImplicitAny for unannotated Effect.fn and Effect.fnUntraced callback parameters when no outer contextual function type exists</td><td>✓</td><td>✓</td></tr>
5960
<tr><td><code>floatingEffect</code></td><td>❌</td><td></td><td>Ensures Effects are yielded or assigned to variables, not left floating</td><td>✓</td><td>✓</td></tr>
6061
<tr><td><code>genericEffectServices</code></td><td>⚠️</td><td></td><td>Prevents services with type parameters that cannot be discriminated at runtime</td><td>✓</td><td>✓</td></tr>
6162
<tr><td><code>missingEffectContext</code></td><td>❌</td><td></td><td>Reports missing service requirements in Effect context channel</td><td>✓</td><td>✓</td></tr>

packages/harness-effect-v3/__snapshots__/completions.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ exports[`Completion effectDataClasses > effectDataClasses_directImportTaggedErro
248248
exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2:5 1`] = `
249249
[
250250
{
251-
"insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0",
251+
"insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0",
252252
"isSnippet": true,
253253
"kind": "string",
254254
"name": "@effect-diagnostics",
@@ -259,7 +259,7 @@ exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2:
259259
"sortText": "11",
260260
},
261261
{
262-
"insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0",
262+
"insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0",
263263
"isSnippet": true,
264264
"kind": "string",
265265
"name": "@effect-diagnostics-next-line",
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
effectFnImplicitAny_skipNextLine from 709 to 710
2+
effectFnImplicitAny_skipFile from 709 to 710
3+
effectFnImplicitAny_skipNextLine from 712 to 713
4+
effectFnImplicitAny_skipFile from 712 to 713
5+
effectFnImplicitAny_skipNextLine from 580 to 585
6+
effectFnImplicitAny_skipFile from 580 to 585
7+
effectFnImplicitAny_skipNextLine from 408 to 413
8+
effectFnImplicitAny_skipFile from 408 to 413
9+
effectFnImplicitAny_skipNextLine from 239 to 244
10+
effectFnImplicitAny_skipFile from 239 to 244
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
input
2+
6:60 - 6:65 | 1 | Parameter 'input' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type. effect(effectFnImplicitAny)
3+
4+
input
5+
11:65 - 11:70 | 1 | Parameter 'input' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type. effect(effectFnImplicitAny)
6+
7+
input
8+
14:62 - 14:67 | 1 | Parameter 'input' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type. effect(effectFnImplicitAny)
9+
10+
a
11+
19:44 - 19:45 | 1 | Parameter 'a' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type. effect(effectFnImplicitAny)
12+
13+
b
14+
19:47 - 19:48 | 1 | Parameter 'b' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type. effect(effectFnImplicitAny)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
effectFnImplicitAny_skipNextLine from 175 to 180
2+
effectFnImplicitAny_skipFile from 175 to 180
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
input
2+
6:45 - 6:50 | 1 | Parameter 'input' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type. effect(effectFnImplicitAny)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// @strict
2+
// @effect-diagnostics effectFnImplicitAny:error
3+
import * as Effect from "effect/Effect"
4+
5+
// Should trigger - standalone Effect.fn generator callback falls back to any
6+
export const standalone = Effect.fn("standalone")(function*(input) {
7+
return input
8+
})
9+
10+
// Should trigger - standalone Effect.fn regular callback falls back to any
11+
export const standaloneRegular = Effect.fn("standaloneRegular")((input) => Effect.succeed(input))
12+
13+
// Should trigger - standalone Effect.fnUntraced callback falls back to any
14+
export const standaloneUntraced = Effect.fnUntraced(function*(input) {
15+
return input
16+
})
17+
18+
// Should trigger - multiple params are all implicit any
19+
export const multiple = Effect.fn(function*(a, b) {
20+
return [a, b] as const
21+
})
22+
23+
// Should not trigger - outer contextual any matches normal noImplicitAny behavior
24+
declare const acceptsAny: (f: (input: any) => Effect.Effect<any>) => void
25+
acceptsAny(Effect.fn("acceptsAny")(function*(input) {
26+
return input
27+
}))
28+
29+
// Should not trigger - outer contextual function type provides a concrete input type
30+
declare const acceptsString: (f: (input: string) => Effect.Effect<number>) => void
31+
acceptsString(Effect.fn("acceptsString")((input) => Effect.succeed(input.length)))
32+
33+
// Should not trigger - destructuring receives its type from the outer callback type
34+
declare const acceptsRequest: (f: (input: { readonly id: string }) => Effect.Effect<string>) => void
35+
acceptsRequest(Effect.fn("acceptsRequest")(function*({ id }) {
36+
return id
37+
}))
38+
39+
// Should not trigger - explicit parameter types are already present
40+
export const typed = Effect.fn("typed")((input: string) => Effect.succeed(input.length))
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @strict
2+
// @effect-diagnostics *:off
3+
// @effect-diagnostics effectFnImplicitAny:error
4+
import * as Effect from "effect/Effect"
5+
6+
export const preview = Effect.fn("preview")((input) => Effect.succeed(input))

packages/harness-effect-v4/__snapshots__/completions.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ exports[`Completion effectDataClasses > effectDataClasses.ts at 4:35 1`] = `
143143
exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2:5 1`] = `
144144
[
145145
{
146-
"insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0",
146+
"insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0",
147147
"isSnippet": true,
148148
"kind": "string",
149149
"name": "@effect-diagnostics",
@@ -154,7 +154,7 @@ exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2:
154154
"sortText": "11",
155155
},
156156
{
157-
"insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0",
157+
"insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnImplicitAny,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,globalFetch,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nodeBuiltinImport,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0",
158158
"isSnippet": true,
159159
"kind": "string",
160160
"name": "@effect-diagnostics-next-line",

0 commit comments

Comments
 (0)