Skip to content

Commit

Permalink
feat(eslint-plugin): [camelcase] add genericType option (#925)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk authored and bradzacher committed Nov 21, 2019
1 parent ceb6f1c commit d785c61
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 3 deletions.
100 changes: 98 additions & 2 deletions packages/eslint-plugin/docs/rules/camelcase.md
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
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
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,
},
],
},
],
});

0 comments on commit d785c61

Please sign in to comment.