Skip to content

Commit

Permalink
Support inheritance in ReactComponentRule
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzuza committed Mar 15, 2018
1 parent f6079ec commit 98ea77f
Showing 1 changed file with 26 additions and 28 deletions.
54 changes: 26 additions & 28 deletions tools/tslint-rules/myReactComponentRule.ts
@@ -1,7 +1,7 @@
import * as ts from 'typescript';
import * as Lint from 'tslint';

export class Rule extends Lint.Rules.AbstractRule {
export class Rule extends Lint.Rules.TypedRule {
/* tslint:disable:object-literal-sort-keys */
static metadata: Lint.IRuleMetadata = {
ruleName: 'my-react-component',
Expand All @@ -15,50 +15,48 @@ export class Rule extends Lint.Rules.AbstractRule {
};
/* tslint:enable:object-literal-sort-keys */

static FAILURE_STRING = (className: string) => `React component ${className} must be PascalCased and prefixed by Component`;
static FAILURE_STRING = (className: string) =>
`React component ${className} must be PascalCased and prefixed by Component`;

static validate(name: string): boolean {
return isUpperCase(name[0]) && !name.includes('_') && name.endsWith('Component');
}

apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker());
}
}

function walk(ctx: Lint.WalkContext<void>) {
function walk(ctx: Lint.WalkContext<void>, tc: ts.TypeChecker) {
return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void {
if (isClassLikeDeclaration(node) && node.name !== undefined && isReactComponent(node)) {
if (!Rule.validate(node.name!.text)) {
ctx.addFailureAtNode(node.name!, Rule.FAILURE_STRING(node.name!.text));
}
if (
isClassLikeDeclaration(node) && node.name !== undefined &&
containsType(tc.getTypeAtLocation(node), isReactComponentType) &&
!Rule.validate(node.name!.text)) {
ctx.addFailureAtNode(node.name!, Rule.FAILURE_STRING(node.name!.text));
}

return ts.forEachChild(node, cb);
});
}
/* tslint:disable:no-any */
function containsType(type: ts.Type, predicate: (name: any) => boolean): boolean {
if (type.symbol !== undefined && predicate(type.symbol)) {
return true;
}

function isClassLikeDeclaration(node: ts.Node): node is ts.ClassLikeDeclaration {
return node.kind === ts.SyntaxKind.ClassDeclaration ||
node.kind === ts.SyntaxKind.ClassExpression;
const bases = type.getBaseTypes();
return bases && bases.some((t) => containsType(t, predicate));
}

function isReactComponent(node: ts.Node): boolean {
let result = false;
const classDeclaration = <ts.ClassDeclaration> node;
if (classDeclaration.heritageClauses) {
classDeclaration.heritageClauses.forEach((hc) => {
if (hc.token === ts.SyntaxKind.ExtendsKeyword && hc.types) {

hc.types.forEach(type => {
if (type.getText() === 'React.Component') {
result = true;
}
});
}
});
}
function isReactComponentType(symbol: any) {
return symbol.name === 'Component' && symbol.parent && symbol.parent.name === 'React';
}
/* tslint:enable:no-any */

return result;
function isClassLikeDeclaration(node: ts.Node): node is ts.ClassLikeDeclaration {
return node.kind === ts.SyntaxKind.ClassDeclaration ||
node.kind === ts.SyntaxKind.ClassExpression;
}

function isUpperCase(str: string): boolean {
Expand Down

0 comments on commit 98ea77f

Please sign in to comment.