@effect/language-service 0.56.0 → 0.57.1

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/README.md CHANGED
@@ -72,6 +72,7 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
72
72
  - Warn when catch callbacks in `Effect.tryPromise`, `Effect.tryMap`, or `Effect.tryMapPromise` return `unknown` or `any` types
73
73
  - Warn when using `Effect.runSync`, `Effect.runPromise`, `Effect.runFork`, or `Effect.runCallback` inside an Effect
74
74
  - Warn when using `Schema.Union` with multiple `Schema.Literal` calls that can be simplified to a single `Schema.Literal` call
75
+ - Warn when using `yield* Effect.fail()` with yieldable error types that can be yielded directly
75
76
 
76
77
  ### Completions
77
78
 
@@ -99,6 +100,11 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
99
100
  - Toggle between pipe styles `X.pipe(Y)` and `pipe(X, Y)`
100
101
  - Layer Magic: Automatically compose and build layers based on service dependencies
101
102
 
103
+ ### Codegens
104
+
105
+ - Automatically adds type annotations to exported constants based on their initializer types using `// @effect-codegens annotate`
106
+ - Automatically implements service accessors in `Effect.Service`, `Context.Tag` or `Effect.Tag` declarations using `// @effect-codegens accessors`
107
+
102
108
  ### Miscellaneous
103
109
  - Renaming a class name, will rename the identifier as well for TaggedError, TaggedClass, etc...
104
110
  - "Go to definition" for RpcClient will resolve to the Rpc definition
package/cli.js CHANGED
@@ -32083,6 +32083,51 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
32083
32083
  if (!symbol3) return typeParserIssue("Node has no symbol", void 0, givenNode);
32084
32084
  return isSymbolExportOfPackageModule(symbol3, packageName, memberName, isCorrectSourceFile);
32085
32085
  };
32086
+ const findSymbolsMatchingPackageAndExportedName = (packageName, exportedSymbolName) => cachedBy(
32087
+ fn2("TypeParser.findSymbolsMatchingPackageAndExportedName")(function* (_fromSourceFile) {
32088
+ const result = [];
32089
+ for (const sourceFile of program.getSourceFiles()) {
32090
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
32091
+ if (!moduleSymbol) continue;
32092
+ const symbol3 = typeChecker.tryGetMemberInModuleExports(exportedSymbolName, moduleSymbol);
32093
+ if (!symbol3) continue;
32094
+ const packageInfo = yield* getSourceFilePackageInfo(sourceFile);
32095
+ if (!packageInfo || packageInfo.name.toLowerCase() !== packageName.toLowerCase()) continue;
32096
+ result.push([symbol3, sourceFile]);
32097
+ }
32098
+ return result;
32099
+ }),
32100
+ `TypeParser.findSymbolsMatchingPackageAndExportedName(${packageName}, ${exportedSymbolName})`,
32101
+ (sourceFile) => sourceFile
32102
+ );
32103
+ const isCauseTypeSourceFile = cachedBy(
32104
+ fn2("TypeParser.isCauseTypeSourceFile")(function* (sourceFile) {
32105
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
32106
+ if (!moduleSymbol) return yield* typeParserIssue("Node has no symbol", void 0, sourceFile);
32107
+ const causeTypeSymbol = typeChecker.tryGetMemberInModuleExports("Cause", moduleSymbol);
32108
+ if (!causeTypeSymbol) return yield* typeParserIssue("Cause type not found", void 0, sourceFile);
32109
+ const type2 = typeChecker.getDeclaredTypeOfSymbol(causeTypeSymbol);
32110
+ yield* pipeableType(type2, sourceFile);
32111
+ return sourceFile;
32112
+ }),
32113
+ "TypeParser.isCauseTypeSourceFile",
32114
+ (sourceFile) => sourceFile
32115
+ );
32116
+ const effectCauseYieldableErrorTypes = cachedBy(
32117
+ fn2("TypeParser.effectCauseYieldableErrorTypes")(function* (fromSourceFile) {
32118
+ const symbols = yield* findSymbolsMatchingPackageAndExportedName("effect", "YieldableError")(fromSourceFile);
32119
+ const result = [];
32120
+ for (const [symbol3, sourceFile] of symbols) {
32121
+ const causeFile = yield* isCauseTypeSourceFile(sourceFile);
32122
+ if (!causeFile) continue;
32123
+ const type2 = typeChecker.getDeclaredTypeOfSymbol(symbol3);
32124
+ result.push(type2);
32125
+ }
32126
+ return result;
32127
+ }),
32128
+ "TypeParser.effectCauseYieldableErrorTypes",
32129
+ (fromSourceFile) => fromSourceFile
32130
+ );
32086
32131
  function covariantTypeArgument(type2) {
32087
32132
  const signatures = typeChecker.getSignaturesOfType(type2, ts.SignatureKind.Call);
32088
32133
  if (signatures.length !== 1) {
@@ -33072,6 +33117,7 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
33072
33117
  effectGen,
33073
33118
  effectFnUntracedGen,
33074
33119
  effectFnGen,
33120
+ effectCauseYieldableErrorTypes,
33075
33121
  unnecessaryEffectGen: unnecessaryEffectGen2,
33076
33122
  effectSchemaType,
33077
33123
  contextTag,
@@ -33382,8 +33428,93 @@ var accessors = createCodegen({
33382
33428
  })
33383
33429
  });
33384
33430
 
33431
+ // src/codegens/annotate.ts
33432
+ var annotate3 = createCodegen({
33433
+ name: "annotate",
33434
+ apply: fn2("annotate.apply")(function* (sourceFile, textRange) {
33435
+ const ts = yield* service2(TypeScriptApi);
33436
+ const tsUtils = yield* service2(TypeScriptUtils);
33437
+ const typeChecker = yield* service2(TypeCheckerApi);
33438
+ const typeCheckerUtils = yield* service2(TypeCheckerUtils);
33439
+ const parse6 = (node) => gen3(function* () {
33440
+ let variableDeclarations = [];
33441
+ const result = [];
33442
+ if (ts.isVariableStatement(node)) {
33443
+ variableDeclarations = [...variableDeclarations, ...node.declarationList.declarations];
33444
+ } else if (ts.isVariableDeclarationList(node)) {
33445
+ variableDeclarations = [...variableDeclarations, ...node.declarations];
33446
+ } else if (ts.isVariableDeclaration(node)) {
33447
+ variableDeclarations = [...variableDeclarations, node];
33448
+ }
33449
+ if (variableDeclarations.length === 0) {
33450
+ return yield* fail18(new CodegenNotApplicableError("not a variable declaration"));
33451
+ }
33452
+ for (const variableDeclaration of variableDeclarations) {
33453
+ if (!variableDeclaration.initializer) continue;
33454
+ const initializerType = typeChecker.getTypeAtLocation(variableDeclaration.initializer);
33455
+ const initializerTypeNode = fromNullable(typeCheckerUtils.typeToSimplifiedTypeNode(
33456
+ initializerType,
33457
+ node,
33458
+ ts.NodeBuilderFlags.NoTruncation
33459
+ )).pipe(
33460
+ orElse(
33461
+ () => fromNullable(typeCheckerUtils.typeToSimplifiedTypeNode(
33462
+ initializerType,
33463
+ void 0,
33464
+ ts.NodeBuilderFlags.NoTruncation
33465
+ ))
33466
+ ),
33467
+ getOrUndefined
33468
+ );
33469
+ if (!initializerTypeNode) continue;
33470
+ const typeNodeString = typeChecker.typeToString(initializerType, void 0, ts.TypeFormatFlags.NoTruncation);
33471
+ const hash3 = cyrb53(typeNodeString);
33472
+ result.push({ variableDeclaration, initializerTypeNode, hash: hash3 });
33473
+ }
33474
+ if (result.length === 0) {
33475
+ return yield* fail18(new CodegenNotApplicableError("no variable declarations with initializers"));
33476
+ }
33477
+ const hash2 = cyrb53(result.map((_) => _.hash).join("/"));
33478
+ return {
33479
+ hash: hash2,
33480
+ result
33481
+ };
33482
+ });
33483
+ const nodeAndCommentRange = tsUtils.findNodeWithLeadingCommentAtPosition(sourceFile, textRange.pos);
33484
+ if (!nodeAndCommentRange) return yield* fail18(new CodegenNotApplicableError("no node and comment range"));
33485
+ return yield* pipe(
33486
+ parse6(nodeAndCommentRange.node),
33487
+ map34(
33488
+ (_) => ({
33489
+ hash: _.hash,
33490
+ description: "Annotate with type",
33491
+ apply: gen3(function* () {
33492
+ const changeTracker = yield* service2(ChangeTracker);
33493
+ for (const { initializerTypeNode, variableDeclaration } of _.result) {
33494
+ if (variableDeclaration.type) {
33495
+ changeTracker.deleteRange(sourceFile, {
33496
+ pos: variableDeclaration.name.end,
33497
+ end: variableDeclaration.type.end
33498
+ });
33499
+ }
33500
+ changeTracker.insertNodeAt(
33501
+ sourceFile,
33502
+ variableDeclaration.name.end,
33503
+ initializerTypeNode,
33504
+ {
33505
+ prefix: ": "
33506
+ }
33507
+ );
33508
+ }
33509
+ })
33510
+ })
33511
+ )
33512
+ );
33513
+ })
33514
+ });
33515
+
33385
33516
  // src/codegens.ts
33386
- var codegens = [accessors];
33517
+ var codegens = [accessors, annotate3];
33387
33518
 
33388
33519
  // src/cli/codegen.ts
33389
33520
  var NoFilesToCodegenError = class extends TaggedError("NoFilesToCodegenError") {
@@ -35397,6 +35528,11 @@ var runEffectInsideEffect = createDiagnostic({
35397
35528
  apply: fn2("runEffectInsideEffect.apply")(function* (sourceFile, report) {
35398
35529
  const ts = yield* service2(TypeScriptApi);
35399
35530
  const typeParser = yield* service2(TypeParser);
35531
+ const tsUtils = yield* service2(TypeScriptUtils);
35532
+ const parseEffectMethod = (node, methodName) => pipe(
35533
+ typeParser.isNodeReferenceToEffectModuleApi(methodName)(node),
35534
+ map34(() => ({ node, methodName }))
35535
+ );
35400
35536
  const nodeToVisit = [];
35401
35537
  const appendNodeToVisit = (node) => {
35402
35538
  nodeToVisit.push(node);
@@ -35407,11 +35543,12 @@ var runEffectInsideEffect = createDiagnostic({
35407
35543
  const node = nodeToVisit.shift();
35408
35544
  ts.forEachChild(node, appendNodeToVisit);
35409
35545
  if (!ts.isCallExpression(node)) continue;
35546
+ if (node.arguments.length === 0) continue;
35410
35547
  const isEffectRunCall = yield* pipe(
35411
- typeParser.isNodeReferenceToEffectModuleApi("runPromise")(node.expression),
35412
- orElse14(() => typeParser.isNodeReferenceToEffectModuleApi("runSync")(node.expression)),
35413
- orElse14(() => typeParser.isNodeReferenceToEffectModuleApi("runFork")(node.expression)),
35414
- orElse14(() => typeParser.isNodeReferenceToEffectModuleApi("runCallback")(node.expression)),
35548
+ parseEffectMethod(node.expression, "runPromise"),
35549
+ orElse14(() => parseEffectMethod(node.expression, "runSync")),
35550
+ orElse14(() => parseEffectMethod(node.expression, "runFork")),
35551
+ orElse14(() => parseEffectMethod(node.expression, "runCallback")),
35415
35552
  option4
35416
35553
  );
35417
35554
  if (isNone2(isEffectRunCall)) continue;
@@ -35431,18 +35568,90 @@ var runEffectInsideEffect = createDiagnostic({
35431
35568
  orElse14(() => typeParser.effectFnGen(possiblyEffectGen)),
35432
35569
  option4
35433
35570
  );
35434
- if (isSome2(isInEffectGen)) {
35571
+ if (isSome2(isInEffectGen) && isInEffectGen.value.body.statements.length > 0) {
35435
35572
  const nodeText = sourceFile.text.substring(
35436
35573
  ts.getTokenPosOfNode(node.expression, sourceFile),
35437
35574
  node.expression.end
35438
35575
  );
35439
- const messageText = nodeIntroduceScope && nodeIntroduceScope !== isInEffectGen.value.generatorFunction ? `Using ${nodeText} inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.
35440
- Consider extracting the Runtime by using for example Effect.runtime and then use Runtime.run* with the extracted runtime instead.` : `Using ${nodeText} inside an Effect is not recommended. Effects inside generators can usually just be yielded.`;
35441
- report({
35442
- location: node.expression,
35443
- messageText,
35444
- fixes: []
35445
- });
35576
+ if (nodeIntroduceScope && nodeIntroduceScope !== isInEffectGen.value.generatorFunction) {
35577
+ const fixAddRuntime = gen3(function* () {
35578
+ const changeTracker = yield* service2(ChangeTracker);
35579
+ const runtimeModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Runtime") || "Runtime";
35580
+ const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
35581
+ let runtimeIdentifier = void 0;
35582
+ for (const statement of isInEffectGen.value.generatorFunction.body.statements) {
35583
+ if (ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) {
35584
+ const declaration = statement.declarationList.declarations[0];
35585
+ if (declaration.initializer && ts.isYieldExpression(declaration.initializer) && declaration.initializer.asteriskToken && declaration.initializer.expression) {
35586
+ const yieldedExpression = declaration.initializer.expression;
35587
+ if (ts.isCallExpression(yieldedExpression)) {
35588
+ const maybeEffectRuntime = yield* pipe(
35589
+ typeParser.isNodeReferenceToEffectModuleApi("runtime")(yieldedExpression.expression),
35590
+ option4
35591
+ );
35592
+ if (isSome2(maybeEffectRuntime) && ts.isIdentifier(declaration.name)) {
35593
+ runtimeIdentifier = ts.idText(declaration.name);
35594
+ }
35595
+ }
35596
+ }
35597
+ }
35598
+ }
35599
+ if (!runtimeIdentifier) {
35600
+ changeTracker.insertNodeAt(
35601
+ sourceFile,
35602
+ isInEffectGen.value.body.statements[0].pos,
35603
+ ts.factory.createVariableStatement(
35604
+ void 0,
35605
+ ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(
35606
+ "effectRuntime",
35607
+ void 0,
35608
+ void 0,
35609
+ ts.factory.createYieldExpression(
35610
+ ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
35611
+ ts.factory.createCallExpression(
35612
+ ts.factory.createPropertyAccessExpression(
35613
+ ts.factory.createIdentifier(effectModuleIdentifier),
35614
+ "runtime"
35615
+ ),
35616
+ [ts.factory.createTypeReferenceNode("never")],
35617
+ []
35618
+ )
35619
+ )
35620
+ )], ts.NodeFlags.Const)
35621
+ ),
35622
+ {
35623
+ prefix: "\n",
35624
+ suffix: "\n"
35625
+ }
35626
+ );
35627
+ }
35628
+ changeTracker.deleteRange(sourceFile, {
35629
+ pos: ts.getTokenPosOfNode(node.expression, sourceFile),
35630
+ end: node.arguments[0].pos
35631
+ });
35632
+ changeTracker.insertText(
35633
+ sourceFile,
35634
+ node.arguments[0].pos,
35635
+ `${runtimeModuleIdentifier}.${isEffectRunCall.value.methodName}(${runtimeIdentifier || "effectRuntime"}, `
35636
+ );
35637
+ });
35638
+ report({
35639
+ location: node.expression,
35640
+ messageText: `Using ${nodeText} inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.
35641
+ Consider extracting the Runtime by using for example Effect.runtime and then use Runtime.${isEffectRunCall.value.methodName} with the extracted runtime instead.`,
35642
+ fixes: [{
35643
+ fixName: "runEffectInsideEffect_fix",
35644
+ description: "Use a runtime to run the Effect",
35645
+ apply: fixAddRuntime
35646
+ }]
35647
+ });
35648
+ } else {
35649
+ report({
35650
+ location: node.expression,
35651
+ messageText: `Using ${nodeText} inside an Effect is not recommended. Effects inside generators can usually just be yielded.`,
35652
+ fixes: []
35653
+ });
35654
+ }
35446
35655
  }
35447
35656
  currentParent = currentParent.parent;
35448
35657
  }
@@ -35872,6 +36081,66 @@ var unnecessaryEffectGen = createDiagnostic({
35872
36081
  })
35873
36082
  });
35874
36083
 
36084
+ // src/diagnostics/unnecessaryFailYieldableError.ts
36085
+ var unnecessaryFailYieldableError = createDiagnostic({
36086
+ name: "unnecessaryFailYieldableError",
36087
+ code: 29,
36088
+ severity: "suggestion",
36089
+ apply: fn2("unnecessaryFailYieldableError.apply")(function* (sourceFile, report) {
36090
+ const ts = yield* service2(TypeScriptApi);
36091
+ const typeParser = yield* service2(TypeParser);
36092
+ const typeChecker = yield* service2(TypeCheckerApi);
36093
+ const yieldableErrorTypes = yield* pipe(
36094
+ typeParser.effectCauseYieldableErrorTypes(sourceFile),
36095
+ orElse14(() => succeed17([]))
36096
+ );
36097
+ const nodeToVisit = [];
36098
+ const appendNodeToVisit = (node) => {
36099
+ nodeToVisit.push(node);
36100
+ return void 0;
36101
+ };
36102
+ ts.forEachChild(sourceFile, appendNodeToVisit);
36103
+ while (nodeToVisit.length > 0) {
36104
+ const node = nodeToVisit.shift();
36105
+ ts.forEachChild(node, appendNodeToVisit);
36106
+ if (ts.isYieldExpression(node) && node.asteriskToken && node.expression && ts.isCallExpression(node.expression)) {
36107
+ const callExpression = node.expression;
36108
+ yield* pipe(
36109
+ typeParser.isNodeReferenceToEffectModuleApi("fail")(callExpression.expression),
36110
+ map34(() => {
36111
+ if (callExpression.arguments.length > 0) {
36112
+ const failArgument = callExpression.arguments[0];
36113
+ const argumentType = typeChecker.getTypeAtLocation(failArgument);
36114
+ const isYieldableError = yieldableErrorTypes.some(
36115
+ (yieldableType) => typeChecker.isTypeAssignableTo(argumentType, yieldableType)
36116
+ );
36117
+ if (isYieldableError) {
36118
+ report({
36119
+ location: node,
36120
+ messageText: `This Effect.fail call uses a yieldable error type as argument. You can yield* the error directly instead.`,
36121
+ fixes: [{
36122
+ fixName: "unnecessaryFailYieldableError_fix",
36123
+ description: "Replace yield* Effect.fail with yield*",
36124
+ apply: gen3(function* () {
36125
+ const changeTracker = yield* service2(ChangeTracker);
36126
+ changeTracker.replaceNode(
36127
+ sourceFile,
36128
+ callExpression,
36129
+ failArgument
36130
+ );
36131
+ })
36132
+ }]
36133
+ });
36134
+ }
36135
+ }
36136
+ }),
36137
+ ignore3
36138
+ );
36139
+ }
36140
+ }
36141
+ })
36142
+ });
36143
+
35875
36144
  // src/diagnostics/unnecessaryPipe.ts
35876
36145
  var unnecessaryPipe = createDiagnostic({
35877
36146
  name: "unnecessaryPipe",
@@ -36059,6 +36328,7 @@ var diagnostics = [
36059
36328
  floatingEffect,
36060
36329
  missingStarInYieldEffectGen,
36061
36330
  unnecessaryEffectGen,
36331
+ unnecessaryFailYieldableError,
36062
36332
  missingReturnYieldStar,
36063
36333
  leakingRequirements,
36064
36334
  unnecessaryPipe,