@effect/language-service 0.81.0 → 0.82.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.
@@ -5549,6 +5549,7 @@ var classSelfMismatch = createDiagnostic({
5549
5549
  if (ts.isClassDeclaration(node) && node.name && node.heritageClauses) {
5550
5550
  const result = yield* pipe(
5551
5551
  typeParser.extendsEffectService(node),
5552
+ orElse2(() => typeParser.extendsServiceMapService(node)),
5552
5553
  orElse2(() => typeParser.extendsContextTag(node)),
5553
5554
  orElse2(() => typeParser.extendsEffectTag(node)),
5554
5555
  orElse2(() => typeParser.extendsSchemaClass(node)),
@@ -5924,6 +5925,89 @@ var effectFnIife = createDiagnostic({
5924
5925
  })
5925
5926
  });
5926
5927
 
5928
+ // src/diagnostics/effectFnImplicitAny.ts
5929
+ var getParameterName = (typescript, name) => {
5930
+ if (typescript.isIdentifier(name)) {
5931
+ return typescript.idText(name);
5932
+ }
5933
+ return "parameter";
5934
+ };
5935
+ var hasOuterContextualFunctionType = (typescript, typeChecker, node) => {
5936
+ const contextualType = typeChecker.getContextualType(node);
5937
+ if (!contextualType) {
5938
+ return false;
5939
+ }
5940
+ return typeChecker.getSignaturesOfType(contextualType, typescript.SignatureKind.Call).length > 0;
5941
+ };
5942
+ var effectFnImplicitAny = createDiagnostic({
5943
+ name: "effectFnImplicitAny",
5944
+ code: 54,
5945
+ description: "Mirrors noImplicitAny for unannotated Effect.fn and Effect.fnUntraced callback parameters when no outer contextual function type exists",
5946
+ group: "correctness",
5947
+ severity: "error",
5948
+ fixable: false,
5949
+ supportedEffect: ["v3", "v4"],
5950
+ apply: fn("effectFnImplicitAny.apply")(function* (sourceFile, report) {
5951
+ const ts = yield* service(TypeScriptApi);
5952
+ const program = yield* service(TypeScriptProgram);
5953
+ const typeChecker = yield* service(TypeCheckerApi);
5954
+ const typeParser = yield* service(TypeParser);
5955
+ const noImplicitAny = program.getCompilerOptions().noImplicitAny ?? program.getCompilerOptions().strict ?? false;
5956
+ if (!noImplicitAny) {
5957
+ return;
5958
+ }
5959
+ const nodeToVisit = [sourceFile];
5960
+ const appendNodeToVisit = (node) => {
5961
+ nodeToVisit.push(node);
5962
+ return void 0;
5963
+ };
5964
+ while (nodeToVisit.length > 0) {
5965
+ const node = nodeToVisit.pop();
5966
+ ts.forEachChild(node, appendNodeToVisit);
5967
+ const parsed = yield* pipe(
5968
+ typeParser.effectFn(node),
5969
+ map4((result) => ({
5970
+ call: result.node,
5971
+ fn: result.regularFunction
5972
+ })),
5973
+ orElse2(
5974
+ () => pipe(
5975
+ typeParser.effectFnGen(node),
5976
+ map4((result) => ({
5977
+ call: result.node,
5978
+ fn: result.generatorFunction
5979
+ }))
5980
+ )
5981
+ ),
5982
+ orElse2(
5983
+ () => pipe(
5984
+ typeParser.effectFnUntracedGen(node),
5985
+ map4((result) => ({
5986
+ call: result.node,
5987
+ fn: result.generatorFunction
5988
+ }))
5989
+ )
5990
+ ),
5991
+ orUndefined
5992
+ );
5993
+ if (!parsed || hasOuterContextualFunctionType(ts, typeChecker, parsed.call)) {
5994
+ continue;
5995
+ }
5996
+ for (const parameter of parsed.fn.parameters) {
5997
+ if (parameter.type || parameter.initializer) {
5998
+ continue;
5999
+ }
6000
+ const parameterName = getParameterName(ts, parameter.name);
6001
+ report({
6002
+ location: parameter.name,
6003
+ messageText: `Parameter '${parameterName}' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type.`,
6004
+ fixes: []
6005
+ });
6006
+ }
6007
+ }
6008
+ })
6009
+ });
6010
+
5927
6011
  // src/diagnostics/effectFnOpportunity.ts
5928
6012
  var effectFnOpportunity = createDiagnostic({
5929
6013
  name: "effectFnOpportunity",
@@ -7458,6 +7542,24 @@ var leakingRequirements = createDiagnostic({
7458
7542
  const typeCheckerUtils = yield* service(TypeCheckerUtils);
7459
7543
  const typeParser = yield* service(TypeParser);
7460
7544
  const tsUtils = yield* service(TypeScriptUtils);
7545
+ const isExpectedLeakingServiceSuppressed = (leakedServiceName, startNode) => {
7546
+ const sourceFile2 = tsUtils.getSourceFileOfNode(startNode);
7547
+ if (!sourceFile2) return false;
7548
+ return !!ts.findAncestor(startNode, (current) => {
7549
+ const ranges = ts.getLeadingCommentRanges(sourceFile2.text, current.pos) ?? [];
7550
+ const isSuppressed = ranges.some((range) => {
7551
+ const commentText = sourceFile2.text.slice(range.pos, range.end);
7552
+ return commentText.split("\n").filter((line) => line.includes("@effect-expect-leaking")).some((line) => {
7553
+ const markerIndex = line.indexOf("@effect-expect-leaking");
7554
+ return markerIndex !== -1 && line.slice(markerIndex + "@effect-expect-leaking".length).includes(leakedServiceName);
7555
+ });
7556
+ });
7557
+ if (isSuppressed) {
7558
+ return true;
7559
+ }
7560
+ return ts.isClassDeclaration(current) || ts.isVariableStatement(current) || ts.isExpressionStatement(current) || ts.isStatement(current) ? "quit" : false;
7561
+ });
7562
+ };
7461
7563
  const parseLeakedRequirements = cachedBy(
7462
7564
  fn("leakingServices.checkServiceLeaking")(
7463
7565
  function* (service2, atLocation) {
@@ -7539,8 +7641,12 @@ var leakingRequirements = createDiagnostic({
7539
7641
  (_, service2) => service2
7540
7642
  );
7541
7643
  function reportLeakingRequirements(node, requirements) {
7542
- if (requirements.length === 0) return;
7543
- const requirementsStr = requirements.map((_) => typeChecker.typeToString(_)).join(" | ");
7644
+ const filteredRequirements = requirements.filter(
7645
+ (requirement) => !isExpectedLeakingServiceSuppressed(typeChecker.typeToString(requirement), node)
7646
+ );
7647
+ if (filteredRequirements.length === 0) return;
7648
+ const requirementsStr = filteredRequirements.map((_) => typeChecker.typeToString(_)).join(" | ");
7649
+ const firstStr = typeChecker.typeToString(filteredRequirements[0]);
7544
7650
  report({
7545
7651
  location: node,
7546
7652
  messageText: `Methods of this Service require \`${requirementsStr}\` from every caller.
@@ -7549,7 +7655,7 @@ This leaks implementation details into the service's public type \u2014 callers
7549
7655
 
7550
7656
  Resolve these dependencies at Layer creation and provide them to each method, so the service's type reflects its purpose, not its implementation.
7551
7657
 
7552
- 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.
7658
+ 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 \`${firstStr}\` interface), or to this service by adding a \`@effect-expect-leaking ${firstStr}\` JSDoc.
7553
7659
 
7554
7660
  More info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
7555
7661
  fixes: []
@@ -11870,6 +11976,7 @@ var diagnostics = [
11870
11976
  catchUnfailableEffect,
11871
11977
  classSelfMismatch,
11872
11978
  duplicatePackage,
11979
+ effectFnImplicitAny,
11873
11980
  effectGenUsesAdapter,
11874
11981
  missingEffectContext,
11875
11982
  missingEffectError,