diff --git a/packages/eslint-plugin/docs/rules/naming-convention.md b/packages/eslint-plugin/docs/rules/naming-convention.md index ba5a59adda9..f5198ecd88c 100644 --- a/packages/eslint-plugin/docs/rules/naming-convention.md +++ b/packages/eslint-plugin/docs/rules/naming-convention.md @@ -200,7 +200,10 @@ There are two types of selectors, individual selectors, and grouped selectors. Individual Selectors match specific, well-defined sets. There is no overlap between each of the individual selectors. -- `accessor` - matches any accessor. +- `classicAccessor` - matches any accessor. It refers to the methods attached to `get` and `set` syntax. + - Allowed `modifiers`: `abstract`, `override`, `private`, `protected`, `public`, `requiresQuotes`, `static`. + - Allowed `types`: `array`, `boolean`, `function`, `number`, `string`. +- `autoAccessor` - matches any auto-accessor. An auto-accessor is just a class field starting with an `accessor` keyword. - Allowed `modifiers`: `abstract`, `override`, `private`, `protected`, `public`, `requiresQuotes`, `static`. - Allowed `types`: `array`, `boolean`, `function`, `number`, `string`. - `class` - matches any class declaration. @@ -262,7 +265,10 @@ Group Selectors are provided for convenience, and essentially bundle up sets of - `default` - matches everything. - Allowed `modifiers`: all modifiers. - Allowed `types`: none. -- `memberLike` - matches the same as `accessor`, `enumMember`, `method`, `parameterProperty`, `property`. +- `accessor` - matches the same as `classicAccessor` and `autoAccessor`. + - Allowed `modifiers`: `abstract`, `override`, `private`, `protected`, `public`, `requiresQuotes`, `static`. + - Allowed `types`: `array`, `boolean`, `function`, `number`, `string`. +- `memberLike` - matches the same as `classicAccessor`, `autoAccessor`, `enumMember`, `method`, `parameterProperty`, `property`. - Allowed `modifiers`: `abstract`, `async`, `override`, `#private`, `private`, `protected`, `public`, `readonly`, `requiresQuotes`, `static`. - Allowed `types`: none. - `method` - matches the same as `classMethod`, `objectLiteralMethod`, `typeMethod`. 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 7b96072291d..c30029882e1 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts @@ -28,7 +28,7 @@ enum Selectors { // memberLike parameterProperty = 1 << 3, - accessor = 1 << 4, + classicAccessor = 1 << 4, enumMember = 1 << 5, classMethod = 1 << 6, objectLiteralMethod = 1 << 7, @@ -36,12 +36,13 @@ enum Selectors { classProperty = 1 << 9, objectLiteralProperty = 1 << 10, typeProperty = 1 << 11, + autoAccessor = 1 << 12, // typeLike - class = 1 << 12, - interface = 1 << 13, - typeAlias = 1 << 14, - enum = 1 << 15, + class = 1 << 13, + interface = 1 << 14, + typeAlias = 1 << 15, + enum = 1 << 16, typeParameter = 1 << 17, // other @@ -65,7 +66,8 @@ enum MetaSelectors { Selectors.classMethod | Selectors.objectLiteralMethod | Selectors.typeMethod | - Selectors.accessor, + Selectors.classicAccessor | + Selectors.autoAccessor, typeLike = 0 | Selectors.class | Selectors.interface | @@ -80,6 +82,7 @@ enum MetaSelectors { Selectors.classProperty | Selectors.objectLiteralProperty | Selectors.typeProperty, + accessor = 0 | Selectors.classicAccessor | Selectors.autoAccessor, /* eslint-enable @typescript-eslint/prefer-literal-enum-member */ } type MetaSelectorsString = keyof typeof MetaSelectors; 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 d963f3101c5..91e5bfec36f 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts @@ -290,6 +290,24 @@ const SCHEMA: JSONSchema.JSONSchema4 = { 'override', 'async', ]), + ...selectorSchema('classicAccessor', true, [ + 'abstract', + 'private', + 'protected', + 'public', + 'requiresQuotes', + 'static', + 'override', + ]), + ...selectorSchema('autoAccessor', true, [ + 'abstract', + 'private', + 'protected', + 'public', + 'requiresQuotes', + 'static', + 'override', + ]), ...selectorSchema('accessor', true, [ 'abstract', 'private', diff --git a/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts b/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts index b0715c2f8aa..f656769b1b6 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts @@ -419,7 +419,7 @@ const SelectorsAllowedToHaveTypes = Selectors.objectLiteralProperty | Selectors.typeProperty | Selectors.parameterProperty | - Selectors.accessor; + Selectors.classicAccessor; function isCorrectType( node: TSESTree.Node, diff --git a/packages/eslint-plugin/src/rules/naming-convention.ts b/packages/eslint-plugin/src/rules/naming-convention.ts index 2ffbf7b665b..6ef079b8d65 100644 --- a/packages/eslint-plugin/src/rules/naming-convention.ts +++ b/packages/eslint-plugin/src/rules/naming-convention.ts @@ -110,7 +110,8 @@ export default createRule({ | TSESTree.TSAbstractMethodDefinitionNonComputedName | TSESTree.TSAbstractPropertyDefinitionNonComputedName | TSESTree.TSMethodSignatureNonComputedName - | TSESTree.TSPropertySignatureNonComputedName, + | TSESTree.TSPropertySignatureNonComputedName + | TSESTree.AccessorPropertyNonComputedName, modifiers: Set, ): void { const key = node.key; @@ -127,7 +128,9 @@ export default createRule({ | TSESTree.PropertyDefinition | TSESTree.TSAbstractMethodDefinition | TSESTree.TSAbstractPropertyDefinition - | TSESTree.TSParameterProperty, + | TSESTree.TSParameterProperty + | TSESTree.AccessorProperty + | TSESTree.TSAbstractAccessorProperty, ): Set { const modifiers = new Set(); if ('key' in node && node.key.type === AST_NODE_TYPES.PrivateIdentifier) { @@ -148,7 +151,8 @@ export default createRule({ } if ( node.type === AST_NODE_TYPES.TSAbstractPropertyDefinition || - node.type === AST_NODE_TYPES.TSAbstractMethodDefinition + node.type === AST_NODE_TYPES.TSAbstractMethodDefinition || + node.type === AST_NODE_TYPES.TSAbstractAccessorProperty ) { modifiers.add(Modifiers.abstract); } @@ -519,27 +523,47 @@ export default createRule({ // #region accessor 'Property[computed = false]:matches([kind = "get"], [kind = "set"])': { - validator: validators.accessor, + validator: validators.classicAccessor, handler: (node: TSESTree.PropertyNonComputedName, validator): void => { const modifiers = new Set([Modifiers.public]); handleMember(validator, node, modifiers); }, }, - 'MethodDefinition[computed = false]:matches([kind = "get"], [kind = "set"])': - { - validator: validators.accessor, - handler: ( - node: TSESTree.MethodDefinitionNonComputedName, - validator, - ): void => { - const modifiers = getMemberModifiers(node); - handleMember(validator, node, modifiers); - }, + [[ + 'MethodDefinition[computed = false]:matches([kind = "get"], [kind = "set"])', + 'TSAbstractMethodDefinition[computed = false]:matches([kind="get"], [kind="set"])', + ].join(', ')]: { + validator: validators.classicAccessor, + handler: ( + node: TSESTree.MethodDefinitionNonComputedName, + validator, + ): void => { + const modifiers = getMemberModifiers(node); + handleMember(validator, node, modifiers); }, + }, // #endregion accessor + // #region autoAccessor + + [[ + AST_NODE_TYPES.AccessorProperty, + AST_NODE_TYPES.TSAbstractAccessorProperty, + ].join(', ')]: { + validator: validators.autoAccessor, + handler: ( + node: TSESTree.AccessorPropertyNonComputedName, + validator, + ): void => { + const modifiers = getMemberModifiers(node); + handleMember(validator, node, modifiers); + }, + }, + + // #endregion autoAccessor + // #region enumMember // computed is optional, so can't do [computed = false] diff --git a/packages/eslint-plugin/tests/rules/naming-convention/cases/accessor.test.ts b/packages/eslint-plugin/tests/rules/naming-convention/cases/accessor.test.ts index a2f9ff06a21..8b7cacbcc57 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention/cases/accessor.test.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention/cases/accessor.test.ts @@ -3,6 +3,17 @@ import { createTestCases } from './createTestCases'; createTestCases([ { code: [ + 'class Ignored { accessor % = 10; }', + 'class Ignored { accessor #% = 10; }', + 'class Ignored { static accessor % = 10; }', + 'class Ignored { static accessor #% = 10; }', + 'class Ignored { private accessor % = 10; }', + 'class Ignored { private static accessor % = 10; }', + 'class Ignored { override accessor % = 10; }', + 'class Ignored { accessor "%" = 10; }', + 'class Ignored { protected accessor % = 10; }', + 'class Ignored { public accessor % = 10; }', + 'class Ignored { abstract accessor %; }', 'const ignored = { get %() {} };', 'const ignored = { set "%"(ignored) {} };', 'class Ignored { private get %() {} }', diff --git a/packages/eslint-plugin/tests/rules/naming-convention/cases/autoAccessor.test.ts b/packages/eslint-plugin/tests/rules/naming-convention/cases/autoAccessor.test.ts new file mode 100644 index 00000000000..f82e7c7cbce --- /dev/null +++ b/packages/eslint-plugin/tests/rules/naming-convention/cases/autoAccessor.test.ts @@ -0,0 +1,22 @@ +import { createTestCases } from './createTestCases'; + +createTestCases([ + { + code: [ + 'class Ignored { accessor % = 10; }', + 'class Ignored { accessor #% = 10; }', + 'class Ignored { static accessor % = 10; }', + 'class Ignored { static accessor #% = 10; }', + 'class Ignored { private accessor % = 10; }', + 'class Ignored { private static accessor % = 10; }', + 'class Ignored { override accessor % = 10; }', + 'class Ignored { accessor "%" = 10; }', + 'class Ignored { protected accessor % = 10; }', + 'class Ignored { public accessor % = 10; }', + 'class Ignored { abstract accessor %; }', + ], + options: { + selector: 'autoAccessor', + }, + }, +]); diff --git a/packages/eslint-plugin/tests/rules/naming-convention/cases/classicAccessor.test.ts b/packages/eslint-plugin/tests/rules/naming-convention/cases/classicAccessor.test.ts new file mode 100644 index 00000000000..4b03e8ac0bc --- /dev/null +++ b/packages/eslint-plugin/tests/rules/naming-convention/cases/classicAccessor.test.ts @@ -0,0 +1,19 @@ +import { createTestCases } from './createTestCases'; + +createTestCases([ + { + code: [ + 'const ignored = { get %() {} };', + 'const ignored = { set "%"(ignored) {} };', + 'class Ignored { private get %() {} }', + 'class Ignored { private set "%"(ignored) {} }', + 'class Ignored { private static get %() {} }', + 'class Ignored { static get #%() {} }', + 'abstract class Ignored { abstract get %(): number }', + 'abstract class Ignored { abstract set %(ignored: number) }', + ], + options: { + selector: 'classicAccessor', + }, + }, +]); diff --git a/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts b/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts index 7f7f0efcd49..327bc77ab71 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts @@ -291,7 +291,8 @@ export function createTestCases(cases: Cases): void { selector !== 'memberLike' && selector !== 'typeLike' && selector !== 'property' && - selector !== 'method' + selector !== 'method' && + selector !== 'accessor' ? { data: { type: selectorTypeToMessageString(selector), 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 6150f9476f3..85fa8450ad2 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 @@ -2088,7 +2088,7 @@ ruleTester.run('naming-convention', rule, { { messageId: 'doesNotMatchFormat', data: { - type: 'Accessor', + type: 'Classic Accessor', name: 'someGetterOverride', formats: 'snake_case', }, @@ -2096,7 +2096,7 @@ ruleTester.run('naming-convention', rule, { { messageId: 'doesNotMatchFormat', data: { - type: 'Accessor', + type: 'Classic Accessor', name: 'someSetterOverride', formats: 'snake_case', }, diff --git a/packages/eslint-plugin/tests/schema-snapshots/naming-convention.shot b/packages/eslint-plugin/tests/schema-snapshots/naming-convention.shot index 668f54f1a3b..80a4f0e19e1 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/naming-convention.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/naming-convention.shot @@ -132,9 +132,11 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "items": { "enum": [ "accessor", + "autoAccessor", "class", "classMethod", "classProperty", + "classicAccessor", "default", "enum", "enumMember", @@ -1083,6 +1085,140 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "required": ["selector", "format"], "type": "object" }, + { + "additionalProperties": false, + "description": "Selector 'classicAccessor'", + "properties": { + "custom": { + "$ref": "#/$defs/matchRegexConfig" + }, + "failureMessage": { + "type": "string" + }, + "filter": { + "oneOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "$ref": "#/$defs/matchRegexConfig" + } + ] + }, + "format": { + "$ref": "#/$defs/formatOptionsConfig" + }, + "leadingUnderscore": { + "$ref": "#/$defs/underscoreOptions" + }, + "modifiers": { + "additionalItems": false, + "items": { + "enum": [ + "abstract", + "override", + "private", + "protected", + "public", + "requiresQuotes", + "static" + ], + "type": "string" + }, + "type": "array" + }, + "prefix": { + "$ref": "#/$defs/prefixSuffixConfig" + }, + "selector": { + "enum": ["classicAccessor"], + "type": "string" + }, + "suffix": { + "$ref": "#/$defs/prefixSuffixConfig" + }, + "trailingUnderscore": { + "$ref": "#/$defs/underscoreOptions" + }, + "types": { + "additionalItems": false, + "items": { + "$ref": "#/$defs/typeModifiers" + }, + "type": "array" + } + }, + "required": ["selector", "format"], + "type": "object" + }, + { + "additionalProperties": false, + "description": "Selector 'autoAccessor'", + "properties": { + "custom": { + "$ref": "#/$defs/matchRegexConfig" + }, + "failureMessage": { + "type": "string" + }, + "filter": { + "oneOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "$ref": "#/$defs/matchRegexConfig" + } + ] + }, + "format": { + "$ref": "#/$defs/formatOptionsConfig" + }, + "leadingUnderscore": { + "$ref": "#/$defs/underscoreOptions" + }, + "modifiers": { + "additionalItems": false, + "items": { + "enum": [ + "abstract", + "override", + "private", + "protected", + "public", + "requiresQuotes", + "static" + ], + "type": "string" + }, + "type": "array" + }, + "prefix": { + "$ref": "#/$defs/prefixSuffixConfig" + }, + "selector": { + "enum": ["autoAccessor"], + "type": "string" + }, + "suffix": { + "$ref": "#/$defs/prefixSuffixConfig" + }, + "trailingUnderscore": { + "$ref": "#/$defs/underscoreOptions" + }, + "types": { + "additionalItems": false, + "items": { + "$ref": "#/$defs/typeModifiers" + }, + "type": "array" + } + }, + "required": ["selector", "format"], + "type": "object" + }, { "additionalProperties": false, "description": "Selector 'accessor'", @@ -1630,9 +1766,11 @@ type Options = /** Multiple selectors in one config */ prefix?: PrefixSuffixConfig; selector: ( | 'accessor' + | 'autoAccessor' | 'class' | 'classMethod' | 'classProperty' + | 'classicAccessor' | 'default' | 'enum' | 'enumMember' @@ -1680,6 +1818,28 @@ type Options = /** Multiple selectors in one config */ trailingUnderscore?: UnderscoreOptions; types?: TypeModifiers[]; } + /** Selector 'autoAccessor' */ + | { + custom?: MatchRegexConfig; + failureMessage?: string; + filter?: MatchRegexConfig | string; + format: FormatOptionsConfig; + leadingUnderscore?: UnderscoreOptions; + modifiers?: ( + | 'abstract' + | 'override' + | 'private' + | 'protected' + | 'public' + | 'requiresQuotes' + | 'static' + )[]; + prefix?: PrefixSuffixConfig; + selector: 'autoAccessor'; + suffix?: PrefixSuffixConfig; + trailingUnderscore?: UnderscoreOptions; + types?: TypeModifiers[]; + } /** Selector 'class' */ | { custom?: MatchRegexConfig; @@ -1740,6 +1900,28 @@ type Options = /** Multiple selectors in one config */ trailingUnderscore?: UnderscoreOptions; types?: TypeModifiers[]; } + /** Selector 'classicAccessor' */ + | { + custom?: MatchRegexConfig; + failureMessage?: string; + filter?: MatchRegexConfig | string; + format: FormatOptionsConfig; + leadingUnderscore?: UnderscoreOptions; + modifiers?: ( + | 'abstract' + | 'override' + | 'private' + | 'protected' + | 'public' + | 'requiresQuotes' + | 'static' + )[]; + prefix?: PrefixSuffixConfig; + selector: 'classicAccessor'; + suffix?: PrefixSuffixConfig; + trailingUnderscore?: UnderscoreOptions; + types?: TypeModifiers[]; + } /** Selector 'default' */ | { custom?: MatchRegexConfig;