criticizer
Version:
Linting for Angular applications, following angular.io/styleguide.
81 lines (71 loc) • 3.04 kB
text/typescript
import * as Lint from 'tslint';
import * as ts from 'typescript';
import {sprintf} from 'sprintf-js';
import SyntaxKind = require('./util/syntaxKind');
export interface IUsePropertyDecoratorConfig {
propertyName: string;
decoratorName: string | string[];
errorMessage: string;
}
export class UsePropertyDecorator extends Lint.Rules.AbstractRule {
public static formatFailureString(config: IUsePropertyDecoratorConfig, decoratorName: string, className: string) {
let decorators = config.decoratorName;
if (decorators instanceof Array) {
decorators = (<string[]>decorators).map(d => `"@${d}"`).join(', ');
} else {
decorators = `"@${decorators}"`;
}
return sprintf(config.errorMessage, decoratorName, className, config.propertyName, decorators);
}
constructor(private config: IUsePropertyDecoratorConfig, ruleName: string, value: any, disabledIntervals: Lint.IDisabledInterval[]) {
super(ruleName, value, disabledIntervals);
}
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
let documentRegistry = ts.createDocumentRegistry();
let languageServiceHost = Lint.createLanguageServiceHost('file.ts', sourceFile.getFullText());
return this.applyWithWalker(
new DirectiveMetadataWalker(sourceFile,
this.getOptions(),
ts.createLanguageService(languageServiceHost, documentRegistry), this.config));
}
}
class DirectiveMetadataWalker extends Lint.RuleWalker {
private languageService : ts.LanguageService;
private typeChecker : ts.TypeChecker;
constructor(sourceFile: ts.SourceFile, options: Lint.IOptions,
languageService : ts.LanguageService, private config: IUsePropertyDecoratorConfig) {
super(sourceFile, options);
this.languageService = languageService;
this.typeChecker = languageService.getProgram().getTypeChecker();
}
visitClassDeclaration(node: ts.ClassDeclaration) {
(<ts.Decorator[]>node.decorators || [])
.forEach(this.validateDecorator.bind(this, node.name.text));
super.visitClassDeclaration(node);
}
private validateDecorator(className: string, decorator: ts.Decorator) {
let baseExpr = <any>decorator.expression || {};
let expr = baseExpr.expression || {};
let name = expr.text;
let args = baseExpr.arguments || [];
let arg = args[0];
if (/^(Component|Directive)$/.test(name) && arg) {
this.validateProperty(className, name, arg);
}
}
private validateProperty(className: string, decoratorName: string, arg: ts.ObjectLiteralExpression) {
if (arg.kind === SyntaxKind.current().ObjectLiteralExpression) {
(<ts.ObjectLiteralExpression>arg)
.properties
.filter(prop => (<any>prop.name).text === this.config.propertyName)
.forEach(prop => {
let p = <any>prop;
this.addFailure(
this.createFailure(
p.getStart(),
p.getWidth(),
UsePropertyDecorator.formatFailureString(this.config, decoratorName, className)));
});
}
}
}