@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/language-service",
3
- "version": "0.69.0",
3
+ "version": "0.69.2",
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
@@ -1226,6 +1226,7 @@ var defaults = {
1226
1226
  }],
1227
1227
  extendedKeyDetection: false,
1228
1228
  pipeableMinArgCount: 2,
1229
+ effectFn: ["span"],
1229
1230
  layerGraphFollowDepth: 0,
1230
1231
  mermaidProvider: "mermaid.live"
1231
1232
  };
@@ -1265,6 +1266,7 @@ function parse(config) {
1265
1266
  keyPatterns: isObject(config) && hasProperty(config, "keyPatterns") && isArray(config.keyPatterns) ? parseKeyPatterns(config.keyPatterns) : defaults.keyPatterns,
1266
1267
  extendedKeyDetection: isObject(config) && hasProperty(config, "extendedKeyDetection") && isBoolean(config.extendedKeyDetection) ? config.extendedKeyDetection : defaults.extendedKeyDetection,
1267
1268
  pipeableMinArgCount: isObject(config) && hasProperty(config, "pipeableMinArgCount") && isNumber(config.pipeableMinArgCount) ? config.pipeableMinArgCount : defaults.pipeableMinArgCount,
1269
+ effectFn: isObject(config) && hasProperty(config, "effectFn") && isArray(config.effectFn) && config.effectFn.every(isString) ? config.effectFn.map((_) => _.toLowerCase()) : defaults.effectFn,
1268
1270
  layerGraphFollowDepth: isObject(config) && hasProperty(config, "layerGraphFollowDepth") && isNumber(config.layerGraphFollowDepth) ? config.layerGraphFollowDepth : defaults.layerGraphFollowDepth,
1269
1271
  mermaidProvider: isObject(config) && hasProperty(config, "mermaidProvider") && isString(config.mermaidProvider) ? config.mermaidProvider : defaults.mermaidProvider
1270
1272
  };
@@ -3075,6 +3077,7 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3075
3077
  node
3076
3078
  );
3077
3079
  }
3080
+ const traceExpression = ts.isCallExpression(node.expression) && node.expression.arguments.length > 0 ? node.expression.arguments[0] : void 0;
3078
3081
  const propertyAccess = expressionToTest;
3079
3082
  const pipeArguments2 = node.arguments.slice(1);
3080
3083
  return pipe(
@@ -3084,7 +3087,8 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3084
3087
  generatorFunction,
3085
3088
  effectModule: propertyAccess.expression,
3086
3089
  body: generatorFunction.body,
3087
- pipeArguments: pipeArguments2
3090
+ pipeArguments: pipeArguments2,
3091
+ traceExpression
3088
3092
  }))
3089
3093
  );
3090
3094
  },
@@ -3167,6 +3171,7 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3167
3171
  if (!ts.isPropertyAccessExpression(expressionToTest)) {
3168
3172
  return typeParserIssue("Node is not a property access expression", void 0, node);
3169
3173
  }
3174
+ const traceExpression = ts.isCallExpression(node.expression) && node.expression.arguments.length > 0 ? node.expression.arguments[0] : void 0;
3170
3175
  const propertyAccess = expressionToTest;
3171
3176
  const pipeArguments2 = node.arguments.slice(1);
3172
3177
  return pipe(
@@ -3175,7 +3180,8 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3175
3180
  node,
3176
3181
  effectModule: propertyAccess.expression,
3177
3182
  regularFunction,
3178
- pipeArguments: pipeArguments2
3183
+ pipeArguments: pipeArguments2,
3184
+ traceExpression
3179
3185
  }))
3180
3186
  );
3181
3187
  },
@@ -4884,7 +4890,8 @@ var effectFnIife = createDiagnostic({
4884
4890
  kind: "fn",
4885
4891
  effectModule: result.effectModule,
4886
4892
  generatorFunction: result.generatorFunction,
4887
- pipeArguments: result.pipeArguments
4893
+ pipeArguments: result.pipeArguments,
4894
+ traceExpression: result.traceExpression
4888
4895
  })),
4889
4896
  orElse2(
4890
4897
  () => pipe(
@@ -4893,7 +4900,8 @@ var effectFnIife = createDiagnostic({
4893
4900
  kind: "fnUntraced",
4894
4901
  effectModule: result.effectModule,
4895
4902
  generatorFunction: result.generatorFunction,
4896
- pipeArguments: result.pipeArguments
4903
+ pipeArguments: result.pipeArguments,
4904
+ traceExpression: void 0
4897
4905
  }))
4898
4906
  )
4899
4907
  ),
@@ -4904,14 +4912,15 @@ var effectFnIife = createDiagnostic({
4904
4912
  kind: "fn",
4905
4913
  effectModule: result.effectModule,
4906
4914
  generatorFunction: void 0,
4907
- pipeArguments: result.pipeArguments
4915
+ pipeArguments: result.pipeArguments,
4916
+ traceExpression: result.traceExpression
4908
4917
  }))
4909
4918
  )
4910
4919
  ),
4911
4920
  option
4912
4921
  );
4913
4922
  if (isNone2(parsed)) continue;
4914
- const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2 } = parsed.value;
4923
+ const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2, traceExpression } = parsed.value;
4915
4924
  const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
4916
4925
  const fixes = [];
4917
4926
  if (generatorFunction && generatorFunction.parameters.length === 0) {
@@ -4940,9 +4949,10 @@ var effectFnIife = createDiagnostic({
4940
4949
  })
4941
4950
  });
4942
4951
  }
4952
+ const traceExpressionText = traceExpression ? sourceFile.text.slice(traceExpression.pos, traceExpression.end) : void 0;
4943
4953
  report({
4944
4954
  location: node,
4945
- 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).`,
4955
+ 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` : ``}.`,
4946
4956
  fixes
4947
4957
  });
4948
4958
  }
@@ -4961,6 +4971,7 @@ var effectFnOpportunity = createDiagnostic({
4961
4971
  const typeCheckerUtils = yield* service(TypeCheckerUtils);
4962
4972
  const typeParser = yield* service(TypeParser);
4963
4973
  const tsUtils = yield* service(TypeScriptUtils);
4974
+ const pluginOptions = yield* service(LanguageServicePluginOptions);
4964
4975
  const sourceEffectModuleName = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
4965
4976
  sourceFile,
4966
4977
  "effect",
@@ -5037,6 +5048,18 @@ var effectFnOpportunity = createDiagnostic({
5037
5048
  }
5038
5049
  return false;
5039
5050
  };
5051
+ const tryExtractWithSpanExpression = (expr) => gen(function* () {
5052
+ if (!ts.isCallExpression(expr)) return void 0;
5053
+ const callee = expr.expression;
5054
+ const isWithSpan = yield* pipe(
5055
+ typeParser.isNodeReferenceToEffectModuleApi("withSpan")(callee),
5056
+ map4(() => true),
5057
+ orElse2(() => succeed(false))
5058
+ );
5059
+ if (!isWithSpan) return void 0;
5060
+ if (expr.arguments.length === 0) return void 0;
5061
+ return expr.arguments[0];
5062
+ });
5040
5063
  const tryParseGenOpportunity = (fnNode) => gen(function* () {
5041
5064
  const bodyExpression = getBodyExpression(fnNode);
5042
5065
  if (!bodyExpression) return yield* TypeParserIssue.issue;
@@ -5047,7 +5070,15 @@ var effectFnOpportunity = createDiagnostic({
5047
5070
  );
5048
5071
  const { effectModule, generatorFunction } = yield* typeParser.effectGen(subject);
5049
5072
  const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
5050
- return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2 };
5073
+ let explicitTraceExpression;
5074
+ if (pipeArguments2.length > 0) {
5075
+ const lastArg = pipeArguments2[pipeArguments2.length - 1];
5076
+ const withSpanExpr = yield* tryExtractWithSpanExpression(lastArg);
5077
+ if (withSpanExpr) {
5078
+ explicitTraceExpression = withSpanExpr;
5079
+ }
5080
+ }
5081
+ return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2, explicitTraceExpression };
5051
5082
  });
5052
5083
  const isInsideEffectFn = (fnNode) => {
5053
5084
  const parent = fnNode.parent;
@@ -5075,6 +5106,9 @@ var effectFnOpportunity = createDiagnostic({
5075
5106
  if (ts.isFunctionExpression(node) && node.name) {
5076
5107
  return yield* TypeParserIssue.issue;
5077
5108
  }
5109
+ if (node.type) {
5110
+ return yield* TypeParserIssue.issue;
5111
+ }
5078
5112
  if (yield* isInsideEffectFn(node)) {
5079
5113
  return yield* TypeParserIssue.issue;
5080
5114
  }
@@ -5102,7 +5136,8 @@ var effectFnOpportunity = createDiagnostic({
5102
5136
  return succeed({
5103
5137
  effectModuleName: sourceEffectModuleName,
5104
5138
  pipeArguments: [],
5105
- generatorFunction: void 0
5139
+ generatorFunction: void 0,
5140
+ explicitTraceExpression: void 0
5106
5141
  });
5107
5142
  })
5108
5143
  );
@@ -5110,7 +5145,8 @@ var effectFnOpportunity = createDiagnostic({
5110
5145
  node,
5111
5146
  nameIdentifier,
5112
5147
  effectModuleName: opportunity.effectModuleName,
5113
- traceName,
5148
+ inferredTraceName: traceName,
5149
+ explicitTraceExpression: opportunity.explicitTraceExpression,
5114
5150
  pipeArguments: opportunity.pipeArguments,
5115
5151
  generatorFunction: opportunity.generatorFunction,
5116
5152
  hasParamsInPipeArgs: areParametersReferencedIn(node, opportunity.pipeArguments)
@@ -5129,7 +5165,7 @@ var effectFnOpportunity = createDiagnostic({
5129
5165
  if (ts.isArrowFunction(node)) return false;
5130
5166
  return node.asteriskToken !== void 0;
5131
5167
  };
5132
- const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceName, pipeArguments2) => {
5168
+ const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceNameOrExpression, pipeArguments2) => {
5133
5169
  const isGenerator = isGeneratorFunction(innerFunction);
5134
5170
  const newFunction = ts.factory.createFunctionExpression(
5135
5171
  void 0,
@@ -5144,11 +5180,12 @@ var effectFnOpportunity = createDiagnostic({
5144
5180
  ts.factory.createIdentifier(effectModuleName),
5145
5181
  "fn"
5146
5182
  );
5147
- if (traceName) {
5183
+ if (traceNameOrExpression) {
5184
+ const traceArg = typeof traceNameOrExpression === "string" ? ts.factory.createStringLiteral(traceNameOrExpression) : traceNameOrExpression;
5148
5185
  fnExpression = ts.factory.createCallExpression(
5149
5186
  fnExpression,
5150
5187
  void 0,
5151
- [ts.factory.createStringLiteral(traceName)]
5188
+ [traceArg]
5152
5189
  );
5153
5190
  }
5154
5191
  const effectFnCall = ts.factory.createCallExpression(fnExpression, void 0, [newFunction, ...pipeArguments2]);
@@ -5190,19 +5227,35 @@ var effectFnOpportunity = createDiagnostic({
5190
5227
  const target = yield* pipe(parseEffectFnOpportunityTarget(node), option);
5191
5228
  if (isNone2(target)) continue;
5192
5229
  if (target.value.hasParamsInPipeArgs) continue;
5193
- const { effectModuleName, nameIdentifier, node: targetNode, pipeArguments: pipeArguments2, traceName } = target.value;
5230
+ const {
5231
+ effectModuleName,
5232
+ explicitTraceExpression,
5233
+ inferredTraceName,
5234
+ nameIdentifier,
5235
+ node: targetNode,
5236
+ pipeArguments: pipeArguments2
5237
+ } = target.value;
5194
5238
  const innerFunction = target.value.generatorFunction ?? targetNode;
5195
5239
  const fixes = [];
5196
- fixes.push({
5197
- fixName: "effectFnOpportunity_toEffectFn",
5198
- description: traceName ? `Convert to Effect.fn("${traceName}")` : "Convert to Effect.fn",
5199
- apply: gen(function* () {
5200
- const changeTracker = yield* service(ChangeTracker);
5201
- const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, traceName, pipeArguments2);
5202
- changeTracker.replaceNode(sourceFile, targetNode, newNode);
5203
- })
5204
- });
5205
- if (target.value.generatorFunction) {
5240
+ if (pluginOptions.effectFn.includes("span") && explicitTraceExpression) {
5241
+ fixes.push({
5242
+ fixName: "effectFnOpportunity_toEffectFnWithSpan",
5243
+ description: "Convert to Effect.fn (with span from withSpan)",
5244
+ apply: gen(function* () {
5245
+ const changeTracker = yield* service(ChangeTracker);
5246
+ const finalPipeArguments = pipeArguments2.slice(0, -1);
5247
+ const newNode = createEffectFnNode(
5248
+ targetNode,
5249
+ innerFunction,
5250
+ effectModuleName,
5251
+ explicitTraceExpression,
5252
+ finalPipeArguments
5253
+ );
5254
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
5255
+ })
5256
+ });
5257
+ }
5258
+ if (pluginOptions.effectFn.includes("untraced") && target.value.generatorFunction) {
5206
5259
  fixes.push({
5207
5260
  fixName: "effectFnOpportunity_toEffectFnUntraced",
5208
5261
  description: "Convert to Effect.fnUntraced",
@@ -5213,10 +5266,67 @@ var effectFnOpportunity = createDiagnostic({
5213
5266
  })
5214
5267
  });
5215
5268
  }
5216
- const pipeArgsSuffix = pipeArguments2.length > 0 ? ` Effect.fn also accepts the piped transformations as additional arguments.` : ``;
5269
+ if (pluginOptions.effectFn.includes("no-span")) {
5270
+ fixes.push({
5271
+ fixName: "effectFnOpportunity_toEffectFnNoSpan",
5272
+ description: "Convert to Effect.fn (no span)",
5273
+ apply: gen(function* () {
5274
+ const changeTracker = yield* service(ChangeTracker);
5275
+ const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, void 0, pipeArguments2);
5276
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
5277
+ })
5278
+ });
5279
+ }
5280
+ if (pluginOptions.effectFn.includes("inferred-span") && inferredTraceName && !explicitTraceExpression) {
5281
+ fixes.push({
5282
+ fixName: "effectFnOpportunity_toEffectFnSpanInferred",
5283
+ description: `Convert to Effect.fn("${inferredTraceName}")`,
5284
+ apply: gen(function* () {
5285
+ const changeTracker = yield* service(ChangeTracker);
5286
+ const newNode = createEffectFnNode(
5287
+ targetNode,
5288
+ innerFunction,
5289
+ effectModuleName,
5290
+ inferredTraceName,
5291
+ pipeArguments2
5292
+ );
5293
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
5294
+ })
5295
+ });
5296
+ }
5297
+ if (fixes.length === 0) continue;
5298
+ const generateExpectedSignature = () => {
5299
+ const firstFix = fixes[0];
5300
+ if (!firstFix) return "Effect.fn(function*() { ... })";
5301
+ const typeParamNames = targetNode.typeParameters ? `<${targetNode.typeParameters.map((tp) => ts.idText(tp.name)).join(", ")}>` : "";
5302
+ const paramNames = targetNode.parameters.map((param) => {
5303
+ if (ts.isIdentifier(param.name)) {
5304
+ return ts.idText(param.name);
5305
+ }
5306
+ return "_";
5307
+ }).join(", ");
5308
+ const fnSignature = `function*${typeParamNames}(${paramNames}) { ... }`;
5309
+ const pipeArgsForWithSpan = pipeArguments2.slice(0, -1);
5310
+ const pipeArgsSuffix = (args2) => args2.length > 0 ? ", ...pipeTransformations" : "";
5311
+ switch (firstFix.fixName) {
5312
+ case "effectFnOpportunity_toEffectFnWithSpan": {
5313
+ const traceName = explicitTraceExpression ? sourceFile.text.slice(explicitTraceExpression.pos, explicitTraceExpression.end).trim() : void 0;
5314
+ return `${effectModuleName}.fn(${traceName})(${fnSignature}${pipeArgsSuffix(pipeArgsForWithSpan)})`;
5315
+ }
5316
+ case "effectFnOpportunity_toEffectFnUntraced":
5317
+ return `${effectModuleName}.fnUntraced(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
5318
+ case "effectFnOpportunity_toEffectFnNoSpan":
5319
+ return `${effectModuleName}.fn(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
5320
+ case "effectFnOpportunity_toEffectFnSpanInferred":
5321
+ return `${effectModuleName}.fn("${inferredTraceName}")(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
5322
+ default:
5323
+ return `${effectModuleName}.fn(${fnSignature})`;
5324
+ }
5325
+ };
5326
+ const expectedSignature = generateExpectedSignature();
5217
5327
  report({
5218
5328
  location: nameIdentifier ?? targetNode,
5219
- 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}`,
5329
+ messageText: `Can be rewritten as a reusable function: ${expectedSignature}`,
5220
5330
  fixes
5221
5331
  });
5222
5332
  }