Enforces naming conventions for everything across a codebase (naming-convention
)
Enforcing naming conventions helps keep the codebase consistent, and reduces overhead when thinking about how to name a variable.
Additionally, a well-designed style guide can help communicate intent, such as by enforcing all private properties begin with an _
, and all global-level constants are written in UPPER_CASE
.
There are many different rules that have existed over time, but they have had the problem of not having enough granularity, meaning it was hard to have a well defined style guide, and most of the time you needed 3 or more rules at once to enforce different conventions, hoping they didn't conflict.
Rule Details
This rule allows you to enforce conventions for any identifier, using granular selectors to create a fine-grained style guide.
Note - this rule only needs type information in specific cases, detailed below
Options
This rule accepts an array of objects, with each object describing a different naming convention. Each property will be described in detail below. Also see the examples section below for illustrated examples.
type Options = {
// format options
format:
| (
| 'camelCase'
| 'strictCamelCase'
| 'PascalCase'
| 'StrictPascalCase'
| 'snake_case'
| 'UPPER_CASE'
)[]
| null;
custom?: {
regex: string;
match: boolean;
};
leadingUnderscore?:
| 'forbid'
| 'require'
| 'requireDouble'
| 'allow'
| 'allowDouble'
| 'allowSingleOrDouble';
trailingUnderscore?:
| 'forbid'
| 'require'
| 'requireDouble'
| 'allow'
| 'allowDouble'
| 'allowSingleOrDouble';
prefix?: string[];
suffix?: string[];
// selector options
selector: Selector | Selector[];
filter?:
| string
| {
regex: string;
match: boolean;
};
// the allowed values for these are dependent on the selector - see below
modifiers?: Modifiers<Selector>[];
types?: Types<Selector>[];
}[];
// the default config is similar to ESLint's camelcase rule but more strict
const defaultOptions: Options = [
{
selector: 'default',
format: ['camelCase'],
leadingUnderscore: 'allow',
trailingUnderscore: 'allow',
},
{
selector: 'variable',
format: ['camelCase', 'UPPER_CASE'],
leadingUnderscore: 'allow',
trailingUnderscore: 'allow',
},
{
selector: 'typeLike',
format: ['PascalCase'],
},
];
Format Options
Every single selector can have the same set of format options. For information about how each selector is applied, see "How does the rule evaluate a name's format?".
format
The format
option defines the allowed formats for the identifier. This option accepts an array of the following values, and the identifier can match any of them:
camelCase
- standard camelCase format - no underscores are allowed between characters, and consecutive capitals are allowed (i.e. bothmyID
andmyId
are valid).strictCamelCase
- same ascamelCase
, but consecutive capitals are not allowed (i.e.myId
is valid, butmyID
is not).PascalCase
- same ascamelCase
, except the first character must be upper-case.StrictPascalCase
- same asstrictCamelCase
, except the first character must be upper-case.snake_case
- standard snake_case format - all characters must be lower-case, and underscores are allowed.UPPER_CASE
- same assnake_case
, except all characters must be upper-case.
Instead of an array, you may also pass null
. This signifies "this selector shall not have its format checked".
This can be useful if you want to enforce no particular format for a specific selector, after applying a group selector.
custom
The custom
option defines a custom regex that the identifier must (or must not) match. This option allows you to have a bit more finer-grained control over identifiers, letting you ban (or force) certain patterns and substrings.
Accepts an object with the following properties:
regex
- a string that is then passed into RegExp to create a new regular expression:new RegExp(regex)
match
- true if the identifier must match theregex
, false if the identifier must not match theregex
.
filter
The filter
option operates similar to custom
, accepting the same shaped object, except that it controls if the rest of the configuration should or should not be applied to an identifier.
You can use this to include or exclude specific identifiers from specific configurations.
Accepts an object with the following properties:
regex
- a string that is then passed into RegExp to create a new regular expression:new RegExp(regex)
match
- true if the identifier must match theregex
, false if the identifier must not match theregex
.
Alternatively, filter
accepts a regular expression (anything accepted into new RegExp(filter)
). In this case, it's treated as if you had passed an object with the regex and match: true
.
leadingUnderscore
/ trailingUnderscore
The leadingUnderscore
/ trailingUnderscore
options control whether leading/trailing underscores are considered valid. Accepts one of the following values:
forbid
- a leading/trailing underscore is not allowed at all.require
- a single leading/trailing underscore must be included.requireDouble
- two leading/trailing underscores must be included.allow
- existence of a single leading/trailing underscore is not explicitly enforced.allowDouble
- existence of a double leading/trailing underscore is not explicitly enforced.allowSingleOrDouble
- existence of a single or a double leading/trailing underscore is not explicitly enforced.
prefix
/ suffix
The prefix
/ suffix
options control which prefix/suffix strings must exist for the identifier. Accepts an array of strings.
If these are provided, the identifier must start with one of the provided values. For example, if you provide { prefix: ['IFace', 'Class', 'Type'] }
, then the following names are valid: IFaceFoo
, ClassBar
, TypeBaz
, but the name Bang
is not valid, as it contains none of the prefixes.
Note: As documented above, the prefix is trimmed before format is validated, therefore PascalCase must be used to allow variables such as isEnabled
using the prefix is
.
Selector Options
selector
allows you to specify what types of identifiers to target.- Accepts one or array of selectors to define an option block that applies to one or multiple selectors.
- For example, if you provide
{ selector: ['variable', 'function'] }
, then it will apply the same option to variable and function nodes. - See Allowed Selectors, Modifiers and Types below for the complete list of allowed selectors.
modifiers
allows you to specify which modifiers to granularly apply to, such as the accessibility (private
/public
/protected
), or if the thing isstatic
, etc.- The name must match all of the modifiers.
- For example, if you provide
{ modifiers: ['private', 'static', 'readonly'] }
, then it will only match something that isprivate static readonly
, and something that is justprivate
will not match. - The following
modifiers
are allowed:const
- matches a variable declared as beingconst
(const x = 1
).destructured
- matches a variable declared via an object destructuring pattern (const {x, z = 2}
).- Note that this does not match renamed destructured properties (
const {x: y, a: b = 2}
).
- Note that this does not match renamed destructured properties (
global
- matches a variable/function declared in the top-level scope.exported
- matches anything that is exported from the module.unused
- matches anything that is not used.requiresQuotes
- matches any name that requires quotes as it is not a valid identifier (i.e. has a space, a dash, etc in it).public
- matches any member that is either explicitly declared aspublic
, or has no visibility modifier (i.e. implicitly public).readonly
,static
,abstract
,protected
,private
- matches any member explicitly declared with the given modifier.
types
allows you to specify which types to match. This option supports simple, primitive types only (boolean
,string
,number
,array
,function
).- The name must match one of the types.
- NOTE - Using this option will require that you lint with type information.
- For example, this lets you do things like enforce that
boolean
variables are prefixed with a verb. - The following
types
are allowed:boolean
matches any type assignable toboolean | null | undefined
string
matches any type assignable tostring | null | undefined
number
matches any type assignable tonumber | null | undefined
array
matches any type assignable toArray<unknown> | null | undefined
function
matches any type assignable toFunction | null | undefined
The ordering of selectors does not matter. The implementation will automatically sort the selectors to ensure they match from most-specific to least specific. It will keep checking selectors in that order until it finds one that matches the name. See "How does the rule automatically order selectors?"
Allowed Selectors, Modifiers and Types
There are two types of selectors, individual selectors, and grouped selectors.
Individual Selectors
Individual Selectors match specific, well-defined sets. There is no overlap between each of the individual selectors.
variable
- matches anyvar
/let
/const
variable name.- Allowed
modifiers
:const
,destructured
,global
,exported
,unused
. - Allowed
types
:boolean
,string
,number
,function
,array
.
- Allowed
function
- matches any named function declaration or named function expression.- Allowed
modifiers
:global
,exported
,unused
. - Allowed
types
: none.
- Allowed
parameter
- matches any function parameter. Does not match parameter properties.- Allowed
modifiers
:destructured
,unused
. - Allowed
types
:boolean
,string
,number
,function
,array
.
- Allowed
classProperty
- matches any class property. Does not match properties that have direct function expression or arrow function expression values.- Allowed
modifiers
:abstract
,private
,protected
,public
,readonly
,requiresQuotes
,static
. - Allowed
types
:boolean
,string
,number
,function
,array
.
- Allowed
objectLiteralProperty
- matches any object literal property. Does not match properties that have direct function expression or arrow function expression values.- Allowed
modifiers
:public
,requiresQuotes
. - Allowed
types
:boolean
,string
,number
,function
,array
.
- Allowed
typeProperty
- matches any object type property. Does not match properties that have direct function expression or arrow function expression values.- Allowed
modifiers
:public
,readonly
,requiresQuotes
. - Allowed
types
:boolean
,string
,number
,function
,array
.
- Allowed
parameterProperty
- matches any parameter property.- Allowed
modifiers
:private
,protected
,public
,readonly
. - Allowed
types
:boolean
,string
,number
,function
,array
.
- Allowed
classMethod
- matches any class method. Also matches properties that have direct function expression or arrow function expression values. Does not match accessors.- Allowed
modifiers
:abstract
,private
,protected
,public
,requiresQuotes
,static
. - Allowed
types
: none.
- Allowed
objectLiteralMethod
- matches any object literal method. Also matches properties that have direct function expression or arrow function expression values. Does not match accessors.- Allowed
modifiers
:public
,requiresQuotes
. - Allowed
types
: none.
- Allowed
typeMethod
- matches any object type method. Also matches properties that have direct function expression or arrow function expression values. Does not match accessors.- Allowed
modifiers
:public
,requiresQuotes
. - Allowed
types
: none.
- Allowed
accessor
- matches any accessor.- Allowed
modifiers
:abstract
,private
,protected
,public
,requiresQuotes
,static
. - Allowed
types
:boolean
,string
,number
,function
,array
.
- Allowed
enumMember
- matches any enum member.- Allowed
modifiers
:requiresQuotes
. - Allowed
types
: none.
- Allowed
class
- matches any class declaration.- Allowed
modifiers
:abstract
,exported
,unused
. - Allowed
types
: none.
- Allowed
interface
- matches any interface declaration.- Allowed
modifiers
:exported
,unused
. - Allowed
types
: none.
- Allowed
typeAlias
- matches any type alias declaration.- Allowed
modifiers
:exported
,unused
. - Allowed
types
: none.
- Allowed
enum
- matches any enum declaration.- Allowed
modifiers
:exported
,unused
. - Allowed
types
: none.
- Allowed
typeParameter
- matches any generic type parameter declaration.- Allowed
modifiers
:unused
. - Allowed
types
: none.
- Allowed
Group Selectors
Group Selectors are provided for convenience, and essentially bundle up sets of individual selectors.
default
- matches everything.- Allowed
modifiers
: all modifiers. - Allowed
types
: none.
- Allowed
variableLike
- matches the same asvariable
,function
andparameter
.- Allowed
modifiers
:unused
. - Allowed
types
: none.
- Allowed
memberLike
- matches the same asproperty
,parameterProperty
,method
,accessor
,enumMember
.- Allowed
modifiers
:private
,protected
,public
,static
,readonly
,abstract
,requiresQuotes
. - Allowed
types
: none.
- Allowed
typeLike
- matches the same asclass
,interface
,typeAlias
,enum
,typeParameter
.- Allowed
modifiers
:abstract
,unused
. - Allowed
types
: none.
- Allowed
property
- matches the same asclassProperty
,objectLiteralProperty
,typeProperty
.- Allowed
modifiers
:private
,protected
,public
,static
,readonly
,abstract
,requiresQuotes
. - Allowed
types
:boolean
,string
,number
,function
,array
.
- Allowed
method
- matches the same asclassMethod
,objectLiteralMethod
,typeMethod
.- Allowed
modifiers
:private
,protected
,public
,static
,readonly
,abstract
,requiresQuotes
. - Allowed
types
: none.
- Allowed
FAQ
This is a big rule, and there's a lot of docs. Here are a few clarifications that people often ask about or figure out via trial-and-error.
How does the rule evaluate a selector?
Each selector is checked in the following way:
- check the
selector
- if
selector
is one individual selector โ the name's type must be of that type. - if
selector
is a group selector โ the name's type must be one of the grouped types. - if
selector
is an array of selectors โ apply the above for each selector in the array.
- if
- check the
filter
- if
filter
is omitted โ skip this step. - if the name matches the
filter
โ continue evaluating this selector. - if the name does not match the
filter
โ skip this selector and continue to the next selector.
- if
- check the
types
- if
types
is omitted โ skip this step. - if the name has a type in
types
โ continue evaluating this selector. - if the name does not have a type in
types
โ skip this selector and continue to the next selector.
- if
A name is considered to pass the config if it:
- Matches one selector and passes all of that selector's format checks.
- Matches no selectors.
A name is considered to fail the config if it matches one selector and fails one that selector's format checks.
How does the rule automatically order selectors?
Each identifier should match exactly one selector. It may match multiple group selectors - but only ever one selector. With that in mind - the base sort order works out to be:
- Individual Selectors
- Grouped Selectors
- Default Selector
Within each of these categories, some further sorting occurs based on what selector options are supplied:
filter
is given the highest priority above all else.types
modifiers
- everything else
For example, if you provide the following config:
[
/* 1 */ { selector: 'default', format: ['camelCase'] },
/* 2 */ { selector: 'variable', format: ['snake_case'] },
/* 3 */ { selector: 'variable', types: ['boolean'], format: ['UPPER_CASE'] },
/* 4 */ { selector: 'variableLike', format: ['PascalCase'] },
];
Then for the code const x = 1
, the rule will validate the selectors in the following order: 3
, 2
, 4
, 1
.
To clearly spell it out:
- (3) is tested first because it has
types
and is an individual selector. - (2) is tested next because it is an individual selector.
- (4) is tested next as it is a grouped selector.
- (1) is tested last as it is the base default selector.
Its worth noting that whilst this order is applied, all selectors may not run on a name. This is explained in "How does the rule evaluate a name's format?"
How does the rule evaluate a name's format?
When the format of an identifier is checked, it is checked in the following order:
- validate leading underscore
- validate trailing underscore
- validate prefix
- validate suffix
- validate custom
- validate format
For steps 1-4, if the identifier matches the option, the matching part will be removed. This is done so that you can apply formats like PascalCase without worrying about prefixes or underscores causing it to not match.
One final note is that if the name were to become empty via this trimming process, it is considered to match all format
s. An example of where this might be useful is for generic type parameters, where you want all names to be prefixed with T
, but also want to allow for the single character T
name.
Here are some examples to help illustrate
Name: _IMyInterface
Selector:
{
"leadingUnderscore": "require",
"prefix": ["I"],
"format": ["UPPER_CASE", "StrictPascalCase"]
}
name = _IMyInterface
- validate leading underscore
- config is provided
- check name โ pass
- Trim underscore โ
name = IMyInterface
- validate trailing underscore
- config is not provided โ skip
- validate prefix
- config is provided
- check name โ pass
- Trim prefix โ
name = MyInterface
- validate suffix
- config is not provided โ skip
- validate custom
- config is not provided โ skip
- validate format
- for each format...
format = 'UPPER_CASE'
- check format โ fail.
- Important to note that if you supply multiple formats - the name only needs to match one of them!
- check format โ fail.
format = 'StrictPascalCase'
- check format โ success.
- for each format...
- success
Name: IMyInterface
Selector:
{
"format": ["StrictPascalCase"],
"trailingUnderscore": "allow",
"custom": {
"regex": "^I[A-Z]",
"match": false
}
}
name = IMyInterface
- validate leading underscore
- config is not provided โ skip
- validate trailing underscore
- config is provided
- check name โ pass
- Trim underscore โ
name = IMyInterface
- validate prefix
- config is not provided โ skip
- validate suffix
- config is not provided โ skip
- validate custom
- config is provided
regex = new RegExp("^I[A-Z]")
regex.test(name) === custom.match
- fail โ report and exit
What happens if I provide a modifiers
to a Group Selector?
Some group selectors accept modifiers
. For the most part these will work exactly the same as with individual selectors.
There is one exception to this in that a modifier might not apply to all individual selectors covered by a group selector.
For example - memberLike
includes the enumMember
selector, and it allows the protected
modifier.
An enumMember
can never ever be protected
, which means that the following config will never match any enumMember
:
{
"selector": "memberLike",
"modifiers": ["protected"]
}
To help with matching, members that cannot specify an accessibility will always have the public
modifier. This means that the following config will always match any enumMember
:
{
"selector": "memberLike",
"modifiers": ["public"]
}
Examples
Enforce that all variables, functions and properties follow are camelCase
{
"@typescript-eslint/naming-convention": [
"error",
{ "selector": "variableLike", "format": ["camelCase"] }
]
}
Enforce that private members are prefixed with an underscore
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "memberLike",
"modifiers": ["private"],
"format": ["camelCase"],
"leadingUnderscore": "require"
}
]
}
Enforce that boolean variables are prefixed with an allowed verb
Note: As documented above, the prefix is trimmed before format is validated, thus PascalCase must be used to allow variables such as isEnabled
.
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "variable",
"types": ["boolean"],
"format": ["PascalCase"],
"prefix": ["is", "should", "has", "can", "did", "will"]
}
]
}
Enforce that all variables are either in camelCase or UPPER_CASE
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "variable",
"format": ["camelCase", "UPPER_CASE"]
}
]
}
Enforce that all const variables are in UPPER_CASE
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "variable",
"modifiers": ["const"],
"format": ["UPPER_CASE"]
}
]
}
Enforce that type parameters (generics) are prefixed with T
This allows you to emulate the old generic-type-naming
rule.
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "typeParameter",
"format": ["PascalCase"],
"prefix": ["T"]
}
]
}
Enforce that interface names do not begin with an I
This allows you to emulate the old interface-name-prefix
rule.
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "interface",
"format": ["PascalCase"],
"custom": {
"regex": "^I[A-Z]",
"match": false
}
}
]
}
Enforce that variable and function names are in camelCase
This allows you to lint multiple type with same pattern.
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": ["variable", "function"],
"format": ["camelCase"],
"leadingUnderscore": "allow"
}
]
}
Ignore properties that require quotes
Sometimes you have to use a quoted name that breaks the convention (for example, HTTP headers). If this is a common thing in your codebase, then you have a few options.
If you simply want to allow all property names that require quotes, you can use the requiresQuotes
modifier to match any property name that requires quoting, and use format: null
to ignore the name.
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": [
"classProperty",
"objectLiteralProperty",
"typeProperty",
"classMethod",
"objectLiteralMethod",
"typeMethod",
"accessor",
"enumMember"
],
"format": null,
"modifiers": ["requiresQuotes"]
}
]
}
If you have a small and known list of exceptions, you can use the filter
option to ignore these specific names only:
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "property",
"format": ["strictCamelCase"],
"filter": {
// you can expand this regex to add more allowed names
"regex": "^(Property-Name-One|Property-Name-Two)$",
"match": false
}
}
]
}
You can use the filter
option to ignore names with specific characters:
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "property",
"format": ["strictCamelCase"],
"filter": {
// you can expand this regex as you find more cases that require quoting that you want to allow
"regex": "[- ]",
"match": false
}
}
]
}
Note that there is no way to ignore any name that is quoted - only names that are required to be quoted.
This is intentional - adding quotes around a name is not an escape hatch for proper naming.
If you want an escape hatch for a specific name - you should can use an eslint-disable
comment.
Ignore destructured names
Sometimes you might want to allow destructured properties to retain their original name, even if it breaks your naming convention.
You can use the destructured
modifier to match these names, and explicitly set format: null
to apply no formatting:
{
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "variable",
"modifiers": ["destructured"],
"format": null
}
]
}
Enforce the codebase follows ESLint's camelcase
conventions
{
"camelcase": "off",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "default",
"format": ["camelCase"]
},
{
"selector": "variable",
"format": ["camelCase", "UPPER_CASE"]
},
{
"selector": "parameter",
"format": ["camelCase"],
"leadingUnderscore": "allow"
},
{
"selector": "memberLike",
"modifiers": ["private"],
"format": ["camelCase"],
"leadingUnderscore": "require"
},
{
"selector": "typeLike",
"format": ["PascalCase"]
}
]
}
When Not To Use It
If you do not want to enforce naming conventions for anything.
Attributes
- โ Recommended
- ๐ง Fixable
- ๐ญ Requires type information