@effect/language-service 0.21.2 → 0.21.4
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 +40 -1
- package/index.js +133 -6
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/transform.js +133 -6
- package/transform.js.map +1 -1
package/README.md
CHANGED
|
@@ -6,11 +6,11 @@ This package implements a TypeScript language service plugin that allows additio
|
|
|
6
6
|
|
|
7
7
|
1. `npm install @effect/language-service --save-dev` in your project
|
|
8
8
|
2. inside your tsconfig.json, you should add the plugin configuration as follows:
|
|
9
|
-
|
|
10
9
|
```json
|
|
11
10
|
{
|
|
12
11
|
"compilerOptions": {
|
|
13
12
|
"plugins": [
|
|
13
|
+
// ... other LSPs (if any) and as last
|
|
14
14
|
{
|
|
15
15
|
"name": "@effect/language-service"
|
|
16
16
|
}
|
|
@@ -133,3 +133,42 @@ Effect.succeed(1); // This will not be reported as a floating effect
|
|
|
133
133
|
// @effect-diagnostics effect/floatingEffect:error
|
|
134
134
|
Effect.succeed(1); // This will be reported as a floating effect
|
|
135
135
|
```
|
|
136
|
+
|
|
137
|
+
or you can set the severity for the entire project in the global plugin configuration
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"compilerOptions": {
|
|
142
|
+
"plugins": [
|
|
143
|
+
{
|
|
144
|
+
// ...
|
|
145
|
+
"diagnosticSeverity": { // allows to change per-rule default severity of the diagnostic in the whole project
|
|
146
|
+
"floatingEffect": "warning" // example for a rule, allowed values are off,error,warning,message,suggestion
|
|
147
|
+
},
|
|
148
|
+
// ...
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Known gotchas
|
|
156
|
+
|
|
157
|
+
### Svelte VSCode extension and SvelteKit
|
|
158
|
+
|
|
159
|
+
The Svelte LSP does not properly compose with other LSPs when using SvelteKit. So the Effect LSP should be loaded as last entry to ensure proper composition.
|
|
160
|
+
|
|
161
|
+
If you did not installed the Svelte LSP into your local project but instead through the Svelte VSCode extension, we recommend instead to install locally and add it as first entry. That way it won't be applied by the VSCode extension.
|
|
162
|
+
|
|
163
|
+
Your tsconfig should look like this:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"compilerOptions": {
|
|
168
|
+
"plugins": [
|
|
169
|
+
{ "name": "typescript-svelte-plugin" },
|
|
170
|
+
{ "name": "@effect/language-service" }
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
package/index.js
CHANGED
|
@@ -1191,6 +1191,8 @@ function parsePackageContentNameAndVersionFromScope(v) {
|
|
|
1191
1191
|
const packageJsonContent = packageJsonScope.contents.packageJsonContent;
|
|
1192
1192
|
if (!hasProperty(packageJsonContent, "name")) return;
|
|
1193
1193
|
if (!hasProperty(packageJsonContent, "version")) return;
|
|
1194
|
+
if (!hasProperty(packageJsonScope, "packageDirectory")) return;
|
|
1195
|
+
if (!isString(packageJsonScope.packageDirectory)) return;
|
|
1194
1196
|
const { name, version } = packageJsonContent;
|
|
1195
1197
|
if (!isString(name)) return;
|
|
1196
1198
|
if (!isString(version)) return;
|
|
@@ -1199,7 +1201,8 @@ function parsePackageContentNameAndVersionFromScope(v) {
|
|
|
1199
1201
|
name: name.toLowerCase(),
|
|
1200
1202
|
version: version.toLowerCase(),
|
|
1201
1203
|
hasEffectInPeerDependencies,
|
|
1202
|
-
contents: packageJsonContent
|
|
1204
|
+
contents: packageJsonContent,
|
|
1205
|
+
packageDirectory: packageJsonScope.packageDirectory
|
|
1203
1206
|
};
|
|
1204
1207
|
}
|
|
1205
1208
|
|
|
@@ -2864,7 +2867,7 @@ var duplicatePackage = createDiagnostic({
|
|
|
2864
2867
|
if (!(packageInfo.name === "effect" || packageInfo.hasEffectInPeerDependencies)) return;
|
|
2865
2868
|
if (options.allowedDuplicatedPackages.indexOf(packageInfo.name) > -1) return;
|
|
2866
2869
|
resolvedPackages[packageInfo.name] = resolvedPackages[packageInfo.name] || {};
|
|
2867
|
-
resolvedPackages[packageInfo.name][packageInfo.version] = packageInfo.
|
|
2870
|
+
resolvedPackages[packageInfo.name][packageInfo.version] = packageInfo.packageDirectory;
|
|
2868
2871
|
});
|
|
2869
2872
|
checkedPackagesCache.set(sourceFile.fileName, resolvedPackages);
|
|
2870
2873
|
programResolvedCacheSize.set(sourceFile.fileName, newResolvedModuleSize);
|
|
@@ -2877,7 +2880,9 @@ var duplicatePackage = createDiagnostic({
|
|
|
2877
2880
|
category: ts.DiagnosticCategory.Warning,
|
|
2878
2881
|
messageText: `Package ${packageName} is referenced multiple times with different versions (${versions.join(", ")}) and may cause unexpected type errors.
|
|
2879
2882
|
Cleanup your dependencies and your package lockfile to avoid multiple instances of this package and reload the project.
|
|
2880
|
-
If this is intended set the LSP config "allowedDuplicatedPackages" to ${JSON.stringify(options.allowedDuplicatedPackages.concat([packageName]))}
|
|
2883
|
+
If this is intended set the LSP config "allowedDuplicatedPackages" to ${JSON.stringify(options.allowedDuplicatedPackages.concat([packageName]))}.
|
|
2884
|
+
|
|
2885
|
+
${versions.map((version) => `- found ${version} at ${resolvedPackages[packageName][version]}`).join("\n")}`,
|
|
2881
2886
|
fixes: []
|
|
2882
2887
|
});
|
|
2883
2888
|
}
|
|
@@ -2934,6 +2939,54 @@ var floatingEffect = createDiagnostic({
|
|
|
2934
2939
|
})
|
|
2935
2940
|
});
|
|
2936
2941
|
|
|
2942
|
+
// src/diagnostics/genericEffectServices.ts
|
|
2943
|
+
var genericEffectServices = createDiagnostic({
|
|
2944
|
+
name: "genericEffectServices",
|
|
2945
|
+
code: 10,
|
|
2946
|
+
apply: fn("genericEffectServices.apply")(function* (sourceFile) {
|
|
2947
|
+
const ts = yield* service(TypeScriptApi);
|
|
2948
|
+
const typeParser = yield* service(TypeParser);
|
|
2949
|
+
const typeChecker = yield* service(TypeCheckerApi);
|
|
2950
|
+
const effectDiagnostics = [];
|
|
2951
|
+
const nodeToVisit = [];
|
|
2952
|
+
const appendNodeToVisit = (node) => {
|
|
2953
|
+
nodeToVisit.push(node);
|
|
2954
|
+
return void 0;
|
|
2955
|
+
};
|
|
2956
|
+
ts.forEachChild(sourceFile, appendNodeToVisit);
|
|
2957
|
+
while (nodeToVisit.length > 0) {
|
|
2958
|
+
const node = nodeToVisit.shift();
|
|
2959
|
+
const typesToCheck = [];
|
|
2960
|
+
if (ts.isClassDeclaration(node) && node.name && node.typeParameters && node.heritageClauses) {
|
|
2961
|
+
const classSym = typeChecker.getSymbolAtLocation(node.name);
|
|
2962
|
+
if (classSym) {
|
|
2963
|
+
const type = typeChecker.getTypeOfSymbol(classSym);
|
|
2964
|
+
typesToCheck.push([type, node.name]);
|
|
2965
|
+
}
|
|
2966
|
+
} else {
|
|
2967
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
2968
|
+
continue;
|
|
2969
|
+
}
|
|
2970
|
+
for (const [type, reportAt] of typesToCheck) {
|
|
2971
|
+
yield* pipe(
|
|
2972
|
+
typeParser.contextTag(type, node),
|
|
2973
|
+
map4(() => {
|
|
2974
|
+
effectDiagnostics.push({
|
|
2975
|
+
node: reportAt,
|
|
2976
|
+
category: ts.DiagnosticCategory.Warning,
|
|
2977
|
+
messageText: `Effect Services with type parameters are not supported because they cannot be properly discriminated at runtime, which may cause unexpected behavior.`,
|
|
2978
|
+
fixes: []
|
|
2979
|
+
});
|
|
2980
|
+
}),
|
|
2981
|
+
orElse3(() => sync(() => ts.forEachChild(node, appendNodeToVisit))),
|
|
2982
|
+
ignore
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
return effectDiagnostics;
|
|
2987
|
+
})
|
|
2988
|
+
});
|
|
2989
|
+
|
|
2937
2990
|
// src/diagnostics/leakingRequirements.ts
|
|
2938
2991
|
var leakingRequirements = createDiagnostic({
|
|
2939
2992
|
name: "leakingRequirements",
|
|
@@ -3012,7 +3065,7 @@ var leakingRequirements = createDiagnostic({
|
|
|
3012
3065
|
const typesToCheck = [];
|
|
3013
3066
|
if (ts.isCallExpression(node)) {
|
|
3014
3067
|
typesToCheck.push([typeChecker.getTypeAtLocation(node), node]);
|
|
3015
|
-
} else if (ts.isClassDeclaration(node) && node.name) {
|
|
3068
|
+
} else if (ts.isClassDeclaration(node) && node.name && node.heritageClauses) {
|
|
3016
3069
|
const classSym = typeChecker.getSymbolAtLocation(node.name);
|
|
3017
3070
|
if (classSym) {
|
|
3018
3071
|
const type = typeChecker.getTypeOfSymbol(classSym);
|
|
@@ -3208,7 +3261,7 @@ var missingReturnYieldStar = createDiagnostic({
|
|
|
3208
3261
|
effectDiagnostics.push({
|
|
3209
3262
|
node,
|
|
3210
3263
|
category: ts.DiagnosticCategory.Error,
|
|
3211
|
-
messageText: `Yielded Effect never
|
|
3264
|
+
messageText: `Yielded Effect never succeeds, so it is best to use a 'return yield*' instead.`,
|
|
3212
3265
|
fixes: fix
|
|
3213
3266
|
});
|
|
3214
3267
|
});
|
|
@@ -3292,6 +3345,78 @@ var missingStarInYieldEffectGen = createDiagnostic({
|
|
|
3292
3345
|
})
|
|
3293
3346
|
});
|
|
3294
3347
|
|
|
3348
|
+
// src/diagnostics/returnEffectInGen.ts
|
|
3349
|
+
var returnEffectInGen = createDiagnostic({
|
|
3350
|
+
name: "returnEffectInGen",
|
|
3351
|
+
code: 11,
|
|
3352
|
+
apply: fn("returnEffectInGen.apply")(function* (sourceFile) {
|
|
3353
|
+
const ts = yield* service(TypeScriptApi);
|
|
3354
|
+
const typeChecker = yield* service(TypeCheckerApi);
|
|
3355
|
+
const typeParser = yield* service(TypeParser);
|
|
3356
|
+
const effectDiagnostics = [];
|
|
3357
|
+
const brokenReturnStatements = /* @__PURE__ */ new Set();
|
|
3358
|
+
const nodeToVisit = [];
|
|
3359
|
+
const appendNodeToVisit = (node) => {
|
|
3360
|
+
nodeToVisit.push(node);
|
|
3361
|
+
return void 0;
|
|
3362
|
+
};
|
|
3363
|
+
ts.forEachChild(sourceFile, appendNodeToVisit);
|
|
3364
|
+
while (nodeToVisit.length > 0) {
|
|
3365
|
+
const node = nodeToVisit.shift();
|
|
3366
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
3367
|
+
if (ts.isReturnStatement(node) && node.expression) {
|
|
3368
|
+
if (ts.isYieldExpression(node.expression)) continue;
|
|
3369
|
+
const generatorOrRegularFunction = ts.findAncestor(
|
|
3370
|
+
node,
|
|
3371
|
+
(_) => ts.isFunctionExpression(_) || ts.isFunctionDeclaration(_) || ts.isMethodDeclaration(_) || ts.isArrowFunction(_) || ts.isGetAccessor(_)
|
|
3372
|
+
);
|
|
3373
|
+
if (!(generatorOrRegularFunction && "asteriskToken" in generatorOrRegularFunction && generatorOrRegularFunction.asteriskToken)) continue;
|
|
3374
|
+
const type = typeChecker.getTypeAtLocation(node.expression);
|
|
3375
|
+
const maybeEffect = yield* option(typeParser.effectType(type, node.expression));
|
|
3376
|
+
if (isSome2(maybeEffect)) {
|
|
3377
|
+
const maybeEffectSubtype = yield* option(typeParser.effectSubtype(type, node.expression));
|
|
3378
|
+
if (isNone2(maybeEffectSubtype)) {
|
|
3379
|
+
if (generatorOrRegularFunction && generatorOrRegularFunction.parent) {
|
|
3380
|
+
const effectGenNode = generatorOrRegularFunction.parent;
|
|
3381
|
+
yield* pipe(
|
|
3382
|
+
typeParser.effectGen(effectGenNode),
|
|
3383
|
+
orElse3(() => typeParser.effectFnUntracedGen(effectGenNode)),
|
|
3384
|
+
orElse3(() => typeParser.effectFnGen(effectGenNode)),
|
|
3385
|
+
map4(() => brokenReturnStatements.add(node)),
|
|
3386
|
+
ignore
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
brokenReturnStatements.forEach((node) => {
|
|
3394
|
+
const fix = node.expression ? [{
|
|
3395
|
+
fixName: "returnEffectInGen_fix",
|
|
3396
|
+
description: "Add yield* statement",
|
|
3397
|
+
apply: gen2(function* () {
|
|
3398
|
+
const changeTracker = yield* service(ChangeTracker);
|
|
3399
|
+
changeTracker.replaceNode(
|
|
3400
|
+
sourceFile,
|
|
3401
|
+
node.expression,
|
|
3402
|
+
ts.factory.createYieldExpression(
|
|
3403
|
+
ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
|
|
3404
|
+
node.expression
|
|
3405
|
+
)
|
|
3406
|
+
);
|
|
3407
|
+
})
|
|
3408
|
+
}] : [];
|
|
3409
|
+
effectDiagnostics.push({
|
|
3410
|
+
node,
|
|
3411
|
+
category: ts.DiagnosticCategory.Suggestion,
|
|
3412
|
+
messageText: `You are returning an Effect-able type inside a generator function, and will result in nested Effect<Effect<...>>. Maybe you wanted to return yield* instead? Nested Effect-able types may be intended if you plan to later manually flatten or unwrap this Effect.`,
|
|
3413
|
+
fixes: fix
|
|
3414
|
+
});
|
|
3415
|
+
});
|
|
3416
|
+
return effectDiagnostics;
|
|
3417
|
+
})
|
|
3418
|
+
});
|
|
3419
|
+
|
|
3295
3420
|
// src/diagnostics/unnecessaryEffectGen.ts
|
|
3296
3421
|
var unnecessaryEffectGen = createDiagnostic({
|
|
3297
3422
|
name: "unnecessaryEffectGen",
|
|
@@ -3396,7 +3521,9 @@ var diagnostics = [
|
|
|
3396
3521
|
unnecessaryEffectGen,
|
|
3397
3522
|
missingReturnYieldStar,
|
|
3398
3523
|
leakingRequirements,
|
|
3399
|
-
unnecessaryPipe
|
|
3524
|
+
unnecessaryPipe,
|
|
3525
|
+
genericEffectServices,
|
|
3526
|
+
returnEffectInGen
|
|
3400
3527
|
];
|
|
3401
3528
|
|
|
3402
3529
|
// src/goto/effectRpcDefinition.ts
|