@effect/language-service 0.49.0 → 0.51.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
@@ -5,6 +5,8 @@ This package implements a TypeScript language service plugin that allows additio
5
5
  ## Installation
6
6
 
7
7
  1. `npm install @effect/language-service --save-dev` in your project
8
+ - For monorepos: We suggest installing `@effect/language-service` in the monorepo root and configuring it in the root `tsconfig.json` for consistent behavior across all packages
9
+ - For any other package: Install directly in the package where you want to use it
8
10
  2. Inside your tsconfig.json, you should add the plugin configuration as follows:
9
11
  ```jsonc
10
12
  {
@@ -53,6 +55,7 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
53
55
  - Warn on leaking requirements in Effect services
54
56
  - Warn on Scope as requirement of a Layer
55
57
  - Warn on subsequent `Effect.provide` anti-pattern
58
+ - Warn when using `Effect.provide` with Layer outside of application entry points
56
59
  - Detect wrong `Self` type parameter for APIs like `Effect.Service` or `Schema.TaggedError` and similar 
57
60
  - Unnecessary usages of `Effect.gen` or `pipe()`
58
61
  - Warn when using `Effect.gen` with the old generator adapter pattern
@@ -64,6 +67,7 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
64
67
  - Warn when `Effect.Service` is used with a primitive type instead of an object type
65
68
  - Warn when schema classes override the default constructor behavior
66
69
  - Warn when `@effect-diagnostics-next-line` comments have no effect (i.e., they don't suppress any diagnostic)
70
+ - Detect nested function calls that can be converted to pipeable style for better readability
67
71
 
68
72
  ### Completions
69
73
 
package/cli.js CHANGED
@@ -31198,7 +31198,8 @@ var defaults = {
31198
31198
  pattern: "default",
31199
31199
  skipLeadingPath: ["src/"]
31200
31200
  }],
31201
- extendedKeyDetection: false
31201
+ extendedKeyDetection: false,
31202
+ pipeableMinArgCount: 1
31202
31203
  };
31203
31204
  function parseKeyPatterns(patterns) {
31204
31205
  const result = [];
@@ -31233,7 +31234,8 @@ function parse4(config2) {
31233
31234
  renames: isObject(config2) && hasProperty(config2, "renames") && isBoolean(config2.renames) ? config2.renames : defaults.renames,
31234
31235
  noExternal: isObject(config2) && hasProperty(config2, "noExternal") && isBoolean(config2.noExternal) ? config2.noExternal : defaults.noExternal,
31235
31236
  keyPatterns: isObject(config2) && hasProperty(config2, "keyPatterns") && isArray(config2.keyPatterns) ? parseKeyPatterns(config2.keyPatterns) : defaults.keyPatterns,
31236
- extendedKeyDetection: isObject(config2) && hasProperty(config2, "extendedKeyDetection") && isBoolean(config2.extendedKeyDetection) ? config2.extendedKeyDetection : defaults.extendedKeyDetection
31237
+ extendedKeyDetection: isObject(config2) && hasProperty(config2, "extendedKeyDetection") && isBoolean(config2.extendedKeyDetection) ? config2.extendedKeyDetection : defaults.extendedKeyDetection,
31238
+ pipeableMinArgCount: isObject(config2) && hasProperty(config2, "pipeableMinArgCount") && isNumber(config2.pipeableMinArgCount) ? config2.pipeableMinArgCount : defaults.pipeableMinArgCount
31237
31239
  };
31238
31240
  }
31239
31241
 
@@ -33724,6 +33726,74 @@ More info at https://effect.website/docs/requirements-management/layers/#avoidin
33724
33726
  })
33725
33727
  });
33726
33728
 
33729
+ // src/diagnostics/missedPipeableOpportunity.ts
33730
+ var missedPipeableOpportunity = createDiagnostic({
33731
+ name: "missedPipeableOpportunity",
33732
+ code: 26,
33733
+ severity: "off",
33734
+ apply: fn2("missedPipeableOpportunity.apply")(function* (sourceFile, report) {
33735
+ const ts = yield* service2(TypeScriptApi);
33736
+ const typeChecker = yield* service2(TypeCheckerApi);
33737
+ const typeParser = yield* service2(TypeParser);
33738
+ const options3 = yield* service2(LanguageServicePluginOptions);
33739
+ const nodeToVisit = [sourceFile];
33740
+ const prependNodeToVisit = (node) => {
33741
+ nodeToVisit.unshift(node);
33742
+ return void 0;
33743
+ };
33744
+ const callChainNodes = /* @__PURE__ */ new WeakMap();
33745
+ while (nodeToVisit.length > 0) {
33746
+ const node = nodeToVisit.shift();
33747
+ if (ts.isCallExpression(node) && node.arguments.length === 1 && node.parent) {
33748
+ const parentChain = callChainNodes.get(node.parent) || [];
33749
+ callChainNodes.set(node, parentChain.concat(node));
33750
+ } else if (node.parent && callChainNodes.has(node.parent) && ts.isExpression(node)) {
33751
+ const parentChain = callChainNodes.get(node.parent) || [];
33752
+ const originalParentChain = parentChain.slice();
33753
+ parentChain.push(node);
33754
+ while (parentChain.length > options3.pipeableMinArgCount) {
33755
+ const subject = parentChain.pop();
33756
+ const resultType = typeChecker.getTypeAtLocation(subject);
33757
+ const pipeableType = yield* pipe(typeParser.pipeableType(resultType, subject), orElse14(() => void_8));
33758
+ if (pipeableType) {
33759
+ report({
33760
+ location: parentChain[0],
33761
+ messageText: `Nested function calls can be converted to pipeable style for better readability.`,
33762
+ fixes: [{
33763
+ fixName: "missedPipeableOpportunity_fix",
33764
+ description: "Convert to pipe style",
33765
+ apply: gen3(function* () {
33766
+ const changeTracker = yield* service2(ChangeTracker);
33767
+ changeTracker.replaceNode(
33768
+ sourceFile,
33769
+ parentChain[0],
33770
+ ts.factory.createCallExpression(
33771
+ ts.factory.createPropertyAccessExpression(
33772
+ subject,
33773
+ "pipe"
33774
+ ),
33775
+ void 0,
33776
+ pipe(
33777
+ parentChain,
33778
+ filter2(ts.isCallExpression),
33779
+ map4((call) => call.expression),
33780
+ reverse
33781
+ )
33782
+ )
33783
+ );
33784
+ })
33785
+ }]
33786
+ });
33787
+ originalParentChain.forEach((node2) => callChainNodes.delete(node2));
33788
+ break;
33789
+ }
33790
+ }
33791
+ }
33792
+ ts.forEachChild(node, prependNodeToVisit);
33793
+ }
33794
+ })
33795
+ });
33796
+
33727
33797
  // src/diagnostics/missingEffectContext.ts
33728
33798
  var missingEffectContext = createDiagnostic({
33729
33799
  name: "missingEffectContext",
@@ -35003,6 +35073,50 @@ var strictBooleanExpressions = createDiagnostic({
35003
35073
  })
35004
35074
  });
35005
35075
 
35076
+ // src/diagnostics/strictEffectProvide.ts
35077
+ var strictEffectProvide = createDiagnostic({
35078
+ name: "strictEffectProvide",
35079
+ code: 27,
35080
+ severity: "off",
35081
+ apply: fn2("strictEffectProvide.apply")(function* (sourceFile, report) {
35082
+ const ts = yield* service2(TypeScriptApi);
35083
+ const typeChecker = yield* service2(TypeCheckerApi);
35084
+ const typeParser = yield* service2(TypeParser);
35085
+ const parseEffectProvideWithLayer = (node) => gen3(function* () {
35086
+ if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression) || !ts.isIdentifier(node.expression.name) || ts.idText(node.expression.name) !== "provide" || node.arguments.length === 0) {
35087
+ return yield* typeParserIssue("Not an Effect.provide call");
35088
+ }
35089
+ yield* typeParser.importedEffectModule(node.expression.expression);
35090
+ return yield* firstSuccessOf2(
35091
+ node.arguments.map((arg) => {
35092
+ const argType = typeChecker.getTypeAtLocation(arg);
35093
+ return typeParser.layerType(argType, arg);
35094
+ })
35095
+ );
35096
+ });
35097
+ const nodeToVisit = [];
35098
+ const appendNodeToVisit = (node) => {
35099
+ nodeToVisit.push(node);
35100
+ return void 0;
35101
+ };
35102
+ ts.forEachChild(sourceFile, appendNodeToVisit);
35103
+ while (nodeToVisit.length > 0) {
35104
+ const node = nodeToVisit.shift();
35105
+ ts.forEachChild(node, appendNodeToVisit);
35106
+ if (ts.isCallExpression(node)) {
35107
+ const layerCheck = yield* pipe(parseEffectProvideWithLayer(node), option4);
35108
+ if (isSome2(layerCheck)) {
35109
+ report({
35110
+ location: node,
35111
+ messageText: "Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once.",
35112
+ fixes: []
35113
+ });
35114
+ }
35115
+ }
35116
+ }
35117
+ })
35118
+ });
35119
+
35006
35120
  // src/diagnostics/tryCatchInEffectGen.ts
35007
35121
  var tryCatchInEffectGen = createDiagnostic({
35008
35122
  name: "tryCatchInEffectGen",
@@ -35292,7 +35406,9 @@ var diagnostics = [
35292
35406
  overriddenSchemaConstructor,
35293
35407
  unsupportedServiceAccessors,
35294
35408
  nonObjectEffectServiceType,
35295
- deterministicKeys
35409
+ deterministicKeys,
35410
+ missedPipeableOpportunity,
35411
+ strictEffectProvide
35296
35412
  ];
35297
35413
 
35298
35414
  // src/cli/diagnostics.ts