@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/index.js CHANGED
@@ -8958,6 +8958,57 @@ var effectMapVoid = createDiagnostic({
8958
8958
  })
8959
8959
  });
8960
8960
 
8961
+ // src/diagnostics/effectSucceedWithVoid.ts
8962
+ var effectSucceedWithVoid = createDiagnostic({
8963
+ name: "effectSucceedWithVoid",
8964
+ code: 47,
8965
+ description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
8966
+ severity: "suggestion",
8967
+ apply: fn("effectSucceedWithVoid.apply")(function* (sourceFile, report) {
8968
+ const ts = yield* service(TypeScriptApi);
8969
+ const typeParser = yield* service(TypeParser);
8970
+ const tsUtils = yield* service(TypeScriptUtils);
8971
+ const nodeToVisit = [];
8972
+ const appendNodeToVisit = (node) => {
8973
+ nodeToVisit.push(node);
8974
+ return void 0;
8975
+ };
8976
+ ts.forEachChild(sourceFile, appendNodeToVisit);
8977
+ while (nodeToVisit.length > 0) {
8978
+ const node = nodeToVisit.shift();
8979
+ ts.forEachChild(node, appendNodeToVisit);
8980
+ if (ts.isCallExpression(node)) {
8981
+ const isSucceedCall = yield* pipe(
8982
+ typeParser.isNodeReferenceToEffectModuleApi("succeed")(node.expression),
8983
+ option
8984
+ );
8985
+ if (isSome2(isSucceedCall)) {
8986
+ const argument = node.arguments[0];
8987
+ if (!argument) continue;
8988
+ if (!tsUtils.isVoidExpression(argument)) continue;
8989
+ report({
8990
+ location: node,
8991
+ messageText: "Effect.void can be used instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
8992
+ fixes: [{
8993
+ fixName: "effectSucceedWithVoid_fix",
8994
+ description: "Replace with Effect.void",
8995
+ apply: gen(function* () {
8996
+ const changeTracker = yield* service(ChangeTracker);
8997
+ const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
8998
+ const newNode = ts.factory.createPropertyAccessExpression(
8999
+ ts.factory.createIdentifier(effectModuleIdentifier),
9000
+ ts.factory.createIdentifier("void")
9001
+ );
9002
+ changeTracker.replaceNode(sourceFile, node, newNode);
9003
+ })
9004
+ }]
9005
+ });
9006
+ }
9007
+ }
9008
+ }
9009
+ })
9010
+ });
9011
+
8961
9012
  // src/diagnostics/floatingEffect.ts
8962
9013
  var floatingEffect = createDiagnostic({
8963
9014
  name: "floatingEffect",
@@ -9101,8 +9152,7 @@ var globalErrorInEffectCatch = createDiagnostic({
9101
9152
  );
9102
9153
  report({
9103
9154
  location: node.expression,
9104
- 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.
9105
- Instead, use tagged errors or custom errors with a discriminator property to get properly type-checked errors.`,
9155
+ 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.`,
9106
9156
  fixes: []
9107
9157
  });
9108
9158
  }
@@ -9119,7 +9169,7 @@ Instead, use tagged errors or custom errors with a discriminator property to get
9119
9169
  var globalErrorInEffectFailure = createDiagnostic({
9120
9170
  name: "globalErrorInEffectFailure",
9121
9171
  code: 35,
9122
- description: "Warns when Effect.fail is called with the global Error type",
9172
+ description: "Warns when the global Error type is used in an Effect failure channel",
9123
9173
  severity: "warning",
9124
9174
  apply: fn("globalErrorInEffectFailure.apply")(function* (sourceFile, report) {
9125
9175
  const ts = yield* service(TypeScriptApi);
@@ -9134,27 +9184,35 @@ var globalErrorInEffectFailure = createDiagnostic({
9134
9184
  while (nodeToVisit.length > 0) {
9135
9185
  const node = nodeToVisit.shift();
9136
9186
  ts.forEachChild(node, appendNodeToVisit);
9137
- if (ts.isCallExpression(node)) {
9138
- yield* pipe(
9139
- typeParser.isNodeReferenceToEffectModuleApi("fail")(node.expression),
9140
- flatMap4(() => {
9141
- if (node.arguments.length > 0) {
9142
- const failArgument = node.arguments[0];
9143
- const argumentType = typeCheckerUtils.getTypeAtLocation(failArgument);
9144
- if (argumentType && typeCheckerUtils.isGlobalErrorType(argumentType)) {
9145
- return sync(
9146
- () => report({
9147
- location: node,
9148
- 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.`,
9149
- fixes: []
9150
- })
9151
- );
9187
+ if (ts.isNewExpression(node)) {
9188
+ const newExpressionType = typeCheckerUtils.getTypeAtLocation(node);
9189
+ if (!newExpressionType || !typeCheckerUtils.isGlobalErrorType(newExpressionType)) {
9190
+ continue;
9191
+ }
9192
+ let current = node.parent;
9193
+ while (current) {
9194
+ const currentType = typeCheckerUtils.getTypeAtLocation(current);
9195
+ if (currentType) {
9196
+ const effectTypeResult = yield* pipe(
9197
+ typeParser.effectType(currentType, current),
9198
+ option
9199
+ );
9200
+ if (effectTypeResult._tag === "Some") {
9201
+ const effectType = effectTypeResult.value;
9202
+ const failureMembers = typeCheckerUtils.unrollUnionMembers(effectType.E);
9203
+ const hasGlobalError = failureMembers.some((member) => typeCheckerUtils.isGlobalErrorType(member));
9204
+ if (hasGlobalError) {
9205
+ report({
9206
+ location: node,
9207
+ 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.`,
9208
+ fixes: []
9209
+ });
9152
9210
  }
9211
+ break;
9153
9212
  }
9154
- return void_;
9155
- }),
9156
- ignore
9157
- );
9213
+ }
9214
+ current = current.parent;
9215
+ }
9158
9216
  }
9159
9217
  }
9160
9218
  })
@@ -9743,9 +9801,29 @@ var missedPipeableOpportunity = createDiagnostic({
9743
9801
  transformations: flow2.transformations.slice(0, firstPipeableIndex)
9744
9802
  });
9745
9803
  const afterTransformations = flow2.transformations.slice(pipeableEndIndex);
9804
+ const getOriginalSubjectNode = () => {
9805
+ if (firstPipeableIndex === 0) {
9806
+ return flow2.subject.node;
9807
+ }
9808
+ let current = flow2.node;
9809
+ for (let i = flow2.transformations.length; i > firstPipeableIndex; i--) {
9810
+ const t = flow2.transformations[i - 1];
9811
+ if (t.kind === "call" && ts.isCallExpression(current) && current.arguments.length > 0) {
9812
+ current = current.arguments[0];
9813
+ } else {
9814
+ return void 0;
9815
+ }
9816
+ }
9817
+ return current;
9818
+ };
9819
+ const originalSubjectNode = getOriginalSubjectNode();
9820
+ const subjectText = originalSubjectNode ? sourceFile.text.slice(
9821
+ ts.getTokenPosOfNode(originalSubjectNode, sourceFile),
9822
+ originalSubjectNode.end
9823
+ ).trim() : "";
9746
9824
  report({
9747
9825
  location: flow2.node,
9748
- messageText: `Nested function calls can be converted to pipeable style for better readability.`,
9826
+ messageText: `Nested function calls can be converted to pipeable style for better readability; consider using ${subjectText}.pipe(...) instead.`,
9749
9827
  fixes: [{
9750
9828
  fixName: "missedPipeableOpportunity_fix",
9751
9829
  description: "Convert to pipe style",
@@ -11800,6 +11878,7 @@ var diagnostics = [
11800
11878
  globalErrorInEffectFailure,
11801
11879
  layerMergeAllWithDependencies,
11802
11880
  effectMapVoid,
11881
+ effectSucceedWithVoid,
11803
11882
  effectFnIife,
11804
11883
  effectFnOpportunity,
11805
11884
  redundantSchemaTagIdentifier,