@effect/language-service 0.67.0 → 0.68.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
@@ -82,6 +82,7 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
82
82
  - Warn when `Layer.mergeAll` contains layers with interdependencies (where one layer provides a service that another layer in the same call requires)
83
83
  - Suggest using `Effect.fn` for functions that return `Effect.gen` for better tracing and concise syntax
84
84
  - Suggest removing redundant identifier argument when it equals the tag value in `Schema.TaggedClass`, `Schema.TaggedError`, or `Schema.TaggedRequest`
85
+ - Suggest using `Schema.is` instead of `instanceof` for Effect Schema types
85
86
 
86
87
  ### Completions
87
88
 
package/cli.js CHANGED
@@ -26161,6 +26161,7 @@ var text7 = (name) => makeSingle(name, empty2(), text6);
26161
26161
  var getHelp3 = (self) => getHelpInternal2(self);
26162
26162
  var getUsage = (self) => getUsageInternal(self);
26163
26163
  var map29 = /* @__PURE__ */ dual(2, (self, f) => makeMap(self, (a) => right2(f(a))));
26164
+ var mapEffect5 = /* @__PURE__ */ dual(2, (self, f) => makeMap(self, f));
26164
26165
  var optional3 = (self) => withDefault2(map29(self, some2), none2());
26165
26166
  var orElse14 = /* @__PURE__ */ dual(2, (self, that) => orElseEither4(self, that).pipe(map29(merge)));
26166
26167
  var orElseEither4 = /* @__PURE__ */ dual(2, (self, that) => makeOrElse(self, that));
@@ -27122,6 +27123,7 @@ var directory2 = directory;
27122
27123
  var file3 = file2;
27123
27124
  var integer5 = integer4;
27124
27125
  var text8 = text7;
27126
+ var mapEffect6 = mapEffect5;
27125
27127
  var optional4 = optional3;
27126
27128
  var repeated4 = repeated3;
27127
27129
  var withDefault3 = withDefault2;
@@ -27173,8 +27175,8 @@ var none11 = /* @__PURE__ */ (() => {
27173
27175
  })();
27174
27176
  var getHelp4 = (self) => getHelpInternal3(self);
27175
27177
  var getUsage2 = (self) => getUsageInternal2(self);
27176
- var map30 = /* @__PURE__ */ dual(2, (self, f) => mapEffect6(self, (a) => succeed7(f(a))));
27177
- var mapEffect6 = /* @__PURE__ */ dual(2, (self, f) => makeMap2(self, f));
27178
+ var map30 = /* @__PURE__ */ dual(2, (self, f) => mapEffect7(self, (a) => succeed7(f(a))));
27179
+ var mapEffect7 = /* @__PURE__ */ dual(2, (self, f) => makeMap2(self, f));
27178
27180
  var validate5 = /* @__PURE__ */ dual(3, (self, args3, config2) => validateInternal2(self, args3, config2));
27179
27181
  var wizard3 = /* @__PURE__ */ dual(2, (self, config2) => wizardInternal3(self, config2));
27180
27182
  var allTupled2 = (arg) => {
@@ -27615,8 +27617,8 @@ var getFishCompletions4 = (self, executable) => getFishCompletionsInternal(self,
27615
27617
  var getZshCompletions4 = (self, executable) => getZshCompletionsInternal(self, executable);
27616
27618
  var getSubcommands = (self) => fromIterable6(getSubcommandsInternal(self));
27617
27619
  var getUsage3 = (self) => getUsageInternal3(self);
27618
- var map32 = /* @__PURE__ */ dual(2, (self, f) => mapEffect7(self, (a) => right2(f(a))));
27619
- var mapEffect7 = /* @__PURE__ */ dual(2, (self, f) => {
27620
+ var map32 = /* @__PURE__ */ dual(2, (self, f) => mapEffect8(self, (a) => right2(f(a))));
27621
+ var mapEffect8 = /* @__PURE__ */ dual(2, (self, f) => {
27620
27622
  const op = Object.create(proto23);
27621
27623
  op._tag = "Map";
27622
27624
  op.command = self;
@@ -27921,7 +27923,7 @@ var withDescriptionInternal = (self, description) => {
27921
27923
  return op;
27922
27924
  }
27923
27925
  case "Map": {
27924
- return mapEffect7(withDescriptionInternal(self.command, description), self.f);
27926
+ return mapEffect8(withDescriptionInternal(self.command, description), self.f);
27925
27927
  }
27926
27928
  case "Subcommands": {
27927
27929
  const op = Object.create(proto23);
@@ -28147,6 +28149,7 @@ var helpRequestedError = (command) => {
28147
28149
 
28148
28150
  // node_modules/.pnpm/@effect+cli@0.73.0_@effect+platform@0.94.1_@effect+printer-ansi@0.47.0_@effect+printer@0.47.0_effect@3.19.14/node_modules/@effect/cli/dist/esm/ValidationError.js
28149
28151
  var helpRequested = helpRequestedError;
28152
+ var invalidValue2 = invalidValue;
28150
28153
 
28151
28154
  // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Logger.js
28152
28155
  var withMinimumLogLevel2 = withMinimumLogLevel;
@@ -30211,7 +30214,7 @@ var runMain3 = runMain2;
30211
30214
  // package.json
30212
30215
  var package_default = {
30213
30216
  name: "@effect/language-service",
30214
- version: "0.67.0",
30217
+ version: "0.68.0",
30215
30218
  packageManager: "pnpm@8.11.0",
30216
30219
  publishConfig: {
30217
30220
  access: "public",
@@ -37030,6 +37033,69 @@ var importFromBarrel = createDiagnostic({
37030
37033
  })
37031
37034
  });
37032
37035
 
37036
+ // src/diagnostics/instanceOfSchema.ts
37037
+ var instanceOfSchema = createDiagnostic({
37038
+ name: "instanceOfSchema",
37039
+ code: 45,
37040
+ description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
37041
+ severity: "off",
37042
+ apply: fn2("instanceOfSchema.apply")(function* (sourceFile, report) {
37043
+ const ts = yield* service2(TypeScriptApi);
37044
+ const typeParser = yield* service2(TypeParser);
37045
+ const typeCheckerUtils = yield* service2(TypeCheckerUtils);
37046
+ const nodeToVisit = [];
37047
+ const appendNodeToVisit = (node) => {
37048
+ nodeToVisit.push(node);
37049
+ return void 0;
37050
+ };
37051
+ ts.forEachChild(sourceFile, appendNodeToVisit);
37052
+ while (nodeToVisit.length > 0) {
37053
+ const node = nodeToVisit.shift();
37054
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
37055
+ const leftExpr = node.left;
37056
+ const rightExpr = node.right;
37057
+ const rightType = typeCheckerUtils.getTypeAtLocation(rightExpr);
37058
+ if (!rightType) {
37059
+ ts.forEachChild(node, appendNodeToVisit);
37060
+ continue;
37061
+ }
37062
+ const isSchemaType = yield* pipe(
37063
+ typeParser.effectSchemaType(rightType, rightExpr),
37064
+ option5
37065
+ );
37066
+ if (isSchemaType._tag === "Some") {
37067
+ report({
37068
+ location: node,
37069
+ messageText: "Consider using Schema.is instead of instanceof for Effect Schema types.",
37070
+ fixes: [{
37071
+ fixName: "instanceOfSchema_fix",
37072
+ description: "Replace with Schema.is",
37073
+ apply: gen3(function* () {
37074
+ const changeTracker = yield* service2(ChangeTracker);
37075
+ const schemaIsCall = ts.factory.createCallExpression(
37076
+ ts.factory.createPropertyAccessExpression(
37077
+ ts.factory.createIdentifier("Schema"),
37078
+ "is"
37079
+ ),
37080
+ void 0,
37081
+ [rightExpr]
37082
+ );
37083
+ const fullCall = ts.factory.createCallExpression(
37084
+ schemaIsCall,
37085
+ void 0,
37086
+ [leftExpr]
37087
+ );
37088
+ changeTracker.replaceNode(sourceFile, node, fullCall);
37089
+ })
37090
+ }]
37091
+ });
37092
+ }
37093
+ }
37094
+ ts.forEachChild(node, appendNodeToVisit);
37095
+ }
37096
+ })
37097
+ });
37098
+
37033
37099
  // src/diagnostics/layerMergeAllWithDependencies.ts
37034
37100
  var layerMergeAllWithDependencies = createDiagnostic({
37035
37101
  name: "layerMergeAllWithDependencies",
@@ -37237,12 +37303,18 @@ var leakingRequirements = createDiagnostic({
37237
37303
  );
37238
37304
  function reportLeakingRequirements(node, requirements) {
37239
37305
  if (requirements.length === 0) return;
37306
+ const requirementsStr = requirements.map((_) => typeChecker.typeToString(_)).join(" | ");
37240
37307
  report({
37241
37308
  location: node,
37242
- messageText: `This Service is leaking the ${requirements.map((_) => typeChecker.typeToString(_)).join(" | ")} requirement.
37243
- If these requirements cannot be cached and are expected to be provided per method invocation (e.g. HttpServerRequest), you can either safely disable this diagnostic for this line through quickfixes or mark the service declaration with a JSDoc @effect-leakable-service.
37244
- Services should usually be collected in the layer creation body, and then provided at each method that requires them.
37245
- More info at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
37309
+ messageText: `Methods of this Service require \`${requirementsStr}\` from every caller.
37310
+
37311
+ This leaks implementation details into the service's public type \u2014 callers shouldn't need to know *how* the service works internally, only *what* it provides.
37312
+
37313
+ Resolve these dependencies at Layer creation and provide them to each method, so the service's type reflects its purpose, not its implementation.
37314
+
37315
+ To suppress this diagnostic for specific dependency types that are intentionally passed through (e.g., HttpServerRequest), add \`@effect-leakable-service\` JSDoc to their interface declarations (e.g., the \`${typeChecker.typeToString(requirements[0])}\` interface), not to this service.
37316
+
37317
+ More info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
37246
37318
  fixes: []
37247
37319
  });
37248
37320
  }
@@ -39420,6 +39492,7 @@ var unsupportedServiceAccessors = createDiagnostic({
39420
39492
  // src/diagnostics.ts
39421
39493
  var diagnostics = [
39422
39494
  anyUnknownInErrorContext,
39495
+ instanceOfSchema,
39423
39496
  catchAllToMapError,
39424
39497
  catchUnfailableEffect,
39425
39498
  classSelfMismatch,
@@ -41702,6 +41775,14 @@ var renderCodeActions = (result, assessmentState) => gen2(function* () {
41702
41775
  });
41703
41776
 
41704
41777
  // src/cli/quickfixes.ts
41778
+ var validDiagnosticNames = new Set(diagnostics.map((_) => _.name));
41779
+ var validDiagnosticCodes = new Set(diagnostics.map((_) => String(_.code)));
41780
+ var diagnosticCodeByName = new Map(diagnostics.map((_) => [_.name, _.code]));
41781
+ var ColumnRequiresLineError = class extends TaggedError("ColumnRequiresLineError") {
41782
+ get message() {
41783
+ return "The --column option requires --line to be specified.";
41784
+ }
41785
+ };
41705
41786
  var isSkipFix = (fixName) => fixName.endsWith("_skipNextLine") || fixName.endsWith("_skipFile");
41706
41787
  var renderQuickFix = (sourceFile, fix) => {
41707
41788
  const lines3 = [];
@@ -41767,9 +41848,42 @@ var quickfixes = make58(
41767
41848
  project: file3("project").pipe(
41768
41849
  optional4,
41769
41850
  withDescription3("The full path of the project tsconfig.json file to check for quick fixes.")
41851
+ ),
41852
+ code: text8("code").pipe(
41853
+ withDescription3("Filter by diagnostic name or code (e.g., 'floatingEffect' or '5')."),
41854
+ mapEffect6((value5) => {
41855
+ if (validDiagnosticNames.has(value5)) {
41856
+ return succeed7(diagnosticCodeByName.get(value5));
41857
+ }
41858
+ if (validDiagnosticCodes.has(value5)) {
41859
+ return succeed7(Number(value5));
41860
+ }
41861
+ const validValues = [...validDiagnosticNames].sort().join(", ");
41862
+ return fail7(
41863
+ invalidValue2(
41864
+ p2(`Invalid diagnostic code '${value5}'. Valid values: ${validValues}`)
41865
+ )
41866
+ );
41867
+ }),
41868
+ optional4
41869
+ ),
41870
+ line: integer5("line").pipe(
41871
+ withDescription3("Filter by line number (1-based)."),
41872
+ optional4
41873
+ ),
41874
+ column: integer5("column").pipe(
41875
+ withDescription3("Filter by column number (1-based). Requires --line to be specified."),
41876
+ optional4
41877
+ ),
41878
+ fix: text8("fix").pipe(
41879
+ withDescription3("Filter by fix name (e.g., 'floatingEffect_yieldStar')."),
41880
+ optional4
41770
41881
  )
41771
41882
  },
41772
- fn("quickfixes")(function* ({ file: file5, project: project3 }) {
41883
+ fn("quickfixes")(function* ({ code: code2, column: column3, file: file5, fix, line: line4, project: project3 }) {
41884
+ if (isSome2(column3) && isNone2(line4)) {
41885
+ return yield* new ColumnRequiresLineError();
41886
+ }
41773
41887
  const path2 = yield* Path2;
41774
41888
  const tsInstance = yield* TypeScriptContext;
41775
41889
  const filesToCheck = isSome2(project3) ? yield* getFileNamesInTsConfig(project3.value) : /* @__PURE__ */ new Set();
@@ -41813,6 +41927,12 @@ var quickfixes = make58(
41813
41927
  const diagnosticMap = /* @__PURE__ */ new Map();
41814
41928
  for (const diagnostic of result.diagnostics) {
41815
41929
  if (diagnostic.start === void 0) continue;
41930
+ if (isSome2(code2) && diagnostic.code !== code2.value) continue;
41931
+ if (isSome2(line4)) {
41932
+ const pos = tsInstance.getLineAndCharacterOfPosition(sourceFile, diagnostic.start);
41933
+ if (pos.line !== line4.value - 1) continue;
41934
+ if (isSome2(column3) && pos.character !== column3.value - 1) continue;
41935
+ }
41816
41936
  const key = `${diagnostic.start}-${diagnostic.start + (diagnostic.length ?? 0)}-${diagnostic.code}`;
41817
41937
  const ruleName = Object.values(diagnostics).find((_) => _.code === diagnostic.code)?.name ?? `unknown(${diagnostic.code})`;
41818
41938
  if (!diagnosticMap.has(key)) {
@@ -41834,6 +41954,7 @@ var quickfixes = make58(
41834
41954
  );
41835
41955
  for (const codeFix of result.codeFixes) {
41836
41956
  if (isSkipFix(codeFix.fixName)) continue;
41957
+ if (isSome2(fix) && codeFix.fixName !== fix.value) continue;
41837
41958
  const key = `${codeFix.start}-${codeFix.end}-${codeFix.code}`;
41838
41959
  const info2 = diagnosticMap.get(key);
41839
41960
  if (!info2) continue;