Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Commit

Permalink
Properly handle computed/static property names. (#1848)
Browse files Browse the repository at this point in the history
  • Loading branch information
andy-hanson authored and nchen63 committed Dec 23, 2016
1 parent e4d897b commit f44457d
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 34 deletions.
74 changes: 40 additions & 34 deletions src/rules/adjacentOverloadSignaturesRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ class AdjacentOverloadSignaturesWalker extends Lint.RuleWalker {
}

public visitInterfaceDeclaration(node: ts.InterfaceDeclaration): void {
this.checkOverloadsAdjacent(node.members, (member) => {
return getTextOfPropertyName(member);
});
this.checkOverloadsAdjacent(node.members, getTextOfPropertyName);
super.visitInterfaceDeclaration(node);
}

Expand All @@ -78,64 +76,72 @@ class AdjacentOverloadSignaturesWalker extends Lint.RuleWalker {
this.checkOverloadsAdjacent(statements, (statement) => {
if (statement.kind === ts.SyntaxKind.FunctionDeclaration) {
const name = (statement as ts.FunctionDeclaration).name;
return name && name.text;
return name && { name: name.text, key: name.text };
} else {
return undefined;
}
});
}

private visitMembers(members: Array<ts.TypeElement | ts.ClassElement>) {
this.checkOverloadsAdjacent(members, (member) => {
return getTextOfPropertyName(member);
});
this.checkOverloadsAdjacent(members, getTextOfPropertyName);
}

/** 'getOverloadName' may return undefined for nodes that cannot be overloads, e.g. a `const` declaration. */
private checkOverloadsAdjacent<T extends ts.Node>(overloads: T[], getOverloadName: (node: T) => string | undefined) {
let last: string | undefined = undefined;
const seen: { [name: string]: true } = Object.create(null);
private checkOverloadsAdjacent<T extends ts.Node>(overloads: T[], getOverload: (node: T) => Overload | undefined) {
let lastKey: string | undefined = undefined;
const seen: { [key: string]: true } = Object.create(null);
for (const node of overloads) {
const name = getOverloadName(node);
if (name !== undefined) {
if (name in seen && last !== name) {
const overload = getOverload(node);
if (overload) {
const { name, key } = overload;
if (key in seen && lastKey !== key) {
this.addFailureAtNode(node, Rule.FAILURE_STRING_FACTORY(name));
}
seen[name] = true;
seen[key] = true;
lastKey = key;
} else {
lastKey = undefined;
}
last = name;
}
}
}

interface Overload {
/** Unique key for this overload. */
key: string;
/** Display name for the overload. `foo` and `static foo` have the same name but different keys. */
name: string;
}

function isLiteralExpression(node: ts.Node): node is ts.LiteralExpression {
return node.kind === ts.SyntaxKind.StringLiteral || node.kind === ts.SyntaxKind.NumericLiteral;
}

function getTextOfPropertyName(node: ts.InterfaceDeclaration | ts.TypeElement | ts.ClassElement): string {
let nameText: string;
function getTextOfPropertyName(node: ts.InterfaceDeclaration | ts.TypeElement | ts.ClassElement): Overload | undefined {
if (node.name == null) {
if (node.kind === ts.SyntaxKind.Constructor) {
return "constructor";
}
return node.kind === ts.SyntaxKind.Constructor ? { name: "constructor", key: "constructor" } : undefined;
}

const propertyInfo = getPropertyInfo(node.name);
if (!propertyInfo) {
return undefined;
}
switch (node.name.kind) {

const { name, computed } = propertyInfo;
const isStatic = Lint.hasModifier(node.modifiers, ts.SyntaxKind.StaticKeyword);
const key = (computed ? "0" : "1") + (isStatic ? "0" : "1") + name;
return { name, key };
}

function getPropertyInfo(name: ts.PropertyName): { name: string, computed?: boolean } | undefined {
switch (name.kind) {
case ts.SyntaxKind.Identifier:
nameText = (node.name as ts.Identifier).text;
break;
return { name: (name as ts.Identifier).text };
case ts.SyntaxKind.ComputedPropertyName:
const { expression } = (node.name as ts.ComputedPropertyName);
if (isLiteralExpression(expression)) {
nameText = expression.text;
}
break;
const { expression } = (name as ts.ComputedPropertyName);
return isLiteralExpression(expression) ? { name: expression.text } : { name: expression.getText(), computed: true };
default:
if (isLiteralExpression(node.name)) {
nameText = (<ts.StringLiteral> node.name).text;
}
return isLiteralExpression(name) ? { name: (name as ts.StringLiteral).text } : undefined;
}

const suffix = Lint.hasModifier(node.modifiers, ts.SyntaxKind.StaticKeyword) ? " __static__" : "";
return nameText + suffix;
}
21 changes: 21 additions & 0 deletions test/rules/adjacent-overload-signatures/test.ts.lint
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ interface i6 {
toString(): string;
}

interface i7 {
// Computed properties with different source code text OK
[Symbol.iterator](): void;
[Symbol.toStringTag](): void;
}

interface i8 {
// Computed property with same source code text as regular property OK
[Symbol.iterator](): void;
x: number;
"Symbol.iterator"(): void;
}


// bad

interface b1 {
Expand Down Expand Up @@ -127,3 +141,10 @@ class Bar {
;
}

interface I {
// Catches computed properties with same source code text
[Symbol.iterator](): void;
x: number;
[Symbol.iterator](): void;
~~~~~~~~~~~~~~~~~~~~~~~~~~ [All 'Symbol.iterator' signatures should be adjacent]
}

0 comments on commit f44457d

Please sign in to comment.