@effect/language-service 0.25.1 → 0.27.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
@@ -47,6 +47,7 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
47
47
  - Unnecessary usages of `Effect.gen` or `pipe()`
48
48
  - Warn when importing from a barrel file instead of from the module directly
49
49
  - Warn on usage of try/catch inside `Effect.gen` and family
50
+ - Detect unnecessary pipe chains like `X.pipe(Y).pipe(Z)`
50
51
 
51
52
  ### Completions
52
53
 
@@ -67,6 +68,7 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
67
68
  - Toggle return type signature: With a single refactor, adds or removes type annotations from the definition.
68
69
  - Remove unnecessary `Effect.gen` definitions that contains a single `yield` statement.
69
70
  - Wrap an `Effect` expression with `Effect.gen`
71
+ - Toggle between pipe styles `X.pipe(Y)` and `pipe(X, Y)`
70
72
 
71
73
  ### Miscellaneous
72
74
  - "Go to definition" for RpcClient will resolve to the Rpc definition
@@ -119,8 +121,9 @@ Your `tsconfig.json` should look like this:
119
121
 
120
122
  To get diagnostics you need to install `ts-patch` which will make it possible to run `tspc`.
121
123
 
122
- Running `tspc` in your project will now also run the plugin and give you the diagnostics at compile time.
123
- Effect diagnostics will be shown only after standard TypeScript diagnostics have been satisfied.
124
+ Running `tspc` in your project will now also run the plugin and give you the error diagnostics at compile time.
125
+ Effect error diagnostics will be shown only after standard TypeScript diagnostics have been satisfied.
126
+ Beware that setting noEmit will completely skip the effect diagnostics.
124
127
 
125
128
  ```ts
126
129
  $ npx tspc
package/cli.js CHANGED
@@ -31428,11 +31428,16 @@ function make62(ts2, typeChecker) {
31428
31428
  const pipeCall = cachedBy(
31429
31429
  function(node) {
31430
31430
  if (ts2.isCallExpression(node) && ts2.isPropertyAccessExpression(node.expression) && ts2.isIdentifier(node.expression.name) && node.expression.name.text === "pipe") {
31431
- return succeed17({ node, subject: node.expression.expression, args: Array.from(node.arguments) });
31431
+ return succeed17({
31432
+ node,
31433
+ subject: node.expression.expression,
31434
+ args: Array.from(node.arguments),
31435
+ kind: "pipeable"
31436
+ });
31432
31437
  }
31433
31438
  if (ts2.isCallExpression(node) && ts2.isIdentifier(node.expression) && node.expression.text === "pipe" && node.arguments.length > 0) {
31434
31439
  const [subject, ...args3] = node.arguments;
31435
- return succeed17({ node, subject, args: args3 });
31440
+ return succeed17({ node, subject, args: args3, kind: "pipe" });
31436
31441
  }
31437
31442
  return typeParserIssue("Node is not a pipe call", void 0, node);
31438
31443
  },
@@ -31471,6 +31476,7 @@ function make62(ts2, typeChecker) {
31471
31476
  unnecessaryEffectGen: unnecessaryEffectGen2,
31472
31477
  effectSchemaType,
31473
31478
  contextTag,
31479
+ pipeableType,
31474
31480
  pipeCall,
31475
31481
  scopeType
31476
31482
  };
@@ -32404,10 +32410,77 @@ Consider using "scoped" instead to get rid of the scope in the requirements.`,
32404
32410
  })
32405
32411
  });
32406
32412
 
32413
+ // src/diagnostics/strictBooleanExpressions.ts
32414
+ var strictBooleanExpressions = createDiagnostic({
32415
+ name: "strictBooleanExpressions",
32416
+ code: 17,
32417
+ severity: "off",
32418
+ apply: fn("strictBooleanExpressions.apply")(function* (sourceFile, report) {
32419
+ const ts2 = yield* service2(TypeScriptApi);
32420
+ const typeChecker = yield* service2(TypeCheckerApi);
32421
+ const conditionChecks = /* @__PURE__ */ new WeakMap();
32422
+ const nodeToVisit = [];
32423
+ const appendNodeToVisit = (node) => {
32424
+ nodeToVisit.push(node);
32425
+ return void 0;
32426
+ };
32427
+ ts2.forEachChild(sourceFile, appendNodeToVisit);
32428
+ while (nodeToVisit.length > 0) {
32429
+ const node = nodeToVisit.shift();
32430
+ ts2.forEachChild(node, appendNodeToVisit);
32431
+ const nodes = [];
32432
+ if (ts2.isIfStatement(node)) {
32433
+ conditionChecks.set(node, true);
32434
+ nodes.push(node.expression);
32435
+ } else if (ts2.isWhileStatement(node)) {
32436
+ conditionChecks.set(node, true);
32437
+ nodes.push(node.expression);
32438
+ } else if (ts2.isConditionalExpression(node)) {
32439
+ conditionChecks.set(node, true);
32440
+ nodes.push(node.condition);
32441
+ } else if (ts2.isPrefixUnaryExpression(node) && node.operator === ts2.SyntaxKind.ExclamationToken) {
32442
+ conditionChecks.set(node, true);
32443
+ nodes.push(node.operand);
32444
+ } else if (ts2.isBinaryExpression(node) && node.operatorToken.kind === ts2.SyntaxKind.BarBarToken) {
32445
+ if (conditionChecks.has(node.parent)) conditionChecks.set(node, true);
32446
+ nodes.push(node.left);
32447
+ nodes.push(node.right);
32448
+ } else if (ts2.isBinaryExpression(node) && node.operatorToken.kind === ts2.SyntaxKind.AmpersandAmpersandToken) {
32449
+ if (conditionChecks.has(node.parent)) conditionChecks.set(node, true);
32450
+ nodes.push(node.left);
32451
+ nodes.push(node.right);
32452
+ }
32453
+ for (const nodeToCheck of nodes) {
32454
+ if (!nodeToCheck) continue;
32455
+ if (!conditionChecks.has(nodeToCheck.parent)) continue;
32456
+ const nodeType = typeChecker.getTypeAtLocation(nodeToCheck);
32457
+ const constrainedType = typeChecker.getBaseConstraintOfType(nodeType);
32458
+ let typesToCheck = [constrainedType || nodeType];
32459
+ while (typesToCheck.length > 0) {
32460
+ const type2 = typesToCheck.pop();
32461
+ if (type2.isUnion()) {
32462
+ typesToCheck = typesToCheck.concat(type2.types);
32463
+ continue;
32464
+ }
32465
+ if (type2.flags & ts2.TypeFlags.Boolean) continue;
32466
+ if (type2.flags & ts2.TypeFlags.Never) continue;
32467
+ if (type2.flags & ts2.TypeFlags.BooleanLiteral) continue;
32468
+ const typeName = typeChecker.typeToString(type2);
32469
+ report({
32470
+ node: nodeToCheck,
32471
+ messageText: `Unexpected \`${typeName}\` type in condition, expected strictly a boolean instead.`,
32472
+ fixes: []
32473
+ });
32474
+ }
32475
+ }
32476
+ }
32477
+ })
32478
+ });
32479
+
32407
32480
  // src/diagnostics/tryCatchInEffectGen.ts
32408
32481
  var tryCatchInEffectGen = createDiagnostic({
32409
32482
  name: "tryCatchInEffectGen",
32410
- code: 12,
32483
+ code: 15,
32411
32484
  severity: "suggestion",
32412
32485
  apply: fn("tryCatchInEffectGen.apply")(function* (sourceFile, report) {
32413
32486
  const ts2 = yield* service2(TypeScriptApi);
@@ -32537,6 +32610,80 @@ var unnecessaryPipe = createDiagnostic({
32537
32610
  })
32538
32611
  });
32539
32612
 
32613
+ // src/diagnostics/unnecessaryPipeChain.ts
32614
+ var unnecessaryPipeChain = createDiagnostic({
32615
+ name: "unnecessaryPipeChain",
32616
+ code: 16,
32617
+ severity: "suggestion",
32618
+ apply: fn("unnecessaryPipeChain.apply")(function* (sourceFile, report) {
32619
+ const ts2 = yield* service2(TypeScriptApi);
32620
+ const typeParser = yield* service2(TypeParser);
32621
+ const nodeToVisit = [];
32622
+ const appendNodeToVisit = (node) => {
32623
+ nodeToVisit.push(node);
32624
+ return void 0;
32625
+ };
32626
+ ts2.forEachChild(sourceFile, appendNodeToVisit);
32627
+ while (nodeToVisit.length > 0) {
32628
+ const node = nodeToVisit.shift();
32629
+ ts2.forEachChild(node, appendNodeToVisit);
32630
+ if (ts2.isCallExpression(node)) {
32631
+ yield* pipe(
32632
+ typeParser.pipeCall(node),
32633
+ flatMap18(
32634
+ (pipeCall) => map33(typeParser.pipeCall(pipeCall.subject), (innerCall) => ({ pipeCall, innerCall }))
32635
+ ),
32636
+ map33(({ innerCall, pipeCall }) => {
32637
+ report({
32638
+ node,
32639
+ messageText: `Chained pipe calls can be simplified to a single pipe call`,
32640
+ fixes: [{
32641
+ fixName: "unnecessaryPipeChain_fix",
32642
+ description: "Rewrite as single pipe call",
32643
+ apply: gen3(function* () {
32644
+ const changeTracker = yield* service2(
32645
+ ChangeTracker
32646
+ );
32647
+ switch (innerCall.kind) {
32648
+ case "pipe": {
32649
+ changeTracker.replaceNode(
32650
+ sourceFile,
32651
+ node,
32652
+ ts2.factory.createCallExpression(
32653
+ ts2.factory.createIdentifier("pipe"),
32654
+ void 0,
32655
+ [innerCall.subject, ...innerCall.args, ...pipeCall.args]
32656
+ )
32657
+ );
32658
+ break;
32659
+ }
32660
+ case "pipeable": {
32661
+ changeTracker.replaceNode(
32662
+ sourceFile,
32663
+ node,
32664
+ ts2.factory.createCallExpression(
32665
+ ts2.factory.createPropertyAccessExpression(
32666
+ innerCall.subject,
32667
+ "pipe"
32668
+ ),
32669
+ void 0,
32670
+ [...innerCall.args, ...pipeCall.args]
32671
+ )
32672
+ );
32673
+ break;
32674
+ }
32675
+ }
32676
+ })
32677
+ }]
32678
+ });
32679
+ }),
32680
+ ignore3
32681
+ );
32682
+ }
32683
+ }
32684
+ })
32685
+ });
32686
+
32540
32687
  // src/diagnostics.ts
32541
32688
  var diagnostics = [
32542
32689
  duplicatePackage,
@@ -32553,7 +32700,9 @@ var diagnostics = [
32553
32700
  tryCatchInEffectGen,
32554
32701
  importFromBarrel,
32555
32702
  scopeInLayerEffect,
32556
- effectInVoidSuccess
32703
+ effectInVoidSuccess,
32704
+ unnecessaryPipeChain,
32705
+ strictBooleanExpressions
32557
32706
  ];
32558
32707
 
32559
32708
  // src/cli.ts