diff --git a/packages/eslint-plugin/docs/rules/camelcase.md b/packages/eslint-plugin/docs/rules/camelcase.md index 84c4d9d13b5..5fa0c8e530f 100644 --- a/packages/eslint-plugin/docs/rules/camelcase.md +++ b/packages/eslint-plugin/docs/rules/camelcase.md @@ -30,8 +30,10 @@ variable that will be imported into the local module scope. This rule has an object option: -- `"properties": "always"` (default) enforces camelcase style for property names -- `"properties": "never"` does not check property names +- `"properties": "never"` (default) does not check property names +- `"properties": "always"` enforces camelcase style for property names +- `"genericType": "never"` (default) does not check generic identifiers +- `"genericType": "always"` enforces camelcase style for generic identifiers - `"ignoreDestructuring": false` (default) enforces camelcase style for destructured identifiers - `"ignoreDestructuring": true` does not check destructured identifiers - `allow` (`string[]`) list of properties to accept. Accept regex. @@ -129,6 +131,100 @@ var obj = { }; ``` +### genericType: "always" + +Examples of **incorrect** code for this rule with the default `{ "genericType": "always" }` option: + +```typescript +/* eslint @typescript-eslint/camelcase: ["error", { "genericType": "always" }] */ + +interface Foo {} +function foo() {} +class Foo {} +type Foo = {}; +class Foo { + method() {} +} + +interface Foo {} +function foo() {} +class Foo {} +type Foo = {}; +class Foo { + method() {} +} + +interface Foo {} +function foo() {} +class Foo {} +type Foo = {}; +class Foo { + method() {} +} +``` + +Examples of **correct** code for this rule with the default `{ "genericType": "always" }` option: + +```typescript +/* eslint @typescript-eslint/camelcase: ["error", { "genericType": "always" }] */ + +interface Foo {} +function foo() {} +class Foo {} +type Foo = {}; +class Foo { + method() {} +} + +interface Foo {} +function foo() {} +class Foo {} +type Foo = {}; +class Foo { + method() {} +} + +interface Foo {} +function foo() {} +class Foo {} +type Foo = {}; +class Foo { + method() {} +} +``` + +### genericType: "never" + +Examples of **correct** code for this rule with the `{ "genericType": "never" }` option: + +```typescript +/* eslint @typescript-eslint/camelcase: ["error", { "genericType": "never" }] */ + +interface Foo {} +function foo() {} +class Foo {} +type Foo = {}; +class Foo { + method() {} +} + +interface Foo {} +function foo() {} +class Foo {} +type Foo = {}; +class Foo { + method() {} +} + +interface Foo {} +function foo() {} +class Foo {} +type Foo = {}; +class Foo { + method() {} +} +``` + ### ignoreDestructuring: false Examples of **incorrect** code for this rule with the default `{ "ignoreDestructuring": false }` option: diff --git a/packages/eslint-plugin/src/rules/camelcase.ts b/packages/eslint-plugin/src/rules/camelcase.ts index 0928484b629..a2be8a1c695 100644 --- a/packages/eslint-plugin/src/rules/camelcase.ts +++ b/packages/eslint-plugin/src/rules/camelcase.ts @@ -8,6 +8,19 @@ import * as util from '../util'; type Options = util.InferOptionsTypeFromRule; type MessageIds = util.InferMessageIdsTypeFromRule; +const schema = util.deepMerge( + Array.isArray(baseRule.meta.schema) + ? baseRule.meta.schema[0] + : baseRule.meta.schema, + { + properties: { + genericType: { + enum: ['always', 'never'], + }, + }, + }, +); + export default util.createRule({ name: 'camelcase', meta: { @@ -17,7 +30,7 @@ export default util.createRule({ category: 'Stylistic Issues', recommended: 'error', }, - schema: baseRule.meta.schema, + schema: [schema], messages: baseRule.meta.messages, }, defaultOptions: [ @@ -25,6 +38,7 @@ export default util.createRule({ allow: ['^UNSAFE_'], ignoreDestructuring: false, properties: 'never', + genericType: 'never', }, ], create(context, [options]) { @@ -36,6 +50,7 @@ export default util.createRule({ AST_NODE_TYPES.TSAbstractClassProperty, ]; + const genericType = options.genericType; const properties = options.properties; const allow = (options.allow || []).map(entry => ({ name: entry, @@ -117,6 +132,14 @@ export default util.createRule({ return; } + if (parent && parent.type === AST_NODE_TYPES.TSTypeParameter) { + if (genericType === 'always' && isUnderscored(name)) { + report(node); + } + + return; + } + if (parent && parent.type === AST_NODE_TYPES.OptionalMemberExpression) { // Report underscored object names if ( diff --git a/packages/eslint-plugin/tests/rules/camelcase.test.ts b/packages/eslint-plugin/tests/rules/camelcase.test.ts index d2d3287ce66..617cdf3e40f 100644 --- a/packages/eslint-plugin/tests/rules/camelcase.test.ts +++ b/packages/eslint-plugin/tests/rules/camelcase.test.ts @@ -79,6 +79,100 @@ ruleTester.run('camelcase', rule, { code: 'abstract class Foo { abstract bar: number = 0; }', options: [{ properties: 'always' }], }, + { + code: 'interface Foo {}', + options: [{ genericType: 'never' }], + }, + { + code: 'interface Foo {}', + options: [{ genericType: 'always' }], + }, + { + code: 'interface Foo {}', + options: [{ genericType: 'always' }], + }, + { + code: 'function fn() {}', + options: [{ genericType: 'never' }], + }, + { + code: 'function fn() {}', + options: [{ genericType: 'always' }], + }, + { + code: 'function fn() {}', + options: [{ genericType: 'always' }], + }, + { + code: 'class Foo {}', + options: [{ genericType: 'never' }], + }, + { + code: 'class Foo {}', + options: [{ genericType: 'always' }], + }, + { + code: 'class Foo {}', + options: [{ genericType: 'always' }], + }, + { + code: ` +class Foo { + method() {} +} + `, + options: [{ genericType: 'never' }], + }, + { + code: ` +class Foo { + method() {} +} + `, + options: [{ genericType: 'always' }], + }, + { + code: ` +class Foo { + method() {} +} + `, + options: [{ genericType: 'always' }], + }, + { + code: ` +type Foo = {} + `, + options: [{ genericType: 'always' }], + }, + { + code: ` +type Foo = {} + `, + options: [{ genericType: 'always' }], + }, + { + code: ` +type Foo = {} + `, + options: [{ genericType: 'never' }], + }, + { + code: ` +class Foo { + FOO_method() {} +} + `, + options: [{ allow: ['^FOO'] }], + }, + { + code: ` +class Foo { + method() {} +} + `, + options: [{}], + }, { code: 'const foo = foo?.baz;', }, @@ -238,5 +332,117 @@ ruleTester.run('camelcase', rule, { }, ], }, + { + code: 'interface Foo {}', + options: [{ genericType: 'always' }], + errors: [ + { + messageId: 'notCamelCase', + data: { + name: 't_foo', + }, + line: 1, + column: 15, + }, + ], + }, + { + code: 'function fn() {}', + options: [{ genericType: 'always' }], + errors: [ + { + messageId: 'notCamelCase', + data: { + name: 't_foo', + }, + line: 1, + column: 13, + }, + ], + }, + { + code: 'class Foo {}', + options: [{ genericType: 'always' }], + errors: [ + { + messageId: 'notCamelCase', + data: { + name: 't_foo', + }, + line: 1, + column: 11, + }, + ], + }, + { + code: ` +class Foo { + method() {} +} + `, + options: [{ genericType: 'always' }], + errors: [ + { + messageId: 'notCamelCase', + data: { + name: 't_foo', + }, + line: 3, + column: 10, + }, + ], + }, + { + code: ` +class Foo { + method() {} +} + `, + options: [{ genericType: 'always' }], + errors: [ + { + messageId: 'notCamelCase', + data: { + name: 't_foo', + }, + line: 3, + column: 10, + }, + { + messageId: 'notCamelCase', + data: { + name: 't_bar', + }, + line: 3, + column: 24, + }, + ], + }, + { + code: ` +class Foo { + method() {} +} + `, + options: [{ genericType: 'always' }], + errors: [ + { + messageId: 'notCamelCase', + data: { + name: 't_foo', + }, + line: 3, + column: 10, + }, + { + messageId: 'notCamelCase', + data: { + name: 't_bar', + }, + line: 3, + column: 18, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 2ec44b6dbc2..62f45419b18 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -35,6 +35,7 @@ declare module 'eslint/lib/rules/camelcase' { allow?: string[]; ignoreDestructuring?: boolean; properties?: 'always' | 'never'; + genericType?: 'never' | 'always'; }, ], {