Skip to content

Commit f507b7c

Browse files
committed
feat: add ng2 walker
Fix #6
1 parent b01926c commit f507b7c

File tree

4 files changed

+132
-0
lines changed

4 files changed

+132
-0
lines changed

manual_typings/chai-spies.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare var spy: any;
2+
3+
declare module "chai-spies" {
4+
export = spy;
5+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"homepage": "https://github.com/mgechev/ng2lint#readme",
3030
"devDependencies": {
3131
"chai": "^3.5.0",
32+
"chai-spies": "^0.7.1",
3233
"mocha": "^2.4.5",
3334
"ts-node": "^0.5.5",
3435
"typings": "^0.6.8"

src/util/ng2Walker.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as Lint from 'tslint/lib/lint';
2+
import * as ts from 'typescript';
3+
4+
const getDecoratorName = (decorator: ts.Decorator) => {
5+
let baseExpr = <any>decorator.expression || {};
6+
let expr = baseExpr.expression || {};
7+
return expr.text;
8+
};
9+
10+
const getDecoratorStringArgs = (decorator: ts.Decorator) => {
11+
let baseExpr = <any>decorator.expression || {};
12+
let expr = baseExpr.expression || {};
13+
let args = baseExpr.arguments || [];
14+
return args.map(a => (a.kind === ts.SyntaxKind.StringLiteral) ? a.text : null);
15+
};
16+
17+
export class Ng2Walker extends Lint.RuleWalker {
18+
visitClassDeclaration(declaration: ts.ClassDeclaration) {
19+
(declaration.decorators || []).forEach(this.visitClassDecorator.bind(this));
20+
super.visitClassDeclaration(declaration);
21+
}
22+
visitMethodDeclaration(method: ts.MethodDeclaration) {
23+
(method.decorators || []).forEach(this.visitMethodDecorator.bind(this));
24+
super.visitMethodDeclaration(method);
25+
}
26+
protected visitMethodDecorator(decorator: ts.Decorator) {
27+
let name = getDecoratorName(decorator);
28+
if (name === 'HostListener') {
29+
this.visitNg2HostListener(<ts.MethodDeclaration>decorator.parent, decorator, getDecoratorStringArgs(decorator));
30+
}
31+
}
32+
visitPropertyDeclaration(prop: ts.PropertyDeclaration) {
33+
(prop.decorators || []).forEach(this.visitPropertyDecorator.bind(this));
34+
super.visitPropertyDeclaration(prop);
35+
}
36+
protected visitPropertyDecorator(decorator: ts.Decorator) {
37+
let name = getDecoratorName(decorator);
38+
switch (name) {
39+
case 'Input':
40+
this.visitNg2Input(<ts.PropertyDeclaration>decorator.parent, decorator, getDecoratorStringArgs(decorator));
41+
break;
42+
case 'Output':
43+
this.visitNg2Output(<ts.PropertyDeclaration>decorator.parent, decorator, getDecoratorStringArgs(decorator));
44+
break;
45+
case 'HostBinding':
46+
this.visitNg2HostBinding(<ts.PropertyDeclaration>decorator.parent, decorator, getDecoratorStringArgs(decorator));
47+
break;
48+
}
49+
}
50+
protected visitClassDecorator(decorator: ts.Decorator) {
51+
let name = getDecoratorName(decorator);
52+
if (name === 'Component') {
53+
this.visitNg2Component(<ts.ClassDeclaration>decorator.parent, decorator);
54+
} else if (name === 'Directive') {
55+
this.visitNg2Directive(<ts.ClassDeclaration>decorator.parent, decorator);
56+
}
57+
}
58+
protected visitNg2Component(controller: ts.ClassDeclaration, decorator: ts.Decorator) {}
59+
protected visitNg2Directive(controller: ts.ClassDeclaration, decorator: ts.Decorator) {}
60+
protected visitNg2Input(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {}
61+
protected visitNg2Output(property: ts.PropertyDeclaration, output: ts.Decorator, args: string[]) {}
62+
protected visitNg2HostBinding(property: ts.PropertyDeclaration, decorator: ts.Decorator, args: string[]) {}
63+
protected visitNg2HostListener(method: ts.MethodDeclaration, decorator: ts.Decorator, args: string[]) {}
64+
}

test/util/ng2Walker.spec.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as ts from 'typescript';
2+
import * as tslint from 'tslint/lib/lint';
3+
4+
import {Ng2Walker} from '../../src/util/ng2Walker';
5+
import chai = require('chai');
6+
import * as spies from 'chai-spies';
7+
8+
chai.use(spies);
9+
10+
const chaiSpy = (<any>chai).spy;
11+
describe('ng2Walker', () => {
12+
it('should visit components and directives', () => {
13+
let source = `
14+
@Component({
15+
selector: 'foo',
16+
template: 'bar'
17+
})
18+
class Baz {}
19+
@Directive({
20+
selector: '[baz]'
21+
})
22+
class Foobar {}
23+
`;
24+
let ruleArgs: tslint.IOptions = {
25+
ruleName: 'foo',
26+
ruleArguments: ['foo'],
27+
disabledIntervals: null
28+
};
29+
let sf = ts.createSourceFile('foo', source, null);
30+
let walker = new Ng2Walker(sf, ruleArgs);
31+
let cmpSpy = chaiSpy.on(walker, 'visitNg2Component');
32+
let dirSpy = chaiSpy.on(walker, 'visitNg2Directive');
33+
walker.walk(sf);
34+
(<any>chai.expect(cmpSpy).to.have.been).called();
35+
(<any>chai.expect(dirSpy).to.have.been).called();
36+
});
37+
it('should visit inputs and outputs with args', () => {
38+
let source = `
39+
@Component({
40+
selector: 'foo',
41+
})
42+
class Baz {
43+
@Input('bar')
44+
foo;
45+
@Output('baz')
46+
foobar;
47+
}
48+
`;
49+
let ruleArgs: tslint.IOptions = {
50+
ruleName: 'foo',
51+
ruleArguments: ['foo'],
52+
disabledIntervals: null
53+
};
54+
let sf = ts.createSourceFile('foo', source, null);
55+
let walker = new Ng2Walker(sf, ruleArgs);
56+
let outputsSpy = chaiSpy.on(walker, 'visitNg2Output');
57+
let inputsSpy = chaiSpy.on(walker, 'visitNg2Input');
58+
walker.walk(sf);
59+
(<any>chai.expect(outputsSpy).to.have.been).called();
60+
(<any>chai.expect(inputsSpy).to.have.been).called();
61+
});
62+
});

0 commit comments

Comments
 (0)