@effect/language-service 0.63.2 → 0.64.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/cli.js +1250 -402
- package/cli.js.map +1 -1
- package/effect-lsp-patch-utils.js +424 -62
- package/effect-lsp-patch-utils.js.map +1 -1
- package/index.js +452 -77
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/transform.js +424 -62
- package/transform.js.map +1 -1
package/package.json
CHANGED
package/transform.js
CHANGED
|
@@ -2695,6 +2695,12 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
|
|
|
2695
2695
|
);
|
|
2696
2696
|
const extendsCauseYieldableError = cachedBy(
|
|
2697
2697
|
fn("TypeParser.extendsCauseYieldableError")(function* (givenType) {
|
|
2698
|
+
if (givenType.flags & ts.TypeFlags.Never) {
|
|
2699
|
+
return yield* typeParserIssue("Type is never", givenType);
|
|
2700
|
+
}
|
|
2701
|
+
if (givenType.flags & ts.TypeFlags.Any) {
|
|
2702
|
+
return yield* typeParserIssue("Type is any", givenType);
|
|
2703
|
+
}
|
|
2698
2704
|
const symbols = yield* findSymbolsMatchingPackageAndExportedName("effect", "YieldableError")();
|
|
2699
2705
|
for (const [symbol3, sourceFile] of symbols) {
|
|
2700
2706
|
const causeFile = yield* pipe(isCauseTypeSourceFile(sourceFile), orElse2(() => void_));
|
|
@@ -2789,18 +2795,19 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
|
|
|
2789
2795
|
);
|
|
2790
2796
|
const effectType = cachedBy(
|
|
2791
2797
|
fn("TypeParser.effectType")(function* (type, atLocation) {
|
|
2792
|
-
let result = typeParserIssue("Type has no effect variance struct", type, atLocation);
|
|
2793
2798
|
const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
|
|
2794
|
-
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration
|
|
2799
|
+
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration
|
|
2795
2800
|
);
|
|
2801
|
+
if (propertiesSymbols.length === 0) {
|
|
2802
|
+
return yield* typeParserIssue("Type has no effect variance struct", type, atLocation);
|
|
2803
|
+
}
|
|
2796
2804
|
propertiesSymbols.sort(
|
|
2797
2805
|
(a, b) => ts.symbolName(b).indexOf("EffectTypeId") - ts.symbolName(a).indexOf("EffectTypeId")
|
|
2798
2806
|
);
|
|
2799
|
-
|
|
2807
|
+
return yield* firstSuccessOf(propertiesSymbols.map((propertySymbol) => {
|
|
2800
2808
|
const propertyType = typeChecker.getTypeOfSymbolAtLocation(propertySymbol, atLocation);
|
|
2801
|
-
|
|
2802
|
-
}
|
|
2803
|
-
return yield* result;
|
|
2809
|
+
return effectVarianceStruct(propertyType, atLocation);
|
|
2810
|
+
}));
|
|
2804
2811
|
}),
|
|
2805
2812
|
"TypeParser.effectType",
|
|
2806
2813
|
(type) => type
|
|
@@ -2839,22 +2846,18 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
|
|
|
2839
2846
|
fn("TypeParser.layerType")(function* (type, atLocation) {
|
|
2840
2847
|
yield* pipeableType(type, atLocation);
|
|
2841
2848
|
const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
|
|
2842
|
-
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration
|
|
2849
|
+
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration
|
|
2843
2850
|
);
|
|
2851
|
+
if (propertiesSymbols.length === 0) {
|
|
2852
|
+
return yield* typeParserIssue("Type has no layer variance struct", type, atLocation);
|
|
2853
|
+
}
|
|
2844
2854
|
propertiesSymbols.sort(
|
|
2845
2855
|
(a, b) => ts.symbolName(b).indexOf("LayerTypeId") - ts.symbolName(a).indexOf("LayerTypeId")
|
|
2846
2856
|
);
|
|
2847
|
-
|
|
2857
|
+
return yield* firstSuccessOf(propertiesSymbols.map((propertySymbol) => {
|
|
2848
2858
|
const propertyType = typeChecker.getTypeOfSymbolAtLocation(propertySymbol, atLocation);
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
atLocation
|
|
2852
|
-
));
|
|
2853
|
-
if (isSome2(varianceArgs)) {
|
|
2854
|
-
return varianceArgs.value;
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
return yield* typeParserIssue("Type has no layer variance struct", type, atLocation);
|
|
2859
|
+
return layerVarianceStruct(propertyType, atLocation);
|
|
2860
|
+
}));
|
|
2858
2861
|
}),
|
|
2859
2862
|
"TypeParser.layerType",
|
|
2860
2863
|
(type) => type
|
|
@@ -3151,20 +3154,16 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
|
|
|
3151
3154
|
const ast = typeChecker.getPropertyOfType(type, "ast");
|
|
3152
3155
|
if (!ast) return yield* typeParserIssue("Has no 'ast' property", type, atLocation);
|
|
3153
3156
|
const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
|
|
3154
|
-
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration
|
|
3157
|
+
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration
|
|
3155
3158
|
);
|
|
3159
|
+
if (propertiesSymbols.length === 0) {
|
|
3160
|
+
return yield* typeParserIssue("Type has no schema variance struct", type, atLocation);
|
|
3161
|
+
}
|
|
3156
3162
|
propertiesSymbols.sort((a, b) => ts.symbolName(b).indexOf("TypeId") - ts.symbolName(a).indexOf("TypeId"));
|
|
3157
|
-
|
|
3163
|
+
return yield* firstSuccessOf(propertiesSymbols.map((propertySymbol) => {
|
|
3158
3164
|
const propertyType = typeChecker.getTypeOfSymbolAtLocation(propertySymbol, atLocation);
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
atLocation
|
|
3162
|
-
));
|
|
3163
|
-
if (isSome2(varianceArgs)) {
|
|
3164
|
-
return varianceArgs.value;
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
return yield* typeParserIssue("Type has no schema variance struct", type, atLocation);
|
|
3165
|
+
return effectSchemaVarianceStruct(propertyType, atLocation);
|
|
3166
|
+
}));
|
|
3168
3167
|
}),
|
|
3169
3168
|
"TypeParser.effectSchemaType",
|
|
3170
3169
|
(type) => type
|
|
@@ -3200,33 +3199,54 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
|
|
|
3200
3199
|
fn("TypeParser.contextTag")(function* (type, atLocation) {
|
|
3201
3200
|
yield* pipeableType(type, atLocation);
|
|
3202
3201
|
const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
|
|
3203
|
-
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration
|
|
3202
|
+
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration
|
|
3204
3203
|
);
|
|
3204
|
+
if (propertiesSymbols.length === 0) {
|
|
3205
|
+
return yield* typeParserIssue("Type has no tag variance struct", type, atLocation);
|
|
3206
|
+
}
|
|
3205
3207
|
propertiesSymbols.sort((a, b) => ts.symbolName(b).indexOf("TypeId") - ts.symbolName(a).indexOf("TypeId"));
|
|
3206
|
-
|
|
3208
|
+
return yield* firstSuccessOf(propertiesSymbols.map((propertySymbol) => {
|
|
3207
3209
|
const propertyType = typeChecker.getTypeOfSymbolAtLocation(propertySymbol, atLocation);
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
atLocation
|
|
3211
|
-
));
|
|
3212
|
-
if (isSome2(varianceArgs)) {
|
|
3213
|
-
return varianceArgs.value;
|
|
3214
|
-
}
|
|
3215
|
-
}
|
|
3216
|
-
return yield* typeParserIssue("Type has no tag variance struct", type, atLocation);
|
|
3210
|
+
return contextTagVarianceStruct(propertyType, atLocation);
|
|
3211
|
+
}));
|
|
3217
3212
|
}),
|
|
3218
3213
|
"TypeParser.contextTag",
|
|
3219
3214
|
(type) => type
|
|
3220
3215
|
);
|
|
3216
|
+
const effectFunctionImportedName = cachedBy(
|
|
3217
|
+
fn("TypeParser.effectFunctionImportedName")(function* (sourceFile) {
|
|
3218
|
+
return tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Function");
|
|
3219
|
+
}),
|
|
3220
|
+
"TypeParser.effectFunctionImportedName",
|
|
3221
|
+
(node) => node
|
|
3222
|
+
);
|
|
3221
3223
|
const pipeCall = cachedBy(
|
|
3222
3224
|
function(node) {
|
|
3223
3225
|
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.name) && ts.idText(node.expression.name) === "pipe") {
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3226
|
+
const baseExpression = node.expression.expression;
|
|
3227
|
+
return pipe(
|
|
3228
|
+
effectFunctionImportedName(tsUtils.getSourceFileOfNode(node)),
|
|
3229
|
+
flatMap2((functionIdentifier) => {
|
|
3230
|
+
if (functionIdentifier && ts.isIdentifier(baseExpression) && ts.idText(baseExpression) === functionIdentifier) {
|
|
3231
|
+
if (node.arguments.length === 0) {
|
|
3232
|
+
return typeParserIssue("Node is not a pipe call", void 0, node);
|
|
3233
|
+
}
|
|
3234
|
+
const [subject, ...args2] = node.arguments;
|
|
3235
|
+
return succeed({
|
|
3236
|
+
node,
|
|
3237
|
+
subject,
|
|
3238
|
+
args: args2,
|
|
3239
|
+
kind: "pipe"
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
return succeed({
|
|
3243
|
+
node,
|
|
3244
|
+
subject: baseExpression,
|
|
3245
|
+
args: Array.from(node.arguments),
|
|
3246
|
+
kind: "pipeable"
|
|
3247
|
+
});
|
|
3248
|
+
})
|
|
3249
|
+
);
|
|
3230
3250
|
}
|
|
3231
3251
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && ts.idText(node.expression) === "pipe" && node.arguments.length > 0) {
|
|
3232
3252
|
const [subject, ...args2] = node.arguments;
|
|
@@ -3241,17 +3261,10 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
|
|
|
3241
3261
|
fn("TypeParser.scopeType")(function* (type, atLocation) {
|
|
3242
3262
|
yield* pipeableType(type, atLocation);
|
|
3243
3263
|
const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
|
|
3244
|
-
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration
|
|
3264
|
+
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration
|
|
3245
3265
|
);
|
|
3246
|
-
propertiesSymbols.
|
|
3247
|
-
|
|
3248
|
-
);
|
|
3249
|
-
for (const propertySymbol of propertiesSymbols) {
|
|
3250
|
-
const computedPropertyExpression = propertySymbol.valueDeclaration.name;
|
|
3251
|
-
const symbol3 = typeChecker.getSymbolAtLocation(computedPropertyExpression.expression);
|
|
3252
|
-
if (symbol3 && ts.symbolName(symbol3) === "ScopeTypeId") {
|
|
3253
|
-
return type;
|
|
3254
|
-
}
|
|
3266
|
+
if (propertiesSymbols.some((s) => ts.symbolName(s).indexOf("ScopeTypeId") !== -1)) {
|
|
3267
|
+
return type;
|
|
3255
3268
|
}
|
|
3256
3269
|
return yield* typeParserIssue("Type has no scope type id", type, atLocation);
|
|
3257
3270
|
}),
|
|
@@ -3757,12 +3770,38 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
|
|
|
3757
3770
|
"TypeParser.extendsEffectSqlModelClass",
|
|
3758
3771
|
(atLocation) => atLocation
|
|
3759
3772
|
);
|
|
3773
|
+
const isEffectLayerTypeSourceFile = cachedBy(
|
|
3774
|
+
fn("TypeParser.isEffectLayerTypeSourceFile")(function* (sourceFile) {
|
|
3775
|
+
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
3776
|
+
if (!moduleSymbol) return yield* typeParserIssue("Node has no symbol", void 0, sourceFile);
|
|
3777
|
+
const layerTypeSymbol = typeChecker.tryGetMemberInModuleExports("Layer", moduleSymbol);
|
|
3778
|
+
if (!layerTypeSymbol) return yield* typeParserIssue("Layer type not found", void 0, sourceFile);
|
|
3779
|
+
const type = typeChecker.getDeclaredTypeOfSymbol(layerTypeSymbol);
|
|
3780
|
+
yield* layerType(type, sourceFile);
|
|
3781
|
+
return sourceFile;
|
|
3782
|
+
}),
|
|
3783
|
+
"TypeParser.isEffectLayerTypeSourceFile",
|
|
3784
|
+
(sourceFile) => sourceFile
|
|
3785
|
+
);
|
|
3786
|
+
const isNodeReferenceToEffectLayerModuleApi = (memberName) => cachedBy(
|
|
3787
|
+
fn("TypeParser.isNodeReferenceToEffectLayerModuleApi")(function* (node) {
|
|
3788
|
+
return yield* isNodeReferenceToExportOfPackageModule(
|
|
3789
|
+
node,
|
|
3790
|
+
"effect",
|
|
3791
|
+
isEffectLayerTypeSourceFile,
|
|
3792
|
+
memberName
|
|
3793
|
+
);
|
|
3794
|
+
}),
|
|
3795
|
+
`TypeParser.isNodeReferenceToEffectLayerModuleApi(${memberName})`,
|
|
3796
|
+
(node) => node
|
|
3797
|
+
);
|
|
3760
3798
|
return {
|
|
3761
3799
|
isNodeReferenceToEffectModuleApi,
|
|
3762
3800
|
isNodeReferenceToEffectSchemaModuleApi,
|
|
3763
3801
|
isNodeReferenceToEffectDataModuleApi,
|
|
3764
3802
|
isNodeReferenceToEffectContextModuleApi,
|
|
3765
3803
|
isNodeReferenceToEffectSqlModelModuleApi,
|
|
3804
|
+
isNodeReferenceToEffectLayerModuleApi,
|
|
3766
3805
|
effectType,
|
|
3767
3806
|
strictEffectType,
|
|
3768
3807
|
layerType,
|
|
@@ -3896,6 +3935,100 @@ var anyUnknownInErrorContext = createDiagnostic({
|
|
|
3896
3935
|
})
|
|
3897
3936
|
});
|
|
3898
3937
|
|
|
3938
|
+
// src/diagnostics/catchAllToMapError.ts
|
|
3939
|
+
var catchAllToMapError = createDiagnostic({
|
|
3940
|
+
name: "catchAllToMapError",
|
|
3941
|
+
code: 39,
|
|
3942
|
+
description: "Suggests using Effect.mapError instead of Effect.catchAll when the callback only wraps the error with Effect.fail",
|
|
3943
|
+
severity: "suggestion",
|
|
3944
|
+
apply: fn("catchAllToMapError.apply")(function* (sourceFile, report) {
|
|
3945
|
+
const ts = yield* service(TypeScriptApi);
|
|
3946
|
+
const typeParser = yield* service(TypeParser);
|
|
3947
|
+
const getFunctionBody = (node) => {
|
|
3948
|
+
if (ts.isArrowFunction(node)) {
|
|
3949
|
+
return node.body;
|
|
3950
|
+
}
|
|
3951
|
+
if (ts.isFunctionExpression(node)) {
|
|
3952
|
+
return node.body;
|
|
3953
|
+
}
|
|
3954
|
+
return void 0;
|
|
3955
|
+
};
|
|
3956
|
+
const getEffectFailCallInfo = (body) => {
|
|
3957
|
+
return gen(function* () {
|
|
3958
|
+
if (ts.isCallExpression(body)) {
|
|
3959
|
+
const isFailCall = yield* pipe(
|
|
3960
|
+
typeParser.isNodeReferenceToEffectModuleApi("fail")(body.expression),
|
|
3961
|
+
option
|
|
3962
|
+
);
|
|
3963
|
+
if (isSome2(isFailCall) && body.arguments.length >= 1) {
|
|
3964
|
+
return some2({ failCall: body, failArg: body.arguments[0] });
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
if (ts.isBlock(body)) {
|
|
3968
|
+
const statements = body.statements;
|
|
3969
|
+
if (statements.length === 1) {
|
|
3970
|
+
const stmt = statements[0];
|
|
3971
|
+
if (ts.isReturnStatement(stmt) && stmt.expression && ts.isCallExpression(stmt.expression)) {
|
|
3972
|
+
const isFailCall = yield* pipe(
|
|
3973
|
+
typeParser.isNodeReferenceToEffectModuleApi("fail")(stmt.expression.expression),
|
|
3974
|
+
option
|
|
3975
|
+
);
|
|
3976
|
+
if (isSome2(isFailCall) && stmt.expression.arguments.length >= 1) {
|
|
3977
|
+
return some2({ failCall: stmt.expression, failArg: stmt.expression.arguments[0] });
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
return none2();
|
|
3983
|
+
});
|
|
3984
|
+
};
|
|
3985
|
+
const nodeToVisit = [];
|
|
3986
|
+
const appendNodeToVisit = (node) => {
|
|
3987
|
+
nodeToVisit.push(node);
|
|
3988
|
+
return void 0;
|
|
3989
|
+
};
|
|
3990
|
+
ts.forEachChild(sourceFile, appendNodeToVisit);
|
|
3991
|
+
while (nodeToVisit.length > 0) {
|
|
3992
|
+
const node = nodeToVisit.shift();
|
|
3993
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
3994
|
+
if (ts.isCallExpression(node)) {
|
|
3995
|
+
const isCatchAllCall = yield* pipe(
|
|
3996
|
+
typeParser.isNodeReferenceToEffectModuleApi("catchAll")(node.expression),
|
|
3997
|
+
option
|
|
3998
|
+
);
|
|
3999
|
+
if (isSome2(isCatchAllCall)) {
|
|
4000
|
+
const callback = node.arguments[0];
|
|
4001
|
+
if (!callback) continue;
|
|
4002
|
+
const functionBody = getFunctionBody(callback);
|
|
4003
|
+
if (!functionBody) continue;
|
|
4004
|
+
const failCallInfo = yield* getEffectFailCallInfo(functionBody);
|
|
4005
|
+
if (isNone2(failCallInfo)) continue;
|
|
4006
|
+
const { failArg, failCall } = failCallInfo.value;
|
|
4007
|
+
report({
|
|
4008
|
+
location: node.expression,
|
|
4009
|
+
messageText: `You can use Effect.mapError instead of Effect.catchAll + Effect.fail to transform the error type.`,
|
|
4010
|
+
fixes: [{
|
|
4011
|
+
fixName: "catchAllToMapError_fix",
|
|
4012
|
+
description: "Replace with Effect.mapError",
|
|
4013
|
+
apply: gen(function* () {
|
|
4014
|
+
const changeTracker = yield* service(ChangeTracker);
|
|
4015
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
4016
|
+
changeTracker.replaceNode(
|
|
4017
|
+
sourceFile,
|
|
4018
|
+
node.expression.name,
|
|
4019
|
+
ts.factory.createIdentifier("mapError")
|
|
4020
|
+
);
|
|
4021
|
+
}
|
|
4022
|
+
changeTracker.replaceNode(sourceFile, failCall, failArg);
|
|
4023
|
+
})
|
|
4024
|
+
}]
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
})
|
|
4030
|
+
});
|
|
4031
|
+
|
|
3899
4032
|
// src/diagnostics/catchUnfailableEffect.ts
|
|
3900
4033
|
var catchUnfailableEffect = createDiagnostic({
|
|
3901
4034
|
name: "catchUnfailableEffect",
|
|
@@ -4458,6 +4591,65 @@ var genericEffectServices = createDiagnostic({
|
|
|
4458
4591
|
})
|
|
4459
4592
|
});
|
|
4460
4593
|
|
|
4594
|
+
// src/diagnostics/globalErrorInEffectCatch.ts
|
|
4595
|
+
var globalErrorInEffectCatch = createDiagnostic({
|
|
4596
|
+
name: "globalErrorInEffectCatch",
|
|
4597
|
+
code: 36,
|
|
4598
|
+
description: "Warns when catch callbacks return global Error type instead of typed errors",
|
|
4599
|
+
severity: "warning",
|
|
4600
|
+
apply: fn("globalErrorInEffectCatch.apply")(function* (sourceFile, report) {
|
|
4601
|
+
const ts = yield* service(TypeScriptApi);
|
|
4602
|
+
const typeParser = yield* service(TypeParser);
|
|
4603
|
+
const typeChecker = yield* service(TypeCheckerApi);
|
|
4604
|
+
const typeCheckerUtils = yield* service(TypeCheckerUtils);
|
|
4605
|
+
const nodeToVisit = [];
|
|
4606
|
+
const appendNodeToVisit = (node) => {
|
|
4607
|
+
nodeToVisit.push(node);
|
|
4608
|
+
return void 0;
|
|
4609
|
+
};
|
|
4610
|
+
ts.forEachChild(sourceFile, appendNodeToVisit);
|
|
4611
|
+
while (nodeToVisit.length > 0) {
|
|
4612
|
+
const node = nodeToVisit.shift();
|
|
4613
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
4614
|
+
if (ts.isCallExpression(node)) {
|
|
4615
|
+
const isEffectWithCatch = yield* pipe(
|
|
4616
|
+
typeParser.isNodeReferenceToEffectModuleApi("tryPromise")(node.expression),
|
|
4617
|
+
orElse2(() => typeParser.isNodeReferenceToEffectModuleApi("try")(node.expression)),
|
|
4618
|
+
orElse2(() => typeParser.isNodeReferenceToEffectModuleApi("tryMap")(node.expression)),
|
|
4619
|
+
orElse2(() => typeParser.isNodeReferenceToEffectModuleApi("tryMapPromise")(node.expression)),
|
|
4620
|
+
orElse2(() => void_)
|
|
4621
|
+
);
|
|
4622
|
+
if (isEffectWithCatch) {
|
|
4623
|
+
const signature = typeChecker.getResolvedSignature(node);
|
|
4624
|
+
if (signature) {
|
|
4625
|
+
const objectType = typeChecker.getParameterType(signature, 0);
|
|
4626
|
+
const catchFunctionSymbol = typeChecker.getPropertyOfType(objectType, "catch");
|
|
4627
|
+
if (catchFunctionSymbol) {
|
|
4628
|
+
const catchFunctionType = typeChecker.getTypeOfSymbolAtLocation(catchFunctionSymbol, node);
|
|
4629
|
+
const signatures = typeChecker.getSignaturesOfType(catchFunctionType, ts.SignatureKind.Call);
|
|
4630
|
+
if (signatures.length > 0) {
|
|
4631
|
+
const returnType = typeChecker.getReturnTypeOfSignature(signatures[0]);
|
|
4632
|
+
if (returnType && typeCheckerUtils.isGlobalErrorType(returnType)) {
|
|
4633
|
+
const nodeText = sourceFile.text.substring(
|
|
4634
|
+
ts.getTokenPosOfNode(node.expression, sourceFile),
|
|
4635
|
+
node.expression.end
|
|
4636
|
+
);
|
|
4637
|
+
report({
|
|
4638
|
+
location: node.expression,
|
|
4639
|
+
messageText: `The 'catch' callback in ${nodeText} returns the global Error type. It's not recommended to use the global Error type in Effect failures as they can get merged together.
|
|
4640
|
+
Instead, use tagged errors or custom errors with a discriminator property to get properly type-checked errors.`,
|
|
4641
|
+
fixes: []
|
|
4642
|
+
});
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
})
|
|
4651
|
+
});
|
|
4652
|
+
|
|
4461
4653
|
// src/diagnostics/globalErrorInEffectFailure.ts
|
|
4462
4654
|
var globalErrorInEffectFailure = createDiagnostic({
|
|
4463
4655
|
name: "globalErrorInEffectFailure",
|
|
@@ -4488,7 +4680,7 @@ var globalErrorInEffectFailure = createDiagnostic({
|
|
|
4488
4680
|
return sync(
|
|
4489
4681
|
() => report({
|
|
4490
4682
|
location: node,
|
|
4491
|
-
messageText: `Effect.fail is called with the global Error type. It's not recommended to use the global Error type in Effect failures as they can get merged together. Instead, use tagged errors
|
|
4683
|
+
messageText: `Effect.fail is called with the global Error type. It's not recommended to use the global Error type in Effect failures as they can get merged together. Instead, use tagged errors or custom errors with a discriminator property to get properly type-checked errors.`,
|
|
4492
4684
|
fixes: []
|
|
4493
4685
|
})
|
|
4494
4686
|
);
|
|
@@ -4643,6 +4835,119 @@ var importFromBarrel = createDiagnostic({
|
|
|
4643
4835
|
})
|
|
4644
4836
|
});
|
|
4645
4837
|
|
|
4838
|
+
// src/diagnostics/layerMergeAllWithDependencies.ts
|
|
4839
|
+
var layerMergeAllWithDependencies = createDiagnostic({
|
|
4840
|
+
name: "layerMergeAllWithDependencies",
|
|
4841
|
+
code: 37,
|
|
4842
|
+
description: "Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires",
|
|
4843
|
+
severity: "warning",
|
|
4844
|
+
apply: fn("layerMergeAllWithDependencies.apply")(function* (sourceFile, report) {
|
|
4845
|
+
const ts = yield* service(TypeScriptApi);
|
|
4846
|
+
const typeChecker = yield* service(TypeCheckerApi);
|
|
4847
|
+
const typeCheckerUtils = yield* service(TypeCheckerUtils);
|
|
4848
|
+
const typeParser = yield* service(TypeParser);
|
|
4849
|
+
const tsUtils = yield* service(TypeScriptUtils);
|
|
4850
|
+
const layerModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
|
|
4851
|
+
sourceFile,
|
|
4852
|
+
"effect",
|
|
4853
|
+
"Layer"
|
|
4854
|
+
) || "Layer";
|
|
4855
|
+
const nodeToVisit = [];
|
|
4856
|
+
const appendNodeToVisit = (node) => {
|
|
4857
|
+
nodeToVisit.push(node);
|
|
4858
|
+
return void 0;
|
|
4859
|
+
};
|
|
4860
|
+
ts.forEachChild(sourceFile, appendNodeToVisit);
|
|
4861
|
+
while (nodeToVisit.length > 0) {
|
|
4862
|
+
const node = nodeToVisit.shift();
|
|
4863
|
+
if (ts.isCallExpression(node)) {
|
|
4864
|
+
const checkLayerMergeAll = yield* pipe(
|
|
4865
|
+
typeParser.isNodeReferenceToEffectLayerModuleApi("mergeAll")(node.expression),
|
|
4866
|
+
orElse2(() => void_)
|
|
4867
|
+
);
|
|
4868
|
+
if (checkLayerMergeAll) {
|
|
4869
|
+
const layerArgs = node.arguments;
|
|
4870
|
+
if (layerArgs.length > 1) {
|
|
4871
|
+
const layerInfos = [];
|
|
4872
|
+
const actuallyProvidedMap = /* @__PURE__ */ new Map();
|
|
4873
|
+
for (const arg of layerArgs) {
|
|
4874
|
+
const argType = typeCheckerUtils.getTypeAtLocation(arg);
|
|
4875
|
+
if (!argType) continue;
|
|
4876
|
+
const layerTypeParsedOption = yield* option(typeParser.layerType(argType, arg));
|
|
4877
|
+
if (isNone2(layerTypeParsedOption)) continue;
|
|
4878
|
+
const layerTypeParsed = layerTypeParsedOption.value;
|
|
4879
|
+
const providedMembers = typeCheckerUtils.unrollUnionMembers(layerTypeParsed.ROut);
|
|
4880
|
+
for (const providedType of providedMembers) {
|
|
4881
|
+
if (providedType.flags & ts.TypeFlags.Never) continue;
|
|
4882
|
+
const isPassThrough = typeChecker.isTypeAssignableTo(providedType, layerTypeParsed.RIn);
|
|
4883
|
+
if (!isPassThrough) {
|
|
4884
|
+
actuallyProvidedMap.set(providedType, arg);
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
layerInfos.push({
|
|
4888
|
+
arg,
|
|
4889
|
+
requirementsType: layerTypeParsed.RIn
|
|
4890
|
+
});
|
|
4891
|
+
}
|
|
4892
|
+
const providerToConsumers = /* @__PURE__ */ new Map();
|
|
4893
|
+
for (const layer of layerInfos) {
|
|
4894
|
+
for (const [providedType, providerArg] of actuallyProvidedMap) {
|
|
4895
|
+
if (providerArg === layer.arg) continue;
|
|
4896
|
+
if (typeChecker.isTypeAssignableTo(providedType, layer.requirementsType)) {
|
|
4897
|
+
const consumers = providerToConsumers.get(providerArg) || [];
|
|
4898
|
+
consumers.push({ consumer: layer.arg, providedType });
|
|
4899
|
+
providerToConsumers.set(providerArg, consumers);
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4903
|
+
for (const [providerArg, consumers] of providerToConsumers) {
|
|
4904
|
+
const providedTypes = Array.from(new Set(consumers.map((c) => typeChecker.typeToString(c.providedType)))).join(", ");
|
|
4905
|
+
report({
|
|
4906
|
+
location: providerArg,
|
|
4907
|
+
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.`,
|
|
4908
|
+
fixes: [{
|
|
4909
|
+
fixName: "layerMergeAllWithDependencies_fix",
|
|
4910
|
+
description: "Move layer to Layer.provideMerge",
|
|
4911
|
+
apply: gen(function* () {
|
|
4912
|
+
const changeTracker = yield* service(ChangeTracker);
|
|
4913
|
+
const providerIndex = layerArgs.indexOf(providerArg);
|
|
4914
|
+
if (providerIndex === -1) return;
|
|
4915
|
+
const providerArgNode = providerArg;
|
|
4916
|
+
if (providerIndex === 0 && layerArgs.length > 1) {
|
|
4917
|
+
changeTracker.deleteRange(sourceFile, {
|
|
4918
|
+
pos: providerArgNode.pos,
|
|
4919
|
+
end: layerArgs[1].pos
|
|
4920
|
+
});
|
|
4921
|
+
} else if (providerIndex > 0) {
|
|
4922
|
+
changeTracker.deleteRange(sourceFile, {
|
|
4923
|
+
pos: layerArgs[providerIndex - 1].end,
|
|
4924
|
+
end: providerArgNode.end
|
|
4925
|
+
});
|
|
4926
|
+
}
|
|
4927
|
+
const provideMergeCall = ts.factory.createCallExpression(
|
|
4928
|
+
ts.factory.createPropertyAccessExpression(
|
|
4929
|
+
ts.factory.createIdentifier(layerModuleIdentifier),
|
|
4930
|
+
ts.factory.createIdentifier("provideMerge")
|
|
4931
|
+
),
|
|
4932
|
+
void 0,
|
|
4933
|
+
[providerArgNode]
|
|
4934
|
+
);
|
|
4935
|
+
changeTracker.insertNodeAt(sourceFile, node.end, provideMergeCall, {
|
|
4936
|
+
prefix: ".pipe("
|
|
4937
|
+
});
|
|
4938
|
+
changeTracker.insertText(sourceFile, node.end, ")");
|
|
4939
|
+
})
|
|
4940
|
+
}]
|
|
4941
|
+
});
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
4947
|
+
}
|
|
4948
|
+
})
|
|
4949
|
+
});
|
|
4950
|
+
|
|
4646
4951
|
// src/diagnostics/leakingRequirements.ts
|
|
4647
4952
|
var leakingRequirements = createDiagnostic({
|
|
4648
4953
|
name: "leakingRequirements",
|
|
@@ -4741,6 +5046,7 @@ var leakingRequirements = createDiagnostic({
|
|
|
4741
5046
|
location: node,
|
|
4742
5047
|
messageText: `This Service is leaking the ${requirements.map((_) => typeChecker.typeToString(_)).join(" | ")} requirement.
|
|
4743
5048
|
If these requirements cannot be cached and are expected to be provided per method invocation (e.g. HttpServerRequest), you can either safely disable this diagnostic for this line through quickfixes or mark the service declaration with a JSDoc @effect-leakable-service.
|
|
5049
|
+
Services should usually be collected in the layer creation body, and then provided at each method that requires them.
|
|
4744
5050
|
More info at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
|
|
4745
5051
|
fixes: []
|
|
4746
5052
|
});
|
|
@@ -5150,6 +5456,55 @@ var missingEffectServiceDependency = createDiagnostic({
|
|
|
5150
5456
|
})
|
|
5151
5457
|
});
|
|
5152
5458
|
|
|
5459
|
+
// src/diagnostics/missingLayerContext.ts
|
|
5460
|
+
var missingLayerContext = createDiagnostic({
|
|
5461
|
+
name: "missingLayerContext",
|
|
5462
|
+
code: 38,
|
|
5463
|
+
description: "Reports missing service requirements in Layer context channel",
|
|
5464
|
+
severity: "error",
|
|
5465
|
+
apply: fn("missingLayerContext.apply")(function* (sourceFile, report) {
|
|
5466
|
+
const typeChecker = yield* service(TypeCheckerApi);
|
|
5467
|
+
const typeParser = yield* service(TypeParser);
|
|
5468
|
+
const typeCheckerUtils = yield* service(TypeCheckerUtils);
|
|
5469
|
+
const checkForMissingContextTypes = (node, expectedType, valueNode, realType) => pipe(
|
|
5470
|
+
all(
|
|
5471
|
+
typeParser.layerType(expectedType, node),
|
|
5472
|
+
typeParser.layerType(realType, valueNode)
|
|
5473
|
+
),
|
|
5474
|
+
map4(
|
|
5475
|
+
([expectedLayer, realLayer]) => typeCheckerUtils.getMissingTypeEntriesInTargetType(
|
|
5476
|
+
realLayer.RIn,
|
|
5477
|
+
expectedLayer.RIn
|
|
5478
|
+
)
|
|
5479
|
+
)
|
|
5480
|
+
);
|
|
5481
|
+
const sortTypes = sort(typeCheckerUtils.deterministicTypeOrder);
|
|
5482
|
+
const entries = getEffectLspPatchSourceFileMetadata(sourceFile)?.relationErrors || typeCheckerUtils.expectedAndRealType(sourceFile);
|
|
5483
|
+
for (const [node, expectedType, valueNode, realType] of entries) {
|
|
5484
|
+
if (expectedType !== realType) {
|
|
5485
|
+
yield* pipe(
|
|
5486
|
+
checkForMissingContextTypes(
|
|
5487
|
+
node,
|
|
5488
|
+
expectedType,
|
|
5489
|
+
valueNode,
|
|
5490
|
+
realType
|
|
5491
|
+
),
|
|
5492
|
+
map4(
|
|
5493
|
+
(missingTypes) => missingTypes.length > 0 ? report(
|
|
5494
|
+
{
|
|
5495
|
+
location: node,
|
|
5496
|
+
messageText: `Missing '${sortTypes(missingTypes).map((_) => typeChecker.typeToString(_)).join(" | ")}' in the expected Layer context.`,
|
|
5497
|
+
fixes: []
|
|
5498
|
+
}
|
|
5499
|
+
) : void 0
|
|
5500
|
+
),
|
|
5501
|
+
ignore
|
|
5502
|
+
);
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
})
|
|
5506
|
+
});
|
|
5507
|
+
|
|
5153
5508
|
// src/diagnostics/missingReturnYieldStar.ts
|
|
5154
5509
|
var missingReturnYieldStar = createDiagnostic({
|
|
5155
5510
|
name: "missingReturnYieldStar",
|
|
@@ -5687,9 +6042,11 @@ var parse2 = fn("writeTagClassAccessors.parse")(function* (node) {
|
|
|
5687
6042
|
const typeParser = yield* service(TypeParser);
|
|
5688
6043
|
const typeCheckerUtils = yield* service(TypeCheckerUtils);
|
|
5689
6044
|
if (!ts.isClassDeclaration(node)) return yield* fail("not a class declaration");
|
|
5690
|
-
const { Service, accessors: accessors2, className } = yield* pipe(
|
|
5691
|
-
typeParser.extendsEffectService(node),
|
|
5692
|
-
orElse2(
|
|
6045
|
+
const { Service, accessors: accessors2, className, kind } = yield* pipe(
|
|
6046
|
+
map4(typeParser.extendsEffectService(node), (_) => ({ kind: "effectService", ..._ })),
|
|
6047
|
+
orElse2(
|
|
6048
|
+
() => map4(typeParser.extendsEffectTag(node), (_) => ({ kind: "effectTag", accessors: true, ..._ }))
|
|
6049
|
+
),
|
|
5693
6050
|
orElse2(() => fail("not a class extending Effect.Service call"))
|
|
5694
6051
|
);
|
|
5695
6052
|
if (accessors2 !== true) return yield* fail("accessors are not enabled in the Effect.Service call");
|
|
@@ -5711,7 +6068,7 @@ var parse2 = fn("writeTagClassAccessors.parse")(function* (node) {
|
|
|
5711
6068
|
const hash2 = involvedMembers.map(({ property, propertyType }) => {
|
|
5712
6069
|
return ts.symbolName(property) + ": " + typeChecker.typeToString(propertyType);
|
|
5713
6070
|
}).concat([ts.idText(className)]).join("\n");
|
|
5714
|
-
return { Service, className, atLocation: node, hash: cyrb53(hash2), involvedMembers };
|
|
6071
|
+
return { Service, className, atLocation: node, hash: cyrb53(hash2), involvedMembers, kind };
|
|
5715
6072
|
});
|
|
5716
6073
|
var writeTagClassAccessors = createRefactor({
|
|
5717
6074
|
name: "writeTagClassAccessors",
|
|
@@ -7636,9 +7993,10 @@ var unsupportedServiceAccessors = createDiagnostic({
|
|
|
7636
7993
|
);
|
|
7637
7994
|
if (missingMembers.length > 0) {
|
|
7638
7995
|
const memberNames = missingMembers.map(({ property }) => `'${ts.symbolName(property)}'`).join(", ");
|
|
7996
|
+
const suggestedFix = parseResult.kind === "effectTag" ? "\nEffect.Tag does not allow to disable accessors, so you may want to use Context.Tag instead." : "";
|
|
7639
7997
|
report({
|
|
7640
7998
|
location: parseResult.className,
|
|
7641
|
-
messageText: `Even if accessors are enabled, accessors for ${memberNames} won't be available because the signature have generic type parameters or multiple call signatures
|
|
7999
|
+
messageText: `Even if accessors are enabled, accessors for ${memberNames} won't be available because the signature have generic type parameters or multiple call signatures.${suggestedFix}`,
|
|
7642
8000
|
fixes: [{
|
|
7643
8001
|
fixName: "unsupportedServiceAccessors_enableCodegen",
|
|
7644
8002
|
description: "Enable accessors codegen",
|
|
@@ -7659,6 +8017,7 @@ var unsupportedServiceAccessors = createDiagnostic({
|
|
|
7659
8017
|
// src/diagnostics.ts
|
|
7660
8018
|
var diagnostics = [
|
|
7661
8019
|
anyUnknownInErrorContext,
|
|
8020
|
+
catchAllToMapError,
|
|
7662
8021
|
catchUnfailableEffect,
|
|
7663
8022
|
classSelfMismatch,
|
|
7664
8023
|
duplicatePackage,
|
|
@@ -7666,6 +8025,7 @@ var diagnostics = [
|
|
|
7666
8025
|
missingEffectContext,
|
|
7667
8026
|
missingEffectError,
|
|
7668
8027
|
missingEffectServiceDependency,
|
|
8028
|
+
missingLayerContext,
|
|
7669
8029
|
floatingEffect,
|
|
7670
8030
|
missingStarInYieldEffectGen,
|
|
7671
8031
|
unnecessaryEffectGen,
|
|
@@ -7693,7 +8053,9 @@ var diagnostics = [
|
|
|
7693
8053
|
runEffectInsideEffect,
|
|
7694
8054
|
schemaUnionOfLiterals,
|
|
7695
8055
|
schemaStructWithTag,
|
|
7696
|
-
|
|
8056
|
+
globalErrorInEffectCatch,
|
|
8057
|
+
globalErrorInEffectFailure,
|
|
8058
|
+
layerMergeAllWithDependencies
|
|
7697
8059
|
];
|
|
7698
8060
|
|
|
7699
8061
|
// src/transform.ts
|