Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): add rule naming-conventions #1318

Merged
merged 36 commits into from Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4396290
feat: variable, function, parameter, parameterProperty, property
bradzacher Dec 9, 2019
73d895c
feat: method
bradzacher Dec 9, 2019
e3a14b5
feat: accessors
bradzacher Dec 9, 2019
d8967e1
feat: enumMember
bradzacher Dec 9, 2019
3a431a2
feat: config ordering
bradzacher Dec 9, 2019
2356643
feat: meta selectors
bradzacher Dec 10, 2019
284fe4c
docs: add docs
bradzacher Dec 10, 2019
910acec
docs: typos and restructure a bit
bradzacher Dec 11, 2019
1db8b8c
feat: support simple primitive types
bradzacher Dec 11, 2019
aa949d6
feat: class, interface, typeAlias, enum, typeParameter
bradzacher Dec 11, 2019
7b68c47
docs: add examples
bradzacher Dec 11, 2019
19ece7f
feat: support array and function types
bradzacher Dec 11, 2019
f28058d
docs: remove deprecated rules from readme
bradzacher Dec 12, 2019
503a7b0
docs: fix spelling
bradzacher Dec 18, 2019
e21d472
feat: nits
bradzacher Dec 18, 2019
409a616
fix: correct default config to closely match eslint's camelcase
bradzacher Dec 19, 2019
9797f56
Merge branch 'master' into naming-conventions
bradzacher Dec 19, 2019
c894ecc
Merge branch 'master' into naming-conventions
bradzacher Dec 19, 2019
888c495
Merge branch 'master' into naming-conventions
armano2 Dec 22, 2019
bb70949
fix: typos, use NonComputedName types
bradzacher Dec 22, 2019
8acb0d3
Merge branch 'master' into naming-conventions
bradzacher Dec 29, 2019
1a85712
fix: lint error
bradzacher Dec 29, 2019
39ea8cc
fix: spelling
bradzacher Dec 30, 2019
c2b8ce9
feat: matches for some selectors
bradzacher Dec 30, 2019
69c1d5e
Merge branch 'master' into naming-conventions
bradzacher Jan 1, 2020
380dcc2
Merge branch 'master' into naming-conventions
bradzacher Jan 3, 2020
d8e7d99
Merge branch 'master' into naming-conventions
bradzacher Jan 5, 2020
0154b40
docs: new heading format
bradzacher Jan 5, 2020
e81a2b2
Merge branch 'master' into naming-conventions
bradzacher Jan 6, 2020
f25f3b0
Merge branch 'master' into naming-conventions
bradzacher Jan 9, 2020
a16e6f4
feat: add custom regex option
bradzacher Jan 9, 2020
557e827
feat: deprecate interface-name-prefix
bradzacher Jan 9, 2020
c7ed80e
docs: regen docs for deprecation
bradzacher Jan 9, 2020
6596a02
fix: config checker tests
bradzacher Jan 13, 2020
fda403a
Merge branch 'master' into naming-conventions
bradzacher Jan 13, 2020
d514d41
Merge branch 'master' into naming-conventions
bradzacher Jan 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion .cspell.json
Expand Up @@ -9,7 +9,8 @@
"**/**/CHANGELOG.md",
"**/**/CONTRIBUTORS.md",
"**/**/ROADMAP.md",
"**/*.{json,snap}"
"**/*.{json,snap}",
".cspell.json"
],
"dictionaries": [
"typescript",
Expand Down Expand Up @@ -54,6 +55,8 @@
"destructure",
"destructured",
"erroring",
"ESLint",
"ESLint's",
"espree",
"estree",
"linebreaks",
Expand Down
6 changes: 1 addition & 5 deletions packages/eslint-plugin/README.md
Expand Up @@ -101,20 +101,16 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used | :heavy_check_mark: | | |
| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | |
| [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | |
| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names | :heavy_check_mark: | | |
| [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions | :heavy_check_mark: | | |
| [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | | :wrench: | |
| [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | |
| [`@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: | | |
| [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods | | | |
| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | |
| [`@typescript-eslint/generic-type-naming`](./docs/rules/generic-type-naming.md) | Enforces naming of generic type variables | | | |
| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | |
| [`@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: | | |
| [`@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: | |
| [`@typescript-eslint/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility | | | |
| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order | | | |
| [`@typescript-eslint/naming-convention`](./docs/rules/naming-convention.md) | Enforces naming conventions for everything across a codebase | | | :thought_balloon: |
| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/no-dynamic-delete`](./docs/rules/no-dynamic-delete.md) | Disallow the delete operator with computed key expressions | | :wrench: | |
| [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | :heavy_check_mark: | | |
Expand Down
338 changes: 338 additions & 0 deletions packages/eslint-plugin/docs/rules/naming-convention.md
@@ -0,0 +1,338 @@
# 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.

```ts
type Options = {
// format options
format: (
| 'camelCase'
| 'strictCamelCase'
| 'PascalCase'
| 'StrictPascalCase'
| 'snake_case'
| 'UPPER_CASE'
)[];
custom?: {
regex: string;
match: boolean;
};
leadingUnderscore?: 'forbid' | 'allow' | 'require';
trailingUnderscore?: 'forbid' | 'allow' | 'require';
prefix?: string[];
suffix?: string[];

// selector options
selector: Selector;
filter?: string;
// the allowed values for these are dependent on the selector - see below
modifiers?: Modifiers<Selector>[];
types?: Types<Selector>[];
}[];

// the default config essentially does the same thing as ESLint's camelcase rule
bradzacher marked this conversation as resolved.
Show resolved Hide resolved
const defaultOptions: Options = [
{
selector: 'default',
format: ['camelCase'],
leadingUnderscore: 'allow',
trailingUnderscore: 'allow',
},

{
selector: 'variable',
format: ['camelCase', 'UPPER_CASE'],
leadingUnderscore: 'allow',
trailingUnderscore: 'allow',
},
bradzacher marked this conversation as resolved.
Show resolved Hide resolved

{
selector: 'typeLike',
format: ['PascalCase'],
},
];
```

### Format Options

Every single selector can have the same set of format options.
When the format of an identifier is checked, it is checked in the following order:

1. validate leading underscore
1. validate trailing underscore
1. validate prefix
1. validate suffix
1. validate custom
1. validate format

At each step, if the identifier matches the option, the matching part will be removed.
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:

1. `name = _IMyInterface`
1. validate leading underscore - pass
- Trim leading underscore - `name = IMyInterface`
1. validate trailing underscore - no check
1. validate prefix - pass
- Trim prefix - `name = MyInterface`
1. validate suffix - no check
1. validate format - pass

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.

#### `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. both `myID` and `myId` are valid).
- `strictCamelCase` - same as `camelCase`, but consecutive capitals are not allowed (i.e. `myId` is valid, but `myID` is not).
- `PascalCase` - same as `camelCase`, except the first character must be upper-case.
- `StrictPascalCase` - same as `strictCamelCase`, 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 as `snake_case`, except all characters must be upper-case.

### `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` - accepts a regular expression (anything accepted into `new RegExp(filter)`).
- `match` - true if the identifier _must_ match the `regex`, false if the identifier _must not_ match the `regex`.

#### `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.
- `allow` - existence of a leading/trailing underscore is not explicitly enforced.
- `require` - a leading/trailing underscore must be included.

#### `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.

### Selector Options

- `selector` (see "Allowed Selectors, Modifiers and Types" below).
- `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.
- `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.
- The name must match _all_ of the modifiers.
- 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.
- `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.
- `boolean` matches any type assignable to `boolean | null | undefined`
- `string` matches any type assignable to `string | null | undefined`
- `number` matches any type assignable to `number | null | undefined`
- `array` matches any type assignable to `Array<unknown> | null | undefined`
- `function` matches any type assignable to `Function | 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.

For example, if you provide the following config:

```ts
[
/* 1 */ { selector: 'default', format: ['camelCase'] },
/* 2 */ { selector: 'variable', format: ['snake_case'] },
/* 3 */ { selector: 'variable', type: ['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`.

#### 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 any `var` / `let` / `const` variable name.
- Allowed `modifiers`: none.
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
- `function` - matches any named function declaration or named function expression.
- Allowed `modifiers`: none.
- Allowed `types`: none.
- `parameter` - matches any function parameter. Does not match parameter properties.
- Allowed `modifiers`: none.
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
- `property` - matches any object, class, or object type property. Does not match properties that have direct function expression or arrow function expression values.
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
- `parameterProperty` - matches any parameter property.
- Allowed `modifiers`: `private`, `protected`, `public`, `readonly`.
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
- `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.
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
- Allowed `types`: none.
- `accessor` - matches any accessor.
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
- `enumMember` - matches any enum member.
- Allowed `modifiers`: none.
- Allowed `types`: none.
- `class` - matches any class declaration.
- Allowed `modifiers`: `abstract`.
- Allowed `types`: none.
- `interface` - matches any interface declaration.
- Allowed `modifiers`: none.
- Allowed `types`: none.
- `typeAlias` - matches any type alias declaration.
- Allowed `modifiers`: none.
- Allowed `types`: none.
- `enum` - matches any enum declaration.
- Allowed `modifiers`: none.
- Allowed `types`: none.
- `typeParameter` - matches any generic type parameter declaration.
- Allowed `modifiers`: none.
- Allowed `types`: none.

##### Group Selectors

Group Selectors are provided for convenience, and essentially bundle up sets of individual selectors.

- `default` - matches everything.
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
- Allowed `types`: none.
- `variableLike` - matches the same as `variable`, `function` and `parameter`.
- Allowed `modifiers`: none.
- Allowed `types`: none.
- `memberLike` - matches the same as `property`, `parameterProperty`, `method`, `accessor`, `enumMember`.
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
- Allowed `types`: none.
- `typeLike` - matches the same as `class`, `interface`, `typeAlias`, `enum`, `typeParameter`.
- Allowed `modifiers`: `abstract`.
- Allowed `types`: none.

## Examples

### Enforce that all variables, functions and properties follow are camelCase

```json
{
"@typescript-eslint/naming-conventions": [
"error",
{ "selector": "variableLike", "format": ["camelCase"] }
]
}
```

### Enforce that private members are prefixed with an underscore

```json
{
"@typescript-eslint/naming-conventions": [
"error",
{
"selector": "memberLike",
"modifier": ["private"],
"format": ["camelCase"],
"leadingUnderscore": "require"
}
]
}
```

### Enforce that boolean variables are prefixed with an allowed verb

```json
{
"@typescript-eslint/naming-conventions": [
"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

```json
{
"@typescript-eslint/naming-conventions": [
"error",
{
"selector": "variable",
"format": ["camelCase", "UPPER_CASE"]
}
]
}
```

### Enforce that type parameters (generics) are prefixed with `T`

```json
{
"@typescript-eslint/naming-conventions": [
"error",
{
"selector": "typeParameter",
"format": ["PascalCase"],
"prefix": ["T"]
}
]
}
```

### Enforce the codebase follows ESLint's `camelcase` conventions

```json
{
"@typescript-eslint/naming-conventions": [
"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.