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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat(rules) : add contextual lifecycle rule
  • Loading branch information
wKoza committed Aug 5, 2017
commit 0f8bb83a6c6730ac8aef3ef23c9222795859deec
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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';

Loading