@effect/language-service 0.69.1 → 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/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.1",
30217
+ version: "0.69.2",
30218
30218
  packageManager: "pnpm@8.11.0",
30219
30219
  publishConfig: {
30220
30220
  access: "public",
@@ -32039,6 +32039,7 @@ var defaults = {
32039
32039
  }],
32040
32040
  extendedKeyDetection: false,
32041
32041
  pipeableMinArgCount: 2,
32042
+ effectFn: ["span"],
32042
32043
  layerGraphFollowDepth: 0,
32043
32044
  mermaidProvider: "mermaid.live"
32044
32045
  };
@@ -32078,6 +32079,7 @@ function parse4(config2) {
32078
32079
  keyPatterns: isObject(config2) && hasProperty(config2, "keyPatterns") && isArray(config2.keyPatterns) ? parseKeyPatterns(config2.keyPatterns) : defaults.keyPatterns,
32079
32080
  extendedKeyDetection: isObject(config2) && hasProperty(config2, "extendedKeyDetection") && isBoolean(config2.extendedKeyDetection) ? config2.extendedKeyDetection : defaults.extendedKeyDetection,
32080
32081
  pipeableMinArgCount: isObject(config2) && hasProperty(config2, "pipeableMinArgCount") && isNumber(config2.pipeableMinArgCount) ? config2.pipeableMinArgCount : defaults.pipeableMinArgCount,
32082
+ effectFn: isObject(config2) && hasProperty(config2, "effectFn") && isArray(config2.effectFn) && config2.effectFn.every(isString) ? config2.effectFn.map((_) => _.toLowerCase()) : defaults.effectFn,
32081
32083
  layerGraphFollowDepth: isObject(config2) && hasProperty(config2, "layerGraphFollowDepth") && isNumber(config2.layerGraphFollowDepth) ? config2.layerGraphFollowDepth : defaults.layerGraphFollowDepth,
32082
32084
  mermaidProvider: isObject(config2) && hasProperty(config2, "mermaidProvider") && isString(config2.mermaidProvider) ? config2.mermaidProvider : defaults.mermaidProvider
32083
32085
  };
@@ -33302,6 +33304,7 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
33302
33304
  node
33303
33305
  );
33304
33306
  }
33307
+ const traceExpression = ts.isCallExpression(node.expression) && node.expression.arguments.length > 0 ? node.expression.arguments[0] : void 0;
33305
33308
  const propertyAccess = expressionToTest;
33306
33309
  const pipeArguments2 = node.arguments.slice(1);
33307
33310
  return pipe(
@@ -33311,7 +33314,8 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
33311
33314
  generatorFunction,
33312
33315
  effectModule: propertyAccess.expression,
33313
33316
  body: generatorFunction.body,
33314
- pipeArguments: pipeArguments2
33317
+ pipeArguments: pipeArguments2,
33318
+ traceExpression
33315
33319
  }))
33316
33320
  );
33317
33321
  },
@@ -33394,6 +33398,7 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
33394
33398
  if (!ts.isPropertyAccessExpression(expressionToTest)) {
33395
33399
  return typeParserIssue("Node is not a property access expression", void 0, node);
33396
33400
  }
33401
+ const traceExpression = ts.isCallExpression(node.expression) && node.expression.arguments.length > 0 ? node.expression.arguments[0] : void 0;
33397
33402
  const propertyAccess = expressionToTest;
33398
33403
  const pipeArguments2 = node.arguments.slice(1);
33399
33404
  return pipe(
@@ -33402,7 +33407,8 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
33402
33407
  node,
33403
33408
  effectModule: propertyAccess.expression,
33404
33409
  regularFunction,
33405
- pipeArguments: pipeArguments2
33410
+ pipeArguments: pipeArguments2,
33411
+ traceExpression
33406
33412
  }))
33407
33413
  );
33408
33414
  },
@@ -36299,7 +36305,8 @@ var effectFnIife = createDiagnostic({
36299
36305
  kind: "fn",
36300
36306
  effectModule: result.effectModule,
36301
36307
  generatorFunction: result.generatorFunction,
36302
- pipeArguments: result.pipeArguments
36308
+ pipeArguments: result.pipeArguments,
36309
+ traceExpression: result.traceExpression
36303
36310
  })),
36304
36311
  orElse15(
36305
36312
  () => pipe(
@@ -36308,7 +36315,8 @@ var effectFnIife = createDiagnostic({
36308
36315
  kind: "fnUntraced",
36309
36316
  effectModule: result.effectModule,
36310
36317
  generatorFunction: result.generatorFunction,
36311
- pipeArguments: result.pipeArguments
36318
+ pipeArguments: result.pipeArguments,
36319
+ traceExpression: void 0
36312
36320
  }))
36313
36321
  )
36314
36322
  ),
@@ -36319,14 +36327,15 @@ var effectFnIife = createDiagnostic({
36319
36327
  kind: "fn",
36320
36328
  effectModule: result.effectModule,
36321
36329
  generatorFunction: void 0,
36322
- pipeArguments: result.pipeArguments
36330
+ pipeArguments: result.pipeArguments,
36331
+ traceExpression: result.traceExpression
36323
36332
  }))
36324
36333
  )
36325
36334
  ),
36326
36335
  option5
36327
36336
  );
36328
36337
  if (isNone2(parsed)) continue;
36329
- const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2 } = parsed.value;
36338
+ const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2, traceExpression } = parsed.value;
36330
36339
  const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
36331
36340
  const fixes = [];
36332
36341
  if (generatorFunction && generatorFunction.parameters.length === 0) {
@@ -36355,9 +36364,10 @@ var effectFnIife = createDiagnostic({
36355
36364
  })
36356
36365
  });
36357
36366
  }
36367
+ const traceExpressionText = traceExpression ? sourceFile.text.slice(traceExpression.pos, traceExpression.end) : void 0;
36358
36368
  report({
36359
36369
  location: node,
36360
- 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).`,
36370
+ 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` : ``}.`,
36361
36371
  fixes
36362
36372
  });
36363
36373
  }
@@ -36376,6 +36386,7 @@ var effectFnOpportunity = createDiagnostic({
36376
36386
  const typeCheckerUtils = yield* service2(TypeCheckerUtils);
36377
36387
  const typeParser = yield* service2(TypeParser);
36378
36388
  const tsUtils = yield* service2(TypeScriptUtils);
36389
+ const pluginOptions = yield* service2(LanguageServicePluginOptions);
36379
36390
  const sourceEffectModuleName = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
36380
36391
  sourceFile,
36381
36392
  "effect",
@@ -36452,6 +36463,18 @@ var effectFnOpportunity = createDiagnostic({
36452
36463
  }
36453
36464
  return false;
36454
36465
  };
36466
+ const tryExtractWithSpanExpression = (expr) => gen3(function* () {
36467
+ if (!ts.isCallExpression(expr)) return void 0;
36468
+ const callee = expr.expression;
36469
+ const isWithSpan = yield* pipe(
36470
+ typeParser.isNodeReferenceToEffectModuleApi("withSpan")(callee),
36471
+ map34(() => true),
36472
+ orElse15(() => succeed17(false))
36473
+ );
36474
+ if (!isWithSpan) return void 0;
36475
+ if (expr.arguments.length === 0) return void 0;
36476
+ return expr.arguments[0];
36477
+ });
36455
36478
  const tryParseGenOpportunity = (fnNode) => gen3(function* () {
36456
36479
  const bodyExpression = getBodyExpression(fnNode);
36457
36480
  if (!bodyExpression) return yield* TypeParserIssue.issue;
@@ -36462,7 +36485,15 @@ var effectFnOpportunity = createDiagnostic({
36462
36485
  );
36463
36486
  const { effectModule, generatorFunction } = yield* typeParser.effectGen(subject);
36464
36487
  const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
36465
- return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2 };
36488
+ let explicitTraceExpression;
36489
+ if (pipeArguments2.length > 0) {
36490
+ const lastArg = pipeArguments2[pipeArguments2.length - 1];
36491
+ const withSpanExpr = yield* tryExtractWithSpanExpression(lastArg);
36492
+ if (withSpanExpr) {
36493
+ explicitTraceExpression = withSpanExpr;
36494
+ }
36495
+ }
36496
+ return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2, explicitTraceExpression };
36466
36497
  });
36467
36498
  const isInsideEffectFn = (fnNode) => {
36468
36499
  const parent = fnNode.parent;
@@ -36490,6 +36521,9 @@ var effectFnOpportunity = createDiagnostic({
36490
36521
  if (ts.isFunctionExpression(node) && node.name) {
36491
36522
  return yield* TypeParserIssue.issue;
36492
36523
  }
36524
+ if (node.type) {
36525
+ return yield* TypeParserIssue.issue;
36526
+ }
36493
36527
  if (yield* isInsideEffectFn(node)) {
36494
36528
  return yield* TypeParserIssue.issue;
36495
36529
  }
@@ -36517,7 +36551,8 @@ var effectFnOpportunity = createDiagnostic({
36517
36551
  return succeed17({
36518
36552
  effectModuleName: sourceEffectModuleName,
36519
36553
  pipeArguments: [],
36520
- generatorFunction: void 0
36554
+ generatorFunction: void 0,
36555
+ explicitTraceExpression: void 0
36521
36556
  });
36522
36557
  })
36523
36558
  );
@@ -36525,7 +36560,8 @@ var effectFnOpportunity = createDiagnostic({
36525
36560
  node,
36526
36561
  nameIdentifier,
36527
36562
  effectModuleName: opportunity.effectModuleName,
36528
- traceName,
36563
+ inferredTraceName: traceName,
36564
+ explicitTraceExpression: opportunity.explicitTraceExpression,
36529
36565
  pipeArguments: opportunity.pipeArguments,
36530
36566
  generatorFunction: opportunity.generatorFunction,
36531
36567
  hasParamsInPipeArgs: areParametersReferencedIn(node, opportunity.pipeArguments)
@@ -36544,7 +36580,7 @@ var effectFnOpportunity = createDiagnostic({
36544
36580
  if (ts.isArrowFunction(node)) return false;
36545
36581
  return node.asteriskToken !== void 0;
36546
36582
  };
36547
- const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceName, pipeArguments2) => {
36583
+ const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceNameOrExpression, pipeArguments2) => {
36548
36584
  const isGenerator = isGeneratorFunction2(innerFunction);
36549
36585
  const newFunction = ts.factory.createFunctionExpression(
36550
36586
  void 0,
@@ -36559,11 +36595,12 @@ var effectFnOpportunity = createDiagnostic({
36559
36595
  ts.factory.createIdentifier(effectModuleName),
36560
36596
  "fn"
36561
36597
  );
36562
- if (traceName) {
36598
+ if (traceNameOrExpression) {
36599
+ const traceArg = typeof traceNameOrExpression === "string" ? ts.factory.createStringLiteral(traceNameOrExpression) : traceNameOrExpression;
36563
36600
  fnExpression = ts.factory.createCallExpression(
36564
36601
  fnExpression,
36565
36602
  void 0,
36566
- [ts.factory.createStringLiteral(traceName)]
36603
+ [traceArg]
36567
36604
  );
36568
36605
  }
36569
36606
  const effectFnCall = ts.factory.createCallExpression(fnExpression, void 0, [newFunction, ...pipeArguments2]);
@@ -36605,19 +36642,35 @@ var effectFnOpportunity = createDiagnostic({
36605
36642
  const target = yield* pipe(parseEffectFnOpportunityTarget(node), option5);
36606
36643
  if (isNone2(target)) continue;
36607
36644
  if (target.value.hasParamsInPipeArgs) continue;
36608
- const { effectModuleName, nameIdentifier, node: targetNode, pipeArguments: pipeArguments2, traceName } = target.value;
36645
+ const {
36646
+ effectModuleName,
36647
+ explicitTraceExpression,
36648
+ inferredTraceName,
36649
+ nameIdentifier,
36650
+ node: targetNode,
36651
+ pipeArguments: pipeArguments2
36652
+ } = target.value;
36609
36653
  const innerFunction = target.value.generatorFunction ?? targetNode;
36610
36654
  const fixes = [];
36611
- fixes.push({
36612
- fixName: "effectFnOpportunity_toEffectFn",
36613
- description: traceName ? `Convert to Effect.fn("${traceName}")` : "Convert to Effect.fn",
36614
- apply: gen3(function* () {
36615
- const changeTracker = yield* service2(ChangeTracker);
36616
- const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, traceName, pipeArguments2);
36617
- changeTracker.replaceNode(sourceFile, targetNode, newNode);
36618
- })
36619
- });
36620
- if (target.value.generatorFunction) {
36655
+ if (pluginOptions.effectFn.includes("span") && explicitTraceExpression) {
36656
+ fixes.push({
36657
+ fixName: "effectFnOpportunity_toEffectFnWithSpan",
36658
+ description: "Convert to Effect.fn (with span from withSpan)",
36659
+ apply: gen3(function* () {
36660
+ const changeTracker = yield* service2(ChangeTracker);
36661
+ const finalPipeArguments = pipeArguments2.slice(0, -1);
36662
+ const newNode = createEffectFnNode(
36663
+ targetNode,
36664
+ innerFunction,
36665
+ effectModuleName,
36666
+ explicitTraceExpression,
36667
+ finalPipeArguments
36668
+ );
36669
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
36670
+ })
36671
+ });
36672
+ }
36673
+ if (pluginOptions.effectFn.includes("untraced") && target.value.generatorFunction) {
36621
36674
  fixes.push({
36622
36675
  fixName: "effectFnOpportunity_toEffectFnUntraced",
36623
36676
  description: "Convert to Effect.fnUntraced",
@@ -36628,12 +36681,67 @@ var effectFnOpportunity = createDiagnostic({
36628
36681
  })
36629
36682
  });
36630
36683
  }
36631
- const pipeArgsSuffix = pipeArguments2.length > 0 ? ` Effect.fn also accepts the piped transformations as additional arguments.` : ``;
36632
- const suggestConsultingQuickFixes = ` Your editor quickfixes or the "effect-language-service" cli can show you how to convert to Effect.fn or Effect.fnUntraced.`;
36633
- const orFnUntraced = target.value.generatorFunction ? `, or Effect.fnUntraced` : ``;
36684
+ if (pluginOptions.effectFn.includes("no-span")) {
36685
+ fixes.push({
36686
+ fixName: "effectFnOpportunity_toEffectFnNoSpan",
36687
+ description: "Convert to Effect.fn (no span)",
36688
+ apply: gen3(function* () {
36689
+ const changeTracker = yield* service2(ChangeTracker);
36690
+ const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, void 0, pipeArguments2);
36691
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
36692
+ })
36693
+ });
36694
+ }
36695
+ if (pluginOptions.effectFn.includes("inferred-span") && inferredTraceName && !explicitTraceExpression) {
36696
+ fixes.push({
36697
+ fixName: "effectFnOpportunity_toEffectFnSpanInferred",
36698
+ description: `Convert to Effect.fn("${inferredTraceName}")`,
36699
+ apply: gen3(function* () {
36700
+ const changeTracker = yield* service2(ChangeTracker);
36701
+ const newNode = createEffectFnNode(
36702
+ targetNode,
36703
+ innerFunction,
36704
+ effectModuleName,
36705
+ inferredTraceName,
36706
+ pipeArguments2
36707
+ );
36708
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
36709
+ })
36710
+ });
36711
+ }
36712
+ if (fixes.length === 0) continue;
36713
+ const generateExpectedSignature = () => {
36714
+ const firstFix = fixes[0];
36715
+ if (!firstFix) return "Effect.fn(function*() { ... })";
36716
+ const typeParamNames = targetNode.typeParameters ? `<${targetNode.typeParameters.map((tp) => ts.idText(tp.name)).join(", ")}>` : "";
36717
+ const paramNames = targetNode.parameters.map((param) => {
36718
+ if (ts.isIdentifier(param.name)) {
36719
+ return ts.idText(param.name);
36720
+ }
36721
+ return "_";
36722
+ }).join(", ");
36723
+ const fnSignature = `function*${typeParamNames}(${paramNames}) { ... }`;
36724
+ const pipeArgsForWithSpan = pipeArguments2.slice(0, -1);
36725
+ const pipeArgsSuffix = (args3) => args3.length > 0 ? ", ...pipeTransformations" : "";
36726
+ switch (firstFix.fixName) {
36727
+ case "effectFnOpportunity_toEffectFnWithSpan": {
36728
+ const traceName = explicitTraceExpression ? sourceFile.text.slice(explicitTraceExpression.pos, explicitTraceExpression.end).trim() : void 0;
36729
+ return `${effectModuleName}.fn(${traceName})(${fnSignature}${pipeArgsSuffix(pipeArgsForWithSpan)})`;
36730
+ }
36731
+ case "effectFnOpportunity_toEffectFnUntraced":
36732
+ return `${effectModuleName}.fnUntraced(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
36733
+ case "effectFnOpportunity_toEffectFnNoSpan":
36734
+ return `${effectModuleName}.fn(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
36735
+ case "effectFnOpportunity_toEffectFnSpanInferred":
36736
+ return `${effectModuleName}.fn("${inferredTraceName}")(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
36737
+ default:
36738
+ return `${effectModuleName}.fn(${fnSignature})`;
36739
+ }
36740
+ };
36741
+ const expectedSignature = generateExpectedSignature();
36634
36742
  report({
36635
36743
  location: nameIdentifier ?? targetNode,
36636
- messageText: `This function could benefit from Effect.fn's automatic tracing and concise syntax${orFnUntraced}.${pipeArgsSuffix}${suggestConsultingQuickFixes}`,
36744
+ messageText: `Can be rewritten as a reusable function: ${expectedSignature}`,
36637
36745
  fixes
36638
36746
  });
36639
36747
  }