@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 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.contents;
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 completes, so it is best to use a 'return yield*' instead.`,
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