@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/index.js CHANGED
@@ -3479,6 +3479,7 @@ var defaults = {
3479
3479
  }],
3480
3480
  extendedKeyDetection: false,
3481
3481
  pipeableMinArgCount: 2,
3482
+ effectFn: ["span"],
3482
3483
  layerGraphFollowDepth: 0,
3483
3484
  mermaidProvider: "mermaid.live"
3484
3485
  };
@@ -3518,6 +3519,7 @@ function parse(config) {
3518
3519
  keyPatterns: isObject(config) && hasProperty(config, "keyPatterns") && isArray(config.keyPatterns) ? parseKeyPatterns(config.keyPatterns) : defaults.keyPatterns,
3519
3520
  extendedKeyDetection: isObject(config) && hasProperty(config, "extendedKeyDetection") && isBoolean(config.extendedKeyDetection) ? config.extendedKeyDetection : defaults.extendedKeyDetection,
3520
3521
  pipeableMinArgCount: isObject(config) && hasProperty(config, "pipeableMinArgCount") && isNumber(config.pipeableMinArgCount) ? config.pipeableMinArgCount : defaults.pipeableMinArgCount,
3522
+ effectFn: isObject(config) && hasProperty(config, "effectFn") && isArray(config.effectFn) && config.effectFn.every(isString) ? config.effectFn.map((_) => _.toLowerCase()) : defaults.effectFn,
3521
3523
  layerGraphFollowDepth: isObject(config) && hasProperty(config, "layerGraphFollowDepth") && isNumber(config.layerGraphFollowDepth) ? config.layerGraphFollowDepth : defaults.layerGraphFollowDepth,
3522
3524
  mermaidProvider: isObject(config) && hasProperty(config, "mermaidProvider") && isString(config.mermaidProvider) ? config.mermaidProvider : defaults.mermaidProvider
3523
3525
  };
@@ -5421,6 +5423,7 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
5421
5423
  node
5422
5424
  );
5423
5425
  }
5426
+ const traceExpression = ts.isCallExpression(node.expression) && node.expression.arguments.length > 0 ? node.expression.arguments[0] : void 0;
5424
5427
  const propertyAccess = expressionToTest;
5425
5428
  const pipeArguments2 = node.arguments.slice(1);
5426
5429
  return pipe(
@@ -5430,7 +5433,8 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
5430
5433
  generatorFunction,
5431
5434
  effectModule: propertyAccess.expression,
5432
5435
  body: generatorFunction.body,
5433
- pipeArguments: pipeArguments2
5436
+ pipeArguments: pipeArguments2,
5437
+ traceExpression
5434
5438
  }))
5435
5439
  );
5436
5440
  },
@@ -5513,6 +5517,7 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
5513
5517
  if (!ts.isPropertyAccessExpression(expressionToTest)) {
5514
5518
  return typeParserIssue("Node is not a property access expression", void 0, node);
5515
5519
  }
5520
+ const traceExpression = ts.isCallExpression(node.expression) && node.expression.arguments.length > 0 ? node.expression.arguments[0] : void 0;
5516
5521
  const propertyAccess = expressionToTest;
5517
5522
  const pipeArguments2 = node.arguments.slice(1);
5518
5523
  return pipe(
@@ -5521,7 +5526,8 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
5521
5526
  node,
5522
5527
  effectModule: propertyAccess.expression,
5523
5528
  regularFunction,
5524
- pipeArguments: pipeArguments2
5529
+ pipeArguments: pipeArguments2,
5530
+ traceExpression
5525
5531
  }))
5526
5532
  );
5527
5533
  },
@@ -8358,7 +8364,8 @@ var effectFnIife = createDiagnostic({
8358
8364
  kind: "fn",
8359
8365
  effectModule: result.effectModule,
8360
8366
  generatorFunction: result.generatorFunction,
8361
- pipeArguments: result.pipeArguments
8367
+ pipeArguments: result.pipeArguments,
8368
+ traceExpression: result.traceExpression
8362
8369
  })),
8363
8370
  orElse2(
8364
8371
  () => pipe(
@@ -8367,7 +8374,8 @@ var effectFnIife = createDiagnostic({
8367
8374
  kind: "fnUntraced",
8368
8375
  effectModule: result.effectModule,
8369
8376
  generatorFunction: result.generatorFunction,
8370
- pipeArguments: result.pipeArguments
8377
+ pipeArguments: result.pipeArguments,
8378
+ traceExpression: void 0
8371
8379
  }))
8372
8380
  )
8373
8381
  ),
@@ -8378,14 +8386,15 @@ var effectFnIife = createDiagnostic({
8378
8386
  kind: "fn",
8379
8387
  effectModule: result.effectModule,
8380
8388
  generatorFunction: void 0,
8381
- pipeArguments: result.pipeArguments
8389
+ pipeArguments: result.pipeArguments,
8390
+ traceExpression: result.traceExpression
8382
8391
  }))
8383
8392
  )
8384
8393
  ),
8385
8394
  option
8386
8395
  );
8387
8396
  if (isNone2(parsed)) continue;
8388
- const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2 } = parsed.value;
8397
+ const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2, traceExpression } = parsed.value;
8389
8398
  const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
8390
8399
  const fixes = [];
8391
8400
  if (generatorFunction && generatorFunction.parameters.length === 0) {
@@ -8414,9 +8423,10 @@ var effectFnIife = createDiagnostic({
8414
8423
  })
8415
8424
  });
8416
8425
  }
8426
+ const traceExpressionText = traceExpression ? sourceFile.text.slice(traceExpression.pos, traceExpression.end) : void 0;
8417
8427
  report({
8418
8428
  location: node,
8419
- 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).`,
8429
+ 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` : ``}.`,
8420
8430
  fixes
8421
8431
  });
8422
8432
  }
@@ -8435,6 +8445,7 @@ var effectFnOpportunity = createDiagnostic({
8435
8445
  const typeCheckerUtils = yield* service(TypeCheckerUtils);
8436
8446
  const typeParser = yield* service(TypeParser);
8437
8447
  const tsUtils = yield* service(TypeScriptUtils);
8448
+ const pluginOptions = yield* service(LanguageServicePluginOptions);
8438
8449
  const sourceEffectModuleName = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
8439
8450
  sourceFile,
8440
8451
  "effect",
@@ -8511,6 +8522,18 @@ var effectFnOpportunity = createDiagnostic({
8511
8522
  }
8512
8523
  return false;
8513
8524
  };
8525
+ const tryExtractWithSpanExpression = (expr) => gen(function* () {
8526
+ if (!ts.isCallExpression(expr)) return void 0;
8527
+ const callee = expr.expression;
8528
+ const isWithSpan = yield* pipe(
8529
+ typeParser.isNodeReferenceToEffectModuleApi("withSpan")(callee),
8530
+ map8(() => true),
8531
+ orElse2(() => succeed(false))
8532
+ );
8533
+ if (!isWithSpan) return void 0;
8534
+ if (expr.arguments.length === 0) return void 0;
8535
+ return expr.arguments[0];
8536
+ });
8514
8537
  const tryParseGenOpportunity = (fnNode) => gen(function* () {
8515
8538
  const bodyExpression = getBodyExpression(fnNode);
8516
8539
  if (!bodyExpression) return yield* TypeParserIssue.issue;
@@ -8521,7 +8544,15 @@ var effectFnOpportunity = createDiagnostic({
8521
8544
  );
8522
8545
  const { effectModule, generatorFunction } = yield* typeParser.effectGen(subject);
8523
8546
  const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
8524
- return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2 };
8547
+ let explicitTraceExpression;
8548
+ if (pipeArguments2.length > 0) {
8549
+ const lastArg = pipeArguments2[pipeArguments2.length - 1];
8550
+ const withSpanExpr = yield* tryExtractWithSpanExpression(lastArg);
8551
+ if (withSpanExpr) {
8552
+ explicitTraceExpression = withSpanExpr;
8553
+ }
8554
+ }
8555
+ return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2, explicitTraceExpression };
8525
8556
  });
8526
8557
  const isInsideEffectFn = (fnNode) => {
8527
8558
  const parent = fnNode.parent;
@@ -8549,6 +8580,9 @@ var effectFnOpportunity = createDiagnostic({
8549
8580
  if (ts.isFunctionExpression(node) && node.name) {
8550
8581
  return yield* TypeParserIssue.issue;
8551
8582
  }
8583
+ if (node.type) {
8584
+ return yield* TypeParserIssue.issue;
8585
+ }
8552
8586
  if (yield* isInsideEffectFn(node)) {
8553
8587
  return yield* TypeParserIssue.issue;
8554
8588
  }
@@ -8576,7 +8610,8 @@ var effectFnOpportunity = createDiagnostic({
8576
8610
  return succeed({
8577
8611
  effectModuleName: sourceEffectModuleName,
8578
8612
  pipeArguments: [],
8579
- generatorFunction: void 0
8613
+ generatorFunction: void 0,
8614
+ explicitTraceExpression: void 0
8580
8615
  });
8581
8616
  })
8582
8617
  );
@@ -8584,7 +8619,8 @@ var effectFnOpportunity = createDiagnostic({
8584
8619
  node,
8585
8620
  nameIdentifier,
8586
8621
  effectModuleName: opportunity.effectModuleName,
8587
- traceName,
8622
+ inferredTraceName: traceName,
8623
+ explicitTraceExpression: opportunity.explicitTraceExpression,
8588
8624
  pipeArguments: opportunity.pipeArguments,
8589
8625
  generatorFunction: opportunity.generatorFunction,
8590
8626
  hasParamsInPipeArgs: areParametersReferencedIn(node, opportunity.pipeArguments)
@@ -8603,7 +8639,7 @@ var effectFnOpportunity = createDiagnostic({
8603
8639
  if (ts.isArrowFunction(node)) return false;
8604
8640
  return node.asteriskToken !== void 0;
8605
8641
  };
8606
- const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceName, pipeArguments2) => {
8642
+ const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceNameOrExpression, pipeArguments2) => {
8607
8643
  const isGenerator = isGeneratorFunction(innerFunction);
8608
8644
  const newFunction = ts.factory.createFunctionExpression(
8609
8645
  void 0,
@@ -8618,11 +8654,12 @@ var effectFnOpportunity = createDiagnostic({
8618
8654
  ts.factory.createIdentifier(effectModuleName),
8619
8655
  "fn"
8620
8656
  );
8621
- if (traceName) {
8657
+ if (traceNameOrExpression) {
8658
+ const traceArg = typeof traceNameOrExpression === "string" ? ts.factory.createStringLiteral(traceNameOrExpression) : traceNameOrExpression;
8622
8659
  fnExpression = ts.factory.createCallExpression(
8623
8660
  fnExpression,
8624
8661
  void 0,
8625
- [ts.factory.createStringLiteral(traceName)]
8662
+ [traceArg]
8626
8663
  );
8627
8664
  }
8628
8665
  const effectFnCall = ts.factory.createCallExpression(fnExpression, void 0, [newFunction, ...pipeArguments2]);
@@ -8664,19 +8701,35 @@ var effectFnOpportunity = createDiagnostic({
8664
8701
  const target = yield* pipe(parseEffectFnOpportunityTarget(node), option);
8665
8702
  if (isNone2(target)) continue;
8666
8703
  if (target.value.hasParamsInPipeArgs) continue;
8667
- const { effectModuleName, nameIdentifier, node: targetNode, pipeArguments: pipeArguments2, traceName } = target.value;
8704
+ const {
8705
+ effectModuleName,
8706
+ explicitTraceExpression,
8707
+ inferredTraceName,
8708
+ nameIdentifier,
8709
+ node: targetNode,
8710
+ pipeArguments: pipeArguments2
8711
+ } = target.value;
8668
8712
  const innerFunction = target.value.generatorFunction ?? targetNode;
8669
8713
  const fixes = [];
8670
- fixes.push({
8671
- fixName: "effectFnOpportunity_toEffectFn",
8672
- description: traceName ? `Convert to Effect.fn("${traceName}")` : "Convert to Effect.fn",
8673
- apply: gen(function* () {
8674
- const changeTracker = yield* service(ChangeTracker);
8675
- const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, traceName, pipeArguments2);
8676
- changeTracker.replaceNode(sourceFile, targetNode, newNode);
8677
- })
8678
- });
8679
- if (target.value.generatorFunction) {
8714
+ if (pluginOptions.effectFn.includes("span") && explicitTraceExpression) {
8715
+ fixes.push({
8716
+ fixName: "effectFnOpportunity_toEffectFnWithSpan",
8717
+ description: "Convert to Effect.fn (with span from withSpan)",
8718
+ apply: gen(function* () {
8719
+ const changeTracker = yield* service(ChangeTracker);
8720
+ const finalPipeArguments = pipeArguments2.slice(0, -1);
8721
+ const newNode = createEffectFnNode(
8722
+ targetNode,
8723
+ innerFunction,
8724
+ effectModuleName,
8725
+ explicitTraceExpression,
8726
+ finalPipeArguments
8727
+ );
8728
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
8729
+ })
8730
+ });
8731
+ }
8732
+ if (pluginOptions.effectFn.includes("untraced") && target.value.generatorFunction) {
8680
8733
  fixes.push({
8681
8734
  fixName: "effectFnOpportunity_toEffectFnUntraced",
8682
8735
  description: "Convert to Effect.fnUntraced",
@@ -8687,10 +8740,67 @@ var effectFnOpportunity = createDiagnostic({
8687
8740
  })
8688
8741
  });
8689
8742
  }
8690
- const pipeArgsSuffix = pipeArguments2.length > 0 ? ` Effect.fn also accepts the piped transformations as additional arguments.` : ``;
8743
+ if (pluginOptions.effectFn.includes("no-span")) {
8744
+ fixes.push({
8745
+ fixName: "effectFnOpportunity_toEffectFnNoSpan",
8746
+ description: "Convert to Effect.fn (no span)",
8747
+ apply: gen(function* () {
8748
+ const changeTracker = yield* service(ChangeTracker);
8749
+ const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, void 0, pipeArguments2);
8750
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
8751
+ })
8752
+ });
8753
+ }
8754
+ if (pluginOptions.effectFn.includes("inferred-span") && inferredTraceName && !explicitTraceExpression) {
8755
+ fixes.push({
8756
+ fixName: "effectFnOpportunity_toEffectFnSpanInferred",
8757
+ description: `Convert to Effect.fn("${inferredTraceName}")`,
8758
+ apply: gen(function* () {
8759
+ const changeTracker = yield* service(ChangeTracker);
8760
+ const newNode = createEffectFnNode(
8761
+ targetNode,
8762
+ innerFunction,
8763
+ effectModuleName,
8764
+ inferredTraceName,
8765
+ pipeArguments2
8766
+ );
8767
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
8768
+ })
8769
+ });
8770
+ }
8771
+ if (fixes.length === 0) continue;
8772
+ const generateExpectedSignature = () => {
8773
+ const firstFix = fixes[0];
8774
+ if (!firstFix) return "Effect.fn(function*() { ... })";
8775
+ const typeParamNames = targetNode.typeParameters ? `<${targetNode.typeParameters.map((tp) => ts.idText(tp.name)).join(", ")}>` : "";
8776
+ const paramNames = targetNode.parameters.map((param) => {
8777
+ if (ts.isIdentifier(param.name)) {
8778
+ return ts.idText(param.name);
8779
+ }
8780
+ return "_";
8781
+ }).join(", ");
8782
+ const fnSignature = `function*${typeParamNames}(${paramNames}) { ... }`;
8783
+ const pipeArgsForWithSpan = pipeArguments2.slice(0, -1);
8784
+ const pipeArgsSuffix = (args2) => args2.length > 0 ? ", ...pipeTransformations" : "";
8785
+ switch (firstFix.fixName) {
8786
+ case "effectFnOpportunity_toEffectFnWithSpan": {
8787
+ const traceName = explicitTraceExpression ? sourceFile.text.slice(explicitTraceExpression.pos, explicitTraceExpression.end).trim() : void 0;
8788
+ return `${effectModuleName}.fn(${traceName})(${fnSignature}${pipeArgsSuffix(pipeArgsForWithSpan)})`;
8789
+ }
8790
+ case "effectFnOpportunity_toEffectFnUntraced":
8791
+ return `${effectModuleName}.fnUntraced(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
8792
+ case "effectFnOpportunity_toEffectFnNoSpan":
8793
+ return `${effectModuleName}.fn(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
8794
+ case "effectFnOpportunity_toEffectFnSpanInferred":
8795
+ return `${effectModuleName}.fn("${inferredTraceName}")(${fnSignature}${pipeArgsSuffix(pipeArguments2)})`;
8796
+ default:
8797
+ return `${effectModuleName}.fn(${fnSignature})`;
8798
+ }
8799
+ };
8800
+ const expectedSignature = generateExpectedSignature();
8691
8801
  report({
8692
8802
  location: nameIdentifier ?? targetNode,
8693
- 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}`,
8803
+ messageText: `Can be rewritten as a reusable function: ${expectedSignature}`,
8694
8804
  fixes
8695
8805
  });
8696
8806
  }