@effect/language-service 0.67.0 → 0.69.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.
@@ -4855,6 +4855,104 @@ ${versions.map((version) => `- found ${version} at ${resolvedPackages[packageNam
4855
4855
  })
4856
4856
  });
4857
4857
 
4858
+ // src/diagnostics/effectFnIife.ts
4859
+ var effectFnIife = createDiagnostic({
4860
+ name: "effectFnIife",
4861
+ code: 46,
4862
+ description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
4863
+ severity: "warning",
4864
+ apply: fn("effectFnIife.apply")(function* (sourceFile, report) {
4865
+ const ts = yield* service(TypeScriptApi);
4866
+ const typeParser = yield* service(TypeParser);
4867
+ const tsUtils = yield* service(TypeScriptUtils);
4868
+ const sourceEffectModuleName = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
4869
+ sourceFile,
4870
+ "effect",
4871
+ "Effect"
4872
+ ) || "Effect";
4873
+ const nodeToVisit = [];
4874
+ const appendNodeToVisit = (node) => {
4875
+ nodeToVisit.push(node);
4876
+ return void 0;
4877
+ };
4878
+ ts.forEachChild(sourceFile, appendNodeToVisit);
4879
+ while (nodeToVisit.length > 0) {
4880
+ const node = nodeToVisit.shift();
4881
+ ts.forEachChild(node, appendNodeToVisit);
4882
+ if (!ts.isCallExpression(node)) continue;
4883
+ const innerCall = node.expression;
4884
+ if (!ts.isCallExpression(innerCall)) continue;
4885
+ const parsed = yield* pipe(
4886
+ typeParser.effectFnGen(innerCall),
4887
+ map4((result) => ({
4888
+ kind: "fn",
4889
+ effectModule: result.effectModule,
4890
+ generatorFunction: result.generatorFunction,
4891
+ pipeArguments: result.pipeArguments
4892
+ })),
4893
+ orElse2(
4894
+ () => pipe(
4895
+ typeParser.effectFnUntracedGen(innerCall),
4896
+ map4((result) => ({
4897
+ kind: "fnUntraced",
4898
+ effectModule: result.effectModule,
4899
+ generatorFunction: result.generatorFunction,
4900
+ pipeArguments: result.pipeArguments
4901
+ }))
4902
+ )
4903
+ ),
4904
+ orElse2(
4905
+ () => pipe(
4906
+ typeParser.effectFn(innerCall),
4907
+ map4((result) => ({
4908
+ kind: "fn",
4909
+ effectModule: result.effectModule,
4910
+ generatorFunction: void 0,
4911
+ pipeArguments: result.pipeArguments
4912
+ }))
4913
+ )
4914
+ ),
4915
+ option
4916
+ );
4917
+ if (isNone2(parsed)) continue;
4918
+ const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2 } = parsed.value;
4919
+ const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
4920
+ const fixes = [];
4921
+ if (generatorFunction && generatorFunction.parameters.length === 0) {
4922
+ fixes.push({
4923
+ fixName: "effectFnIife_toEffectGen",
4924
+ description: "Convert to Effect.gen",
4925
+ apply: gen(function* () {
4926
+ const changeTracker = yield* service(ChangeTracker);
4927
+ const effectGenCall = ts.factory.createCallExpression(
4928
+ ts.factory.createPropertyAccessExpression(
4929
+ ts.factory.createIdentifier(effectModuleName),
4930
+ "gen"
4931
+ ),
4932
+ void 0,
4933
+ [generatorFunction]
4934
+ );
4935
+ let replacementNode = effectGenCall;
4936
+ if (pipeArguments2.length > 0) {
4937
+ replacementNode = ts.factory.createCallExpression(
4938
+ ts.factory.createPropertyAccessExpression(effectGenCall, "pipe"),
4939
+ void 0,
4940
+ [...pipeArguments2]
4941
+ );
4942
+ }
4943
+ changeTracker.replaceNode(sourceFile, node, replacementNode);
4944
+ })
4945
+ });
4946
+ }
4947
+ report({
4948
+ location: node,
4949
+ messageText: `${effectModuleName}.${kind} returns a reusable function that can take arguments, but here it's called immediately. Use Effect.gen instead (optionally with Effect.withSpan for tracing).`,
4950
+ fixes
4951
+ });
4952
+ }
4953
+ })
4954
+ });
4955
+
4858
4956
  // src/diagnostics/effectFnOpportunity.ts
4859
4957
  var effectFnOpportunity = createDiagnostic({
4860
4958
  name: "effectFnOpportunity",
@@ -5622,6 +5720,69 @@ var importFromBarrel = createDiagnostic({
5622
5720
  })
5623
5721
  });
5624
5722
 
5723
+ // src/diagnostics/instanceOfSchema.ts
5724
+ var instanceOfSchema = createDiagnostic({
5725
+ name: "instanceOfSchema",
5726
+ code: 45,
5727
+ description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
5728
+ severity: "off",
5729
+ apply: fn("instanceOfSchema.apply")(function* (sourceFile, report) {
5730
+ const ts = yield* service(TypeScriptApi);
5731
+ const typeParser = yield* service(TypeParser);
5732
+ const typeCheckerUtils = yield* service(TypeCheckerUtils);
5733
+ const nodeToVisit = [];
5734
+ const appendNodeToVisit = (node) => {
5735
+ nodeToVisit.push(node);
5736
+ return void 0;
5737
+ };
5738
+ ts.forEachChild(sourceFile, appendNodeToVisit);
5739
+ while (nodeToVisit.length > 0) {
5740
+ const node = nodeToVisit.shift();
5741
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
5742
+ const leftExpr = node.left;
5743
+ const rightExpr = node.right;
5744
+ const rightType = typeCheckerUtils.getTypeAtLocation(rightExpr);
5745
+ if (!rightType) {
5746
+ ts.forEachChild(node, appendNodeToVisit);
5747
+ continue;
5748
+ }
5749
+ const isSchemaType = yield* pipe(
5750
+ typeParser.effectSchemaType(rightType, rightExpr),
5751
+ option
5752
+ );
5753
+ if (isSchemaType._tag === "Some") {
5754
+ report({
5755
+ location: node,
5756
+ messageText: "Consider using Schema.is instead of instanceof for Effect Schema types.",
5757
+ fixes: [{
5758
+ fixName: "instanceOfSchema_fix",
5759
+ description: "Replace with Schema.is",
5760
+ apply: gen(function* () {
5761
+ const changeTracker = yield* service(ChangeTracker);
5762
+ const schemaIsCall = ts.factory.createCallExpression(
5763
+ ts.factory.createPropertyAccessExpression(
5764
+ ts.factory.createIdentifier("Schema"),
5765
+ "is"
5766
+ ),
5767
+ void 0,
5768
+ [rightExpr]
5769
+ );
5770
+ const fullCall = ts.factory.createCallExpression(
5771
+ schemaIsCall,
5772
+ void 0,
5773
+ [leftExpr]
5774
+ );
5775
+ changeTracker.replaceNode(sourceFile, node, fullCall);
5776
+ })
5777
+ }]
5778
+ });
5779
+ }
5780
+ }
5781
+ ts.forEachChild(node, appendNodeToVisit);
5782
+ }
5783
+ })
5784
+ });
5785
+
5625
5786
  // src/diagnostics/layerMergeAllWithDependencies.ts
5626
5787
  var layerMergeAllWithDependencies = createDiagnostic({
5627
5788
  name: "layerMergeAllWithDependencies",
@@ -5829,12 +5990,18 @@ var leakingRequirements = createDiagnostic({
5829
5990
  );
5830
5991
  function reportLeakingRequirements(node, requirements) {
5831
5992
  if (requirements.length === 0) return;
5993
+ const requirementsStr = requirements.map((_) => typeChecker.typeToString(_)).join(" | ");
5832
5994
  report({
5833
5995
  location: node,
5834
- messageText: `This Service is leaking the ${requirements.map((_) => typeChecker.typeToString(_)).join(" | ")} requirement.
5835
- 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.
5836
- Services should usually be collected in the layer creation body, and then provided at each method that requires them.
5837
- More info at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
5996
+ messageText: `Methods of this Service require \`${requirementsStr}\` from every caller.
5997
+
5998
+ 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.
5999
+
6000
+ Resolve these dependencies at Layer creation and provide them to each method, so the service's type reflects its purpose, not its implementation.
6001
+
6002
+ 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.
6003
+
6004
+ More info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
5838
6005
  fixes: []
5839
6006
  });
5840
6007
  }
@@ -9045,6 +9212,7 @@ var unsupportedServiceAccessors = createDiagnostic({
9045
9212
  // src/diagnostics.ts
9046
9213
  var diagnostics = [
9047
9214
  anyUnknownInErrorContext,
9215
+ instanceOfSchema,
9048
9216
  catchAllToMapError,
9049
9217
  catchUnfailableEffect,
9050
9218
  classSelfMismatch,
@@ -9085,6 +9253,7 @@ var diagnostics = [
9085
9253
  globalErrorInEffectFailure,
9086
9254
  layerMergeAllWithDependencies,
9087
9255
  effectMapVoid,
9256
+ effectFnIife,
9088
9257
  effectFnOpportunity,
9089
9258
  redundantSchemaTagIdentifier,
9090
9259
  schemaSyncInEffect,