@effect/language-service 0.29.0 → 0.30.0

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
@@ -46,10 +46,12 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
46
46
  - Warn on leaking requirements in Effect services
47
47
  - Warn on Scope as requirement of a Layer
48
48
  - Warn on subsequent `Effect.provide` anti-pattern
49
+ - Detect wrong `Self` type parameter for APIs like `Effect.Service` or `Schema.TaggedError` and similar 
49
50
  - Unnecessary usages of `Effect.gen` or `pipe()`
50
51
  - Warn when importing from a barrel file instead of from the module directly
51
52
  - Warn on usage of try/catch inside `Effect.gen` and family
52
53
  - Detect unnecessary pipe chains like `X.pipe(Y).pipe(Z)`
54
+ - Warn when using `Effect.Service` with `accessors: true` but methods have generics or multiple signatures
53
55
 
54
56
  ### Completions
55
57
 
@@ -106,30 +108,6 @@ Few options can be provided alongside the initialization of the Language Service
106
108
  ### DiagnosticSeverty properties list
107
109
 
108
110
  The full list can be found in the [diagnostics](https://github.com/Effect-TS/language-service/tree/main/src/diagnostics) folder.
109
- Here is the current list of diagnostics you can use to override severity:
110
-
111
-
112
- ```bash
113
- duplicatePackage
114
- effectInVoidSuccess
115
- floatingEffect
116
- genericEffectServices
117
- importFromBarrel
118
- leakingRequirements
119
- middlewareAutoImportQuickfixes
120
- missingEffectContext
121
- missingEffectError
122
- missingReturnYieldStar
123
- missingStarInYieldEffectGen
124
- multipleEffectProvide
125
- returnEffectInGen
126
- scopeInLayerEffect
127
- strictBooleanExpressions
128
- tryCatchInEffectGen
129
- unnecessaryEffectGen
130
- unnecessaryPipe
131
- unnecessaryPipeChain
132
- ```
133
111
 
134
112
  ## Why do diagnostics not appear at compile time?
135
113
 
package/cli.js CHANGED
@@ -29927,6 +29927,36 @@ function make64(ts2, tsUtils, typeChecker) {
29927
29927
  "TypeParser.effectSubtype",
29928
29928
  (type2) => type2
29929
29929
  );
29930
+ const importedSchemaModule = cachedBy(
29931
+ fn("TypeParser.importedSchemaModule")(function* (node) {
29932
+ const type2 = typeChecker.getTypeAtLocation(node);
29933
+ const propertySymbol = typeChecker.getPropertyOfType(type2, "Class");
29934
+ if (!propertySymbol) {
29935
+ return yield* typeParserIssue("Type has no 'Class' property", type2, node);
29936
+ }
29937
+ if (!ts2.isExpression(node)) {
29938
+ return yield* typeParserIssue("Node is not an expression", type2, node);
29939
+ }
29940
+ return node;
29941
+ }),
29942
+ "TypeParser.importedSchemaModule",
29943
+ (node) => node
29944
+ );
29945
+ const importedContextModule = cachedBy(
29946
+ fn("TypeParser.importedContextModule")(function* (node) {
29947
+ const type2 = typeChecker.getTypeAtLocation(node);
29948
+ const propertySymbol = typeChecker.getPropertyOfType(type2, "Tag");
29949
+ if (!propertySymbol) {
29950
+ return yield* typeParserIssue("Type has no 'Tag' property", type2, node);
29951
+ }
29952
+ if (!ts2.isExpression(node)) {
29953
+ return yield* typeParserIssue("Node is not an expression", type2, node);
29954
+ }
29955
+ return node;
29956
+ }),
29957
+ "TypeParser.importedContextModule",
29958
+ (node) => node
29959
+ );
29930
29960
  const importedEffectModule = cachedBy(
29931
29961
  fn("TypeParser.importedEffectModule")(function* (node) {
29932
29962
  const type2 = typeChecker.getTypeAtLocation(node);
@@ -30283,6 +30313,226 @@ function make64(ts2, tsUtils, typeChecker) {
30283
30313
  "TypeParser.promiseLike",
30284
30314
  (type2) => type2
30285
30315
  );
30316
+ const extendsSchemaClass = cachedBy(
30317
+ fn("TypeParser.extendsSchemaClass")(function* (atLocation) {
30318
+ if (!atLocation.name) {
30319
+ return yield* typeParserIssue("Class has no name", void 0, atLocation);
30320
+ }
30321
+ const heritageClauses = atLocation.heritageClauses;
30322
+ if (!heritageClauses) {
30323
+ return yield* typeParserIssue("Class has no heritage clauses", void 0, atLocation);
30324
+ }
30325
+ for (const heritageClause of heritageClauses) {
30326
+ for (const typeX of heritageClause.types) {
30327
+ if (ts2.isExpressionWithTypeArguments(typeX)) {
30328
+ const expression = typeX.expression;
30329
+ if (ts2.isCallExpression(expression)) {
30330
+ const schemaCall = expression.expression;
30331
+ if (ts2.isCallExpression(schemaCall) && schemaCall.typeArguments && schemaCall.typeArguments.length > 0) {
30332
+ const selfTypeNode = schemaCall.typeArguments[0];
30333
+ const schemaIdentifier = schemaCall.expression;
30334
+ if (ts2.isPropertyAccessExpression(schemaIdentifier) && ts2.isIdentifier(schemaIdentifier.name) && schemaIdentifier.name.text === "Class") {
30335
+ const parsedSchemaModule = yield* pipe(
30336
+ importedSchemaModule(schemaIdentifier.expression),
30337
+ option4
30338
+ );
30339
+ if (isSome2(parsedSchemaModule)) {
30340
+ return {
30341
+ className: atLocation.name,
30342
+ selfTypeNode,
30343
+ Schema: parsedSchemaModule.value
30344
+ };
30345
+ }
30346
+ }
30347
+ }
30348
+ }
30349
+ }
30350
+ }
30351
+ }
30352
+ return yield* typeParserIssue("Class does not extend Schema.Class", void 0, atLocation);
30353
+ }),
30354
+ "TypeParser.extendsSchemaClass",
30355
+ (atLocation) => atLocation
30356
+ );
30357
+ const extendsSchemaTaggedClass = cachedBy(
30358
+ fn("TypeParser.extendsSchemaTaggedClass")(function* (atLocation) {
30359
+ if (!atLocation.name) {
30360
+ return yield* typeParserIssue("Class has no name", void 0, atLocation);
30361
+ }
30362
+ const heritageClauses = atLocation.heritageClauses;
30363
+ if (!heritageClauses) {
30364
+ return yield* typeParserIssue("Class has no heritage clauses", void 0, atLocation);
30365
+ }
30366
+ for (const heritageClause of heritageClauses) {
30367
+ for (const typeX of heritageClause.types) {
30368
+ if (ts2.isExpressionWithTypeArguments(typeX)) {
30369
+ const expression = typeX.expression;
30370
+ if (ts2.isCallExpression(expression)) {
30371
+ const tagCall = expression.expression;
30372
+ if (ts2.isCallExpression(tagCall)) {
30373
+ const schemaCall = tagCall.expression;
30374
+ if (ts2.isCallExpression(schemaCall) && schemaCall.typeArguments && schemaCall.typeArguments.length > 0) {
30375
+ const selfTypeNode = schemaCall.typeArguments[0];
30376
+ const schemaIdentifier = schemaCall.expression;
30377
+ if (ts2.isPropertyAccessExpression(schemaIdentifier) && ts2.isIdentifier(schemaIdentifier.name) && schemaIdentifier.name.text === "TaggedClass") {
30378
+ const parsedSchemaModule = yield* pipe(
30379
+ importedSchemaModule(schemaIdentifier.expression),
30380
+ option4
30381
+ );
30382
+ if (isSome2(parsedSchemaModule)) {
30383
+ return {
30384
+ className: atLocation.name,
30385
+ selfTypeNode,
30386
+ Schema: parsedSchemaModule.value
30387
+ };
30388
+ }
30389
+ }
30390
+ }
30391
+ }
30392
+ }
30393
+ }
30394
+ }
30395
+ }
30396
+ return yield* typeParserIssue("Class does not extend Schema.TaggedClass", void 0, atLocation);
30397
+ }),
30398
+ "TypeParser.extendsSchemaTaggedClass",
30399
+ (atLocation) => atLocation
30400
+ );
30401
+ const extendsSchemaTaggedError = cachedBy(
30402
+ fn("TypeParser.extendsSchemaTaggedError")(function* (atLocation) {
30403
+ if (!atLocation.name) {
30404
+ return yield* typeParserIssue("Class has no name", void 0, atLocation);
30405
+ }
30406
+ const heritageClauses = atLocation.heritageClauses;
30407
+ if (!heritageClauses) {
30408
+ return yield* typeParserIssue("Class has no heritage clauses", void 0, atLocation);
30409
+ }
30410
+ for (const heritageClause of heritageClauses) {
30411
+ for (const typeX of heritageClause.types) {
30412
+ if (ts2.isExpressionWithTypeArguments(typeX)) {
30413
+ const expression = typeX.expression;
30414
+ if (ts2.isCallExpression(expression)) {
30415
+ const tagCall = expression.expression;
30416
+ if (ts2.isCallExpression(tagCall)) {
30417
+ const schemaCall = tagCall.expression;
30418
+ if (ts2.isCallExpression(schemaCall) && schemaCall.typeArguments && schemaCall.typeArguments.length > 0) {
30419
+ const selfTypeNode = schemaCall.typeArguments[0];
30420
+ const schemaIdentifier = schemaCall.expression;
30421
+ if (ts2.isPropertyAccessExpression(schemaIdentifier) && ts2.isIdentifier(schemaIdentifier.name) && schemaIdentifier.name.text === "TaggedError") {
30422
+ const parsedSchemaModule = yield* pipe(
30423
+ importedSchemaModule(schemaIdentifier.expression),
30424
+ option4
30425
+ );
30426
+ if (isSome2(parsedSchemaModule)) {
30427
+ return {
30428
+ className: atLocation.name,
30429
+ selfTypeNode,
30430
+ Schema: parsedSchemaModule.value
30431
+ };
30432
+ }
30433
+ }
30434
+ }
30435
+ }
30436
+ }
30437
+ }
30438
+ }
30439
+ }
30440
+ return yield* typeParserIssue("Class does not extend Schema.TaggedError", void 0, atLocation);
30441
+ }),
30442
+ "TypeParser.extendsSchemaTaggedError",
30443
+ (atLocation) => atLocation
30444
+ );
30445
+ const extendsSchemaTaggedRequest = cachedBy(
30446
+ fn("TypeParser.extendsSchemaTaggedRequest")(function* (atLocation) {
30447
+ if (!atLocation.name) {
30448
+ return yield* typeParserIssue("Class has no name", void 0, atLocation);
30449
+ }
30450
+ const heritageClauses = atLocation.heritageClauses;
30451
+ if (!heritageClauses) {
30452
+ return yield* typeParserIssue("Class has no heritage clauses", void 0, atLocation);
30453
+ }
30454
+ for (const heritageClause of heritageClauses) {
30455
+ for (const typeX of heritageClause.types) {
30456
+ if (ts2.isExpressionWithTypeArguments(typeX)) {
30457
+ const expression = typeX.expression;
30458
+ if (ts2.isCallExpression(expression)) {
30459
+ const tagCall = expression.expression;
30460
+ if (ts2.isCallExpression(tagCall)) {
30461
+ const schemaCall = tagCall.expression;
30462
+ if (ts2.isCallExpression(schemaCall) && schemaCall.typeArguments && schemaCall.typeArguments.length > 0) {
30463
+ const selfTypeNode = schemaCall.typeArguments[0];
30464
+ const schemaIdentifier = schemaCall.expression;
30465
+ if (ts2.isPropertyAccessExpression(schemaIdentifier) && ts2.isIdentifier(schemaIdentifier.name) && schemaIdentifier.name.text === "TaggedRequest") {
30466
+ const parsedSchemaModule = yield* pipe(
30467
+ importedSchemaModule(schemaIdentifier.expression),
30468
+ option4
30469
+ );
30470
+ if (isSome2(parsedSchemaModule)) {
30471
+ return {
30472
+ className: atLocation.name,
30473
+ selfTypeNode,
30474
+ Schema: parsedSchemaModule.value
30475
+ };
30476
+ }
30477
+ }
30478
+ }
30479
+ }
30480
+ }
30481
+ }
30482
+ }
30483
+ }
30484
+ return yield* typeParserIssue("Class does not extend Schema.TaggedRequest", void 0, atLocation);
30485
+ }),
30486
+ "TypeParser.extendsSchemaTaggedRequest",
30487
+ (atLocation) => atLocation
30488
+ );
30489
+ const extendsContextTag = cachedBy(
30490
+ fn("TypeParser.extendsContextTag")(function* (atLocation) {
30491
+ if (!atLocation.name) {
30492
+ return yield* typeParserIssue("Class has no name", void 0, atLocation);
30493
+ }
30494
+ const classSym = typeChecker.getSymbolAtLocation(atLocation.name);
30495
+ if (!classSym) return yield* typeParserIssue("Class has no symbol", void 0, atLocation);
30496
+ const type2 = typeChecker.getTypeOfSymbol(classSym);
30497
+ const heritageClauses = atLocation.heritageClauses;
30498
+ if (!heritageClauses) {
30499
+ return yield* typeParserIssue("Class has no heritage clauses", void 0, atLocation);
30500
+ }
30501
+ for (const heritageClause of heritageClauses) {
30502
+ for (const typeX of heritageClause.types) {
30503
+ if (ts2.isExpressionWithTypeArguments(typeX)) {
30504
+ const wholeCall = typeX.expression;
30505
+ if (ts2.isCallExpression(wholeCall)) {
30506
+ const contextTagCall = wholeCall.expression;
30507
+ if (ts2.isCallExpression(contextTagCall) && wholeCall.typeArguments && wholeCall.typeArguments.length > 0) {
30508
+ const contextTagIdentifier = contextTagCall.expression;
30509
+ const selfTypeNode = wholeCall.typeArguments[0];
30510
+ if (ts2.isPropertyAccessExpression(contextTagIdentifier) && ts2.isIdentifier(contextTagIdentifier.name) && contextTagIdentifier.name.text === "Tag") {
30511
+ const parsedContextModule = yield* pipe(
30512
+ importedContextModule(contextTagIdentifier.expression),
30513
+ option4
30514
+ );
30515
+ if (isSome2(parsedContextModule)) {
30516
+ const tagType = yield* contextTag(type2, atLocation);
30517
+ return {
30518
+ className: atLocation.name,
30519
+ selfTypeNode,
30520
+ args: contextTagCall.arguments,
30521
+ Identifier: tagType.Identifier,
30522
+ Tag: parsedContextModule.value
30523
+ };
30524
+ }
30525
+ }
30526
+ }
30527
+ }
30528
+ }
30529
+ }
30530
+ }
30531
+ return yield* typeParserIssue("Class does not extend Context.Tag", void 0, atLocation);
30532
+ }),
30533
+ "TypeParser.extendsContextTag",
30534
+ (atLocation) => atLocation
30535
+ );
30286
30536
  const extendsEffectService = cachedBy(
30287
30537
  fn("TypeParser.extendsEffectService")(function* (atLocation) {
30288
30538
  if (!atLocation.name) {
@@ -30358,10 +30608,78 @@ function make64(ts2, tsUtils, typeChecker) {
30358
30608
  pipeCall,
30359
30609
  scopeType,
30360
30610
  promiseLike,
30361
- extendsEffectService
30611
+ extendsEffectService,
30612
+ extendsContextTag,
30613
+ extendsSchemaClass,
30614
+ extendsSchemaTaggedClass,
30615
+ extendsSchemaTaggedError,
30616
+ extendsSchemaTaggedRequest
30362
30617
  };
30363
30618
  }
30364
30619
 
30620
+ // src/diagnostics/classSelfMismatch.ts
30621
+ var classSelfMismatch = createDiagnostic({
30622
+ name: "classSelfMismatch",
30623
+ code: 20,
30624
+ severity: "error",
30625
+ apply: fn("classSelfMismatch.apply")(function* (sourceFile, report) {
30626
+ const ts2 = yield* service2(TypeScriptApi);
30627
+ const typeParser = yield* service2(TypeParser);
30628
+ const nodeToVisit = [];
30629
+ const appendNodeToVisit = (node) => {
30630
+ nodeToVisit.push(node);
30631
+ return void 0;
30632
+ };
30633
+ ts2.forEachChild(sourceFile, appendNodeToVisit);
30634
+ while (nodeToVisit.length > 0) {
30635
+ const node = nodeToVisit.shift();
30636
+ if (ts2.isClassDeclaration(node) && node.name && node.heritageClauses) {
30637
+ const result = yield* pipe(
30638
+ typeParser.extendsEffectService(node),
30639
+ orElse14(() => typeParser.extendsContextTag(node)),
30640
+ orElse14(() => typeParser.extendsSchemaClass(node)),
30641
+ orElse14(() => typeParser.extendsSchemaTaggedClass(node)),
30642
+ orElse14(() => typeParser.extendsSchemaTaggedError(node)),
30643
+ orElse14(() => typeParser.extendsSchemaTaggedRequest(node)),
30644
+ orElse14(() => void_8)
30645
+ );
30646
+ if (result) {
30647
+ const { className, selfTypeNode } = result;
30648
+ let actualName = "";
30649
+ if (ts2.isTypeReferenceNode(selfTypeNode)) {
30650
+ if (ts2.isIdentifier(selfTypeNode.typeName)) {
30651
+ actualName = selfTypeNode.typeName.text;
30652
+ } else if (ts2.isQualifiedName(selfTypeNode.typeName)) {
30653
+ actualName = selfTypeNode.typeName.right.text;
30654
+ }
30655
+ }
30656
+ const expectedName = className.text;
30657
+ if (actualName !== expectedName) {
30658
+ report({
30659
+ location: selfTypeNode,
30660
+ messageText: `Self type parameter should be '${expectedName}'`,
30661
+ fixes: [{
30662
+ fixName: "classSelfMismatch_fix",
30663
+ description: `Replace '${actualName}' with '${expectedName}'`,
30664
+ apply: gen3(function* () {
30665
+ const changeTracker = yield* service2(ChangeTracker);
30666
+ const typeArgs = ts2.isTypeReferenceNode(selfTypeNode) ? selfTypeNode.typeArguments : void 0;
30667
+ const newTypeReference = ts2.factory.createTypeReferenceNode(
30668
+ ts2.factory.createIdentifier(expectedName),
30669
+ typeArgs
30670
+ );
30671
+ changeTracker.replaceNode(sourceFile, selfTypeNode, newTypeReference);
30672
+ })
30673
+ }]
30674
+ });
30675
+ }
30676
+ }
30677
+ }
30678
+ ts2.forEachChild(node, appendNodeToVisit);
30679
+ }
30680
+ })
30681
+ });
30682
+
30365
30683
  // src/diagnostics/duplicatePackage.ts
30366
30684
  var checkedPackagesCache = /* @__PURE__ */ new Map();
30367
30685
  var programResolvedCacheSize = /* @__PURE__ */ new Map();
@@ -31976,8 +32294,64 @@ var unnecessaryPipeChain = createDiagnostic({
31976
32294
  })
31977
32295
  });
31978
32296
 
32297
+ // src/diagnostics/unsupportedServiceAccessors.ts
32298
+ var unsupportedServiceAccessors = createDiagnostic({
32299
+ name: "unsupportedServiceAccessors",
32300
+ code: 21,
32301
+ severity: "warning",
32302
+ apply: fn("unsupportedServiceAccessors.apply")(function* (sourceFile, report) {
32303
+ const ts2 = yield* service2(TypeScriptApi);
32304
+ const nodeToVisit = [];
32305
+ const appendNodeToVisit = (node) => {
32306
+ nodeToVisit.push(node);
32307
+ return void 0;
32308
+ };
32309
+ ts2.forEachChild(sourceFile, appendNodeToVisit);
32310
+ while (nodeToVisit.length > 0) {
32311
+ const node = nodeToVisit.shift();
32312
+ ts2.forEachChild(node, appendNodeToVisit);
32313
+ if (ts2.isClassDeclaration(node)) {
32314
+ const parseResult = yield* pipe(
32315
+ parse5(node),
32316
+ orElse14(() => succeed17(null))
32317
+ );
32318
+ if (parseResult && parseResult.involvedMembers.length > 0) {
32319
+ const existingStaticMembers = /* @__PURE__ */ new Set();
32320
+ node.members?.forEach((member) => {
32321
+ if (ts2.isPropertyDeclaration(member) && member.modifiers?.some((mod) => mod.kind === ts2.SyntaxKind.StaticKeyword)) {
32322
+ if (member.name && ts2.isIdentifier(member.name)) {
32323
+ existingStaticMembers.add(member.name.text);
32324
+ }
32325
+ }
32326
+ });
32327
+ const missingMembers = parseResult.involvedMembers.filter(
32328
+ ({ property }) => !existingStaticMembers.has(property.getName())
32329
+ );
32330
+ if (missingMembers.length > 0) {
32331
+ const memberNames = missingMembers.map(({ property }) => `'${property.getName()}'`).join(", ");
32332
+ report({
32333
+ location: parseResult.className,
32334
+ messageText: `Even if accessors are enabled, accessors for ${memberNames} won't be available because the signature have generic type parameters or multiple call signatures.`,
32335
+ fixes: [{
32336
+ fixName: "unsupportedServiceAccessors_enableCodegen",
32337
+ description: "Enable accessors codegen",
32338
+ apply: gen3(function* () {
32339
+ const changeTracker = yield* service2(ChangeTracker);
32340
+ const comment = "// @effect-codegens accessors\n";
32341
+ changeTracker.insertText(sourceFile, node.getStart(sourceFile), comment);
32342
+ })
32343
+ }]
32344
+ });
32345
+ }
32346
+ }
32347
+ }
32348
+ }
32349
+ })
32350
+ });
32351
+
31979
32352
  // src/diagnostics.ts
31980
32353
  var diagnostics = [
32354
+ classSelfMismatch,
31981
32355
  duplicatePackage,
31982
32356
  missingEffectContext,
31983
32357
  missingEffectError,
@@ -31996,7 +32370,8 @@ var diagnostics = [
31996
32370
  unnecessaryPipeChain,
31997
32371
  strictBooleanExpressions,
31998
32372
  multipleEffectProvide,
31999
- outdatedEffectCodegen
32373
+ outdatedEffectCodegen,
32374
+ unsupportedServiceAccessors
32000
32375
  ];
32001
32376
 
32002
32377
  // src/cli.ts