npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eslint-plugin-no-misleading-return-type

v0.7.0

Published

ESLint rule to detect return type annotations that are misleadingly wider than what your implementation actually returns

Downloads

1,087

Readme

eslint-plugin-no-misleading-return-type

Detect return type annotations that are misleadingly wider than what your implementation actually returns.

Why this rule?

TypeScript allows explicit return type annotations that are wider than what the implementation actually returns. This silently discards the precision you deliberately built into your code.

// The implementation returns a precise error message map,
// but the explicit return type widens it to Record<string, string>.
function getErrorMessages(): Record<string, string> {
  return {
    INVALID_TOKEN: 'Please log in again.',
    RATE_LIMITED: 'Too many requests. Try again later.',
    NETWORK_ERROR: 'Check your network connection.',
  } as const;
}

// Better: let TypeScript infer the precise type.
function getErrorMessages() {
  return {
    INVALID_TOKEN: 'Please log in again.',
    RATE_LIMITED: 'Too many requests. Try again later.',
    NETWORK_ERROR: 'Check your network connection.',
  } as const;
}

This rule reports when an annotated return type is wider than what TypeScript would infer, helping you detect misleadingly wide return annotations and preserve the precision your implementation provides.

Installation

# npm
npm install -D eslint-plugin-no-misleading-return-type
# yarn
yarn add -D eslint-plugin-no-misleading-return-type
# pnpm
pnpm add -D eslint-plugin-no-misleading-return-type

Requirements:

  • Node.js ^18.18.0 || ^20.9.0 || >=21.1.0
  • ESLint ^9.0.0 || ^10.0.0
  • TypeScript >=5.0.0 <6.0.0 (tested: 5.0–5.9)
  • @typescript-eslint/parser with type information enabled

Setup

Add the plugin to your ESLint flat config with TypeScript support:

// eslint.config.ts
import noMisleadingReturnType from "eslint-plugin-no-misleading-return-type";
// or: import * as noMisleadingReturnType from "eslint-plugin-no-misleading-return-type";
import parser from "@typescript-eslint/parser";

export default [
  {
    files: ["**/*.ts", "**/*.tsx"],
    languageOptions: {
      parser,
      parserOptions: {
        projectService: {
          allowDefaultProject: ["*.ts", "*.tsx"],
        },
      },
    },
    plugins: {
      "no-misleading-return-type": noMisleadingReturnType,
    },
    rules: {
      "no-misleading-return-type/no-misleading-return-type": "warn",
    },
  },
];

Type information is required. Use either:

  • projectService: { allowDefaultProject: [...] } (recommended parser setup)
  • project: "./tsconfig.json" (classic tsconfig-based setup)

If you see TypeError: Cannot read properties of undefined (reading 'program'), type information is not configured. Check your parserOptions.

Config Presets

Instead of manual rule configuration, you can use one of the built-in presets. Note: You still need to configure languageOptions with @typescript-eslint/parser and type information.

// eslint.config.ts
import noMisleadingReturnType from "eslint-plugin-no-misleading-return-type";
import parser from "@typescript-eslint/parser";

export default [
  {
    files: ["**/*.ts", "**/*.tsx"],
    languageOptions: {
      parser,
      parserOptions: { projectService: { allowDefaultProject: ["*.ts", "*.tsx"] } },
    },
    ...noMisleadingReturnType.configs.recommended, // warn + suggestion (default)
    // ...noMisleadingReturnType.configs.strict,    // error + suggestion
    // ...noMisleadingReturnType.configs.autofix,   // warn + autofix
  },
];

| Preset | Severity | Fix mode | |--------|----------|----------| | recommended | warn | suggestion | | strict | error | suggestion | | autofix | warn | autofix |

Rule: no-misleading-return-type

What it checks

Reports when a function's explicit return type annotation is wider than TypeScript's inferred type.

  • Reports: Annotated type is wider than inferred (e.g., Record<string, string> vs { readonly INVALID_TOKEN: "..." })
  • Does not report: Annotated type equals inferred or is narrower
  • Does not report: No annotation, void, any, unknown, never, generators, generics, getters/setters, overloads, async Promise<void|any>

Valid (no warning)

// No annotation — TypeScript infers the precise type
function getErrorMessages() {
  return {
    INVALID_TOKEN: 'Please log in again.',
    NETWORK_ERROR: 'Check your network connection.',
  } as const;
}

// Single literal return — widened by this rule to approximate TS return type inference
function getStatus(): string { return "idle"; }
function getCode(): number { return 404; }

// Annotation matches inferred
function getStatus(): "idle" { return "idle"; }

// Escape hatches (intentionally wide types)
function run(): void { console.log("done"); }
function parse(s: string): any { return JSON.parse(s); }

// Async with matching inner type
async function greet(): Promise<"hello"> { return "hello"; }
async function greet(): Promise<string> { return "hello"; }  // single return — widened to string

Invalid (warning)

// as const map widened by explicit annotation
function getErrorMessages(): Record<string, string> {
  return {
    INVALID_TOKEN: 'Please log in again.',
    NETWORK_ERROR: 'Check your network connection.',
  } as const;
}

// Multi-return with union widening
function getStatus(loading: boolean): string {
  if (loading) return "loading";
  return "idle";                                 // inferred: "loading" | "idle", annotation: string
}

// Async multi-return
async function getStatus(x: boolean): Promise<string> {
  if (x) return "a";
  return "b";                                    // inferred: Promise<"a" | "b">, annotation: Promise<string>
}

Options

| Option | Type | Default | Effect | |--------|------|---------|--------| | fix | "suggestion" \| "autofix" \| "none" | "suggestion" | How to offer fixes |

fix modes:

  • "suggestion" — IDE inline suggestions: (1) remove annotation, (2) narrow annotation to inferred type
  • "autofix" — Auto-removes annotation (falls back to suggestion for exported functions with isolatedDeclarations)
  • "none" — Report without any fix

Example:

// eslint.config.ts
{
  rules: {
    "no-misleading-return-type/no-misleading-return-type": [
      "warn",
      { fix: "autofix" }
    ],
  },
}

How this rule approximates inference

This rule uses TypeScript's type checker APIs to approximate the inferred return type. It is not a full re-implementation of TypeScript's inference engine.

  • Single return: Widened via getBaseTypeOfLiteralType (matches TS signature inference)
  • Multiple returns: Literal union from return expressions (matches TS union inference)
  • Async functions: Standard Promise<T> / PromiseLike<T> unwrapped; inner type compared

This approach covers the vast majority of real-world cases. See What is not checked for known limitations.

What is not checked

| Case | Reason | |------|--------| | Overloaded function implementations | Intentionally wider to cover all overload signatures | | override methods | Must match parent class return type. May miss narrowable overrides (trade-off) | | declare functions / abstract methods | No body to analyze | | Getter / setter accessors | Getter-only return type semantics differ from regular functions | | Generator functions | Iterator<T, TReturn, TNext> unwrapping is non-trivial | | Generic functions | Inference depends on call-site instantiation | | Functions returning any, unknown, never, void | Intentional escape hatches |

When to intentionally widen

Some functions legitimately have wide return types. Use eslint-disable to suppress the warning:

// Inferred: "loading" | "idle" — but we expose string for a stable public API contract
// eslint-disable-next-line no-misleading-return-type/no-misleading-return-type
function getStatus(loading: boolean): string {
  if (loading) return 'loading';
  return 'idle';
}

License

MIT — See LICENSE