Skip to content

Commit

Permalink
feat(eslint-plugin): [camelcase] add genericType option
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk committed Aug 30, 2019
1 parent 6bd7f2d commit ada676b
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 3 deletions.
50 changes: 47 additions & 3 deletions 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 @@ -89,6 +104,32 @@ export default util.createRule<Options, MessageIds>({
return false;
}

/**
* Checks if the the node is a valid TypeScript generic type parameter
* @param node the node to be validated
* @returns true if the node is a TypeScript generic type parameter
* @private
*/
function isTSTypeParameter(node: TSESTree.Node): boolean {
const parent = node.parent;
if (!parent) {
return false;
}

if (parent.type === AST_NODE_TYPES.TSTypeParameter) {
return true;
}

if (parent.type === AST_NODE_TYPES.TSTypeReference && parent.parent) {
return (
parent.parent !== undefined &&
parent.parent.type === AST_NODE_TYPES.TSTypeParameter
);
}

return false;
}

return {
Identifier(node): void {
/*
Expand All @@ -103,8 +144,11 @@ export default util.createRule<Options, MessageIds>({
}

// Check TypeScript specific nodes
if (isTSPropertyType(node)) {
if (properties === 'always' && isUnderscored(name)) {
if (isTSPropertyType(node) || isTSTypeParameter(node)) {
if (
[properties, genericType].includes('always') &&
isUnderscored(name)
) {
context.report({
node,
messageId: 'notCamelCase',
Expand Down
184 changes: 184 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,78 @@ 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 extends object> = {}
`,
options: [{ genericType: 'always' }],
},
{
code: `
type Foo<T extends t_object> = {}
`,
options: [{ genericType: 'never' }],
},
],

invalid: [
Expand Down Expand Up @@ -194,5 +266,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,
},
],
},
],
});
1 change: 1 addition & 0 deletions packages/eslint-plugin/typings/eslint-rules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ declare module 'eslint/lib/rules/camelcase' {
allow?: string[];
ignoreDestructuring?: boolean;
properties?: 'always' | 'never';
genericType?: 'never' | 'always';
},
],
{
Expand Down

0 comments on commit ada676b

Please sign in to comment.