Skip to content

Latest commit

 

History

History
528 lines (438 loc) · 20 KB

naming-convention.md

File metadata and controls

528 lines (438 loc) · 20 KB

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. When the format of an identifier is checked, it is checked in the following order:

  1. validate leading underscore
  2. validate trailing underscore
  3. validate prefix
  4. validate suffix
  5. validate custom
  6. validate format

For steps 1-4, 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
  2. validate leading underscore - pass
    • Trim leading underscore - name = IMyInterface
  3. validate trailing underscore - no check
  4. validate prefix - pass
    • Trim prefix - name = MyInterface
  5. validate suffix - no check
  6. validate custom - no check
  7. 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 formats. 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.

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 - accepts a regular expression (anything accepted into new RegExp(regex)).
  • match - true if the identifier must match the regex, false if the identifier must not match the regex.

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 - accepts a regular expression (anything accepted into new RegExp(regex)).
  • match - true if the identifier must match the regex, false if the identifier must not match the regex.

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 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.
    • The following modifiers are allowed:
      • const - matches a variable declared as being const (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}).
      • 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.
      • public - matches any member that is either explicitly declared as public, 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 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:

[
  /* 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.

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: const, destructured, global, exported, unused.
    • Allowed types: boolean, string, number, function, array.
  • function - matches any named function declaration or named function expression.
    • Allowed modifiers: global, exported, unused.
    • Allowed types: none.
  • parameter - matches any function parameter. Does not match parameter properties.
    • Allowed modifiers: unused.
    • Allowed types: boolean, string, number, function, array.
  • classProperty - matches any class 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.
  • objectLiteralProperty - matches any object literal 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.
  • typeProperty - matches any 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.
  • classMethod - matches any class 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.
  • 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: private, protected, public, static, readonly, abstract.
    • Allowed types: none.
  • 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: 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, exported, unused.
    • Allowed types: none.
  • interface - matches any interface declaration.
    • Allowed modifiers: exported, unused.
    • Allowed types: none.
  • typeAlias - matches any type alias declaration.
    • Allowed modifiers: exported, unused.
    • Allowed types: none.
  • enum - matches any enum declaration.
    • Allowed modifiers: exported, unused.
    • Allowed types: none.
  • typeParameter - matches any generic type parameter declaration.
    • Allowed modifiers: unused.
    • 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: unused.
    • 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, unused.
    • Allowed types: none.
  • property - matches the same as classProperty, objectLiteralProperty, typeProperty.
    • Allowed modifiers: private, protected, public, static, readonly, abstract.
    • Allowed types: boolean, string, number, function, array.
  • method - matches the same as classMethod, objectLiteralMethod, typeMethod.
    • Allowed modifiers: private, protected, public, static, readonly, abstract.
    • Allowed types: none.

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 can use the filter option in one of two ways:

You can use the filter option to ignore 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 that require quoting:

{
  "@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
      }
    }
  ]
}

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.