When an inner scope declares a name that an outer scope already has, you can't reach the outer one from there. A reader who saw the outer declaration first mistakes the inner uses for it, and a line moved from one scope to the other silently refers to a different variable.
It's the TypeScript version of no-shadow and takes the same options. Only the hoist default differs. It's "functions-and-types" here and "functions" there.
With bunfig.toml you can set either name. bun lint runs this rule in TypeScript files and no-shadow in JavaScript files, so nothing is reported twice. With an ESLint config, turn on only one of the two.
Configure
Off by default. To turn it on:
[lint.rules]
"@typescript-eslint/no-shadow" = "error" # or "warn"
Every option, set to its default. The ones that are commented out are unset by default.
[lint.rules]
"@typescript-eslint/no-shadow" = ["error", {
allow = [], # list of strings
builtinGlobals = false,
# "all", "functions", "functions-and-types", "never" or "types"
# hoist = "functions-and-types",
ignoreOnInitialization = false,
ignoreTypeValueShadow = true,
ignoreFunctionTypeParameterNameValueShadow = true,
}]
Examples
Reported:
const timeout = 1000;
function retry(action: () => void) {
const timeout = 50;
setTimeout(action, timeout);
}
Not reported:
const timeout = 1000;
function retry(action: () => void) {
const delay = 50;
setTimeout(action, delay);
}
A type parameter hides a type, and an enum member hides a variable inside the enum:
type Item = { id: number };
function first<Item>(items: Item[]) {
return items[0];
}
const Low = 10;
enum Priority {
Low,
High = Low + 1,
}
Options
| Option | Default | What it does |
|---|---|---|
allow | [] | Names that may hide an outer declaration. |
builtinGlobals | false | Also report a name that hides a global, such as Array. |
hoist | "functions-and-types" | When to report a name that comes before the outer declaration it hides. See below. |
ignoreOnInitialization | false | Allow it in a callback that's part of the outer variable's initializer. |
ignoreTypeValueShadow | true | Allow a value to have the name of an outer type, and a type the name of an outer value. |
ignoreFunctionTypeParameterNameValueShadow | true | Allow a parameter in a function type or signature to have the name of an outer variable. |
builtinGlobals
Reported with builtinGlobals: true:
function first(Array: string[]) {
return Array[0];
}
hoist
| Value | Outer declarations that count when they come after the inner name |
|---|---|
"all" | All. |
"functions" | Function declarations. |
"types" | Interfaces and type aliases. |
"functions-and-types" | All three. |
"never" | None. |
Here the outer port is a const that comes later, so this is allowed by default:
function listen() {
const port = 3000;
return port;
}
const port = 8080;
It's reported with hoist: "all":
function listen() {
const port = 3000;
return port;
}
const port = 8080;
ignoreOnInitialization
Not reported with ignoreOnInitialization: true:
declare const users: { active: boolean }[];
const user = users.find(user => user.active);
ignoreTypeValueShadow
A type and a value with the same name don't get in each other's way, so this is allowed by default:
type Config = { port: number };
function start() {
const Config = { port: 3000 };
return Config;
}
ignoreFunctionTypeParameterNameValueShadow
A function type's parameter is only a label. Set the option to false to report it:
const id = 1;
type Lookup = (id: number) => string;