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): [naming-convention] split property and method selectors into more granular classXXX, objectLiteralXXX, typeXXX #2807

Merged
merged 1 commit into from Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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