@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/language-service",
3
- "version": "0.49.0",
3
+ "version": "0.51.0",
4
4
  "description": "A Language-Service Plugin to Refactor and Diagnostic effect-ts projects",
5
5
  "main": "index.cjs",
6
6
  "bin": {
package/transform.js CHANGED
@@ -769,6 +769,7 @@ var unsafeGet = /* @__PURE__ */ dual(2, (self, index) => {
769
769
  });
770
770
  var headNonEmpty = /* @__PURE__ */ unsafeGet(0);
771
771
  var tailNonEmpty = (self) => self.slice(1);
772
+ var reverse = (self) => Array.from(self).reverse();
772
773
  var sort = /* @__PURE__ */ dual(2, (self, O) => {
773
774
  const out = Array.from(self);
774
775
  out.sort(O);
@@ -1202,7 +1203,8 @@ var defaults = {
1202
1203
  pattern: "default",
1203
1204
  skipLeadingPath: ["src/"]
1204
1205
  }],
1205
- extendedKeyDetection: false
1206
+ extendedKeyDetection: false,
1207
+ pipeableMinArgCount: 1
1206
1208
  };
1207
1209
  function parseKeyPatterns(patterns) {
1208
1210
  const result = [];
@@ -1237,7 +1239,8 @@ function parse(config) {
1237
1239
  renames: isObject(config) && hasProperty(config, "renames") && isBoolean(config.renames) ? config.renames : defaults.renames,
1238
1240
  noExternal: isObject(config) && hasProperty(config, "noExternal") && isBoolean(config.noExternal) ? config.noExternal : defaults.noExternal,
1239
1241
  keyPatterns: isObject(config) && hasProperty(config, "keyPatterns") && isArray(config.keyPatterns) ? parseKeyPatterns(config.keyPatterns) : defaults.keyPatterns,
1240
- extendedKeyDetection: isObject(config) && hasProperty(config, "extendedKeyDetection") && isBoolean(config.extendedKeyDetection) ? config.extendedKeyDetection : defaults.extendedKeyDetection
1242
+ extendedKeyDetection: isObject(config) && hasProperty(config, "extendedKeyDetection") && isBoolean(config.extendedKeyDetection) ? config.extendedKeyDetection : defaults.extendedKeyDetection,
1243
+ pipeableMinArgCount: isObject(config) && hasProperty(config, "pipeableMinArgCount") && isNumber(config.pipeableMinArgCount) ? config.pipeableMinArgCount : defaults.pipeableMinArgCount
1241
1244
  };
1242
1245
  }
1243
1246
 
@@ -4271,6 +4274,74 @@ More info at https://effect.website/docs/requirements-management/layers/#avoidin
4271
4274
  })
4272
4275
  });
4273
4276
 
4277
+ // src/diagnostics/missedPipeableOpportunity.ts
4278
+ var missedPipeableOpportunity = createDiagnostic({
4279
+ name: "missedPipeableOpportunity",
4280
+ code: 26,
4281
+ severity: "off",
4282
+ apply: fn("missedPipeableOpportunity.apply")(function* (sourceFile, report) {
4283
+ const ts = yield* service(TypeScriptApi);
4284
+ const typeChecker = yield* service(TypeCheckerApi);
4285
+ const typeParser = yield* service(TypeParser);
4286
+ const options = yield* service(LanguageServicePluginOptions);
4287
+ const nodeToVisit = [sourceFile];
4288
+ const prependNodeToVisit = (node) => {
4289
+ nodeToVisit.unshift(node);
4290
+ return void 0;
4291
+ };
4292
+ const callChainNodes = /* @__PURE__ */ new WeakMap();
4293
+ while (nodeToVisit.length > 0) {
4294
+ const node = nodeToVisit.shift();
4295
+ if (ts.isCallExpression(node) && node.arguments.length === 1 && node.parent) {
4296
+ const parentChain = callChainNodes.get(node.parent) || [];
4297
+ callChainNodes.set(node, parentChain.concat(node));
4298
+ } else if (node.parent && callChainNodes.has(node.parent) && ts.isExpression(node)) {
4299
+ const parentChain = callChainNodes.get(node.parent) || [];
4300
+ const originalParentChain = parentChain.slice();
4301
+ parentChain.push(node);
4302
+ while (parentChain.length > options.pipeableMinArgCount) {
4303
+ const subject = parentChain.pop();
4304
+ const resultType = typeChecker.getTypeAtLocation(subject);
4305
+ const pipeableType = yield* pipe(typeParser.pipeableType(resultType, subject), orElse2(() => void_));
4306
+ if (pipeableType) {
4307
+ report({
4308
+ location: parentChain[0],
4309
+ messageText: `Nested function calls can be converted to pipeable style for better readability.`,
4310
+ fixes: [{
4311
+ fixName: "missedPipeableOpportunity_fix",
4312
+ description: "Convert to pipe style",
4313
+ apply: gen(function* () {
4314
+ const changeTracker = yield* service(ChangeTracker);
4315
+ changeTracker.replaceNode(
4316
+ sourceFile,
4317
+ parentChain[0],
4318
+ ts.factory.createCallExpression(
4319
+ ts.factory.createPropertyAccessExpression(
4320
+ subject,
4321
+ "pipe"
4322
+ ),
4323
+ void 0,
4324
+ pipe(
4325
+ parentChain,
4326
+ filter(ts.isCallExpression),
4327
+ map3((call) => call.expression),
4328
+ reverse
4329
+ )
4330
+ )
4331
+ );
4332
+ })
4333
+ }]
4334
+ });
4335
+ originalParentChain.forEach((node2) => callChainNodes.delete(node2));
4336
+ break;
4337
+ }
4338
+ }
4339
+ }
4340
+ ts.forEachChild(node, prependNodeToVisit);
4341
+ }
4342
+ })
4343
+ });
4344
+
4274
4345
  // src/diagnostics/missingEffectContext.ts
4275
4346
  var missingEffectContext = createDiagnostic({
4276
4347
  name: "missingEffectContext",
@@ -5550,6 +5621,50 @@ var strictBooleanExpressions = createDiagnostic({
5550
5621
  })
5551
5622
  });
5552
5623
 
5624
+ // src/diagnostics/strictEffectProvide.ts
5625
+ var strictEffectProvide = createDiagnostic({
5626
+ name: "strictEffectProvide",
5627
+ code: 27,
5628
+ severity: "off",
5629
+ apply: fn("strictEffectProvide.apply")(function* (sourceFile, report) {
5630
+ const ts = yield* service(TypeScriptApi);
5631
+ const typeChecker = yield* service(TypeCheckerApi);
5632
+ const typeParser = yield* service(TypeParser);
5633
+ const parseEffectProvideWithLayer = (node) => gen(function* () {
5634
+ if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression) || !ts.isIdentifier(node.expression.name) || ts.idText(node.expression.name) !== "provide" || node.arguments.length === 0) {
5635
+ return yield* typeParserIssue("Not an Effect.provide call");
5636
+ }
5637
+ yield* typeParser.importedEffectModule(node.expression.expression);
5638
+ return yield* firstSuccessOf(
5639
+ node.arguments.map((arg) => {
5640
+ const argType = typeChecker.getTypeAtLocation(arg);
5641
+ return typeParser.layerType(argType, arg);
5642
+ })
5643
+ );
5644
+ });
5645
+ const nodeToVisit = [];
5646
+ const appendNodeToVisit = (node) => {
5647
+ nodeToVisit.push(node);
5648
+ return void 0;
5649
+ };
5650
+ ts.forEachChild(sourceFile, appendNodeToVisit);
5651
+ while (nodeToVisit.length > 0) {
5652
+ const node = nodeToVisit.shift();
5653
+ ts.forEachChild(node, appendNodeToVisit);
5654
+ if (ts.isCallExpression(node)) {
5655
+ const layerCheck = yield* pipe(parseEffectProvideWithLayer(node), option);
5656
+ if (isSome2(layerCheck)) {
5657
+ report({
5658
+ location: node,
5659
+ 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.",
5660
+ fixes: []
5661
+ });
5662
+ }
5663
+ }
5664
+ }
5665
+ })
5666
+ });
5667
+
5553
5668
  // src/diagnostics/tryCatchInEffectGen.ts
5554
5669
  var tryCatchInEffectGen = createDiagnostic({
5555
5670
  name: "tryCatchInEffectGen",
@@ -5839,7 +5954,9 @@ var diagnostics = [
5839
5954
  overriddenSchemaConstructor,
5840
5955
  unsupportedServiceAccessors,
5841
5956
  nonObjectEffectServiceType,
5842
- deterministicKeys
5957
+ deterministicKeys,
5958
+ missedPipeableOpportunity,
5959
+ strictEffectProvide
5843
5960
  ];
5844
5961
 
5845
5962
  // src/transform.ts