From c8a6d8096080228b6d122c861fe140ac97f17cbe Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 22 Jan 2023 14:34:45 +0800 Subject: [PATCH] feat(eslint-plugin): [naming-convention] add support for `#private` modifier 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 --- .../docs/rules/naming-convention.md | 13 ++- .../rules/naming-convention-utils/enums.ts | 27 ++--- .../rules/naming-convention-utils/schema.ts | 5 + .../src/rules/naming-convention.ts | 4 +- .../naming-convention.test.ts | 107 ++++++++++++++++++ 5 files changed, 136 insertions(+), 20 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/naming-convention.md b/packages/eslint-plugin/docs/rules/naming-convention.md index f5922ba3653..ab0405592c4 100644 --- a/packages/eslint-plugin/docs/rules/naming-convention.md +++ b/packages/eslint-plugin/docs/rules/naming-convention.md @@ -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: @@ -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. @@ -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`. @@ -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`. @@ -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 diff --git a/packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts b/packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts index bc2547ffa3f..02900ab1d8b 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts @@ -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; diff --git a/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts b/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts index 179b6dd7f43..ff91d2e156d 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts @@ -190,6 +190,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = { ...selectorSchema('memberLike', false, [ 'abstract', 'private', + '#private', 'protected', 'public', 'readonly', @@ -201,6 +202,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = { ...selectorSchema('classProperty', true, [ 'abstract', 'private', + '#private', 'protected', 'public', 'readonly', @@ -226,6 +228,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = { ...selectorSchema('property', true, [ 'abstract', 'private', + '#private', 'protected', 'public', 'readonly', @@ -238,6 +241,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = { ...selectorSchema('classMethod', false, [ 'abstract', 'private', + '#private', 'protected', 'public', 'requiresQuotes', @@ -254,6 +258,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = { ...selectorSchema('method', false, [ 'abstract', 'private', + '#private', 'protected', 'public', 'requiresQuotes', diff --git a/packages/eslint-plugin/src/rules/naming-convention.ts b/packages/eslint-plugin/src/rules/naming-convention.ts index 05f3dd0f2a6..3b500b0b347 100644 --- a/packages/eslint-plugin/src/rules/naming-convention.ts +++ b/packages/eslint-plugin/src/rules/naming-convention.ts @@ -127,7 +127,9 @@ export default util.createRule({ | TSESTree.TSParameterProperty, ): Set { const modifiers = new Set(); - 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); diff --git a/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts b/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts index 9917aec3cb7..b55ce321f00 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts @@ -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: [ { @@ -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', + }, + }, + ], + }, ], });