@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/index.js CHANGED
@@ -2799,6 +2799,51 @@ function make3(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
2799
2799
  if (!symbol3) return typeParserIssue("Node has no symbol", void 0, givenNode);
2800
2800
  return isSymbolExportOfPackageModule(symbol3, packageName, memberName, isCorrectSourceFile);
2801
2801
  };
2802
+ const findSymbolsMatchingPackageAndExportedName = (packageName, exportedSymbolName) => cachedBy(
2803
+ fn("TypeParser.findSymbolsMatchingPackageAndExportedName")(function* (_fromSourceFile) {
2804
+ const result = [];
2805
+ for (const sourceFile of program.getSourceFiles()) {
2806
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
2807
+ if (!moduleSymbol) continue;
2808
+ const symbol3 = typeChecker.tryGetMemberInModuleExports(exportedSymbolName, moduleSymbol);
2809
+ if (!symbol3) continue;
2810
+ const packageInfo = yield* getSourceFilePackageInfo(sourceFile);
2811
+ if (!packageInfo || packageInfo.name.toLowerCase() !== packageName.toLowerCase()) continue;
2812
+ result.push([symbol3, sourceFile]);
2813
+ }
2814
+ return result;
2815
+ }),
2816
+ `TypeParser.findSymbolsMatchingPackageAndExportedName(${packageName}, ${exportedSymbolName})`,
2817
+ (sourceFile) => sourceFile
2818
+ );
2819
+ const isCauseTypeSourceFile = cachedBy(
2820
+ fn("TypeParser.isCauseTypeSourceFile")(function* (sourceFile) {
2821
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
2822
+ if (!moduleSymbol) return yield* typeParserIssue("Node has no symbol", void 0, sourceFile);
2823
+ const causeTypeSymbol = typeChecker.tryGetMemberInModuleExports("Cause", moduleSymbol);
2824
+ if (!causeTypeSymbol) return yield* typeParserIssue("Cause type not found", void 0, sourceFile);
2825
+ const type = typeChecker.getDeclaredTypeOfSymbol(causeTypeSymbol);
2826
+ yield* pipeableType(type, sourceFile);
2827
+ return sourceFile;
2828
+ }),
2829
+ "TypeParser.isCauseTypeSourceFile",
2830
+ (sourceFile) => sourceFile
2831
+ );
2832
+ const effectCauseYieldableErrorTypes = cachedBy(
2833
+ fn("TypeParser.effectCauseYieldableErrorTypes")(function* (fromSourceFile) {
2834
+ const symbols = yield* findSymbolsMatchingPackageAndExportedName("effect", "YieldableError")(fromSourceFile);
2835
+ const result = [];
2836
+ for (const [symbol3, sourceFile] of symbols) {
2837
+ const causeFile = yield* isCauseTypeSourceFile(sourceFile);
2838
+ if (!causeFile) continue;
2839
+ const type = typeChecker.getDeclaredTypeOfSymbol(symbol3);
2840
+ result.push(type);
2841
+ }
2842
+ return result;
2843
+ }),
2844
+ "TypeParser.effectCauseYieldableErrorTypes",
2845
+ (fromSourceFile) => fromSourceFile
2846
+ );
2802
2847
  function covariantTypeArgument(type) {
2803
2848
  const signatures = typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call);
2804
2849
  if (signatures.length !== 1) {
@@ -3788,6 +3833,7 @@ function make3(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3788
3833
  effectGen,
3789
3834
  effectFnUntracedGen,
3790
3835
  effectFnGen,
3836
+ effectCauseYieldableErrorTypes,
3791
3837
  unnecessaryEffectGen: unnecessaryEffectGen2,
3792
3838
  effectSchemaType,
3793
3839
  contextTag,
@@ -4098,8 +4144,93 @@ var accessors = createCodegen({
4098
4144
  })
4099
4145
  });
4100
4146
 
4147
+ // src/codegens/annotate.ts
4148
+ var annotate = createCodegen({
4149
+ name: "annotate",
4150
+ apply: fn("annotate.apply")(function* (sourceFile, textRange) {
4151
+ const ts = yield* service(TypeScriptApi);
4152
+ const tsUtils = yield* service(TypeScriptUtils);
4153
+ const typeChecker = yield* service(TypeCheckerApi);
4154
+ const typeCheckerUtils = yield* service(TypeCheckerUtils);
4155
+ const parse3 = (node) => gen(function* () {
4156
+ let variableDeclarations = [];
4157
+ const result = [];
4158
+ if (ts.isVariableStatement(node)) {
4159
+ variableDeclarations = [...variableDeclarations, ...node.declarationList.declarations];
4160
+ } else if (ts.isVariableDeclarationList(node)) {
4161
+ variableDeclarations = [...variableDeclarations, ...node.declarations];
4162
+ } else if (ts.isVariableDeclaration(node)) {
4163
+ variableDeclarations = [...variableDeclarations, node];
4164
+ }
4165
+ if (variableDeclarations.length === 0) {
4166
+ return yield* fail(new CodegenNotApplicableError("not a variable declaration"));
4167
+ }
4168
+ for (const variableDeclaration of variableDeclarations) {
4169
+ if (!variableDeclaration.initializer) continue;
4170
+ const initializerType = typeChecker.getTypeAtLocation(variableDeclaration.initializer);
4171
+ const initializerTypeNode = fromNullable(typeCheckerUtils.typeToSimplifiedTypeNode(
4172
+ initializerType,
4173
+ node,
4174
+ ts.NodeBuilderFlags.NoTruncation
4175
+ )).pipe(
4176
+ orElse(
4177
+ () => fromNullable(typeCheckerUtils.typeToSimplifiedTypeNode(
4178
+ initializerType,
4179
+ void 0,
4180
+ ts.NodeBuilderFlags.NoTruncation
4181
+ ))
4182
+ ),
4183
+ getOrUndefined
4184
+ );
4185
+ if (!initializerTypeNode) continue;
4186
+ const typeNodeString = typeChecker.typeToString(initializerType, void 0, ts.TypeFormatFlags.NoTruncation);
4187
+ const hash3 = cyrb53(typeNodeString);
4188
+ result.push({ variableDeclaration, initializerTypeNode, hash: hash3 });
4189
+ }
4190
+ if (result.length === 0) {
4191
+ return yield* fail(new CodegenNotApplicableError("no variable declarations with initializers"));
4192
+ }
4193
+ const hash2 = cyrb53(result.map((_) => _.hash).join("/"));
4194
+ return {
4195
+ hash: hash2,
4196
+ result
4197
+ };
4198
+ });
4199
+ const nodeAndCommentRange = tsUtils.findNodeWithLeadingCommentAtPosition(sourceFile, textRange.pos);
4200
+ if (!nodeAndCommentRange) return yield* fail(new CodegenNotApplicableError("no node and comment range"));
4201
+ return yield* pipe(
4202
+ parse3(nodeAndCommentRange.node),
4203
+ map5(
4204
+ (_) => ({
4205
+ hash: _.hash,
4206
+ description: "Annotate with type",
4207
+ apply: gen(function* () {
4208
+ const changeTracker = yield* service(ChangeTracker);
4209
+ for (const { initializerTypeNode, variableDeclaration } of _.result) {
4210
+ if (variableDeclaration.type) {
4211
+ changeTracker.deleteRange(sourceFile, {
4212
+ pos: variableDeclaration.name.end,
4213
+ end: variableDeclaration.type.end
4214
+ });
4215
+ }
4216
+ changeTracker.insertNodeAt(
4217
+ sourceFile,
4218
+ variableDeclaration.name.end,
4219
+ initializerTypeNode,
4220
+ {
4221
+ prefix: ": "
4222
+ }
4223
+ );
4224
+ }
4225
+ })
4226
+ })
4227
+ )
4228
+ );
4229
+ })
4230
+ });
4231
+
4101
4232
  // src/codegens.ts
4102
- var codegens = [accessors];
4233
+ var codegens = [accessors, annotate];
4103
4234
 
4104
4235
  // src/completions/effectCodegensComment.ts
4105
4236
  var effectCodegensComment = createCompletion({
@@ -8236,6 +8367,11 @@ var runEffectInsideEffect = createDiagnostic({
8236
8367
  apply: fn("runEffectInsideEffect.apply")(function* (sourceFile, report) {
8237
8368
  const ts = yield* service(TypeScriptApi);
8238
8369
  const typeParser = yield* service(TypeParser);
8370
+ const tsUtils = yield* service(TypeScriptUtils);
8371
+ const parseEffectMethod = (node, methodName) => pipe(
8372
+ typeParser.isNodeReferenceToEffectModuleApi(methodName)(node),
8373
+ map5(() => ({ node, methodName }))
8374
+ );
8239
8375
  const nodeToVisit = [];
8240
8376
  const appendNodeToVisit = (node) => {
8241
8377
  nodeToVisit.push(node);
@@ -8246,11 +8382,12 @@ var runEffectInsideEffect = createDiagnostic({
8246
8382
  const node = nodeToVisit.shift();
8247
8383
  ts.forEachChild(node, appendNodeToVisit);
8248
8384
  if (!ts.isCallExpression(node)) continue;
8385
+ if (node.arguments.length === 0) continue;
8249
8386
  const isEffectRunCall = yield* pipe(
8250
- typeParser.isNodeReferenceToEffectModuleApi("runPromise")(node.expression),
8251
- orElse2(() => typeParser.isNodeReferenceToEffectModuleApi("runSync")(node.expression)),
8252
- orElse2(() => typeParser.isNodeReferenceToEffectModuleApi("runFork")(node.expression)),
8253
- orElse2(() => typeParser.isNodeReferenceToEffectModuleApi("runCallback")(node.expression)),
8387
+ parseEffectMethod(node.expression, "runPromise"),
8388
+ orElse2(() => parseEffectMethod(node.expression, "runSync")),
8389
+ orElse2(() => parseEffectMethod(node.expression, "runFork")),
8390
+ orElse2(() => parseEffectMethod(node.expression, "runCallback")),
8254
8391
  option
8255
8392
  );
8256
8393
  if (isNone2(isEffectRunCall)) continue;
@@ -8270,18 +8407,90 @@ var runEffectInsideEffect = createDiagnostic({
8270
8407
  orElse2(() => typeParser.effectFnGen(possiblyEffectGen)),
8271
8408
  option
8272
8409
  );
8273
- if (isSome2(isInEffectGen)) {
8410
+ if (isSome2(isInEffectGen) && isInEffectGen.value.body.statements.length > 0) {
8274
8411
  const nodeText = sourceFile.text.substring(
8275
8412
  ts.getTokenPosOfNode(node.expression, sourceFile),
8276
8413
  node.expression.end
8277
8414
  );
8278
- 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.
8279
- 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.`;
8280
- report({
8281
- location: node.expression,
8282
- messageText,
8283
- fixes: []
8284
- });
8415
+ if (nodeIntroduceScope && nodeIntroduceScope !== isInEffectGen.value.generatorFunction) {
8416
+ const fixAddRuntime = gen(function* () {
8417
+ const changeTracker = yield* service(ChangeTracker);
8418
+ const runtimeModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Runtime") || "Runtime";
8419
+ const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
8420
+ let runtimeIdentifier = void 0;
8421
+ for (const statement of isInEffectGen.value.generatorFunction.body.statements) {
8422
+ if (ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) {
8423
+ const declaration = statement.declarationList.declarations[0];
8424
+ if (declaration.initializer && ts.isYieldExpression(declaration.initializer) && declaration.initializer.asteriskToken && declaration.initializer.expression) {
8425
+ const yieldedExpression = declaration.initializer.expression;
8426
+ if (ts.isCallExpression(yieldedExpression)) {
8427
+ const maybeEffectRuntime = yield* pipe(
8428
+ typeParser.isNodeReferenceToEffectModuleApi("runtime")(yieldedExpression.expression),
8429
+ option
8430
+ );
8431
+ if (isSome2(maybeEffectRuntime) && ts.isIdentifier(declaration.name)) {
8432
+ runtimeIdentifier = ts.idText(declaration.name);
8433
+ }
8434
+ }
8435
+ }
8436
+ }
8437
+ }
8438
+ if (!runtimeIdentifier) {
8439
+ changeTracker.insertNodeAt(
8440
+ sourceFile,
8441
+ isInEffectGen.value.body.statements[0].pos,
8442
+ ts.factory.createVariableStatement(
8443
+ void 0,
8444
+ ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(
8445
+ "effectRuntime",
8446
+ void 0,
8447
+ void 0,
8448
+ ts.factory.createYieldExpression(
8449
+ ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
8450
+ ts.factory.createCallExpression(
8451
+ ts.factory.createPropertyAccessExpression(
8452
+ ts.factory.createIdentifier(effectModuleIdentifier),
8453
+ "runtime"
8454
+ ),
8455
+ [ts.factory.createTypeReferenceNode("never")],
8456
+ []
8457
+ )
8458
+ )
8459
+ )], ts.NodeFlags.Const)
8460
+ ),
8461
+ {
8462
+ prefix: "\n",
8463
+ suffix: "\n"
8464
+ }
8465
+ );
8466
+ }
8467
+ changeTracker.deleteRange(sourceFile, {
8468
+ pos: ts.getTokenPosOfNode(node.expression, sourceFile),
8469
+ end: node.arguments[0].pos
8470
+ });
8471
+ changeTracker.insertText(
8472
+ sourceFile,
8473
+ node.arguments[0].pos,
8474
+ `${runtimeModuleIdentifier}.${isEffectRunCall.value.methodName}(${runtimeIdentifier || "effectRuntime"}, `
8475
+ );
8476
+ });
8477
+ report({
8478
+ location: node.expression,
8479
+ messageText: `Using ${nodeText} inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.
8480
+ Consider extracting the Runtime by using for example Effect.runtime and then use Runtime.${isEffectRunCall.value.methodName} with the extracted runtime instead.`,
8481
+ fixes: [{
8482
+ fixName: "runEffectInsideEffect_fix",
8483
+ description: "Use a runtime to run the Effect",
8484
+ apply: fixAddRuntime
8485
+ }]
8486
+ });
8487
+ } else {
8488
+ report({
8489
+ location: node.expression,
8490
+ messageText: `Using ${nodeText} inside an Effect is not recommended. Effects inside generators can usually just be yielded.`,
8491
+ fixes: []
8492
+ });
8493
+ }
8285
8494
  }
8286
8495
  currentParent = currentParent.parent;
8287
8496
  }
@@ -8711,6 +8920,66 @@ var unnecessaryEffectGen = createDiagnostic({
8711
8920
  })
8712
8921
  });
8713
8922
 
8923
+ // src/diagnostics/unnecessaryFailYieldableError.ts
8924
+ var unnecessaryFailYieldableError = createDiagnostic({
8925
+ name: "unnecessaryFailYieldableError",
8926
+ code: 29,
8927
+ severity: "suggestion",
8928
+ apply: fn("unnecessaryFailYieldableError.apply")(function* (sourceFile, report) {
8929
+ const ts = yield* service(TypeScriptApi);
8930
+ const typeParser = yield* service(TypeParser);
8931
+ const typeChecker = yield* service(TypeCheckerApi);
8932
+ const yieldableErrorTypes = yield* pipe(
8933
+ typeParser.effectCauseYieldableErrorTypes(sourceFile),
8934
+ orElse2(() => succeed([]))
8935
+ );
8936
+ const nodeToVisit = [];
8937
+ const appendNodeToVisit = (node) => {
8938
+ nodeToVisit.push(node);
8939
+ return void 0;
8940
+ };
8941
+ ts.forEachChild(sourceFile, appendNodeToVisit);
8942
+ while (nodeToVisit.length > 0) {
8943
+ const node = nodeToVisit.shift();
8944
+ ts.forEachChild(node, appendNodeToVisit);
8945
+ if (ts.isYieldExpression(node) && node.asteriskToken && node.expression && ts.isCallExpression(node.expression)) {
8946
+ const callExpression = node.expression;
8947
+ yield* pipe(
8948
+ typeParser.isNodeReferenceToEffectModuleApi("fail")(callExpression.expression),
8949
+ map5(() => {
8950
+ if (callExpression.arguments.length > 0) {
8951
+ const failArgument = callExpression.arguments[0];
8952
+ const argumentType = typeChecker.getTypeAtLocation(failArgument);
8953
+ const isYieldableError = yieldableErrorTypes.some(
8954
+ (yieldableType) => typeChecker.isTypeAssignableTo(argumentType, yieldableType)
8955
+ );
8956
+ if (isYieldableError) {
8957
+ report({
8958
+ location: node,
8959
+ messageText: `This Effect.fail call uses a yieldable error type as argument. You can yield* the error directly instead.`,
8960
+ fixes: [{
8961
+ fixName: "unnecessaryFailYieldableError_fix",
8962
+ description: "Replace yield* Effect.fail with yield*",
8963
+ apply: gen(function* () {
8964
+ const changeTracker = yield* service(ChangeTracker);
8965
+ changeTracker.replaceNode(
8966
+ sourceFile,
8967
+ callExpression,
8968
+ failArgument
8969
+ );
8970
+ })
8971
+ }]
8972
+ });
8973
+ }
8974
+ }
8975
+ }),
8976
+ ignore
8977
+ );
8978
+ }
8979
+ }
8980
+ })
8981
+ });
8982
+
8714
8983
  // src/diagnostics/unnecessaryPipe.ts
8715
8984
  var unnecessaryPipe = createDiagnostic({
8716
8985
  name: "unnecessaryPipe",
@@ -8898,6 +9167,7 @@ var diagnostics = [
8898
9167
  floatingEffect,
8899
9168
  missingStarInYieldEffectGen,
8900
9169
  unnecessaryEffectGen,
9170
+ unnecessaryFailYieldableError,
8901
9171
  missingReturnYieldStar,
8902
9172
  leakingRequirements,
8903
9173
  unnecessaryPipe,