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): [member-naming] should match constructor args #771

69 changes: 54 additions & 15 deletions packages/eslint-plugin/src/rules/member-naming.ts
@@ -1,4 +1,7 @@
import { TSESTree } from '@typescript-eslint/experimental-utils';
import {
TSESTree,
AST_NODE_TYPES,
} from '@typescript-eslint/experimental-utils';
import * as util from '../util';

interface Config<T = string> {
Expand Down Expand Up @@ -61,37 +64,73 @@ export default util.createRule<Options, MessageIds>({
return acc;
}, {});

/**
* Check that the property name matches the convention for its
* accessibility.
* @param {ASTNode} node the named node to evaluate.
* @returns {void}
* @private
*/
function getParameterNode(
node: TSESTree.TSParameterProperty,
): TSESTree.Identifier | null {
if (node.parameter.type === AST_NODE_TYPES.AssignmentPattern) {
return node.parameter.left as TSESTree.Identifier;
}

if (node.parameter.type === AST_NODE_TYPES.Identifier) {
return node.parameter;
}

return null;
}

function validateParameterName(node: TSESTree.TSParameterProperty): void {
const parameterNode = getParameterNode(node);
if (!parameterNode) {
return;
}

validate(parameterNode, parameterNode.name, node.accessibility);
}

function validateName(
node: TSESTree.MethodDefinition | TSESTree.ClassProperty,
): void {
const name = util.getNameFromClassMember(node, sourceCode);
const accessibility: Modifiers = node.accessibility || 'public';
const convention = conventions[accessibility];

const method = node as TSESTree.MethodDefinition;
if (method.kind === 'constructor') {
if (
node.type === AST_NODE_TYPES.MethodDefinition &&
node.kind === 'constructor'
) {
return;
}

validate(
node.key,
util.getNameFromClassMember(node, sourceCode),
node.accessibility,
);
}

/**
* Check that the name matches the convention for its accessibility.
* @param {ASTNode} node the named node to evaluate.
* @param {string} name
* @param {Modifiers} accessibility
* @returns {void}
* @private
*/
function validate(
node: TSESTree.Identifier | TSESTree.Expression,
name: string,
accessibility: Modifiers = 'public',
): void {
const convention = conventions[accessibility];
if (!convention || convention.test(name)) {
return;
}

context.report({
node: node.key,
node,
messageId: 'incorrectName',
data: { accessibility, name, convention },
});
}

return {
TSParameterProperty: validateParameterName,
MethodDefinition: validateName,
ClassProperty: validateName,
};
Expand Down
70 changes: 70 additions & 0 deletions packages/eslint-plugin/tests/rules/member-naming.test.ts
Expand Up @@ -86,6 +86,30 @@ class Class {
},
],
},

{
code: `
class Test {
constructor(public __a: string, protected __b: string, private __c: string = 100) {}
}
`,
options: [
{
protected: '^__',
private: '^__',
public: '^__',
},
],
},
{
code:
// Semantically invalid test case, TS has to throw an error.
`
class Foo {
constructor(private ...name: string[], private [test]: [string]) {}
}
`,
},
],
invalid: [
{
Expand Down Expand Up @@ -329,5 +353,51 @@ class Class {
},
],
},
{
code: `
class Test {
constructor(public a: string, protected b: string, private c: string = 100) {}
}
`,
options: [
{
public: '^__',
protected: '^__',
private: '^__',
},
],
errors: [
{
messageId: 'incorrectName',
data: {
accessibility: 'public',
convention: '/^__/',
name: 'a',
},
line: 3,
column: 24,
},
{
messageId: 'incorrectName',
data: {
accessibility: 'protected',
convention: '/^__/',
name: 'b',
},
line: 3,
column: 45,
},
{
messageId: 'incorrectName',
data: {
accessibility: 'private',
convention: '/^__/',
name: 'c',
},
line: 3,
column: 64,
},
],
},
],
});