@effect/language-service 0.69.2 → 0.71.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/language-service",
3
- "version": "0.69.2",
3
+ "version": "0.71.0",
4
4
  "description": "A Language-Service Plugin to Refactor and Diagnostic effect-ts projects",
5
5
  "main": "index.cjs",
6
6
  "bin": {
package/transform.js CHANGED
@@ -5484,6 +5484,57 @@ var effectMapVoid = createDiagnostic({
5484
5484
  })
5485
5485
  });
5486
5486
 
5487
+ // src/diagnostics/effectSucceedWithVoid.ts
5488
+ var effectSucceedWithVoid = createDiagnostic({
5489
+ name: "effectSucceedWithVoid",
5490
+ code: 47,
5491
+ description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
5492
+ severity: "suggestion",
5493
+ apply: fn("effectSucceedWithVoid.apply")(function* (sourceFile, report) {
5494
+ const ts = yield* service(TypeScriptApi);
5495
+ const typeParser = yield* service(TypeParser);
5496
+ const tsUtils = yield* service(TypeScriptUtils);
5497
+ const nodeToVisit = [];
5498
+ const appendNodeToVisit = (node) => {
5499
+ nodeToVisit.push(node);
5500
+ return void 0;
5501
+ };
5502
+ ts.forEachChild(sourceFile, appendNodeToVisit);
5503
+ while (nodeToVisit.length > 0) {
5504
+ const node = nodeToVisit.shift();
5505
+ ts.forEachChild(node, appendNodeToVisit);
5506
+ if (ts.isCallExpression(node)) {
5507
+ const isSucceedCall = yield* pipe(
5508
+ typeParser.isNodeReferenceToEffectModuleApi("succeed")(node.expression),
5509
+ option
5510
+ );
5511
+ if (isSome2(isSucceedCall)) {
5512
+ const argument = node.arguments[0];
5513
+ if (!argument) continue;
5514
+ if (!tsUtils.isVoidExpression(argument)) continue;
5515
+ report({
5516
+ location: node,
5517
+ messageText: "Effect.void can be used instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
5518
+ fixes: [{
5519
+ fixName: "effectSucceedWithVoid_fix",
5520
+ description: "Replace with Effect.void",
5521
+ apply: gen(function* () {
5522
+ const changeTracker = yield* service(ChangeTracker);
5523
+ const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
5524
+ const newNode = ts.factory.createPropertyAccessExpression(
5525
+ ts.factory.createIdentifier(effectModuleIdentifier),
5526
+ ts.factory.createIdentifier("void")
5527
+ );
5528
+ changeTracker.replaceNode(sourceFile, node, newNode);
5529
+ })
5530
+ }]
5531
+ });
5532
+ }
5533
+ }
5534
+ }
5535
+ })
5536
+ });
5537
+
5487
5538
  // src/diagnostics/floatingEffect.ts
5488
5539
  var floatingEffect = createDiagnostic({
5489
5540
  name: "floatingEffect",
@@ -5627,8 +5678,7 @@ var globalErrorInEffectCatch = createDiagnostic({
5627
5678
  );
5628
5679
  report({
5629
5680
  location: node.expression,
5630
- 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.
5631
- Instead, use tagged errors or custom errors with a discriminator property to get properly type-checked errors.`,
5681
+ messageText: `The 'catch' callback in ${nodeText} returns global 'Error', which loses type safety as untagged errors merge together. Consider using a tagged error and optionally wrapping the original in a 'cause' property.`,
5632
5682
  fixes: []
5633
5683
  });
5634
5684
  }
@@ -5645,7 +5695,7 @@ Instead, use tagged errors or custom errors with a discriminator property to get
5645
5695
  var globalErrorInEffectFailure = createDiagnostic({
5646
5696
  name: "globalErrorInEffectFailure",
5647
5697
  code: 35,
5648
- description: "Warns when Effect.fail is called with the global Error type",
5698
+ description: "Warns when the global Error type is used in an Effect failure channel",
5649
5699
  severity: "warning",
5650
5700
  apply: fn("globalErrorInEffectFailure.apply")(function* (sourceFile, report) {
5651
5701
  const ts = yield* service(TypeScriptApi);
@@ -5660,27 +5710,35 @@ var globalErrorInEffectFailure = createDiagnostic({
5660
5710
  while (nodeToVisit.length > 0) {
5661
5711
  const node = nodeToVisit.shift();
5662
5712
  ts.forEachChild(node, appendNodeToVisit);
5663
- if (ts.isCallExpression(node)) {
5664
- yield* pipe(
5665
- typeParser.isNodeReferenceToEffectModuleApi("fail")(node.expression),
5666
- flatMap2(() => {
5667
- if (node.arguments.length > 0) {
5668
- const failArgument = node.arguments[0];
5669
- const argumentType = typeCheckerUtils.getTypeAtLocation(failArgument);
5670
- if (argumentType && typeCheckerUtils.isGlobalErrorType(argumentType)) {
5671
- return sync(
5672
- () => report({
5673
- location: node,
5674
- 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.`,
5675
- fixes: []
5676
- })
5677
- );
5713
+ if (ts.isNewExpression(node)) {
5714
+ const newExpressionType = typeCheckerUtils.getTypeAtLocation(node);
5715
+ if (!newExpressionType || !typeCheckerUtils.isGlobalErrorType(newExpressionType)) {
5716
+ continue;
5717
+ }
5718
+ let current = node.parent;
5719
+ while (current) {
5720
+ const currentType = typeCheckerUtils.getTypeAtLocation(current);
5721
+ if (currentType) {
5722
+ const effectTypeResult = yield* pipe(
5723
+ typeParser.effectType(currentType, current),
5724
+ option
5725
+ );
5726
+ if (effectTypeResult._tag === "Some") {
5727
+ const effectType = effectTypeResult.value;
5728
+ const failureMembers = typeCheckerUtils.unrollUnionMembers(effectType.E);
5729
+ const hasGlobalError = failureMembers.some((member) => typeCheckerUtils.isGlobalErrorType(member));
5730
+ if (hasGlobalError) {
5731
+ report({
5732
+ location: node,
5733
+ messageText: `Global 'Error' loses type safety as untagged errors merge together in the Effect failure channel. Consider using a tagged error and optionally wrapping the original in a 'cause' property.`,
5734
+ fixes: []
5735
+ });
5678
5736
  }
5737
+ break;
5679
5738
  }
5680
- return void_;
5681
- }),
5682
- ignore
5683
- );
5739
+ }
5740
+ current = current.parent;
5741
+ }
5684
5742
  }
5685
5743
  }
5686
5744
  })
@@ -6269,9 +6327,29 @@ var missedPipeableOpportunity = createDiagnostic({
6269
6327
  transformations: flow2.transformations.slice(0, firstPipeableIndex)
6270
6328
  });
6271
6329
  const afterTransformations = flow2.transformations.slice(pipeableEndIndex);
6330
+ const getOriginalSubjectNode = () => {
6331
+ if (firstPipeableIndex === 0) {
6332
+ return flow2.subject.node;
6333
+ }
6334
+ let current = flow2.node;
6335
+ for (let i = flow2.transformations.length; i > firstPipeableIndex; i--) {
6336
+ const t = flow2.transformations[i - 1];
6337
+ if (t.kind === "call" && ts.isCallExpression(current) && current.arguments.length > 0) {
6338
+ current = current.arguments[0];
6339
+ } else {
6340
+ return void 0;
6341
+ }
6342
+ }
6343
+ return current;
6344
+ };
6345
+ const originalSubjectNode = getOriginalSubjectNode();
6346
+ const subjectText = originalSubjectNode ? sourceFile.text.slice(
6347
+ ts.getTokenPosOfNode(originalSubjectNode, sourceFile),
6348
+ originalSubjectNode.end
6349
+ ).trim() : "";
6272
6350
  report({
6273
6351
  location: flow2.node,
6274
- messageText: `Nested function calls can be converted to pipeable style for better readability.`,
6352
+ messageText: `Nested function calls can be converted to pipeable style for better readability; consider using ${subjectText}.pipe(...) instead.`,
6275
6353
  fixes: [{
6276
6354
  fixName: "missedPipeableOpportunity_fix",
6277
6355
  description: "Convert to pipe style",
@@ -9359,6 +9437,7 @@ var diagnostics = [
9359
9437
  globalErrorInEffectFailure,
9360
9438
  layerMergeAllWithDependencies,
9361
9439
  effectMapVoid,
9440
+ effectSucceedWithVoid,
9362
9441
  effectFnIife,
9363
9442
  effectFnOpportunity,
9364
9443
  redundantSchemaTagIdentifier,