Skip to content

Commit

Permalink
Merge pull request #238 from mgechev/inheritance
Browse files Browse the repository at this point in the history
feat(rules): add support for inheritance chain
  • Loading branch information
mgechev committed Feb 19, 2017
2 parents 1cd2cd1 + 52a9f32 commit 3e2176e
Show file tree
Hide file tree
Showing 16 changed files with 594 additions and 253 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"copy:common": "cp README.md dist",
"build:links": "ts-node build/links.ts --src ./dist",
"prepare:package": "cat package.json | ts-node build/package.ts > dist/package.json",
"test": "rimraf dist && tsc && cp -r test/fixtures dist/test && BUILD_TYPE=dev npm run set:vars && mocha dist/test --recursive",
"test": "rimraf dist && tsc && cp -r test/fixtures dist/test && npm run set:vars && mocha dist/test --recursive",
"test:watch": "rimraf dist && tsc && cp -r test/fixtures dist/test && BUILD_TYPE=dev npm run set:vars && mocha dist/test --watch --recursive",
"set:vars": "ts-node build/vars.ts --src ./dist",
"tscv": "tsc --version",
Expand Down Expand Up @@ -73,8 +73,8 @@
"app-root-path": "^2.0.1",
"css-selector-tokenizer": "^0.7.0",
"cssauron": "^1.4.0",
"semver-dsl": "^1.0.1",
"source-map": "^0.5.6",
"sprintf-js": "^1.0.3"
}
}

9 changes: 3 additions & 6 deletions src/angular/ng2Walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface Ng2WalkerConfig {
expressionVisitorCtrl?: RecursiveAngularExpressionVisitorCtr;
templateVisitorCtrl?: TemplateAstVisitorCtr;
cssVisitorCtrl?: CssAstVisitorCtrl;
languageService?: ts.LanguageService;
}

export class Ng2Walker extends Lint.RuleWalker {
Expand All @@ -41,11 +42,6 @@ export class Ng2Walker extends Lint.RuleWalker {
protected _metadataReader?: MetadataReader) {
super(sourceFile, _originalOptions);
this._metadataReader = this._metadataReader || ng2WalkerFactoryUtils.defaultMetadataReader();
this._config = Object.assign({
templateVisitorCtrl: BasicTemplateAstVisitor,
expressionVisitorCtrl: RecursiveAngularExpressionVisitor,
cssVisitorCtrl: BasicCssAstVisitor
}, this._config || {});

this._config = Object.assign({
templateVisitorCtrl: BasicTemplateAstVisitor,
Expand Down Expand Up @@ -169,7 +165,8 @@ export class Ng2Walker extends Lint.RuleWalker {
const referenceVisitor = new ReferenceCollectorVisitor();
const visitor =
new this._config.templateVisitorCtrl(
sourceFile, this._originalOptions, context, baseStart, this._config.expressionVisitorCtrl);
sourceFile, this._originalOptions, context, baseStart, this._config.expressionVisitorCtrl,
this._config.languageService);
compiler.templateVisitAll(referenceVisitor, roots, null);
visitor._variables = referenceVisitor.variables;
compiler.templateVisitAll(visitor, roots, context.controller);
Expand Down
10 changes: 6 additions & 4 deletions src/angular/templates/basicTemplateAstVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,14 @@ const getExpressionDisplacement = (binding: any) => {


export interface RecursiveAngularExpressionVisitorCtr {
new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata, basePosition: number);
new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata, basePosition: number,
languageService?: ts.LanguageService);
}


export interface TemplateAstVisitorCtr {
new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata,
templateStart: number, expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr);
templateStart: number, expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr, languageService?: ts.LanguageService);
}

export class BasicTemplateAstVisitor extends SourceMappingVisitor implements ast.TemplateAstVisitor {
Expand All @@ -80,13 +81,14 @@ export class BasicTemplateAstVisitor extends SourceMappingVisitor implements ast
private _originalOptions: Lint.IOptions,
protected context: ComponentMetadata,
protected templateStart: number,
private expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr = RecursiveAngularExpressionVisitor) {
private expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr = RecursiveAngularExpressionVisitor,
protected languageService?: ts.LanguageService) {
super(sourceFile, _originalOptions, context.template.template, templateStart);
}

protected visitNg2TemplateAST(ast: e.AST, templateStart: number) {
const templateVisitor =
new this.expressionVisitorCtrl(this.getSourceFile(), this._originalOptions, this.context, templateStart);
new this.expressionVisitorCtrl(this.getSourceFile(), this._originalOptions, this.context, templateStart, this.languageService);
templateVisitor.preDefinedVariables = this._variables;
templateVisitor.visit(ast);
templateVisitor.getFailures().forEach(f => this.addFailure(f));
Expand Down
2 changes: 1 addition & 1 deletion src/angular/templates/recursiveAngularExpressionVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class RecursiveAngularExpressionVisitor extends SourceMappingVisitor impl
public preDefinedVariables = [];

constructor(sourceFile: ts.SourceFile, options: Lint.IOptions,
protected context: ComponentMetadata, protected basePosition: number) {
protected context: ComponentMetadata, protected basePosition: number, protected languageService: ts.LanguageService) {
super(sourceFile, options, context.template.template, basePosition);
}

Expand Down
22 changes: 18 additions & 4 deletions src/angular/templates/templateParser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { __core_private__ as r, NO_ERRORS_SCHEMA } from '@angular/core';
import { __core_private__ as r, NO_ERRORS_SCHEMA, ViewEncapsulation } from '@angular/core';
import * as compiler from '@angular/compiler';

import { Config, DirectiveDeclaration } from '../config';
import { SemVerDSL } from '../../util/ngVersion';

let refId = 0;

Expand Down Expand Up @@ -50,8 +51,20 @@ export const parseTemplate = (template: string, directives: DirectiveDeclaration
const ngConsole = new r.Console();
const htmlParser =
new compiler.I18NHtmlParser(new compiler.HtmlParser());
const tmplParser =
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, ngConsole, []);

let tmplParser: any;

SemVerDSL
.gte('4.0.0-beta.8', () => {
const config = new compiler.CompilerConfig({});
tmplParser =
new TemplateParser(config, expressionParser, elementSchemaRegistry, htmlParser, ngConsole, []);
})
.else(() => {
tmplParser =
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, ngConsole, []);
});

const interpolation = Config.interpolation;

// Make sure it works with 2.2.x & 2.3.x
Expand Down Expand Up @@ -91,7 +104,8 @@ export const parseTemplate = (template: string, directives: DirectiveDeclaration
value: '',
identifier: null
};
return tmplParser.tryParse(
const result = tmplParser.tryParse(
compiler.CompileDirectiveMetadata.create({ type, template: templateMetadata }),
template, defaultDirectives, [], [NO_ERRORS_SCHEMA], '').templateAst;
return result;
};
28 changes: 20 additions & 8 deletions src/noAccessMissingMemberRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {stringDistance} from './util/utils';
import {Ng2Walker} from './angular/ng2Walker';
import {RecursiveAngularExpressionVisitor} from './angular/templates/recursiveAngularExpressionVisitor';
import {ExpTypes} from './angular/expressionTypes';
import {getDeclaredMethodNames, getDeclaredPropertyNames} from './util/classDeclarationUtils';
import {getClassMembers} from './util/classDeclarationUtils';
import * as e from '@angular/compiler/src/expression_parser/ast';

import {Config} from './angular/config';
Expand Down Expand Up @@ -44,8 +44,10 @@ class SymbolAccessValidator extends RecursiveAngularExpressionVisitor {
symbolType = 'property';
}

available = getDeclaredMethodNames(this.context.controller)
.concat(getDeclaredPropertyNames(this.context.controller))
const typeChecker = this.languageService.getProgram().getTypeChecker();

available = getClassMembers(this.context.controller, typeChecker)
.map(p => p.name)
.concat(this.preDefinedVariables);

// Do not support nested properties yet
Expand Down Expand Up @@ -122,14 +124,24 @@ class SymbolAccessValidator extends RecursiveAngularExpressionVisitor {
}
}

export class Rule extends Lint.Rules.AbstractRule {
static FAILURE: string = 'The %s "%s" that you\'re trying to access does not exist in the class declaration.';
export class Rule extends Lint.Rules.TypedRule {
public static FAILURE: string = 'The %s "%s" that you\'re trying to access does not exist in the class declaration.';
public static metadata: Lint.IRuleMetadata = {
ruleName: 'no-access-missing-member',
description: 'Prevents bindings to expressions containing non-existing methods or properties',
optionsDescription: 'Not configurable',
options: null,
type: 'functionality',
typescriptOnly: true
};

public apply(sourceFile:ts.SourceFile): Lint.RuleFailure[] {
public applyWithProgram(sourceFile: ts.SourceFile, languageService: ts.LanguageService): Lint.RuleFailure[] {
const sf = languageService.getProgram().getSourceFiles().filter(sf => sf.fileName === sourceFile.fileName).pop();
return this.applyWithWalker(
new Ng2Walker(sourceFile,
new Ng2Walker(sf,
this.getOptions(), {
expressionVisitorCtrl: SymbolAccessValidator
expressionVisitorCtrl: SymbolAccessValidator,
languageService
}));
}
}
Expand Down
14 changes: 12 additions & 2 deletions src/noUnusedCssRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {Ng2Walker} from './angular/ng2Walker';
import {getComponentDecorator, isSimpleTemplateString, getDecoratorPropertyInitializer} from './util/utils';
import {BasicCssAstVisitor} from './angular/styles/basicCssAstVisitor';
import {BasicTemplateAstVisitor} from './angular/templates/basicTemplateAstVisitor';
import {VERSION} from '@angular/core';
import {
TemplateAst,
ElementAst,
Expand All @@ -15,6 +16,7 @@ import {CssAst, CssSelectorRuleAst, CssSelectorAst} from './angular/styles/cssAs
import {ComponentMetadata, StyleMetadata} from './angular/metadata';
import {ng2WalkerFactoryUtils} from './angular/ng2WalkerFactoryUtils';
import {logger} from './util/logger';
import {SemVerDSL} from './util/ngVersion';

const CssSelectorTokenizer = require('css-selector-tokenizer');

Expand Down Expand Up @@ -213,8 +215,16 @@ export class UnusedCssNg2Visitor extends Ng2Walker {
this.visitNg2Component(meta);
if (meta.template && meta.template.template) {
try {
this.templateAst =
new ElementAst('*', [], [], [], [], [], [], false, parseTemplate(meta.template.template.code), 0, null, null);
const ElementAstCtr = ElementAst as any;
SemVerDSL
.gte('4.0.0-beta.8', () => {
this.templateAst =
new ElementAstCtr('*', [], [], [], [], [], [], false, [], parseTemplate(meta.template.template.code), 0, null, null);
})
.else(() => {
this.templateAst =
new ElementAstCtr('*', [], [], [], [], [], [], false, parseTemplate(meta.template.template.code), 0, null, null);
});
} catch (e) {
logger.error('Cannot parse the template', e);
}
Expand Down
15 changes: 9 additions & 6 deletions src/templatesUsePublicRule.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Lint from 'tslint';
import * as ts from 'typescript';
import {stringDistance} from './util/utils';
import {getDeclaredProperties, getDeclaredMethods} from './util/classDeclarationUtils';
import {getClassMembers} from './util/classDeclarationUtils';
import {Ng2Walker} from './angular/ng2Walker';
import {RecursiveAngularExpressionVisitor} from './angular/templates/recursiveAngularExpressionVisitor';
import * as e from '@angular/compiler/src/expression_parser/ast';
Expand Down Expand Up @@ -34,7 +34,8 @@ class SymbolAccessValidator extends RecursiveAngularExpressionVisitor {
}
ast = <e.PropertyRead>receiver;
}
const allMembers = getDeclaredMethods(this.context.controller).concat(getDeclaredProperties(this.context.controller));
const allMembers = getClassMembers(this.context.controller, this.languageService.getProgram().getTypeChecker())
.map(s => s.declarations[0]);
const member = allMembers.filter((m: any) => m.name && m.name.text === ast.name).pop();
if (member) {
let isPublic = !member.modifiers || !member.modifiers
Expand Down Expand Up @@ -73,14 +74,16 @@ class SymbolAccessValidator extends RecursiveAngularExpressionVisitor {
}
}

export class Rule extends Lint.Rules.AbstractRule {
export class Rule extends Lint.Rules.TypedRule {
static FAILURE: string = 'The %s "%s" that you\'re trying to access does not exist in the class declaration.';

public apply(sourceFile:ts.SourceFile): Lint.RuleFailure[] {
public applyWithProgram(sourceFile: ts.SourceFile, languageService: ts.LanguageService): Lint.RuleFailure[] {
const sf = languageService.getProgram().getSourceFiles().filter(sf => sf.fileName === sourceFile.fileName).pop();
return this.applyWithWalker(
new Ng2Walker(sourceFile,
new Ng2Walker(sf,
this.getOptions(), {
expressionVisitorCtrl: SymbolAccessValidator
expressionVisitorCtrl: SymbolAccessValidator,
languageService
}));
}
}
Expand Down
97 changes: 56 additions & 41 deletions src/useLifeCycleInterfaceRule.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as Lint from 'tslint';
import * as ts from 'typescript';
import {sprintf} from 'sprintf-js';
import SyntaxKind = require('./util/syntaxKind');

const getInterfaceName = (t: any) => {
if (t.expression && t.expression.name) {
Expand All @@ -10,7 +9,7 @@ const getInterfaceName = (t: any) => {
return t.expression.text;
};

export class Rule extends Lint.Rules.AbstractRule {
export class Rule extends Lint.Rules.TypedRule {

static FAILURE:string = 'Implement lifecycle hook interface %s for method %s in class %s ($$09-01$$)';

Expand All @@ -27,56 +26,72 @@ export class Rule extends Lint.Rules.AbstractRule {
'OnDestroy'
];

public apply(sourceFile:ts.SourceFile):Lint.RuleFailure[] {
public applyWithProgram(sourceFile: ts.SourceFile, languageService: ts.LanguageService): Lint.RuleFailure[] {
const sf = languageService.getProgram().getSourceFiles().filter(sf => sf.fileName === sourceFile.fileName).pop();
return this.applyWithWalker(
new ClassMetadataWalker(sourceFile,
this.getOptions()));
new ClassMetadataWalker(sf,
this.getOptions(),
languageService));
}
}

export class ClassMetadataWalker extends Lint.RuleWalker {

visitClassDeclaration(node:ts.ClassDeclaration) {
let syntaxKind = SyntaxKind.current();
let className = node.name.text;
let interfaces = this.extractInterfaces(node,syntaxKind);
let methods = node.members.filter(m=>m.kind === syntaxKind.MethodDeclaration);
this.validateMethods(methods,interfaces,className);
super.visitClassDeclaration(node);
}
constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, protected languageService: ts.LanguageService) {
super(sourceFile, options);
}

private extractInterfaces(node:ts.ClassDeclaration,syntaxKind:SyntaxKind.SyntaxKind):string[]{
let interfaces:string[] = [];
if (node.heritageClauses) {
let interfacesClause = node.heritageClauses.filter(h=>h.token === syntaxKind.ImplementsKeyword);
if (interfacesClause.length !== 0) {
interfaces = interfacesClause[0].types.map(getInterfaceName);
}
}
return interfaces;
visitClassDeclaration(node: ts.ClassDeclaration) {
const typeChecker = this.languageService.getProgram().getTypeChecker();
let interfaces: string[] = [];
let baseType = node;
while (baseType) {
interfaces = interfaces.concat(this.extractInterfaces(baseType));
const baseTypes = typeChecker.getTypeAtLocation(baseType).getBaseTypes();
if (baseTypes[0] && baseTypes[0].getSymbol()) {
baseType = baseTypes[0].getSymbol().declarations[0] as ts.ClassDeclaration;
} else {
baseType = null;
}
}
let className = node.name.text;
let methods = node.members.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration);
this.validateMethods(methods, interfaces, className);
super.visitClassDeclaration(node);
}

private validateMethods( methods:any[],interfaces:string[],className:string){
methods.forEach(m=>{
let n = (<any>m.name).text;
if(n && this.isMethodValidHook(m,interfaces)){
let hookName = n.substr(2, n.lenght);
this.addFailure(
this.createFailure(
m.name.getStart(),
m.name.getWidth(),
sprintf.apply(this, [Rule.FAILURE, hookName,Rule.HOOKS_PREFIX + hookName, className])));
}
});
private extractInterfaces(node: ts.ClassDeclaration): string[] {
let interfaces:string[] = [];
if (node.heritageClauses) {
let interfacesClause = node.heritageClauses.filter(h => h.token === ts.SyntaxKind.ImplementsKeyword);
if (interfacesClause.length !== 0) {
interfaces = interfacesClause[0].types.map(getInterfaceName);
}
}
return interfaces;
}

private isMethodValidHook(m:any,interfaces:string[]):boolean{
let n = (<any>m.name).text;
let isNg:boolean = n.substr(0, 2) === Rule.HOOKS_PREFIX;
private validateMethods(methods:any[],interfaces:string[],className:string){
methods.forEach(m => {
let n = (<any>m.name).text;
if (n && this.isMethodValidHook(m,interfaces)){
let hookName = n.substr(2, n.lenght);
let isHook = Rule.LIFE_CYCLE_HOOKS_NAMES.indexOf(hookName) !== -1;
let isNotIn:boolean = interfaces.indexOf(hookName) === -1;
return isNg && isHook && isNotIn;
}
this.addFailure(
this.createFailure(
m.name.getStart(),
m.name.getWidth(),
sprintf.apply(this, [Rule.FAILURE, hookName,Rule.HOOKS_PREFIX + hookName, className])));
}
});
}

private isMethodValidHook(m:any,interfaces:string[]):boolean{
let n = (<any>m.name).text;
let isNg:boolean = n.substr(0, 2) === Rule.HOOKS_PREFIX;
let hookName = n.substr(2, n.lenght);
let isHook = Rule.LIFE_CYCLE_HOOKS_NAMES.indexOf(hookName) !== -1;
let isNotIn:boolean = interfaces.indexOf(hookName) === -1;
return isNg && isHook && isNotIn;
}

}

0 comments on commit 3e2176e

Please sign in to comment.