@typescript-eslint/naming-convention

Enforce naming conventions for variables, functions, types, members and imports

Also called typescript-eslint/naming-convention, typescript/naming-convention

When names follow one convention, the shape of a name tells you what it is: UserStore is a type, userStore is a value, MAX_USERS is a constant. This rule lets you say which shape each kind of name must have.

Without options, everything is camelCase, variables can also be UPPER_CASE, imports can also be PascalCase, and classes, interfaces, type aliases, enums and type parameters are PascalCase. One leading or trailing underscore is allowed, except on types and imports.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"@typescript-eslint/naming-convention" = "error" # or "warn"

Examples#

example.ts3 problems
const user_name = "ada";

function Load_user() {}

interface user_record {}
example.tsNo problems
const userName = "ada";
const MAX_USERS = 10;

function loadUser(_id: number) {}

interface UserRecord {}

Options#

The options are a list of entries. Each entry picks names with selector (and modifiers, types, filter) and says what they must look like. Your list replaces the defaults.

KeyWhat it does
selectorWhich kind of name, or a list of kinds. See below.
formatThe allowed formats: camelCase, strictCamelCase, PascalCase, StrictPascalCase, snake_case, UPPER_CASE. null skips the format check. The strict ones don't allow two capitals in a row: userId, not userID.
leadingUnderscore, trailingUnderscore"forbid", "allow", "require", "requireDouble", "allowDouble", "allowSingleOrDouble". If not set, an underscore is checked as part of the name, so _id isn't camelCase.
prefix, suffixThe name must start or end with one of these strings.
custom{ "regex": "...", "match": true }: the name must match the pattern, or must not with false.
filterA pattern, or { "regex", "match" }. The entry only applies to names that pass it.
modifiersThe entry only applies to names that have all of these: const, readonly, static, public, protected, private, #private, abstract, destructured, global, exported, unused, requiresQuotes, override, async, default, namespace.
typesFor variables, parameters, properties, getters and setters: the entry only applies to values of these types: boolean, string, number, function, array. This needs type information. Run bun lint --type-aware. Without it, the entry doesn't apply to anything.

Selectors: variable, function, parameter, parameterProperty, classProperty, objectLiteralProperty, typeProperty, classMethod, objectLiteralMethod, typeMethod, classicAccessor, autoAccessor, enumMember, class, interface, typeAlias, enum, typeParameter, import.

import is for default and namespace imports. The names in import { ... } aren't checked.

Group selectors: default (everything), variableLike, memberLike, typeLike, property, method, accessor.

Only one entry applies to each name. A single selector wins over a group selector. Between two entries with the same selector, the one with a filter wins, then the one with modifiers or types.

The rule strips underscores, then the prefix and the suffix, before it checks custom and format. So with the prefix is, isActive is checked as Active:

example.tswith { "selector": "variable", "format": ["PascalCase"], "prefix": ["is", "has"] }No problems
const isActive = true;
const hasAccess = false;
example.tswith { "selector": "variable", "format": ["PascalCase"], "prefix": ["is", "has"] }1 problem
const active = true;

No I in front of interface names:

example.tswith { "selector": "interface", "format": ["PascalCase"], "custom": { "regex": "^I[A-Z]", "match": false } }1 problem
interface IUser {}

Private members start with an underscore:

example.tswith { "selector": "memberLike", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require" }1 problem
class Cache {
  private entries = new Map();
}
example.tswith { "selector": "memberLike", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require" }No problems
class Cache {
  private _entries = new Map();
  size = 0;
}

A name that needs quotes never fits a format. Turn the check off for those:

example.tswith { "selector": "default", "format": ["camelCase"] }1 problem
const headers = { "Content-Type": "text/plain" };
example.tswith { "selector": "default", "format": ["camelCase"] }, { "selector": "objectLiteralProperty", "modifiers": ["requiresQuotes"], "format": null }No problems
const headers = { "Content-Type": "text/plain" };

Skip variables that take their name from the property they destructure:

example.tswith { "selector": "variable", "format": ["camelCase"] }, { "selector": "variable", "modifiers": ["destructured"], "format": null }No problems
const { user_id } = await fetchUser();

When not to use it#

The defaults are strict about property names, which often come from an API, a database or a file format. Expect to write your own list before the rule quiets down.