@effect/language-service 0.28.0 → 0.28.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
@@ -2859,6 +2859,47 @@ function make3(ts, typeChecker) {
2859
2859
  "TypeParser.scopeType",
2860
2860
  (type) => type
2861
2861
  );
2862
+ const promiseLike = cachedBy(
2863
+ function(type, atLocation) {
2864
+ const thenProperty = type.getProperty("then");
2865
+ if (!thenProperty) return typeParserIssue("not a promise - missing then property", type, atLocation);
2866
+ const thenType = typeChecker.getTypeOfSymbolAtLocation(thenProperty, atLocation);
2867
+ if (!thenType) return typeParserIssue("not a promise - missing then property", type, atLocation);
2868
+ for (const callSignature of thenType.getCallSignatures()) {
2869
+ const parameter = callSignature.parameters[0];
2870
+ if (!parameter) continue;
2871
+ const parameterType = callSignature.getTypeParameterAtPosition(0);
2872
+ if (!parameterType) continue;
2873
+ let callbackCallSignatures = [];
2874
+ let toTest = [parameterType];
2875
+ while (toTest.length > 0) {
2876
+ const type2 = toTest.shift();
2877
+ if (!type2) continue;
2878
+ const callSignatures = type2.getCallSignatures();
2879
+ callbackCallSignatures = callbackCallSignatures.concat(callSignatures);
2880
+ if (type2.isUnion()) {
2881
+ toTest = toTest.concat(type2.types);
2882
+ }
2883
+ }
2884
+ for (const callableType of callbackCallSignatures) {
2885
+ const callbackParameter = callableType.parameters[0];
2886
+ if (!callbackParameter) {
2887
+ continue;
2888
+ }
2889
+ const callbackParameterType = callableType.getTypeParameterAtPosition(0);
2890
+ if (!callbackParameterType) {
2891
+ continue;
2892
+ }
2893
+ return succeed({
2894
+ type: callbackParameterType
2895
+ });
2896
+ }
2897
+ }
2898
+ return typeParserIssue("not a promise", type, atLocation);
2899
+ },
2900
+ "TypeParser.promiseLike",
2901
+ (type) => type
2902
+ );
2862
2903
  return {
2863
2904
  effectType,
2864
2905
  strictEffectType,
@@ -2874,7 +2915,8 @@ function make3(ts, typeChecker) {
2874
2915
  contextTag,
2875
2916
  pipeableType,
2876
2917
  pipeCall,
2877
- scopeType
2918
+ scopeType,
2919
+ promiseLike
2878
2920
  };
2879
2921
  }
2880
2922
 
@@ -3589,6 +3631,110 @@ var missingStarInYieldEffectGen = createDiagnostic({
3589
3631
  })
3590
3632
  });
3591
3633
 
3634
+ // src/diagnostics/multipleEffectProvide.ts
3635
+ var multipleEffectProvide = createDiagnostic({
3636
+ name: "multipleEffectProvide",
3637
+ code: 18,
3638
+ severity: "warning",
3639
+ apply: fn("multipleEffectProvide.apply")(function* (sourceFile, report) {
3640
+ const ts = yield* service(TypeScriptApi);
3641
+ const typeChecker = yield* service(TypeCheckerApi);
3642
+ const typeParser = yield* service(TypeParser);
3643
+ const effectModuleIdentifier = yield* pipe(
3644
+ findImportedModuleIdentifierByPackageAndNameOrBarrel(
3645
+ sourceFile,
3646
+ "effect",
3647
+ "Effect"
3648
+ ),
3649
+ map4((_) => _.text),
3650
+ orElse2(() => succeed("Effect"))
3651
+ );
3652
+ const layerModuleIdentifier = yield* pipe(
3653
+ findImportedModuleIdentifierByPackageAndNameOrBarrel(
3654
+ sourceFile,
3655
+ "effect",
3656
+ "Layer"
3657
+ ),
3658
+ map4((_) => _.text),
3659
+ orElse2(() => succeed("Layer"))
3660
+ );
3661
+ const parseEffectProvideLayer = (node) => {
3662
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.name) && node.expression.name.text === "provide" && node.arguments.length > 0) {
3663
+ const layer = node.arguments[0];
3664
+ const type = typeChecker.getTypeAtLocation(layer);
3665
+ return pipe(
3666
+ typeParser.importedEffectModule(node.expression.expression),
3667
+ flatMap2(() => typeParser.layerType(type, layer)),
3668
+ map4(() => ({ layer, node })),
3669
+ orElse2(() => void_)
3670
+ );
3671
+ }
3672
+ return void_;
3673
+ };
3674
+ const parsePipeCall = (node) => gen(function* () {
3675
+ const { args: args2 } = yield* typeParser.pipeCall(node);
3676
+ let currentChunk = 0;
3677
+ const previousLayers = [[]];
3678
+ for (const pipeArg of args2) {
3679
+ const parsedProvide = yield* parseEffectProvideLayer(pipeArg);
3680
+ if (parsedProvide) {
3681
+ previousLayers[currentChunk].push(parsedProvide);
3682
+ } else {
3683
+ currentChunk++;
3684
+ previousLayers.push([]);
3685
+ }
3686
+ }
3687
+ for (const chunk of previousLayers) {
3688
+ if (chunk.length < 2) continue;
3689
+ report({
3690
+ node: chunk[0].node,
3691
+ messageText: "Calling multiple subsequent times Effect.provide is an anti-pattern and can lead to service lifecycle issues. You should combine the layers and provide them once instead.",
3692
+ fixes: [{
3693
+ fixName: "multipleEffectProvide_fix",
3694
+ description: "Combine into a single provide",
3695
+ apply: gen(function* () {
3696
+ const changeTracker = yield* service(ChangeTracker);
3697
+ changeTracker.deleteRange(sourceFile, {
3698
+ pos: chunk[0].node.getStart(sourceFile),
3699
+ end: chunk[chunk.length - 1].node.getEnd()
3700
+ });
3701
+ const newNode = ts.factory.createCallExpression(
3702
+ ts.factory.createPropertyAccessExpression(
3703
+ ts.factory.createIdentifier(effectModuleIdentifier),
3704
+ ts.factory.createIdentifier("provide")
3705
+ ),
3706
+ void 0,
3707
+ [ts.factory.createCallExpression(
3708
+ ts.factory.createPropertyAccessExpression(
3709
+ ts.factory.createIdentifier(layerModuleIdentifier),
3710
+ ts.factory.createIdentifier("mergeAll")
3711
+ ),
3712
+ void 0,
3713
+ chunk.map((c) => c.layer)
3714
+ )]
3715
+ );
3716
+ changeTracker.insertNodeAt(sourceFile, chunk[0].node.getStart(sourceFile), newNode);
3717
+ })
3718
+ }]
3719
+ });
3720
+ }
3721
+ });
3722
+ const nodeToVisit = [];
3723
+ const appendNodeToVisit = (node) => {
3724
+ nodeToVisit.push(node);
3725
+ return void 0;
3726
+ };
3727
+ ts.forEachChild(sourceFile, appendNodeToVisit);
3728
+ while (nodeToVisit.length > 0) {
3729
+ const node = nodeToVisit.shift();
3730
+ ts.forEachChild(node, appendNodeToVisit);
3731
+ if (ts.isCallExpression(node)) {
3732
+ yield* pipe(parsePipeCall(node), ignore);
3733
+ }
3734
+ }
3735
+ })
3736
+ });
3737
+
3592
3738
  // src/diagnostics/returnEffectInGen.ts
3593
3739
  var returnEffectInGen = createDiagnostic({
3594
3740
  name: "returnEffectInGen",
@@ -4050,7 +4196,8 @@ var diagnostics = [
4050
4196
  scopeInLayerEffect,
4051
4197
  effectInVoidSuccess,
4052
4198
  unnecessaryPipeChain,
4053
- strictBooleanExpressions
4199
+ strictBooleanExpressions,
4200
+ multipleEffectProvide
4054
4201
  ];
4055
4202
 
4056
4203
  // src/completions/effectDiagnosticsComment.ts
@@ -11083,6 +11230,273 @@ var wrapWithPipe = createRefactor({
11083
11230
  })
11084
11231
  });
11085
11232
 
11233
+ // src/refactors/writeTagClassAccessors.ts
11234
+ var writeTagClassAccessors = createRefactor({
11235
+ name: "writeTagClassAccessors",
11236
+ description: "Implement Service accessors",
11237
+ apply: fn("writeTagClassAccessors.apply")(function* (sourceFile, textRange) {
11238
+ const ts = yield* service(TypeScriptApi);
11239
+ const typeChecker = yield* service(TypeCheckerApi);
11240
+ const typeParser = yield* service(TypeParser);
11241
+ const effectIdentifier = pipe(
11242
+ yield* option(
11243
+ findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect")
11244
+ ),
11245
+ match({
11246
+ onNone: () => "Effect",
11247
+ onSome: (_) => _.text
11248
+ })
11249
+ );
11250
+ const createConstantProperty = (className, propertyName, type) => ts.factory.createPropertyDeclaration(
11251
+ [ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)],
11252
+ propertyName,
11253
+ void 0,
11254
+ type,
11255
+ ts.factory.createCallExpression(
11256
+ ts.factory.createPropertyAccessExpression(
11257
+ ts.factory.createIdentifier(effectIdentifier),
11258
+ "andThen"
11259
+ ),
11260
+ void 0,
11261
+ [
11262
+ ts.factory.createIdentifier(className.text),
11263
+ ts.factory.createArrowFunction(
11264
+ void 0,
11265
+ void 0,
11266
+ [ts.factory.createParameterDeclaration(
11267
+ void 0,
11268
+ void 0,
11269
+ "_"
11270
+ )],
11271
+ void 0,
11272
+ void 0,
11273
+ ts.factory.createPropertyAccessExpression(
11274
+ ts.factory.createIdentifier("_"),
11275
+ propertyName
11276
+ )
11277
+ )
11278
+ ]
11279
+ )
11280
+ );
11281
+ const createFunctionProperty = (className, propertyName, type, forceAny) => {
11282
+ const arrowBody = ts.factory.createCallExpression(
11283
+ ts.factory.createPropertyAccessExpression(
11284
+ ts.factory.createIdentifier(effectIdentifier),
11285
+ "andThen"
11286
+ ),
11287
+ void 0,
11288
+ [
11289
+ ts.factory.createIdentifier(className.text),
11290
+ ts.factory.createArrowFunction(
11291
+ void 0,
11292
+ void 0,
11293
+ [ts.factory.createParameterDeclaration(
11294
+ void 0,
11295
+ void 0,
11296
+ "_",
11297
+ void 0,
11298
+ forceAny ? ts.factory.createTypeReferenceNode("any") : void 0
11299
+ )],
11300
+ void 0,
11301
+ void 0,
11302
+ ts.factory.createCallExpression(
11303
+ ts.factory.createPropertyAccessExpression(
11304
+ ts.factory.createIdentifier("_"),
11305
+ propertyName
11306
+ ),
11307
+ void 0,
11308
+ [
11309
+ ts.factory.createSpreadElement(ts.factory.createIdentifier("args"))
11310
+ ]
11311
+ )
11312
+ )
11313
+ ]
11314
+ );
11315
+ return ts.factory.createPropertyDeclaration(
11316
+ [ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)],
11317
+ propertyName,
11318
+ void 0,
11319
+ type,
11320
+ ts.factory.createArrowFunction(
11321
+ void 0,
11322
+ void 0,
11323
+ [ts.factory.createParameterDeclaration(
11324
+ void 0,
11325
+ ts.factory.createToken(ts.SyntaxKind.DotDotDotToken),
11326
+ "args",
11327
+ void 0,
11328
+ forceAny ? ts.factory.createArrayTypeNode(ts.factory.createTypeReferenceNode("any")) : void 0
11329
+ )],
11330
+ void 0,
11331
+ void 0,
11332
+ forceAny ? ts.factory.createAsExpression(arrowBody, ts.factory.createTypeReferenceNode("any")) : arrowBody
11333
+ )
11334
+ );
11335
+ };
11336
+ const generateReturnType = (type, atLocation, className) => pipe(
11337
+ typeParser.effectType(type, atLocation),
11338
+ flatMap2((returnedEffect) => {
11339
+ const contextType = returnedEffect.R.flags & ts.TypeFlags.Never ? ts.factory.createTypeReferenceNode(className.text) : ts.factory.createUnionTypeNode(
11340
+ [
11341
+ ts.factory.createTypeReferenceNode(className.text),
11342
+ typeChecker.typeToTypeNode(returnedEffect.R, atLocation, ts.NodeBuilderFlags.NoTruncation)
11343
+ ]
11344
+ );
11345
+ const successType = typeChecker.typeToTypeNode(
11346
+ returnedEffect.A,
11347
+ atLocation,
11348
+ ts.NodeBuilderFlags.NoTruncation
11349
+ );
11350
+ if (!successType) return fail("error generating success type");
11351
+ const failureType = typeChecker.typeToTypeNode(
11352
+ returnedEffect.E,
11353
+ atLocation,
11354
+ ts.NodeBuilderFlags.NoTruncation
11355
+ );
11356
+ if (!failureType) return fail("error generating failure type");
11357
+ const typeNode = ts.factory.createTypeReferenceNode(
11358
+ ts.factory.createQualifiedName(
11359
+ ts.factory.createIdentifier(effectIdentifier),
11360
+ ts.factory.createIdentifier("Effect")
11361
+ ),
11362
+ [successType, failureType, contextType]
11363
+ );
11364
+ return succeed(typeNode);
11365
+ }),
11366
+ orElse2(
11367
+ () => pipe(
11368
+ typeParser.promiseLike(type, atLocation),
11369
+ flatMap2(({ type: type2 }) => {
11370
+ const successType = typeChecker.typeToTypeNode(
11371
+ type2,
11372
+ atLocation,
11373
+ ts.NodeBuilderFlags.NoTruncation
11374
+ );
11375
+ if (!successType) return fail("error generating success type");
11376
+ return succeed(ts.factory.createTypeReferenceNode(
11377
+ ts.factory.createQualifiedName(
11378
+ ts.factory.createIdentifier(effectIdentifier),
11379
+ ts.factory.createIdentifier("Effect")
11380
+ ),
11381
+ [
11382
+ successType,
11383
+ ts.factory.createTypeReferenceNode(
11384
+ ts.factory.createQualifiedName(
11385
+ ts.factory.createIdentifier("Cause"),
11386
+ ts.factory.createIdentifier("UnknownException")
11387
+ )
11388
+ ),
11389
+ ts.factory.createTypeReferenceNode(className.text)
11390
+ ]
11391
+ ));
11392
+ })
11393
+ )
11394
+ ),
11395
+ orElse2(() => {
11396
+ const successType = typeChecker.typeToTypeNode(type, atLocation, ts.NodeBuilderFlags.NoTruncation);
11397
+ if (!successType) return fail("error generating success type");
11398
+ const typeNode = ts.factory.createTypeReferenceNode(
11399
+ ts.factory.createQualifiedName(
11400
+ ts.factory.createIdentifier(effectIdentifier),
11401
+ ts.factory.createIdentifier("Effect")
11402
+ ),
11403
+ [
11404
+ successType,
11405
+ ts.factory.createTypeReferenceNode("never"),
11406
+ ts.factory.createTypeReferenceNode(className.text)
11407
+ ]
11408
+ );
11409
+ return succeed(typeNode);
11410
+ })
11411
+ );
11412
+ const proxySignature = (signature, atLocation, className) => gen(function* () {
11413
+ const signatureDeclaration = typeChecker.signatureToSignatureDeclaration(
11414
+ signature,
11415
+ ts.SyntaxKind.FunctionType,
11416
+ atLocation,
11417
+ ts.NodeBuilderFlags.NoTruncation
11418
+ );
11419
+ if (!signatureDeclaration) return yield* fail("error generating signature");
11420
+ const returnType = yield* generateReturnType(signature.getReturnType(), atLocation, className);
11421
+ return ts.factory.createFunctionTypeNode(
11422
+ signatureDeclaration.typeParameters,
11423
+ signatureDeclaration.parameters,
11424
+ returnType
11425
+ );
11426
+ });
11427
+ const writeAccessors = fn("writeTagClassAccessors.writeAccessors")(
11428
+ function* (service2, className, atLocation) {
11429
+ const changeTracker = yield* service(ChangeTracker);
11430
+ const insertLocation = atLocation.members.length > 0 ? atLocation.members[0].pos : atLocation.getEnd() - 1;
11431
+ for (const property of typeChecker.getPropertiesOfType(service2)) {
11432
+ const servicePropertyType = typeChecker.getTypeOfSymbolAtLocation(property, atLocation);
11433
+ const callSignatures = [];
11434
+ let propertyDeclaration = void 0;
11435
+ for (const signature of servicePropertyType.getCallSignatures()) {
11436
+ yield* pipe(
11437
+ proxySignature(signature, atLocation, className),
11438
+ map4((sig) => {
11439
+ callSignatures.push(sig);
11440
+ }),
11441
+ ignore
11442
+ );
11443
+ }
11444
+ if (callSignatures.length === 0) {
11445
+ yield* pipe(
11446
+ generateReturnType(servicePropertyType, atLocation, className),
11447
+ map4((type) => {
11448
+ propertyDeclaration = createConstantProperty(className, property.getName(), type);
11449
+ }),
11450
+ ignore
11451
+ );
11452
+ } else {
11453
+ const allSignatures = ts.factory.createIntersectionTypeNode(callSignatures);
11454
+ const type = yield* simplifyTypeNode(allSignatures);
11455
+ propertyDeclaration = createFunctionProperty(className, property.getName(), type, callSignatures.length > 1);
11456
+ }
11457
+ if (propertyDeclaration) {
11458
+ const oldProperty = atLocation.members.filter(ts.isPropertyDeclaration).find((p) => {
11459
+ const symbol3 = typeChecker.getSymbolAtLocation(p.name);
11460
+ return symbol3?.getName() === property.getName();
11461
+ });
11462
+ if (oldProperty) {
11463
+ changeTracker.deleteRange(sourceFile, {
11464
+ pos: oldProperty.getStart(sourceFile),
11465
+ end: oldProperty.getEnd()
11466
+ });
11467
+ changeTracker.insertNodeAt(sourceFile, oldProperty.getStart(sourceFile), propertyDeclaration);
11468
+ } else {
11469
+ changeTracker.insertNodeAt(sourceFile, insertLocation, propertyDeclaration, { suffix: "\n" });
11470
+ }
11471
+ }
11472
+ }
11473
+ }
11474
+ );
11475
+ const writeTagClassAccessors2 = (node) => gen(function* () {
11476
+ if (!ts.isClassDeclaration(node)) return yield* fail(new RefactorNotApplicableError());
11477
+ if (!node.name) return yield* fail(new RefactorNotApplicableError());
11478
+ const classSym = typeChecker.getSymbolAtLocation(node.name);
11479
+ if (!classSym) return yield* fail(new RefactorNotApplicableError());
11480
+ const type = typeChecker.getTypeOfSymbol(classSym);
11481
+ const { Service } = yield* pipe(
11482
+ typeParser.contextTag(type, node),
11483
+ orElse2(() => fail(new RefactorNotApplicableError()))
11484
+ );
11485
+ return {
11486
+ kind: "refactor.rewrite.effect.writeTagClassAccessors",
11487
+ description: "Implement Service accessors",
11488
+ apply: pipe(
11489
+ writeAccessors(Service, node.name, node),
11490
+ provideService(TypeCheckerApi, typeChecker),
11491
+ provideService(TypeScriptApi, ts)
11492
+ )
11493
+ };
11494
+ });
11495
+ const parentNodes = yield* getAncestorNodesInRange(sourceFile, textRange);
11496
+ return yield* firstSuccessOf(parentNodes.map(writeTagClassAccessors2));
11497
+ })
11498
+ });
11499
+
11086
11500
  // src/refactors.ts
11087
11501
  var refactors = [
11088
11502
  asyncAwaitToGen,
@@ -11100,7 +11514,8 @@ var refactors = [
11100
11514
  wrapWithEffectGen,
11101
11515
  wrapWithPipe,
11102
11516
  effectGenToFn,
11103
- togglePipeStyle
11517
+ togglePipeStyle,
11518
+ writeTagClassAccessors
11104
11519
  ];
11105
11520
 
11106
11521
  // src/index.ts