Skip to content

Commit

Permalink
feat(eslint-plugin): [naming-convention] add support for #private m…
Browse files Browse the repository at this point in the history
…odifier on class members (#6259)

* Add `#private` as a modifier

Numbers for TypeModifiers are changed as instructed by the comment in the Modifiers enum

* Add support for `#private` modifier

* Add tests

* Add docs

Co-authored-by: Josh Goldberg <git@joshuakgoldberg.com>
  • Loading branch information
hlysine and JoshuaKGoldberg committed Jan 22, 2023
1 parent d64a9b9 commit c8a6d80
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 20 deletions.
13 changes: 7 additions & 6 deletions packages/eslint-plugin/docs/rules/naming-convention.md
Expand Up @@ -159,7 +159,7 @@ If these are provided, the identifier must start with one of the provided values
- Accepts one or array of selectors to define an option block that applies to one or multiple selectors.
- For example, if you provide `{ selector: ['function', 'variable'] }`, 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`/`protected`/`public`), or if the thing is `static`, etc.
- `modifiers` allows you to specify which modifiers to granularly apply to, such as the accessibility (`#private`/`private`/`protected`/`public`), or if the thing is `static`, etc.
- The name must match _all_ of the modifiers.
- For example, if you provide `{ modifiers: ['private','readonly','static'] }`, 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:
Expand All @@ -172,6 +172,7 @@ If these are provided, the identifier must start with one of the provided values
- `requiresQuotes` - matches any name that requires quotes as it is not a valid identifier (i.e. has a space, a dash, etc in it).
- `public` - matches any member that is either explicitly declared as `public`, or has no visibility modifier (i.e. implicitly public).
- `abstract`,`override`,`private`,`protected`,`readonly`,`static` - matches any member explicitly declared with the given modifier.
- `#private` - matches any member with a private identifier (an identifier that starts with `#`)
- `async` - matches any method, function, or function variable which is async via the `async` keyword (e.g. does not match functions that return promises without using `async` keyword)
- `types` allows you to specify which types to match. This option supports simple, primitive types only (`array`,`boolean`,`function`,`number`,`string`).
- The name must match _one_ of the types.
Expand Down Expand Up @@ -204,7 +205,7 @@ Individual Selectors match specific, well-defined sets. There is no overlap betw
- Allowed `modifiers`: `destructured`, `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`: `abstract`, `override`, `private`, `protected`, `public`, `readonly`, `requiresQuotes`, `static`.
- Allowed `modifiers`: `abstract`, `override`, `#private`, `private`, `protected`, `public`, `readonly`, `requiresQuotes`, `static`.
- Allowed `types`: `array`, `boolean`, `function`, `number`, `string`.
- `objectLiteralProperty` - matches any object literal property. Does not match properties that have direct function expression or arrow function expression values.
- Allowed `modifiers`: `public`, `requiresQuotes`.
Expand All @@ -216,7 +217,7 @@ Individual Selectors match specific, well-defined sets. There is no overlap betw
- Allowed `modifiers`: `private`, `protected`, `public`, `readonly`.
- Allowed `types`: `array`, `boolean`, `function`, `number`, `string`.
- `classMethod` - matches any class method. Also matches properties that have direct function expression or arrow function expression values. Does not match accessors.
- Allowed `modifiers`: `abstract`, `async`, `override`, `private`, `protected`, `public`, `requiresQuotes`, `static`.
- Allowed `modifiers`: `abstract`, `async`, `override`, `#private`, `private`, `protected`, `public`, `requiresQuotes`, `static`.
- 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`: `async`, `public`, `requiresQuotes`.
Expand Down Expand Up @@ -257,16 +258,16 @@ Group Selectors are provided for convenience, and essentially bundle up sets of
- Allowed `modifiers`: `async`, `unused`.
- Allowed `types`: none.
- `memberLike` - matches the same as `accessor`, `enumMember`, `method`, `parameterProperty`, `property`.
- Allowed `modifiers`: `abstract`, `async`, `override`, `private`, `protected`, `public`, `readonly`, `requiresQuotes`, `static`.
- Allowed `modifiers`: `abstract`, `async`, `override`, `#private`, `private`, `protected`, `public`, `readonly`, `requiresQuotes`, `static`.
- Allowed `types`: none.
- `typeLike` - matches the same as `class`, `enum`, `interface`, `typeAlias`, `typeParameter`.
- Allowed `modifiers`: `abstract`, `unused`.
- Allowed `types`: none.
- `property` - matches the same as `classProperty`, `objectLiteralProperty`, `typeProperty`.
- Allowed `modifiers`: `abstract`, `async`, `override`, `private`, `protected`, `public`, `readonly`, `requiresQuotes`, `static`.
- Allowed `modifiers`: `abstract`, `async`, `override`, `#private`, `private`, `protected`, `public`, `readonly`, `requiresQuotes`, `static`.
- Allowed `types`: `array`, `boolean`, `function`, `number`, `string`.
- `method` - matches the same as `classMethod`, `objectLiteralMethod`, `typeMethod`.
- Allowed `modifiers`: `abstract`, `async`, `override`, `private`, `protected`, `public`, `readonly`, `requiresQuotes`, `static`.
- Allowed `modifiers`: `abstract`, `async`, `override`, `#private`, `private`, `protected`, `public`, `readonly`, `requiresQuotes`, `static`.
- Allowed `types`: none.

## FAQ
Expand Down
27 changes: 14 additions & 13 deletions packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts
Expand Up @@ -91,32 +91,33 @@ enum Modifiers {
public = 1 << 3,
protected = 1 << 4,
private = 1 << 5,
abstract = 1 << 6,
'#private' = 1 << 6,
abstract = 1 << 7,
// destructured variable
destructured = 1 << 7,
destructured = 1 << 8,
// variables declared in the top-level scope
global = 1 << 8,
global = 1 << 9,
// things that are exported
exported = 1 << 9,
exported = 1 << 10,
// things that are unused
unused = 1 << 10,
unused = 1 << 11,
// properties that require quoting
requiresQuotes = 1 << 11,
requiresQuotes = 1 << 12,
// class members that are overridden
override = 1 << 12,
override = 1 << 13,
// class methods, object function properties, or functions that are async via the `async` keyword
async = 1 << 13,
async = 1 << 14,

// make sure TypeModifiers starts at Modifiers + 1 or else sorting won't work
}
type ModifiersString = keyof typeof Modifiers;

enum TypeModifiers {
boolean = 1 << 12,
string = 1 << 13,
number = 1 << 14,
function = 1 << 15,
array = 1 << 16,
boolean = 1 << 15,
string = 1 << 16,
number = 1 << 17,
function = 1 << 18,
array = 1 << 19,
}
type TypeModifiersString = keyof typeof TypeModifiers;

Expand Down
Expand Up @@ -190,6 +190,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
...selectorSchema('memberLike', false, [
'abstract',
'private',
'#private',
'protected',
'public',
'readonly',
Expand All @@ -201,6 +202,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
...selectorSchema('classProperty', true, [
'abstract',
'private',
'#private',
'protected',
'public',
'readonly',
Expand All @@ -226,6 +228,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
...selectorSchema('property', true, [
'abstract',
'private',
'#private',
'protected',
'public',
'readonly',
Expand All @@ -238,6 +241,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
...selectorSchema('classMethod', false, [
'abstract',
'private',
'#private',
'protected',
'public',
'requiresQuotes',
Expand All @@ -254,6 +258,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
...selectorSchema('method', false, [
'abstract',
'private',
'#private',
'protected',
'public',
'requiresQuotes',
Expand Down
4 changes: 3 additions & 1 deletion packages/eslint-plugin/src/rules/naming-convention.ts
Expand Up @@ -127,7 +127,9 @@ export default util.createRule<Options, MessageIds>({
| TSESTree.TSParameterProperty,
): Set<Modifiers> {
const modifiers = new Set<Modifiers>();
if (node.accessibility) {
if ('key' in node && node.key.type === AST_NODE_TYPES.PrivateIdentifier) {
modifiers.add(Modifiers['#private']);
} else if (node.accessibility) {
modifiers.add(Modifiers[node.accessibility]);
} else {
modifiers.add(Modifiers.public);
Expand Down
Expand Up @@ -867,6 +867,29 @@ ruleTester.run('naming-convention', rule, {
},
],
},
{
code: `
class foo {
private someAttribute = 1;
#some_attribute = 1;
private someMethod() {}
#some_method() {}
}
`,
parserOptions,
options: [
{
selector: 'memberLike',
format: ['camelCase'],
},
{
selector: ['memberLike'],
modifiers: ['#private'],
format: ['snake_case'],
},
],
},
],
invalid: [
{
Expand Down Expand Up @@ -1972,5 +1995,89 @@ ruleTester.run('naming-convention', rule, {
},
],
},
{
code: `
class foo {
private firstPrivateField = 1;
// ❌ error
private first_private_field = 1;
// ❌ error
#secondPrivateField = 1;
#second_private_field = 1;
}
`,
parserOptions,
options: [
{
selector: 'memberLike',
format: ['camelCase'],
},
{
selector: ['memberLike'],
modifiers: ['#private'],
format: ['snake_case'],
},
],
errors: [
{
messageId: 'doesNotMatchFormat',
data: {
type: 'Class Property',
name: 'first_private_field',
formats: 'camelCase',
},
},
{
messageId: 'doesNotMatchFormat',
data: {
type: 'Class Property',
name: 'secondPrivateField',
formats: 'snake_case',
},
},
],
},
{
code: `
class foo {
private firstPrivateMethod() {}
// ❌ error
private first_private_method() {}
// ❌ error
#secondPrivateMethod() {}
#second_private_method() {}
}
`,
parserOptions,
options: [
{
selector: 'memberLike',
format: ['camelCase'],
},
{
selector: ['memberLike'],
modifiers: ['#private'],
format: ['snake_case'],
},
],
errors: [
{
messageId: 'doesNotMatchFormat',
data: {
type: 'Class Method',
name: 'first_private_method',
formats: 'camelCase',
},
},
{
messageId: 'doesNotMatchFormat',
data: {
type: 'Class Method',
name: 'secondPrivateMethod',
formats: 'snake_case',
},
},
],
},
],
});

0 comments on commit c8a6d80

Please sign in to comment.