From 473b1dfafda8ee1e564e2e95852938dcb87776f2 Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Fri, 27 Oct 2017 10:18:36 -0700 Subject: [PATCH] chore: create tslint rule to allow @HostListener and @HostBinding in abstract classes (#8036) --- .../noHostDecoratorInConcreteRule.ts | 37 +++++++++++++++++++ tslint.json | 9 +++-- 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 tools/tslint-rules/noHostDecoratorInConcreteRule.ts diff --git a/tools/tslint-rules/noHostDecoratorInConcreteRule.ts b/tools/tslint-rules/noHostDecoratorInConcreteRule.ts new file mode 100644 index 00000000000..e3e42fa6f04 --- /dev/null +++ b/tools/tslint-rules/noHostDecoratorInConcreteRule.ts @@ -0,0 +1,37 @@ +import * as ts from 'typescript'; +import * as Lint from 'tslint'; + +export class Rule extends Lint.Rules.AbstractRule { + apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new Walker(sourceFile, this.getOptions())); + } +} + +class Walker extends Lint.RuleWalker { + visitClassDeclaration(node: ts.ClassDeclaration) { + if (!node.modifiers || !this.getOptions().length) { return; } + + // Do not check the class if its abstract. + if (!!node.modifiers.find(modifier => modifier.kind === ts.SyntaxKind.AbstractKeyword)) { + return; + } + + node.members + .filter(el => el.decorators) + .map(el => el.decorators!) + .forEach(decorators => { + decorators.forEach(decorator => { + const decoratorText: string = decorator.getChildAt(1).getText(); + const matchedDecorator: string = this.getOptions().find( + (item: string) => decoratorText.startsWith(item)); + if (!!matchedDecorator) { + this.addFailureFromStartToEnd(decorator.getChildAt(1).pos - 1, decorator.end, + `The @${matchedDecorator} decorator may only be used in abstract classes. In ` + + `concrete classes use \`host\` in the component definition instead.`); + } + }); + }); + + super.visitClassDeclaration(node); + } +} diff --git a/tslint.json b/tslint.json index 31beb1d556e..e3fa8b441da 100644 --- a/tslint.json +++ b/tslint.json @@ -75,9 +75,7 @@ ["fit"], ["fdescribe"], ["xit"], - ["xdescribe"], - { "name": "HostBinding", "message": "Use `host` in the component definition instead." }, - { "name": "HostListener", "message": "Use `host` in the component definition instead." } + ["xdescribe"] ], // Disallows importing the whole RxJS library. Submodules can be still imported. "import-blacklist": [true, "rxjs"], @@ -87,6 +85,11 @@ // Custom Rules "ts-loader": true, "no-exposed-todo": true, + "no-host-decorator-in-concrete": [ + true, + "HostBinding", + "HostListener" + ], "validate-decorators": [true, { "Component": { "encapsulation": "\\.None$",