Skip to content

Commit 9eab26f

Browse files
authored
feat(eslint-plugin): add rule naming-conventions (#1318)
1 parent 25092fd commit 9eab26f

File tree

14 files changed

+2528
-22
lines changed

14 files changed

+2528
-22
lines changed

.cspell.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"**/**/CHANGELOG.md",
1010
"**/**/CONTRIBUTORS.md",
1111
"**/**/ROADMAP.md",
12-
"**/*.{json,snap}"
12+
"**/*.{json,snap}",
13+
".cspell.json"
1314
],
1415
"dictionaries": [
1516
"typescript",
@@ -54,6 +55,8 @@
5455
"destructure",
5556
"destructured",
5657
"erroring",
58+
"ESLint",
59+
"ESLint's",
5760
"espree",
5861
"estree",
5962
"linebreaks",

packages/eslint-plugin/README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,16 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
101101
| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used | :heavy_check_mark: | | |
102102
| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :heavy_check_mark: | :wrench: | |
103103
| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | |
104-
| [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | |
105-
| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names | :heavy_check_mark: | | |
106104
| [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions | :heavy_check_mark: | | |
107105
| [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | | :wrench: | |
108106
| [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | |
109107
| [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | |
110108
| [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods | | | |
111109
| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | |
112-
| [`@typescript-eslint/generic-type-naming`](./docs/rules/generic-type-naming.md) | Enforces naming of generic type variables | | | |
113110
| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | |
114-
| [`@typescript-eslint/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names should or should not prefixed with `I` | :heavy_check_mark: | | |
115111
| [`@typescript-eslint/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | :heavy_check_mark: | :wrench: | |
116-
| [`@typescript-eslint/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility | | | |
117112
| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order | | | |
113+
| [`@typescript-eslint/naming-convention`](./docs/rules/naming-convention.md) | Enforces naming conventions for everything across a codebase | | | :thought_balloon: |
118114
| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | |
119115
| [`@typescript-eslint/no-dynamic-delete`](./docs/rules/no-dynamic-delete.md) | Disallow the delete operator with computed key expressions | | :wrench: | |
120116
| [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | :heavy_check_mark: | | |
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
# Enforces naming conventions for everything across a codebase (`naming-convention`)
2+
3+
Enforcing naming conventions helps keep the codebase consistent, and reduces overhead when thinking about how to name a variable.
4+
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`.
5+
6+
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.
7+
8+
## Rule Details
9+
10+
This rule allows you to enforce conventions for any identifier, using granular selectors to create a fine-grained style guide.
11+
12+
### Note - this rule only needs type information in specific cases, detailed below
13+
14+
## Options
15+
16+
This rule accepts an array of objects, with each object describing a different naming convention.
17+
Each property will be described in detail below. Also see the examples section below for illustrated examples.
18+
19+
```ts
20+
type Options = {
21+
// format options
22+
format: (
23+
| 'camelCase'
24+
| 'strictCamelCase'
25+
| 'PascalCase'
26+
| 'StrictPascalCase'
27+
| 'snake_case'
28+
| 'UPPER_CASE'
29+
)[];
30+
custom?: {
31+
regex: string;
32+
match: boolean;
33+
};
34+
leadingUnderscore?: 'forbid' | 'allow' | 'require';
35+
trailingUnderscore?: 'forbid' | 'allow' | 'require';
36+
prefix?: string[];
37+
suffix?: string[];
38+
39+
// selector options
40+
selector: Selector;
41+
filter?: string;
42+
// the allowed values for these are dependent on the selector - see below
43+
modifiers?: Modifiers<Selector>[];
44+
types?: Types<Selector>[];
45+
}[];
46+
47+
// the default config essentially does the same thing as ESLint's camelcase rule
48+
const defaultOptions: Options = [
49+
{
50+
selector: 'default',
51+
format: ['camelCase'],
52+
leadingUnderscore: 'allow',
53+
trailingUnderscore: 'allow',
54+
},
55+
56+
{
57+
selector: 'variable',
58+
format: ['camelCase', 'UPPER_CASE'],
59+
leadingUnderscore: 'allow',
60+
trailingUnderscore: 'allow',
61+
},
62+
63+
{
64+
selector: 'typeLike',
65+
format: ['PascalCase'],
66+
},
67+
];
68+
```
69+
70+
### Format Options
71+
72+
Every single selector can have the same set of format options.
73+
When the format of an identifier is checked, it is checked in the following order:
74+
75+
1. validate leading underscore
76+
1. validate trailing underscore
77+
1. validate prefix
78+
1. validate suffix
79+
1. validate custom
80+
1. validate format
81+
82+
At each step, if the identifier matches the option, the matching part will be removed.
83+
For example, if you provide the following formatting option: `{ leadingUnderscore: 'allow', prefix: ['I'], format: ['StrictPascalCase'] }`, for the identifier `_IMyInterface`, then the following checks will occur:
84+
85+
1. `name = _IMyInterface`
86+
1. validate leading underscore - pass
87+
- Trim leading underscore - `name = IMyInterface`
88+
1. validate trailing underscore - no check
89+
1. validate prefix - pass
90+
- Trim prefix - `name = MyInterface`
91+
1. validate suffix - no check
92+
1. validate format - pass
93+
94+
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.
95+
96+
#### `format`
97+
98+
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:
99+
100+
- `camelCase` - standard camelCase format - no underscores are allowed between characters, and consecutive capitals are allowed (i.e. both `myID` and `myId` are valid).
101+
- `strictCamelCase` - same as `camelCase`, but consecutive capitals are not allowed (i.e. `myId` is valid, but `myID` is not).
102+
- `PascalCase` - same as `camelCase`, except the first character must be upper-case.
103+
- `StrictPascalCase` - same as `strictCamelCase`, except the first character must be upper-case.
104+
- `snake_case` - standard snake_case format - all characters must be lower-case, and underscores are allowed.
105+
- `UPPER_CASE` - same as `snake_case`, except all characters must be upper-case.
106+
107+
### `custom`
108+
109+
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.
110+
Accepts an object with the following properties:
111+
112+
- `regex` - accepts a regular expression (anything accepted into `new RegExp(filter)`).
113+
- `match` - true if the identifier _must_ match the `regex`, false if the identifier _must not_ match the `regex`.
114+
115+
#### `leadingUnderscore` / `trailingUnderscore`
116+
117+
The `leadingUnderscore` / `trailingUnderscore` options control whether leading/trailing underscores are considered valid. Accepts one of the following values:
118+
119+
- `forbid` - a leading/trailing underscore is not allowed at all.
120+
- `allow` - existence of a leading/trailing underscore is not explicitly enforced.
121+
- `require` - a leading/trailing underscore must be included.
122+
123+
#### `prefix` / `suffix`
124+
125+
The `prefix` / `suffix` options control which prefix/suffix strings must exist for the identifier. Accepts an array of strings.
126+
127+
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.
128+
129+
### Selector Options
130+
131+
- `selector` (see "Allowed Selectors, Modifiers and Types" below).
132+
- `filter` accepts a regular expression (anything accepted into `new RegExp(filter)`). It allows you to limit the scope of this configuration to names that match this regex.
133+
- `modifiers` allows you to specify which modifiers to granularly apply to, such as the accessibility (`private`/`public`/`protected`), or if the thing is `static`, etc.
134+
- The name must match _all_ of the modifiers.
135+
- For example, if you provide `{ modifiers: ['private', 'static', 'readonly'] }`, then it will only match something that is `private static readonly`, and something that is just `private` will not match.
136+
- `types` allows you to specify which types to match. This option supports simple, primitive types only (`boolean`, `string`, `number`, `array`, `function`).
137+
- The name must match _one_ of the types.
138+
- **_NOTE - Using this option will require that you lint with type information._**
139+
- For example, this lets you do things like enforce that `boolean` variables are prefixed with a verb.
140+
- `boolean` matches any type assignable to `boolean | null | undefined`
141+
- `string` matches any type assignable to `string | null | undefined`
142+
- `number` matches any type assignable to `number | null | undefined`
143+
- `array` matches any type assignable to `Array<unknown> | null | undefined`
144+
- `function` matches any type assignable to `Function | null | undefined`
145+
146+
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.
147+
148+
For example, if you provide the following config:
149+
150+
```ts
151+
[
152+
/* 1 */ { selector: 'default', format: ['camelCase'] },
153+
/* 2 */ { selector: 'variable', format: ['snake_case'] },
154+
/* 3 */ { selector: 'variable', type: ['boolean'], format: ['UPPER_CASE'] },
155+
/* 4 */ { selector: 'variableLike', format: ['PascalCase'] },
156+
];
157+
```
158+
159+
Then for the code `const x = 1`, the rule will validate the selectors in the following order: `3`, `2`, `4`, `1`.
160+
161+
#### Allowed Selectors, Modifiers and Types
162+
163+
There are two types of selectors, individual selectors, and grouped selectors.
164+
165+
##### Individual Selectors
166+
167+
Individual Selectors match specific, well-defined sets. There is no overlap between each of the individual selectors.
168+
169+
- `variable` - matches any `var` / `let` / `const` variable name.
170+
- Allowed `modifiers`: none.
171+
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
172+
- `function` - matches any named function declaration or named function expression.
173+
- Allowed `modifiers`: none.
174+
- Allowed `types`: none.
175+
- `parameter` - matches any function parameter. Does not match parameter properties.
176+
- Allowed `modifiers`: none.
177+
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
178+
- `property` - matches any object, class, or object type property. Does not match properties that have direct function expression or arrow function expression values.
179+
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
180+
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
181+
- `parameterProperty` - matches any parameter property.
182+
- Allowed `modifiers`: `private`, `protected`, `public`, `readonly`.
183+
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
184+
- `method` - matches any object, class, or object type method. Also matches properties that have direct function expression or arrow function expression values. Does not match accessors.
185+
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
186+
- Allowed `types`: none.
187+
- `accessor` - matches any accessor.
188+
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
189+
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
190+
- `enumMember` - matches any enum member.
191+
- Allowed `modifiers`: none.
192+
- Allowed `types`: none.
193+
- `class` - matches any class declaration.
194+
- Allowed `modifiers`: `abstract`.
195+
- Allowed `types`: none.
196+
- `interface` - matches any interface declaration.
197+
- Allowed `modifiers`: none.
198+
- Allowed `types`: none.
199+
- `typeAlias` - matches any type alias declaration.
200+
- Allowed `modifiers`: none.
201+
- Allowed `types`: none.
202+
- `enum` - matches any enum declaration.
203+
- Allowed `modifiers`: none.
204+
- Allowed `types`: none.
205+
- `typeParameter` - matches any generic type parameter declaration.
206+
- Allowed `modifiers`: none.
207+
- Allowed `types`: none.
208+
209+
##### Group Selectors
210+
211+
Group Selectors are provided for convenience, and essentially bundle up sets of individual selectors.
212+
213+
- `default` - matches everything.
214+
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
215+
- Allowed `types`: none.
216+
- `variableLike` - matches the same as `variable`, `function` and `parameter`.
217+
- Allowed `modifiers`: none.
218+
- Allowed `types`: none.
219+
- `memberLike` - matches the same as `property`, `parameterProperty`, `method`, `accessor`, `enumMember`.
220+
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
221+
- Allowed `types`: none.
222+
- `typeLike` - matches the same as `class`, `interface`, `typeAlias`, `enum`, `typeParameter`.
223+
- Allowed `modifiers`: `abstract`.
224+
- Allowed `types`: none.
225+
226+
## Examples
227+
228+
### Enforce that all variables, functions and properties follow are camelCase
229+
230+
```json
231+
{
232+
"@typescript-eslint/naming-conventions": [
233+
"error",
234+
{ "selector": "variableLike", "format": ["camelCase"] }
235+
]
236+
}
237+
```
238+
239+
### Enforce that private members are prefixed with an underscore
240+
241+
```json
242+
{
243+
"@typescript-eslint/naming-conventions": [
244+
"error",
245+
{
246+
"selector": "memberLike",
247+
"modifier": ["private"],
248+
"format": ["camelCase"],
249+
"leadingUnderscore": "require"
250+
}
251+
]
252+
}
253+
```
254+
255+
### Enforce that boolean variables are prefixed with an allowed verb
256+
257+
```json
258+
{
259+
"@typescript-eslint/naming-conventions": [
260+
"error",
261+
{
262+
"selector": "variable",
263+
"types": ["boolean"],
264+
"format": ["PascalCase"],
265+
"prefix": ["is", "should", "has", "can", "did", "will"]
266+
}
267+
]
268+
}
269+
```
270+
271+
### Enforce that all variables are either in camelCase or UPPER_CASE
272+
273+
```json
274+
{
275+
"@typescript-eslint/naming-conventions": [
276+
"error",
277+
{
278+
"selector": "variable",
279+
"format": ["camelCase", "UPPER_CASE"]
280+
}
281+
]
282+
}
283+
```
284+
285+
### Enforce that type parameters (generics) are prefixed with `T`
286+
287+
```json
288+
{
289+
"@typescript-eslint/naming-conventions": [
290+
"error",
291+
{
292+
"selector": "typeParameter",
293+
"format": ["PascalCase"],
294+
"prefix": ["T"]
295+
}
296+
]
297+
}
298+
```
299+
300+
### Enforce the codebase follows ESLint's `camelcase` conventions
301+
302+
```json
303+
{
304+
"@typescript-eslint/naming-conventions": [
305+
"error",
306+
{
307+
"selector": "default",
308+
"format": ["camelCase"]
309+
},
310+
311+
{
312+
"selector": "variable",
313+
"format": ["camelCase", "UPPER_CASE"]
314+
},
315+
{
316+
"selector": "parameter",
317+
"format": ["camelCase"],
318+
"leadingUnderscore": "allow"
319+
},
320+
321+
{
322+
"selector": "memberLike",
323+
"modifiers": ["private"],
324+
"format": ["camelCase"],
325+
"leadingUnderscore": "require"
326+
},
327+
328+
{
329+
"selector": "typeLike",
330+
"format": ["PascalCase"]
331+
}
332+
]
333+
}
334+
```
335+
336+
## When Not To Use It
337+
338+
If you do not want to enforce naming conventions for anything.

0 commit comments

Comments
 (0)