@effect/language-service 0.69.0 → 0.69.2

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.
@@ -1225,6 +1225,7 @@ var defaults = {
1225
1225
  }],
1226
1226
  extendedKeyDetection: false,
1227
1227
  pipeableMinArgCount: 2,
1228
+ effectFn: ["span"],
1228
1229
  layerGraphFollowDepth: 0,
1229
1230
  mermaidProvider: "mermaid.live"
1230
1231
  };
@@ -1264,6 +1265,7 @@ function parse(config) {
1264
1265
  keyPatterns: isObject(config) && hasProperty(config, "keyPatterns") && isArray(config.keyPatterns) ? parseKeyPatterns(config.keyPatterns) : defaults.keyPatterns,
1265
1266
  extendedKeyDetection: isObject(config) && hasProperty(config, "extendedKeyDetection") && isBoolean(config.extendedKeyDetection) ? config.extendedKeyDetection : defaults.extendedKeyDetection,
1266
1267
  pipeableMinArgCount: isObject(config) && hasProperty(config, "pipeableMinArgCount") && isNumber(config.pipeableMinArgCount) ? config.pipeableMinArgCount : defaults.pipeableMinArgCount,
1268
+ effectFn: isObject(config) && hasProperty(config, "effectFn") && isArray(config.effectFn) && config.effectFn.every(isString) ? config.effectFn.map((_) => _.toLowerCase()) : defaults.effectFn,
1267
1269
  layerGraphFollowDepth: isObject(config) && hasProperty(config, "layerGraphFollowDepth") && isNumber(config.layerGraphFollowDepth) ? config.layerGraphFollowDepth : defaults.layerGraphFollowDepth,
1268
1270
  mermaidProvider: isObject(config) && hasProperty(config, "mermaidProvider") && isString(config.mermaidProvider) ? config.mermaidProvider : defaults.mermaidProvider
1269
1271
  };
@@ -3079,6 +3081,7 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3079
3081
  node
3080
3082
  );
3081
3083
  }
3084
+ const traceExpression = ts.isCallExpression(node.expression) && node.expression.arguments.length > 0 ? node.expression.arguments[0] : void 0;
3082
3085
  const propertyAccess = expressionToTest;
3083
3086
  const pipeArguments2 = node.arguments.slice(1);
3084
3087
  return pipe(
@@ -3088,7 +3091,8 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3088
3091
  generatorFunction,
3089
3092
  effectModule: propertyAccess.expression,
3090
3093
  body: generatorFunction.body,
3091
- pipeArguments: pipeArguments2
3094
+ pipeArguments: pipeArguments2,
3095
+ traceExpression
3092
3096
  }))
3093
3097
  );
3094
3098
  },
@@ -3171,6 +3175,7 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3171
3175
  if (!ts.isPropertyAccessExpression(expressionToTest)) {
3172
3176
  return typeParserIssue("Node is not a property access expression", void 0, node);
3173
3177
  }
3178
+ const traceExpression = ts.isCallExpression(node.expression) && node.expression.arguments.length > 0 ? node.expression.arguments[0] : void 0;
3174
3179
  const propertyAccess = expressionToTest;
3175
3180
  const pipeArguments2 = node.arguments.slice(1);
3176
3181
  return pipe(
@@ -3179,7 +3184,8 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3179
3184
  node,
3180
3185
  effectModule: propertyAccess.expression,
3181
3186
  regularFunction,
3182
- pipeArguments: pipeArguments2
3187
+ pipeArguments: pipeArguments2,
3188
+ traceExpression
3183
3189
  }))
3184
3190
  );
3185
3191
  },
@@ -4888,7 +4894,8 @@ var effectFnIife = createDiagnostic({
4888
4894
  kind: "fn",
4889
4895
  effectModule: result.effectModule,
4890
4896
  generatorFunction: result.generatorFunction,
4891
- pipeArguments: result.pipeArguments
4897
+ pipeArguments: result.pipeArguments,
4898
+ traceExpression: result.traceExpression
4892
4899
  })),
4893
4900
  orElse2(
4894
4901
  () => pipe(
@@ -4897,7 +4904,8 @@ var effectFnIife = createDiagnostic({
4897
4904
  kind: "fnUntraced",
4898
4905
  effectModule: result.effectModule,
4899
4906
  generatorFunction: result.generatorFunction,
4900
- pipeArguments: result.pipeArguments
4907
+ pipeArguments: result.pipeArguments,
4908
+ traceExpression: void 0
4901
4909
  }))
4902
4910
  )
4903
4911
  ),
@@ -4908,14 +4916,15 @@ var effectFnIife = createDiagnostic({
4908
4916
  kind: "fn",
4909
4917
  effectModule: result.effectModule,
4910
4918
  generatorFunction: void 0,
4911
- pipeArguments: result.pipeArguments
4919
+ pipeArguments: result.pipeArguments,
4920
+ traceExpression: result.traceExpression
4912
4921
  }))
4913
4922
  )
4914
4923
  ),
4915
4924
  option
4916
4925
  );
4917
4926
  if (isNone2(parsed)) continue;
4918
- const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2 } = parsed.value;
4927
+ const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2, traceExpression } = parsed.value;
4919
4928
  const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
4920
4929
  const fixes = [];
4921
4930
  if (generatorFunction && generatorFunction.parameters.length === 0) {
@@ -4944,9 +4953,10 @@ var effectFnIife = createDiagnostic({
4944
4953
  })
4945
4954
  });
4946
4955
  }
4956
+ const traceExpressionText = traceExpression ? sourceFile.text.slice(traceExpression.pos, traceExpression.end) : void 0;
4947
4957
  report({
4948
4958
  location: node,
4949
- messageText: `${effectModuleName}.${kind} returns a reusable function that can take arguments, but here it's called immediately. Use Effect.gen instead (optionally with Effect.withSpan for tracing).`,
4959
+ messageText: `${effectModuleName}.${kind} returns a reusable function that can take arguments, but here it's called immediately. Use Effect.gen instead${traceExpressionText ? ` with Effect.withSpan(${traceExpressionText}) piped in the end to mantain tracing spans` : ``}.`,
4950
4960
  fixes
4951
4961
  });
4952
4962
  }
@@ -4965,6 +4975,7 @@ var effectFnOpportunity = createDiagnostic({
4965
4975
  const typeCheckerUtils = yield* service(TypeCheckerUtils);
4966
4976
  const typeParser = yield* service(TypeParser);
4967
4977
  const tsUtils = yield* service(TypeScriptUtils);
4978
+ const pluginOptions = yield* service(LanguageServicePluginOptions);
4968
4979
  const sourceEffectModuleName = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
4969
4980
  sourceFile,
4970
4981
  "effect",
@@ -5041,6 +5052,18 @@ var effectFnOpportunity = createDiagnostic({
5041
5052
  }
5042
5053
  return false;
5043
5054
  };
5055
+ const tryExtractWithSpanExpression = (expr) => gen(function* () {
5056
+ if (!ts.isCallExpression(expr)) return void 0;
5057
+ const callee = expr.expression;
5058
+ const isWithSpan = yield* pipe(
5059
+ typeParser.isNodeReferenceToEffectModuleApi("withSpan")(callee),
5060
+ map4(() => true),
5061
+ orElse2(() => succeed(false))
5062
+ );
5063
+ if (!isWithSpan) return void 0;
5064
+ if (expr.arguments.length === 0) return void 0;
5065
+ return expr.arguments[0];
5066
+ });
5044
5067
  const tryParseGenOpportunity = (fnNode) => gen(function* () {
5045
5068
  const bodyExpression = getBodyExpression(fnNode);
5046
5069
  if (!bodyExpression) return yield* TypeParserIssue.issue;
@@ -5051,7 +5074,15 @@ var effectFnOpportunity = createDiagnostic({
5051
5074
  );
5052
5075
  const { effectModule, generatorFunction } = yield* typeParser.effectGen(subject);
5053
5076
  const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
5054
- return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2 };
5077
+ let explicitTraceExpression;
5078
+ if (pipeArguments2.length > 0) {
5079
+ const lastArg = pipeArguments2[pipeArguments2.length - 1];
5080
+ const withSpanExpr = yield* tryExtractWithSpanExpression(lastArg);
5081
+ if (withSpanExpr) {
5082
+ explicitTraceExpression = withSpanExpr;
5083
+ }
5084
+ }
5085
+ return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2, explicitTraceExpression };
5055
5086
  });
5056
5087
  const isInsideEffectFn = (fnNode) => {
5057
5088
  const parent = fnNode.parent;
@@ -5079,6 +5110,9 @@ var effectFnOpportunity = createDiagnostic({
5079
5110
  if (ts.isFunctionExpression(node) && node.name) {
5080
5111
  return yield* TypeParserIssue.issue;
5081
5112
  }
5113
+ if (node.type) {
5114
+ return yield* TypeParserIssue.issue;
5115
+ }
5082
5116
  if (yield* isInsideEffectFn(node)) {
5083
5117
  return yield* TypeParserIssue.issue;
5084
5118
  }
@@ -5106,7 +5140,8 @@ var effectFnOpportunity = createDiagnostic({
5106
5140
  return succeed({
5107
5141
  effectModuleName: sourceEffectModuleName,
5108
5142
  pipeArguments: [],
5109
- generatorFunction: void 0
5143
+ generatorFunction: void 0,
5144
+ explicitTraceExpression: void 0
5110
5145
  });
5111
5146
  })
5112
5147
  );
@@ -5114,7 +5149,8 @@ var effectFnOpportunity = createDiagnostic({
5114
5149
  node,
5115
5150
  nameIdentifier,
5116
5151
  effectModuleName: opportunity.effectModuleName,
5117
- traceName,
5152
+ inferredTraceName: traceName,
5153
+ explicitTraceExpression: opportunity.explicitTraceExpression,
5118
5154
  pipeArguments: opportunity.pipeArguments,
5119
5155
  generatorFunction: opportunity.generatorFunction,
5120
5156
  hasParamsInPipeArgs: areParametersReferencedIn(node, opportunity.pipeArguments)
@@ -5133,7 +5169,7 @@ var effectFnOpportunity = createDiagnostic({
5133
5169
  if (ts.isArrowFunction(node)) return false;
5134
5170
  return node.asteriskToken !== void 0;
5135
5171
  };
5136
- const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceName, pipeArguments2) => {
5172
+ const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceNameOrExpression, pipeArguments2) => {
5137
5173
  const isGenerator = isGeneratorFunction(innerFunction);
5138
5174
  const newFunction = ts.factory.createFunctionExpression(
5139
5175
  void 0,
@@ -5148,11 +5184,12 @@ var effectFnOpportunity = createDiagnostic({
5148
5184
  ts.factory.createIdentifier(effectModuleName),
5149
5185
  "fn"
5150
5186
  );
5151
- if (traceName) {
5187
+ if (traceNameOrExpression) {
5188
+ const traceArg = typeof traceNameOrExpression === "string" ? ts.factory.createStringLiteral(traceNameOrExpression) : traceNameOrExpression;
5152
5189
  fnExpression = ts.factory.createCallExpression(
5153
5190
  fnExpression,
5154
5191
  void 0,
5155
- [ts.factory.createStringLiteral(traceName)]
5192
+ [traceArg]
5156
5193
  );
5157
5194
  }
5158
5195
  const effectFnCall = ts.factory.createCallExpression(fnExpression, void 0, [newFunction, ...pipeArguments2]);
@@ -5194,19 +5231,35 @@ var effectFnOpportunity = createDiagnostic({
5194
5231
  const target = yield* pipe(parseEffectFnOpportunityTarget(node), option);
5195
5232
  if (isNone2(target)) continue;
5196
5233
  if (target.value.hasParamsInPipeArgs) continue;
5197
- const { effectModuleName, nameIdentifier, node: targetNode, pipeArguments: pipeArguments2, traceName } = target.value;
5234
+ const {
5235
+ effectModuleName,
5236
+ explicitTraceExpression,
5237
+ inferredTraceName,
5238
+ nameIdentifier,
5239
+ node: targetNode,
5240
+ pipeArguments: pipeArguments2
5241
+ } = target.value;
5198
5242
  const innerFunction = target.value.generatorFunction ?? targetNode;
5199
5243
  const fixes = [];
5200
- fixes.push({
5201
- fixName: "effectFnOpportunity_toEffectFn",
5202
- description: traceName ? `Convert to Effect.fn("${traceName}")` : "Convert to Effect.fn",
5203
- apply: gen(function* () {
5204
- const changeTracker = yield* service(ChangeTracker);
5205
- const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, traceName, pipeArguments2);
5206
- changeTracker.replaceNode(sourceFile, targetNode, newNode);
5207
- })
5208
- });
5209
- if (target.value.generatorFunction) {
5244
+ if (pluginOptions.effectFn.includes("span") && explicitTraceExpression) {
5245
+ fixes.push({
5246
+ fixName: "effectFnOpportunity_toEffectFnWithSpan",
5247
+ description: "Convert to Effect.fn (with span from withSpan)",
5248
+ apply: gen(function* () {
5249
+ const changeTracker = yield* service(ChangeTracker);
5250
+ const finalPipeArguments = pipeArguments2.slice(0, -1);
5251
+ const newNode = createEffectFnNode(
5252
+ targetNode,
5253
+ innerFunction,
5254
+ effectModuleName,
5255
+ explicitTraceExpression,
5256
+ finalPipeArguments
5257
+ );
5258
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
5259
+ })
5260
+ });
5261
+ }
5262
+ if (pluginOptions.effectFn.includes("untraced") && target.value.generatorFunction) {
5210
5263
  fixes.push({
5211
5264
  fixName: "effectFnOpportunity_toEffectFnUntraced",
5212
5265
  description: "Convert to Effect.fnUntraced",
@@ -5217,10 +5270,67 @@ var effectFnOpportunity = createDiagnostic({
5217
5270
  })
5218
5271
  });
5219
5272
  }
5220
- const pipeArgsSuffix = pipeArguments2.length > 0 ? ` Effect.fn also accepts the piped transformations as additional arguments.` : ``;
5273
+ if (pluginOptions.effectFn.includes("no-span")) {
5274
+ fixes.push({
5275
+ fixName: "effectFnOpportunity_toEffectFnNoSpan",
5276
+ description: "Convert to Effect.fn (no span)",
5277
+ apply: gen(function* () {
5278
+ const changeTracker = yield* service(ChangeTracker);
5279
+ const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, void 0, pipeArguments2);
5280
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
5281
+ })
5282
+ });
5283
+ }
5284
+ if (pluginOptions.effectFn.includes("inferred-span") && inferredTraceName && !explicitTraceExpression) {
5285
+ fixes.push({
5286
+ fixName: "effectFnOpportunity_toEffectFnSpanInferred",
5287
+ description: `Convert to Effect.fn("${inferredTraceName}")`,
5288
+ apply: gen(function* () {
5289
+ const changeTracker = yield* service(ChangeTracker);
5290
+ const newNode = createEffectFnNode(
5291
+ targetNode,
5292
+ innerFunction,
5293
+ effectModuleName,
5294
+ inferredTraceName,
5295
+ pipeArguments2
5296
+ );
5297
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
5298
+ })
5299
+ });
5300
+ }
5301
+ if (fixes.length === 0) continue;
5302
+ const generateExpectedSignature = () => {
5303
+ const firstFix = fixes[0];
5304
+ if (!firstFix) return "Effect.fn(function*() { ... })";
5305
+ const typeParamNames = targetNode.typeParameters ? `<${targetNode.typeParameters.map((tp) => ts.idText(tp.name)).join(", ")}>` : "";
5306
+ const paramNames = targetNode.parameters.map((param) => {
5307
+ if (ts.isIdentifier(param.name)) {
5308
+ return ts.idText(param.name);
5309
+ }
5310
+ return "_";
5311
+ }).join(", ");
5312
+ const fnSignature = `function*${typeParamNames}(${paramNames}) { ... }`;
5313
+ const pipeArgsForWithSpan = pipeArguments2.slice(0, -1);
5314
+ const pipeArgsSuffix = (args2) => args2.length > 0 ? ", ...pipeTransformations" : "";
5315
+ switch (firstFix.fixName) {
5316
+ case "effectFnOpportunity_toEffectFnWithSpan": {
5317
+ const traceName = explicitTraceExpression ? sourceFile.text.slice(explicitTraceExpression.pos, explicitTraceExpression.end).trim() : void 0;
5318
+ return `${effectModuleName}.fn(${traceName})(${fnSignature}${pipeArgsSuffix(pipeArgsForWithSpan)})`;
5319
+ }
5320
+ case "effectFnOpportunity_toEffectFnUntraced":
5321
+ return `${effectModuleName}.fnUntraced(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
5322
+ case "effectFnOpportunity_toEffectFnNoSpan":
5323
+ return `${effectModuleName}.fn(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
5324
+ case "effectFnOpportunity_toEffectFnSpanInferred":
5325
+ return `${effectModuleName}.fn("${inferredTraceName}")(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
5326
+ default:
5327
+ return `${effectModuleName}.fn(${fnSignature})`;
5328
+ }
5329
+ };
5330
+ const expectedSignature = generateExpectedSignature();
5221
5331
  report({
5222
5332
  location: nameIdentifier ?? targetNode,
5223
- messageText: target.value.generatorFunction ? `This function could benefit from Effect.fn's automatic tracing and concise syntax, or Effect.fnUntraced to get just a more concise syntax.${pipeArgsSuffix}` : `This function could benefit from Effect.fn's automatic tracing and concise syntax.${pipeArgsSuffix}`,
5333
+ messageText: `Can be rewritten as a reusable function: ${expectedSignature}`,
5224
5334
  fixes
5225
5335
  });
5226
5336
  }