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