@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/README.md CHANGED
@@ -78,12 +78,13 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
78
78
  - Warn when using `Schema.Union` with multiple `Schema.Literal` calls that can be simplified to a single `Schema.Literal` call
79
79
  - Suggest using `Schema.TaggedStruct` instead of `Schema.Struct` when a `_tag` field with `Schema.Literal` is present to make the tag optional in the constructor
80
80
  - Warn when using `yield* Effect.fail()` with yieldable error types that can be yielded directly
81
- - Warn when using `Effect.fail` with the global `Error` type, recommending tagged errors
81
+ - Warn when the global `Error` type is used in an Effect failure channel, recommending tagged errors
82
82
  - Warn when `Layer.mergeAll` contains layers with interdependencies (where one layer provides a service that another layer in the same call requires)
83
83
  - Suggest using `Effect.fn` for functions that return `Effect.gen` for better tracing and concise syntax
84
84
  - Warn when `Effect.fn` or `Effect.fnUntraced` is used as an IIFE (Immediately Invoked Function Expression), suggesting `Effect.gen` instead
85
85
  - Suggest removing redundant identifier argument when it equals the tag value in `Schema.TaggedClass`, `Schema.TaggedError`, or `Schema.TaggedRequest`
86
86
  - Suggest using `Schema.is` instead of `instanceof` for Effect Schema types
87
+ - Suggest using `Effect.void` instead of `Effect.succeed(undefined)` or `Effect.succeed(void 0)`
87
88
 
88
89
  ### Completions
89
90
 
package/cli.js CHANGED
@@ -30214,7 +30214,7 @@ var runMain3 = runMain2;
30214
30214
  // package.json
30215
30215
  var package_default = {
30216
30216
  name: "@effect/language-service",
30217
- version: "0.69.2",
30217
+ version: "0.71.0",
30218
30218
  packageManager: "pnpm@8.11.0",
30219
30219
  publishConfig: {
30220
30220
  access: "public",
@@ -36899,6 +36899,57 @@ var effectMapVoid = createDiagnostic({
36899
36899
  })
36900
36900
  });
36901
36901
 
36902
+ // src/diagnostics/effectSucceedWithVoid.ts
36903
+ var effectSucceedWithVoid = createDiagnostic({
36904
+ name: "effectSucceedWithVoid",
36905
+ code: 47,
36906
+ description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
36907
+ severity: "suggestion",
36908
+ apply: fn2("effectSucceedWithVoid.apply")(function* (sourceFile, report) {
36909
+ const ts = yield* service2(TypeScriptApi);
36910
+ const typeParser = yield* service2(TypeParser);
36911
+ const tsUtils = yield* service2(TypeScriptUtils);
36912
+ const nodeToVisit = [];
36913
+ const appendNodeToVisit = (node) => {
36914
+ nodeToVisit.push(node);
36915
+ return void 0;
36916
+ };
36917
+ ts.forEachChild(sourceFile, appendNodeToVisit);
36918
+ while (nodeToVisit.length > 0) {
36919
+ const node = nodeToVisit.shift();
36920
+ ts.forEachChild(node, appendNodeToVisit);
36921
+ if (ts.isCallExpression(node)) {
36922
+ const isSucceedCall = yield* pipe(
36923
+ typeParser.isNodeReferenceToEffectModuleApi("succeed")(node.expression),
36924
+ option5
36925
+ );
36926
+ if (isSome2(isSucceedCall)) {
36927
+ const argument = node.arguments[0];
36928
+ if (!argument) continue;
36929
+ if (!tsUtils.isVoidExpression(argument)) continue;
36930
+ report({
36931
+ location: node,
36932
+ messageText: "Effect.void can be used instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
36933
+ fixes: [{
36934
+ fixName: "effectSucceedWithVoid_fix",
36935
+ description: "Replace with Effect.void",
36936
+ apply: gen3(function* () {
36937
+ const changeTracker = yield* service2(ChangeTracker);
36938
+ const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
36939
+ const newNode = ts.factory.createPropertyAccessExpression(
36940
+ ts.factory.createIdentifier(effectModuleIdentifier),
36941
+ ts.factory.createIdentifier("void")
36942
+ );
36943
+ changeTracker.replaceNode(sourceFile, node, newNode);
36944
+ })
36945
+ }]
36946
+ });
36947
+ }
36948
+ }
36949
+ }
36950
+ })
36951
+ });
36952
+
36902
36953
  // src/diagnostics/floatingEffect.ts
36903
36954
  var floatingEffect = createDiagnostic({
36904
36955
  name: "floatingEffect",
@@ -37042,8 +37093,7 @@ var globalErrorInEffectCatch = createDiagnostic({
37042
37093
  );
37043
37094
  report({
37044
37095
  location: node.expression,
37045
- 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.
37046
- Instead, use tagged errors or custom errors with a discriminator property to get properly type-checked errors.`,
37096
+ 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.`,
37047
37097
  fixes: []
37048
37098
  });
37049
37099
  }
@@ -37060,7 +37110,7 @@ Instead, use tagged errors or custom errors with a discriminator property to get
37060
37110
  var globalErrorInEffectFailure = createDiagnostic({
37061
37111
  name: "globalErrorInEffectFailure",
37062
37112
  code: 35,
37063
- description: "Warns when Effect.fail is called with the global Error type",
37113
+ description: "Warns when the global Error type is used in an Effect failure channel",
37064
37114
  severity: "warning",
37065
37115
  apply: fn2("globalErrorInEffectFailure.apply")(function* (sourceFile, report) {
37066
37116
  const ts = yield* service2(TypeScriptApi);
@@ -37075,27 +37125,35 @@ var globalErrorInEffectFailure = createDiagnostic({
37075
37125
  while (nodeToVisit.length > 0) {
37076
37126
  const node = nodeToVisit.shift();
37077
37127
  ts.forEachChild(node, appendNodeToVisit);
37078
- if (ts.isCallExpression(node)) {
37079
- yield* pipe(
37080
- typeParser.isNodeReferenceToEffectModuleApi("fail")(node.expression),
37081
- flatMap18(() => {
37082
- if (node.arguments.length > 0) {
37083
- const failArgument = node.arguments[0];
37084
- const argumentType = typeCheckerUtils.getTypeAtLocation(failArgument);
37085
- if (argumentType && typeCheckerUtils.isGlobalErrorType(argumentType)) {
37086
- return sync11(
37087
- () => report({
37088
- location: node,
37089
- 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.`,
37090
- fixes: []
37091
- })
37092
- );
37128
+ if (ts.isNewExpression(node)) {
37129
+ const newExpressionType = typeCheckerUtils.getTypeAtLocation(node);
37130
+ if (!newExpressionType || !typeCheckerUtils.isGlobalErrorType(newExpressionType)) {
37131
+ continue;
37132
+ }
37133
+ let current = node.parent;
37134
+ while (current) {
37135
+ const currentType = typeCheckerUtils.getTypeAtLocation(current);
37136
+ if (currentType) {
37137
+ const effectTypeResult = yield* pipe(
37138
+ typeParser.effectType(currentType, current),
37139
+ option5
37140
+ );
37141
+ if (effectTypeResult._tag === "Some") {
37142
+ const effectType = effectTypeResult.value;
37143
+ const failureMembers = typeCheckerUtils.unrollUnionMembers(effectType.E);
37144
+ const hasGlobalError = failureMembers.some((member) => typeCheckerUtils.isGlobalErrorType(member));
37145
+ if (hasGlobalError) {
37146
+ report({
37147
+ location: node,
37148
+ 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.`,
37149
+ fixes: []
37150
+ });
37093
37151
  }
37152
+ break;
37094
37153
  }
37095
- return void_8;
37096
- }),
37097
- ignore3
37098
- );
37154
+ }
37155
+ current = current.parent;
37156
+ }
37099
37157
  }
37100
37158
  }
37101
37159
  })
@@ -37684,9 +37742,29 @@ var missedPipeableOpportunity = createDiagnostic({
37684
37742
  transformations: flow2.transformations.slice(0, firstPipeableIndex)
37685
37743
  });
37686
37744
  const afterTransformations = flow2.transformations.slice(pipeableEndIndex);
37745
+ const getOriginalSubjectNode = () => {
37746
+ if (firstPipeableIndex === 0) {
37747
+ return flow2.subject.node;
37748
+ }
37749
+ let current = flow2.node;
37750
+ for (let i = flow2.transformations.length; i > firstPipeableIndex; i--) {
37751
+ const t = flow2.transformations[i - 1];
37752
+ if (t.kind === "call" && ts.isCallExpression(current) && current.arguments.length > 0) {
37753
+ current = current.arguments[0];
37754
+ } else {
37755
+ return void 0;
37756
+ }
37757
+ }
37758
+ return current;
37759
+ };
37760
+ const originalSubjectNode = getOriginalSubjectNode();
37761
+ const subjectText = originalSubjectNode ? sourceFile.text.slice(
37762
+ ts.getTokenPosOfNode(originalSubjectNode, sourceFile),
37763
+ originalSubjectNode.end
37764
+ ).trim() : "";
37687
37765
  report({
37688
37766
  location: flow2.node,
37689
- messageText: `Nested function calls can be converted to pipeable style for better readability.`,
37767
+ messageText: `Nested function calls can be converted to pipeable style for better readability; consider using ${subjectText}.pipe(...) instead.`,
37690
37768
  fixes: [{
37691
37769
  fixName: "missedPipeableOpportunity_fix",
37692
37770
  description: "Convert to pipe style",
@@ -39741,6 +39819,7 @@ var diagnostics = [
39741
39819
  globalErrorInEffectFailure,
39742
39820
  layerMergeAllWithDependencies,
39743
39821
  effectMapVoid,
39822
+ effectSucceedWithVoid,
39744
39823
  effectFnIife,
39745
39824
  effectFnOpportunity,
39746
39825
  redundantSchemaTagIdentifier,