Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rules) : add contextual lifecycle rule #388

Merged
merged 1 commit into from Aug 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions package.json
Expand Up @@ -21,7 +21,8 @@
},
"contributors": [
"Minko Gechev <mgechev@gmail.com>",
"Preslav Semov <preslavsemov@gmail.com>"
"Preslav Semov <preslavsemov@gmail.com>",
"William Koza <william.koza@gmail.com>"
],
"repository": {
"type": "git",
Expand All @@ -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",
Expand Down
32 changes: 17 additions & 15 deletions 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 = <any>decorator.expression || {};
Expand Down Expand Up @@ -123,16 +123,18 @@ export class NgWalker extends Lint.RuleWalker {
this.visitNgInjectable(<ts.ClassDeclaration>decorator.parent, decorator);
}


if (name === 'Pipe') {
this.visitNgPipe(<ts.ClassDeclaration>decorator.parent, decorator);
}

// Not invoked @Component or @Pipe, or @Directive
if (!(<ts.CallExpression>decorator.expression).arguments ||
!(<ts.CallExpression>decorator.expression).arguments.length ||
!(<ts.ObjectLiteralExpression>(<ts.CallExpression>decorator.expression).arguments[0]).properties) {
return;
}

if (name === 'Pipe') {
this.visitNgPipe(<ts.ClassDeclaration>decorator.parent, decorator);
}
}

protected visitNgComponent(metadata: ComponentMetadata) {
Expand Down
180 changes: 180 additions & 0 deletions 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)));
}

}
55 changes: 28 additions & 27 deletions 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';