From 0f8bb83a6c6730ac8aef3ef23c9222795859deec Mon Sep 17 00:00:00 2001 From: WilliamKoza Date: Sat, 5 Aug 2017 14:56:37 +0200 Subject: [PATCH] feat(rules) : add contextual lifecycle rule --- package.json | 7 +- src/angular/ngWalker.ts | 32 +- src/contextualLifeCycleRule.ts | 180 +++++++++++ src/index.ts | 55 ++-- test/contextualLifeCycleRule.spec.ts | 463 +++++++++++++++++++++++++++ 5 files changed, 692 insertions(+), 45 deletions(-) create mode 100644 src/contextualLifeCycleRule.ts create mode 100644 test/contextualLifeCycleRule.spec.ts diff --git a/package.json b/package.json index e8a4253c9..fcc6bf069 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ }, "contributors": [ "Minko Gechev ", - "Preslav Semov " + "Preslav Semov ", + "William Koza " ], "repository": { "type": "git", @@ -46,8 +47,8 @@ }, "homepage": "https://github.com/mgechev/codelyzer#readme", "devDependencies": { - "@angular/compiler": "^4.2.3", - "@angular/core": "^4.2.3", + "@angular/compiler": "^4.3.3", + "@angular/core": "^4.3.3", "@types/chai": "^3.4.33", "@types/less": "0.0.31", "@types/mocha": "^2.2.32", diff --git a/src/angular/ngWalker.ts b/src/angular/ngWalker.ts index c4962cbdb..9b726d142 100644 --- a/src/angular/ngWalker.ts +++ b/src/angular/ngWalker.ts @@ -1,28 +1,28 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import * as compiler from '@angular/compiler'; -import {parseTemplate} from './templates/templateParser'; +import { parseTemplate } from './templates/templateParser'; -import {parseCss} from './styles/parseCss'; -import {CssAst} from './styles/cssAst'; -import {BasicCssAstVisitor, CssAstVisitorCtrl} from './styles/basicCssAstVisitor'; +import { parseCss } from './styles/parseCss'; +import { CssAst } from './styles/cssAst'; +import { BasicCssAstVisitor, CssAstVisitorCtrl } from './styles/basicCssAstVisitor'; import { RecursiveAngularExpressionVisitorCtr, BasicTemplateAstVisitor, TemplateAstVisitorCtr } from './templates/basicTemplateAstVisitor'; -import {RecursiveAngularExpressionVisitor} from './templates/recursiveAngularExpressionVisitor'; -import {ReferenceCollectorVisitor} from './templates/referenceCollectorVisitor'; +import { RecursiveAngularExpressionVisitor } from './templates/recursiveAngularExpressionVisitor'; +import { ReferenceCollectorVisitor } from './templates/referenceCollectorVisitor'; -import {MetadataReader} from './metadataReader'; -import {ComponentMetadata, DirectiveMetadata, StyleMetadata} from './metadata'; -import {ngWalkerFactoryUtils} from './ngWalkerFactoryUtils'; +import { MetadataReader } from './metadataReader'; +import { ComponentMetadata, DirectiveMetadata, StyleMetadata } from './metadata'; +import { ngWalkerFactoryUtils } from './ngWalkerFactoryUtils'; -import {Config} from './config'; +import { Config } from './config'; -import {logger} from '../util/logger'; -import {getDecoratorName} from '../util/utils'; +import { logger } from '../util/logger'; +import { getDecoratorName } from '../util/utils'; const getDecoratorStringArgs = (decorator: ts.Decorator) => { let baseExpr = decorator.expression || {}; @@ -123,6 +123,11 @@ export class NgWalker extends Lint.RuleWalker { this.visitNgInjectable(decorator.parent, decorator); } + + if (name === 'Pipe') { + this.visitNgPipe(decorator.parent, decorator); + } + // Not invoked @Component or @Pipe, or @Directive if (!(decorator.expression).arguments || !(decorator.expression).arguments.length || @@ -130,9 +135,6 @@ export class NgWalker extends Lint.RuleWalker { return; } - if (name === 'Pipe') { - this.visitNgPipe(decorator.parent, decorator); - } } protected visitNgComponent(metadata: ComponentMetadata) { diff --git a/src/contextualLifeCycleRule.ts b/src/contextualLifeCycleRule.ts new file mode 100644 index 000000000..fa382fa65 --- /dev/null +++ b/src/contextualLifeCycleRule.ts @@ -0,0 +1,180 @@ +import * as Lint from 'tslint'; +import * as ts from 'typescript'; +import {sprintf} from 'sprintf-js'; +import SyntaxKind = require('./util/syntaxKind'); +import {NgWalker} from './angular/ngWalker'; +import {ComponentMetadata, DirectiveMetadata} from './angular/metadata'; + + +export class Rule extends Lint.Rules.AbstractRule { + public static metadata: Lint.IRuleMetadata = { + ruleName: 'contextual-life-cycle', + type: 'functionality', + description: `Ensure that classes use allowed life cycle method in its body`, + rationale: `Some life cycle methods can only be used in certain class types. + For example, ngOnInit() hook method should not be used in an @Injectable class.`, + options: null, + optionsDescription: `Not configurable.`, + typescriptOnly: true, + }; + + + static FAILURE_STRING: string = 'In the class "%s" which have the "%s" decorator, the ' + + '"%s" hook method is not allowed. ' + + 'Please, drop it.'; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker( + new ClassMetadataWalker(sourceFile, + this)); + } +} + + +export class ClassMetadataWalker extends NgWalker { + + className: string; + isInjectable = false; + isComponent = false; + isDirective = false; + isPipe = false; + + constructor(sourceFile: ts.SourceFile, private rule: Rule) { + super(sourceFile, rule.getOptions()); + } + + visitNgInjectable(controller: ts.ClassDeclaration, decorator: ts.Decorator) { + this.className = controller.name.text; + this.isInjectable = true; + } + + visitNgComponent(metadata: ComponentMetadata) { + this.className = metadata.controller.name.text; + this.isComponent = true; + } + + visitNgDirective(metadata: DirectiveMetadata) { + this.className = metadata.controller.name.text; + this.isDirective = true; + } + + visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) { + this.className = controller.name.text; + this.isPipe = true; + } + + visitMethodDeclaration(method: ts.MethodDeclaration) { + + const methodName = (method.name as ts.StringLiteral).text; + + if (methodName === 'ngOnInit') { + if (this.isInjectable) { + let failureConfig: string[] = [this.className, '@Injectable', 'ngOnInit()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isPipe) { + let failureConfig: string[] = [this.className, '@Pipe', 'ngOnInit()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } + } + + if (methodName === 'ngOnChanges') { + if (this.isInjectable) { + let failureConfig: string[] = [this.className, '@Injectable', 'ngOnChanges()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isPipe) { + let failureConfig: string[] = [this.className, '@Pipe', 'ngOnChanges()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } + } + + if (methodName === 'ngDoCheck') { + if (this.isInjectable) { + let failureConfig: string[] = [this.className, '@Injectable', 'ngDoCheck()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isPipe) { + let failureConfig: string[] = [this.className, '@Pipe', 'ngDoCheck()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } + } + + if (methodName === 'ngAfterContentInit') { + if (this.isInjectable) { + let failureConfig: string[] = [this.className, '@Injectable', 'ngAfterContentInit()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isPipe) { + let failureConfig: string[] = [this.className, '@Pipe', 'ngAfterContentInit()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isDirective) { + let failureConfig: string[] = [this.className, '@Directive', 'ngAfterContentInit()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } + } + + if (methodName === 'ngAfterContentChecked') { + if (this.isInjectable) { + let failureConfig: string[] = [this.className, '@Injectable', 'ngAfterContentChecked()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isPipe) { + let failureConfig: string[] = [this.className, '@Pipe', 'ngAfterContentChecked()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isDirective) { + let failureConfig: string[] = [this.className, '@Directive', 'ngAfterContentChecked()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } + } + + if (methodName === 'ngAfterViewInit') { + if (this.isInjectable) { + let failureConfig: string[] = [this.className, '@Injectable', 'ngAfterViewInit()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isPipe) { + let failureConfig: string[] = [this.className, '@Pipe', 'ngAfterViewInit()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isDirective) { + let failureConfig: string[] = [this.className, '@Directive', 'ngAfterViewInit()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } + } + + if (methodName === 'ngAfterViewChecked') { + if (this.isInjectable) { + let failureConfig: string[] = [this.className, '@Injectable', 'ngAfterViewChecked()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isPipe) { + let failureConfig: string[] = [this.className, '@Pipe', 'ngAfterViewChecked()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } else if (this.isDirective) { + let failureConfig: string[] = [this.className, '@Directive', 'ngAfterViewChecked()']; + failureConfig.unshift(Rule.FAILURE_STRING); + this.generateFailure(method.getStart(), method.getWidth(), failureConfig); + } + } + + } + + private generateFailure(start: number, width: number, failureConfig: string[]) { + this.addFailure( + this.createFailure( + start, + width, + sprintf.apply(this, failureConfig))); + } + +} diff --git a/src/index.ts b/src/index.ts index aa286e2e2..78749ac00 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,29 +1,30 @@ -export { Rule as ComponentClassSuffixRule } from './componentClassSuffixRule'; -export { Rule as ComponentSelectorRule } from './componentSelectorRule'; -export { Rule as DirectiveClassSuffixRule } from './directiveClassSuffixRule'; -export { Rule as DirectiveSelectorRule } from './directiveSelectorRule'; -export { Rule as ImportDestructuringSpacingRule } from './importDestructuringSpacingRule'; -export { Rule as InvokeInjectableRule } from './invokeInjectableRule'; -export { Rule as NoAccessMissingMemberRule } from './noAccessMissingMemberRule'; -export { Rule as NoAttributeParameterDecoratorRule } from './noAttributeParameterDecoratorRule'; -export { Rule as NoForwardRefRule } from './noForwardRefRule'; -export { Rule as NoInputRenameRule } from './noInputRenameRule'; -export { Rule as NoOutputRenameRule } from './noOutputRenameRule'; -export { Rule as NoUnusedCssRule } from './noUnusedCssRule'; -export { Rule as PipeImpureRule } from './pipeImpureRule'; -export { Rule as PipeNamingRule } from './pipeNamingRule'; -export { Rule as TemplatesUsePublicRule } from './templatesUsePublicRule'; -export { Rule as UseHostPropertyDecoratorRule } from './useHostPropertyDecoratorRule'; -export { Rule as UseInputPropertyDecoratorRule } from './useInputPropertyDecoratorRule'; -export { Rule as UseLifeCycleInterfaceRule } from './useLifeCycleInterfaceRule'; -export { Rule as UseOutputPropertyDecoratorRule } from './useOutputPropertyDecoratorRule'; -export { Rule as UsePipeTransformInterfaceRule } from './usePipeTransformInterfaceRule'; -export { Rule as TemplateToNgTemplateRule } from './templateToNgTemplateRule'; -export { Rule as UsePipeDecoratorRule } from './usePipeDecoratorRule'; -export { Rule as UseViewEncapsulationRule } from './useViewEncapsulationRule'; -export { Rule as BananaInBoxRule } from './bananaInBoxRule'; -export { Rule as AngularWhitespaceRule } from './angularWhitespaceRule'; -export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule'; -export { Rule as DecoratorNotAllowedRule } from './decoratorNotAllowedRule'; +export {Rule as AngularWhitespaceRule} from './angularWhitespaceRule'; +export {Rule as BananaInBoxRule} from './bananaInBoxRule'; +export {Rule as ComponentClassSuffixRule} from './componentClassSuffixRule'; +export {Rule as ComponentSelectorRule} from './componentSelectorRule'; +export {Rule as ContextualLifeCycleRule} from './contextualLifeCycleRule'; +export {Rule as DecoratorNotAllowedRule} from './decoratorNotAllowedRule'; +export {Rule as DirectiveClassSuffixRule} from './directiveClassSuffixRule'; +export {Rule as DirectiveSelectorRule} from './directiveSelectorRule'; +export {Rule as ImportDestructuringSpacingRule} from './importDestructuringSpacingRule'; +export {Rule as InvokeInjectableRule} from './invokeInjectableRule'; +export {Rule as NoAccessMissingMemberRule} from './noAccessMissingMemberRule'; +export {Rule as NoAttributeParameterDecoratorRule} from './noAttributeParameterDecoratorRule'; +export {Rule as NoForwardRefRule} from './noForwardRefRule'; +export {Rule as NoInputRenameRule} from './noInputRenameRule'; +export {Rule as NoOutputRenameRule} from './noOutputRenameRule'; +export {Rule as NoUnusedCssRule} from './noUnusedCssRule'; +export {Rule as PipeImpureRule} from './pipeImpureRule'; +export {Rule as PipeNamingRule} from './pipeNamingRule'; +export {Rule as TemplatesUsePublicRule} from './templatesUsePublicRule'; +export {Rule as UseHostPropertyDecoratorRule} from './useHostPropertyDecoratorRule'; +export {Rule as UseInputPropertyDecoratorRule} from './useInputPropertyDecoratorRule'; +export {Rule as UseLifeCycleInterfaceRule} from './useLifeCycleInterfaceRule'; +export {Rule as UseOutputPropertyDecoratorRule} from './useOutputPropertyDecoratorRule'; +export {Rule as UsePipeTransformInterfaceRule} from './usePipeTransformInterfaceRule'; +export {Rule as TemplateToNgTemplateRule} from './templateToNgTemplateRule'; +export {Rule as UsePipeDecoratorRule} from './usePipeDecoratorRule'; +export {Rule as UseViewEncapsulationRule} from './useViewEncapsulationRule'; +export {Rule as TemplatesNoNegatedAsync} from './templatesNoNegatedAsyncRule'; export * from './angular/config'; diff --git a/test/contextualLifeCycleRule.spec.ts b/test/contextualLifeCycleRule.spec.ts new file mode 100644 index 000000000..332512145 --- /dev/null +++ b/test/contextualLifeCycleRule.spec.ts @@ -0,0 +1,463 @@ +import {assertAnnotated, assertFailures, assertSuccess} from './testHelper'; + +describe('contextual-life-cycle', () => { + + describe('success', () => { + + + describe('valid component class', () => { + it('should succeed when is used ngOnInit() method', () => { + let source = ` + @Component() + class TestComponent { + ngOnInit() { this.logger.log('OnInit'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + it('should succeed when is used ngOnChanges() method', () => { + let source = ` + @Component() + class TestComponent { + ngOnChanges() { this.logger.log('OnChanges'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + it('should succeed when is used ngDoCheck() method', () => { + let source = ` + @Component() + class TestComponent { + ngDoCheck() { this.logger.log('DoCheck'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + it('should succeed when is used ngAfterContentInit() method', () => { + let source = ` + @Component() + class TestComponent { + ngAfterContentInit() { this.logger.log('AfterContentInit'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + it('should succeed when is used ngAfterContentChecked() method', () => { + let source = ` + @Component() + class TestComponent { + ngAfterContentChecked() { this.logger.log('AfterContentChecked'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + it('should succeed when is used ngAfterViewInit() method', () => { + let source = ` + @Component() + class TestComponent { + ngAfterViewInit() { this.logger.log('AfterViewInit'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + it('should succeed when is used ngAfterViewChecked() method', () => { + let source = ` + @Component() + class TestComponent { + ngAfterViewChecked() { this.logger.log('AfterViewChecked'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + it('should succeed when is used ngOnDestroy() method', () => { + let source = ` + @Component() + class TestComponent { + ngOnDestroy() { this.logger.log('OnDestroy'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + }); + + + describe('valid directive class', () => { + it('should succeed when is used ngOnInit() method', () => { + let source = ` + @Directive() + class TestDirective { + ngOnInit() { this.logger.log('OnInit'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + it('should succeed when is used ngOnChanges() method', () => { + let source = ` + @Directive() + class TestDirective { + ngOnChanges() { this.logger.log('OnChanges'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + it('should succeed when is used ngDoCheck() method', () => { + let source = ` + @Directive() + class TestDirective { + ngDoCheck() { this.logger.log('DoCheck'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + it('should succeed when is used ngOnDestroy() method', () => { + let source = ` + @Directive() + class TestDirective { + ngOnDestroy() { this.logger.log('OnDestroy'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + }); + + + describe('valid service class', () => { + + it('should succeed when is used ngOnDestroy() method', () => { + let source = ` + @Injectable() + class TestService { + ngOnDestroy() { this.logger.log('OnDestroy'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + }); + + describe('valid pipe class', () => { + + it('should succeed when is used ngOnDestroy() method', () => { + let source = ` + @Pipe() + class TestPipe { + ngOnDestroy() { this.logger.log('OnDestroy'); } + }`; + assertSuccess('contextual-life-cycle', source); + }); + + }); + + }); + + describe('failure', () => { + + describe('valid directive class', () => { + + + it('should fail when is used ngAfterContentInit() method', () => { + let source = ` + @Directive() + class TestDirective { + ngAfterContentInit() { this.logger.log('AfterContentInit'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestDirective" which have the "@Directive" decorator, the ' + + '"ngAfterContentInit()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngAfterContentChecked() method', () => { + let source = ` + @Directive() + class TestDirective { + ngAfterContentChecked() { this.logger.log('AfterContentChecked'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestDirective" which have the "@Directive" decorator, the ' + + '"ngAfterContentChecked()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngAfterViewInit() method', () => { + let source = ` + @Directive() + class TestDirective { + ngAfterViewInit() { this.logger.log('AfterViewInit'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestDirective" which have the "@Directive" decorator, the ' + + '"ngAfterViewInit()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngAfterViewChecked() method', () => { + let source = ` + @Directive() + class TestDirective { + ngAfterViewChecked() { this.logger.log('AfterViewChecked'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestDirective" which have the "@Directive" decorator, the ' + + '"ngAfterViewChecked()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + + }); + + + describe('valid service class', () => { + + it('should fail when is used ngOnInit() method', () => { + let source = ` + @Injectable() + class TestService { + ngOnInit() { this.logger.log('OnInit'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestService" which have the "@Injectable" decorator, the ' + + '"ngOnInit()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngOnChanges() method', () => { + let source = ` + @Injectable() + class TestService { + ngOnChanges() { this.logger.log('OnChanges'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestService" which have the "@Injectable" decorator, the ' + + '"ngOnChanges()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngDoCheck() method', () => { + let source = ` + @Injectable() + class TestService { + ngDoCheck() { this.logger.log('DoCheck'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestService" which have the "@Injectable" decorator, the ' + + '"ngDoCheck()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + + it('should fail when is used ngAfterContentInit() method', () => { + let source = ` + @Injectable() + class TestService { + ngAfterContentInit() { this.logger.log('AfterContentInit'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestService" which have the "@Injectable" decorator, the ' + + '"ngAfterContentInit()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngAfterContentChecked() method', () => { + let source = ` + @Injectable() + class TestService { + ngAfterContentChecked() { this.logger.log('AfterContentChecked'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestService" which have the "@Injectable" decorator, the ' + + '"ngAfterContentChecked()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngAfterViewInit() method', () => { + let source = ` + @Injectable() + class TestService { + ngAfterViewInit() { this.logger.log('AfterViewInit'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestService" which have the "@Injectable" decorator, the ' + + '"ngAfterViewInit()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngAfterViewChecked() method', () => { + let source = ` + @Injectable() + class TestService { + ngAfterViewChecked() { this.logger.log('AfterViewChecked'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestService" which have the "@Injectable" decorator, the ' + + '"ngAfterViewChecked()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + + }); + + describe('valid pipe class', () => { + + it('should fail when is used ngOnInit() method', () => { + let source = ` + @Pipe() + class TestPipe { + ngOnInit() { this.logger.log('OnInit'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestPipe" which have the "@Pipe" decorator, the ' + + '"ngOnInit()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngOnChanges() method', () => { + let source = ` + @Pipe() + class TestPipe { + ngOnChanges() { this.logger.log('OnChanges'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestPipe" which have the "@Pipe" decorator, the ' + + '"ngOnChanges()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngDoCheck() method', () => { + let source = ` + @Pipe() + class TestPipe { + ngDoCheck() { this.logger.log('DoCheck'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestPipe" which have the "@Pipe" decorator, the ' + + '"ngDoCheck()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + + it('should fail when is used ngAfterContentInit() method', () => { + let source = ` + @Pipe() + class TestPipe { + ngAfterContentInit() { this.logger.log('AfterContentInit'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestPipe" which have the "@Pipe" decorator, the ' + + '"ngAfterContentInit()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngAfterContentChecked() method', () => { + let source = ` + @Pipe() + class TestPipe { + ngAfterContentChecked() { this.logger.log('AfterContentChecked'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestPipe" which have the "@Pipe" decorator, the ' + + '"ngAfterContentChecked()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngAfterViewInit() method', () => { + let source = ` + @Pipe() + class TestPipe { + ngAfterViewInit() { this.logger.log('AfterViewInit'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestPipe" which have the "@Pipe" decorator, the ' + + '"ngAfterViewInit()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + it('should fail when is used ngAfterViewChecked() method', () => { + let source = ` + @Pipe() + class TestPipe { + ngAfterViewChecked() { this.logger.log('AfterViewChecked'); } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'contextual-life-cycle', + message: 'In the class "TestPipe" which have the "@Pipe" decorator, the ' + + '"ngAfterViewChecked()" hook method is not allowed. ' + + 'Please, drop it.', + source + }); + }); + + + }); + + }); + +});