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): [camelcase] add genericType option #925

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
100 changes: 98 additions & 2 deletions packages/eslint-plugin/docs/rules/camelcase.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<t_foo> {}
function foo<t_foo>() {}
class Foo<t_foo> {}
type Foo<t_foo> = {};
class Foo {
method<t_foo>() {}
}

interface Foo<t_foo extends object> {}
function foo<t_foo extends object>() {}
class Foo<t_foo extends object> {}
type Foo<t_foo extends object> = {};
class Foo {
method<t_foo extends object>() {}
}

interface Foo<t_foo = object> {}
function foo<t_foo = object>() {}
class Foo<t_foo = object> {}
type Foo<t_foo = object> = {};
class Foo {
method<t_foo = object>() {}
}
```

Examples of **correct** code for this rule with the default `{ "genericType": "always" }` option:

```typescript
/* eslint @typescript-eslint/camelcase: ["error", { "genericType": "always" }] */

interface Foo<T> {}
function foo<t>() {}
class Foo<T> {}
type Foo<T> = {};
class Foo {
method<T>() {}
}

interface Foo<T extends object> {}
function foo<T extends object>() {}
class Foo<T extends object> {}
type Foo<T extends object> = {};
class Foo {
method<T extends object>() {}
}

interface Foo<T = object> {}
function foo<T = object>() {}
class Foo<T = object> {}
type Foo<T = object> = {};
class Foo {
method<T = object>() {}
}
```

### genericType: "never"

Examples of **correct** code for this rule with the `{ "genericType": "never" }` option:

```typescript
/* eslint @typescript-eslint/camelcase: ["error", { "genericType": "never" }] */

interface Foo<t_foo> {}
function foo<t_foo>() {}
class Foo<t_foo> {}
type Foo<t_foo> = {};
class Foo {
method<t_foo>() {}
}

interface Foo<t_foo extends object> {}
function foo<t_foo extends object>() {}
class Foo<t_foo extends object> {}
type Foo<t_foo extends object> = {};
class Foo {
method<t_foo extends object>() {}
}

interface Foo<t_foo = object> {}
function foo<t_foo = object>() {}
class Foo<t_foo = object> {}
type Foo<t_foo = object> = {};
class Foo {
method<t_foo = object>() {}
}
```

### ignoreDestructuring: false

Examples of **incorrect** code for this rule with the default `{ "ignoreDestructuring": false }` option:
Expand Down
25 changes: 24 additions & 1 deletion packages/eslint-plugin/src/rules/camelcase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ import * as util from '../util';
type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;

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<Options, MessageIds>({
name: 'camelcase',
meta: {
Expand All @@ -17,14 +30,15 @@ export default util.createRule<Options, MessageIds>({
category: 'Stylistic Issues',
recommended: 'error',
},
schema: baseRule.meta.schema,
schema: [schema],
messages: baseRule.meta.messages,
},
defaultOptions: [
{
allow: ['^UNSAFE_'],
ignoreDestructuring: false,
properties: 'never',
genericType: 'never',
},
],
create(context, [options]) {
Expand All @@ -36,6 +50,7 @@ export default util.createRule<Options, MessageIds>({
AST_NODE_TYPES.TSAbstractClassProperty,
];

const genericType = options.genericType;
const properties = options.properties;
const allow = (options.allow || []).map(entry => ({
name: entry,
Expand Down Expand Up @@ -117,6 +132,14 @@ export default util.createRule<Options, MessageIds>({
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 (
Expand Down
206 changes: 206 additions & 0 deletions packages/eslint-plugin/tests/rules/camelcase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,100 @@ ruleTester.run('camelcase', rule, {
code: 'abstract class Foo { abstract bar: number = 0; }',
options: [{ properties: 'always' }],
},
{
code: 'interface Foo<t_foo> {}',
options: [{ genericType: 'never' }],
},
{
code: 'interface Foo<T> {}',
options: [{ genericType: 'always' }],
},
{
code: 'interface Foo<t> {}',
options: [{ genericType: 'always' }],
},
{
code: 'function fn<t_foo>() {}',
options: [{ genericType: 'never' }],
},
{
code: 'function fn<T>() {}',
options: [{ genericType: 'always' }],
},
{
code: 'function fn<t>() {}',
options: [{ genericType: 'always' }],
},
{
code: 'class Foo<t_foo> {}',
options: [{ genericType: 'never' }],
},
{
code: 'class Foo<T> {}',
options: [{ genericType: 'always' }],
},
{
code: 'class Foo<t> {}',
options: [{ genericType: 'always' }],
},
{
code: `
class Foo {
method<t_foo>() {}
}
`,
options: [{ genericType: 'never' }],
},
{
code: `
class Foo {
method<T>() {}
}
`,
options: [{ genericType: 'always' }],
},
{
code: `
class Foo {
method<t>() {}
}
`,
options: [{ genericType: 'always' }],
},
{
code: `
type Foo<T> = {}
`,
options: [{ genericType: 'always' }],
},
{
code: `
type Foo<t> = {}
`,
options: [{ genericType: 'always' }],
},
{
code: `
type Foo<t_object> = {}
`,
options: [{ genericType: 'never' }],
},
{
code: `
class Foo {
FOO_method() {}
}
`,
options: [{ allow: ['^FOO'] }],
},
{
code: `
class Foo {
method() {}
}
`,
options: [{}],
},
{
code: 'const foo = foo?.baz;',
},
Expand Down Expand Up @@ -238,5 +332,117 @@ ruleTester.run('camelcase', rule, {
},
],
},
{
code: 'interface Foo<t_foo> {}',
options: [{ genericType: 'always' }],
errors: [
{
messageId: 'notCamelCase',
data: {
name: 't_foo',
},
line: 1,
column: 15,
},
],
},
{
code: 'function fn<t_foo>() {}',
options: [{ genericType: 'always' }],
errors: [
{
messageId: 'notCamelCase',
data: {
name: 't_foo',
},
line: 1,
column: 13,
},
],
},
{
code: 'class Foo<t_foo> {}',
options: [{ genericType: 'always' }],
errors: [
{
messageId: 'notCamelCase',
data: {
name: 't_foo',
},
line: 1,
column: 11,
},
],
},
{
code: `
class Foo {
method<t_foo>() {}
}
`,
options: [{ genericType: 'always' }],
errors: [
{
messageId: 'notCamelCase',
data: {
name: 't_foo',
},
line: 3,
column: 10,
},
],
},
{
code: `
class Foo {
method<t_foo extends t_bar>() {}
}
`,
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<t_foo = t_bar>() {}
}
`,
options: [{ genericType: 'always' }],
errors: [
{
messageId: 'notCamelCase',
data: {
name: 't_foo',
},
line: 3,
column: 10,
},
{
messageId: 'notCamelCase',
data: {
name: 't_bar',
},
line: 3,
column: 18,
},
],
},
],
});