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
Maintainers
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-typeRequirements:
- 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/parserwith 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 yourparserOptions.
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, asyncPromise<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 stringInvalid (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 withisolatedDeclarations)"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
