Skip to content

Commit

Permalink
feat(eslint-plugin): [naming-convention] split property and `method…
Browse files Browse the repository at this point in the history
…` selectors into more granular `classXXX`, `objectLiteralXXX`, `typeXXX` (#2807)

Fixes #1477
Closes #2802

This allows users to target different types of properties differently.

Adds the following selectors (self explanatory - just breaking the selectors up):

- `classProperty`
- `objectLiteralProperty`
- `typeProperty`
- `classMethod`
- `objectLiteralMethod`
- `typeMethod`

For backwards compatibility, also converts
- `property` to a meta selector for `classProperty`, `objectLiteralProperty`, `typeProperty`
- `method` to a meta selector for `classMethod`, `objectLiteralMethod`, `typeMethod`
  • Loading branch information
bradzacher committed Nov 24, 2020
1 parent a8227a6 commit 665b6d4
Show file tree
Hide file tree
Showing 3 changed files with 361 additions and 90 deletions.
40 changes: 32 additions & 8 deletions packages/eslint-plugin/docs/rules/naming-convention.md
Expand Up @@ -154,21 +154,27 @@ If these are provided, the identifier must start with one of the provided values

### Selector Options

- `selector` (see "Allowed Selectors, Modifiers and Types" below).
- `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](#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`).
- `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.
- `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 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.

Expand Down Expand Up @@ -202,13 +208,25 @@ Individual Selectors match specific, well-defined sets. There is no overlap betw
- `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.
- `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`.
- `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.
- `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.
Expand Down Expand Up @@ -249,6 +267,12 @@ Group Selectors are provided for convenience, and essentially bundle up sets of
- `typeLike` - matches the same as `class`, `interface`, `typeAlias`, `enum`, `typeParameter`.
- Allowed `modifiers`: `abstract`.
- 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

Expand Down
149 changes: 108 additions & 41 deletions packages/eslint-plugin/src/rules/naming-convention.ts
Expand Up @@ -41,21 +41,24 @@ enum Selectors {
parameter = 1 << 2,

// memberLike
property = 1 << 3,
parameterProperty = 1 << 4,
method = 1 << 5,
accessor = 1 << 6,
enumMember = 1 << 7,
parameterProperty = 1 << 3,
accessor = 1 << 4,
enumMember = 1 << 5,
classMethod = 1 << 6,
objectLiteralMethod = 1 << 7,
typeMethod = 1 << 8,
classProperty = 1 << 9,
objectLiteralProperty = 1 << 10,
typeProperty = 1 << 11,

// typeLike
class = 1 << 8,
interface = 1 << 9,
typeAlias = 1 << 10,
enum = 1 << 11,
typeParameter = 1 << 12,
class = 1 << 12,
interface = 1 << 13,
typeAlias = 1 << 14,
enum = 1 << 15,
typeParameter = 1 << 17,
}
type SelectorsString = keyof typeof Selectors;
const SELECTOR_COUNT = util.getEnumNames(Selectors).length;

enum MetaSelectors {
default = -1,
Expand All @@ -64,17 +67,29 @@ enum MetaSelectors {
Selectors.function |
Selectors.parameter,
memberLike = 0 |
Selectors.property |
Selectors.classProperty |
Selectors.objectLiteralProperty |
Selectors.typeProperty |
Selectors.parameterProperty |
Selectors.enumMember |
Selectors.method |
Selectors.classMethod |
Selectors.objectLiteralMethod |
Selectors.typeMethod |
Selectors.accessor,
typeLike = 0 |
Selectors.class |
Selectors.interface |
Selectors.typeAlias |
Selectors.enum |
Selectors.typeParameter,
method = 0 |
Selectors.classMethod |
Selectors.objectLiteralMethod |
Selectors.typeProperty,
property = 0 |
Selectors.classProperty |
Selectors.objectLiteralProperty |
Selectors.typeMethod,
}
type MetaSelectorsString = keyof typeof MetaSelectors;
type IndividualAndMetaSelectorsString = SelectorsString | MetaSelectorsString;
Expand Down Expand Up @@ -321,7 +336,23 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
'readonly',
'abstract',
]),
...selectorSchema('property', true, [
...selectorSchema('classProperty', true, [
'private',
'protected',
'public',
'static',
'readonly',
'abstract',
]),
...selectorSchema('objectLiteralProperty', true, [
'private',
'protected',
'public',
'static',
'readonly',
'abstract',
]),
...selectorSchema('typeProperty', true, [
'private',
'protected',
'public',
Expand All @@ -335,6 +366,36 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
'public',
'readonly',
]),
...selectorSchema('property', true, [
'private',
'protected',
'public',
'static',
'readonly',
'abstract',
]),

...selectorSchema('classMethod', false, [
'private',
'protected',
'public',
'static',
'abstract',
]),
...selectorSchema('objectLiteralMethod', false, [
'private',
'protected',
'public',
'static',
'abstract',
]),
...selectorSchema('typeMethod', false, [
'private',
'protected',
'public',
'static',
'abstract',
]),
...selectorSchema('method', false, [
'private',
'protected',
Expand Down Expand Up @@ -584,7 +645,7 @@ export default util.createRule<Options, MessageIds>({
node: TSESTree.PropertyNonComputedName,
): void {
const modifiers = new Set<Modifiers>([Modifiers.public]);
handleMember(validators.property, node, modifiers);
handleMember(validators.objectLiteralProperty, node, modifiers);
},

':matches(ClassProperty, TSAbstractClassProperty)[computed = false][value.type != "ArrowFunctionExpression"][value.type != "FunctionExpression"][value.type != "TSEmptyBodyFunctionExpression"]'(
Expand All @@ -593,7 +654,7 @@ export default util.createRule<Options, MessageIds>({
| TSESTree.TSAbstractClassPropertyNonComputedName,
): void {
const modifiers = getMemberModifiers(node);
handleMember(validators.property, node, modifiers);
handleMember(validators.classProperty, node, modifiers);
},

'TSPropertySignature[computed = false]'(
Expand All @@ -604,7 +665,7 @@ export default util.createRule<Options, MessageIds>({
modifiers.add(Modifiers.readonly);
}

handleMember(validators.property, node, modifiers);
handleMember(validators.typeProperty, node, modifiers);
},

// #endregion property
Expand All @@ -615,14 +676,13 @@ export default util.createRule<Options, MessageIds>({
'Property[computed = false][kind = "init"][value.type = "ArrowFunctionExpression"]',
'Property[computed = false][kind = "init"][value.type = "FunctionExpression"]',
'Property[computed = false][kind = "init"][value.type = "TSEmptyBodyFunctionExpression"]',
'TSMethodSignature[computed = false]',
].join(', ')](
node:
| TSESTree.PropertyNonComputedName
| TSESTree.TSMethodSignatureNonComputedName,
): void {
const modifiers = new Set<Modifiers>([Modifiers.public]);
handleMember(validators.method, node, modifiers);
handleMember(validators.objectLiteralMethod, node, modifiers);
},

[[
Expand All @@ -638,7 +698,14 @@ export default util.createRule<Options, MessageIds>({
| TSESTree.TSAbstractMethodDefinitionNonComputedName,
): void {
const modifiers = getMemberModifiers(node);
handleMember(validators.method, node, modifiers);
handleMember(validators.classMethod, node, modifiers);
},

'TSMethodSignature[computed = false]'(
node: TSESTree.TSMethodSignatureNonComputedName,
): void {
const modifiers = new Set<Modifiers>([Modifiers.public]);
handleMember(validators.typeMethod, node, modifiers);
},

// #endregion method
Expand Down Expand Up @@ -851,21 +918,20 @@ function createValidator(
return b.modifierWeight - a.modifierWeight;
}

/*
meta selectors will always be larger numbers than the normal selectors they contain, as they are the sum of all
of the selectors that they contain.
to give normal selectors a higher priority, shift them all SELECTOR_COUNT bits to the left before comparison, so
they are instead always guaranteed to be larger than the meta selectors.
*/
const aSelector = isMetaSelector(a.selector)
? a.selector
: a.selector << SELECTOR_COUNT;
const bSelector = isMetaSelector(b.selector)
? b.selector
: b.selector << SELECTOR_COUNT;
const aIsMeta = isMetaSelector(a.selector);
const bIsMeta = isMetaSelector(b.selector);

// non-meta selectors should go ahead of meta selectors
if (aIsMeta && !bIsMeta) {
return 1;
}
if (!aIsMeta && bIsMeta) {
return -1;
}

// both aren't meta selectors
// sort descending - the meta selectors are "least important"
return bSelector - aSelector;
return b.selector - a.selector;
});

return (
Expand Down Expand Up @@ -1314,21 +1380,22 @@ function normalizeOption(option: Selector): NormalizedSelector[] {
? option.selector
: [option.selector];

const selectorsAllowedToHaveTypes: (Selectors | MetaSelectors)[] = [
Selectors.variable,
Selectors.parameter,
Selectors.property,
Selectors.parameterProperty,
Selectors.accessor,
];
const selectorsAllowedToHaveTypes =
Selectors.variable |
Selectors.parameter |
Selectors.classProperty |
Selectors.objectLiteralProperty |
Selectors.typeProperty |
Selectors.parameterProperty |
Selectors.accessor;

const config: NormalizedSelector[] = [];
selectors
.map(selector =>
isMetaSelector(selector) ? MetaSelectors[selector] : Selectors[selector],
)
.forEach(selector =>
selectorsAllowedToHaveTypes.includes(selector)
(selectorsAllowedToHaveTypes & selector) !== 0
? config.push({ selector: selector, ...normalizedOption })
: config.push({
selector: selector,
Expand Down

0 comments on commit 665b6d4

Please sign in to comment.