Skip to content

Commit

Permalink
fix(eslint-plugin): fix false positives for adjacent-overload-signatu…
Browse files Browse the repository at this point in the history
…res regarding computed property names (#340)

Currently, the name of a method definition or a class property is always assumed to be a literal or an identifier. However, this fails to account for computed property names.
In those cases, we currently sometimes give the name for a method definition with a computed name as `undefined`, which as a result causes methods with differing computed property names to be marked as overloads of each other. In this PR, we slice the text of the computed property name out to handle these cases.
  • Loading branch information
uniqueiniquity authored and bradzacher committed Mar 8, 2019
1 parent b66363a commit f6e5118
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 5 deletions.
Expand Up @@ -26,6 +26,8 @@ export default util.createRule({
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();

/**
* Gets the name of the member being processed.
* @param member the member being processed.
Expand Down Expand Up @@ -57,7 +59,7 @@ export default util.createRule({
case AST_NODE_TYPES.TSConstructSignatureDeclaration:
return 'new';
case AST_NODE_TYPES.MethodDefinition:
return util.getNameFromPropertyName(member.key);
return util.getNameFromClassMember(member, sourceCode);
}

return null;
Expand Down
Expand Up @@ -20,6 +20,7 @@ export default util.createRule({
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();
/**
* Checks if a method declaration has an accessibility modifier.
* @param methodDefinition The node representing a MethodDefinition.
Expand All @@ -36,7 +37,7 @@ export default util.createRule({
messageId: 'missingAccessibility',
data: {
type: 'method definition',
name: util.getNameFromPropertyName(methodDefinition.key),
name: util.getNameFromClassMember(methodDefinition, sourceCode),
},
});
}
Expand Down
4 changes: 3 additions & 1 deletion packages/eslint-plugin/src/rules/member-naming.ts
Expand Up @@ -51,6 +51,8 @@ export default util.createRule<Options, MessageIds>({
},
defaultOptions: [{}],
create(context, [config]) {
const sourceCode = context.getSourceCode();

const conventions = (Object.keys(config) as Modifiers[]).reduce<
Config<RegExp>
>((acc, accessibility) => {
Expand All @@ -69,7 +71,7 @@ export default util.createRule<Options, MessageIds>({
function validateName(
node: TSESTree.MethodDefinition | TSESTree.ClassProperty,
): void {
const name = util.getNameFromPropertyName(node.key);
const name = util.getNameFromClassMember(node, sourceCode);
const accessibility: Modifiers = node.accessibility || 'public';
const convention = conventions[accessibility];

Expand Down
4 changes: 3 additions & 1 deletion packages/eslint-plugin/src/rules/member-ordering.ts
Expand Up @@ -164,6 +164,8 @@ export default util.createRule<Options, MessageIds>({
},
],
create(context, [options]) {
const sourceCode = context.getSourceCode();

const functionExpressions = [
AST_NODE_TYPES.FunctionExpression,
AST_NODE_TYPES.ArrowFunctionExpression,
Expand Down Expand Up @@ -213,7 +215,7 @@ export default util.createRule<Options, MessageIds>({
case AST_NODE_TYPES.MethodDefinition:
return node.kind === 'constructor'
? 'constructor'
: util.getNameFromPropertyName(node.key);
: util.getNameFromClassMember(node, sourceCode);
case AST_NODE_TYPES.TSConstructSignatureDeclaration:
return 'new';
default:
Expand Down
29 changes: 29 additions & 0 deletions packages/eslint-plugin/src/util/misc.ts
Expand Up @@ -4,6 +4,7 @@

import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
import RuleModule from 'ts-eslint';
import { SourceCode } from 'ts-eslint';

/**
* Check if the context file name is *.ts or *.tsx
Expand Down Expand Up @@ -63,3 +64,31 @@ export function getNameFromPropertyName(
}
return `${propertyName.value}`;
}

/**
* Gets a string name representation of the name of the given MethodDefinition
* or ClassProperty node, with handling for computed property names.
*/
export function getNameFromClassMember(
methodDefinition: TSESTree.MethodDefinition | TSESTree.ClassProperty,
sourceCode: SourceCode,
): string {
if (keyCanBeReadAsPropertyName(methodDefinition.key)) {
return getNameFromPropertyName(methodDefinition.key);
}

return sourceCode.text.slice(...methodDefinition.key.range);
}

/**
* This covers both actual property names, as well as computed properties that are either
* an identifier or a literal at the top level.
*/
function keyCanBeReadAsPropertyName(
node: TSESTree.Expression,
): node is TSESTree.PropertyName {
return (
node.type === AST_NODE_TYPES.Literal ||
node.type === AST_NODE_TYPES.Identifier
);
}
Expand Up @@ -217,6 +217,11 @@ class Test {
// examples from https://github.com/nzakas/eslint-plugin-typescript/issues/138
'export default function<T>(foo : T) {}',
'export default function named<T>(foo : T) {}',
`
interface Foo {
[Symbol.toStringTag](): void;
[Symbol.iterator](): void;
}`,
],
invalid: [
{
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/typings/ts-eslint.d.ts
Expand Up @@ -683,6 +683,7 @@ declare module 'ts-eslint' {
RuleMetaData,
RuleMetaDataDocs,
Scope,
SourceCode,
};
export default RuleModule;
}
2 changes: 1 addition & 1 deletion packages/typescript-estree/src/ts-estree/ts-estree.ts
Expand Up @@ -500,7 +500,7 @@ interface LiteralBase extends BaseNode {
}

interface MethodDefinitionBase extends BaseNode {
key: PropertyName;
key: Expression;
value: FunctionExpression | TSEmptyBodyFunctionExpression;
computed: boolean;
static: boolean;
Expand Down

0 comments on commit f6e5118

Please sign in to comment.