Skip to content

Commit

Permalink
feat(rules): add AoT related rule for binding only to public members
Browse files Browse the repository at this point in the history
Fix #87
  • Loading branch information
mgechev committed Sep 19, 2016
1 parent cd8f068 commit c849808
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/angular/ng2Walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const getExpressionDisplacement = (binding: any) => {
// The whitespace are possible only in:
// `[foo.bar] = "...."`,
// and they are verything except the attrLen and the valLen (-1 because of the close quote).
let whitespace = totalLength - (attrLen + valLen) - 1
let whitespace = totalLength - (attrLen + valLen) - 1;
// The resulted displacement is the length of the attribute + the whitespaces which
// can be located ONLY before the value (the binding).
result = whitespace + attrLen + binding.sourceSpan.start.offset;
Expand Down
80 changes: 80 additions & 0 deletions src/templatesUsePublicRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as Lint from 'tslint/lib/lint';
import * as ts from 'typescript';
import {sprintf} from 'sprintf-js';
import { stringDistance } from './util/utils';
import {Ng2Walker} from './angular/ng2Walker';
import {RecursiveAngularExpressionVisitor} from './angular/recursiveAngularExpressionVisitor';
import {getDeclaredMethodNames, getDeclaredPropertyNames} from './util/classDeclarationUtils';
import * as e from '@angular/compiler/src/expression_parser/ast';
import SyntaxKind = require('./util/syntaxKind');

enum DeclarationType {
Property,
Method
};

class SymbolAccessValidator extends RecursiveAngularExpressionVisitor {
visitPropertyRead(ast: e.PropertyRead, context: any): any {
return this.doCheck(ast, DeclarationType.Property, context);
}

visitMethodCall(ast: e.MethodCall, context: any): any {
this.doCheck(ast, DeclarationType.Method, context);
}

visitPropertyWrite(ast: e.PropertyWrite, context: any): any {
this.doCheck(ast, DeclarationType.Property, context);
}

private doCheck(ast: e.MethodCall | e.PropertyRead | e.PropertyWrite, type: DeclarationType, context: any): any {
const member = this.context.members.filter((m: any) => m.name && m.name.text === ast.name).pop();
if (member) {
let isPublic = !member.modifiers;
if (member.modifiers) {
isPublic = member.modifiers.some(m => m.kind === SyntaxKind.current().PublicKeyword);
}
const width = ast.name.length;
if (!isPublic) {
const failureString = 'You can bind only to public class members.';
this.addFailure(this.createFailure(this.basePosition + ast.span.start, width, failureString));
}
}
}

private getTopSuggestion(list: string[], current: string) {
const result = [];
const tmp = list.map(e => {
return {
element: e,
distance: stringDistance(e, current)
};
}).sort((a, b) => a.distance - b.distance);
const first = tmp.shift();
if (!first) {
return [];
} else {
result.push(first);
let current: any;
while (current = tmp.shift()) {
if (current.distance !== first.distance) {
return result;
} else {
result.push(current);
}
}
return result;
}
}
}

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.';

public apply(sourceFile:ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(
new Ng2Walker(sourceFile,
this.getOptions(),
SymbolAccessValidator));
}
}

109 changes: 109 additions & 0 deletions test/templatesUsePublicRule.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {assertFailure, assertSuccess} from './testHelper';

describe('access-missing-declaration', () => {
describe('invalid expressions', () => {
it('should fail when interpolating private property', () => {
let source = `
@Component({
selector: 'foobar',
template: '<div>{{ foo }}</div>
})
class Test {
private foo: number;
}`;
assertFailure('templates-use-public', source, {
message: 'You can bind only to public class members.',
startPosition: {
line: 3,
character: 29
},
endPosition: {
line: 3,
character: 32
}
});
});

it('should fail when interpolating protected property', () => {
let source = `
@Component({
selector: 'foobar',
template: '<div>{{ foo }}</div>
})
class Test {
protected foo: number;
}`;
assertFailure('templates-use-public', source, {
message: 'You can bind only to public class members.',
startPosition: {
line: 3,
character: 29
},
endPosition: {
line: 3,
character: 32
}
});
});

it('should fail when binding to protected method', () => {
let source = `
@Component({
selector: 'foobar',
template: '<div [bar]="foo()"></div>
})
class Test {
protected foo() {};
}`;
assertFailure('templates-use-public', source, {
message: 'You can bind only to public class members.',
startPosition: {
line: 3,
character: 33
},
endPosition: {
line: 3,
character: 36
}
});
});

});

describe('valid expressions', () => {
it('should succeed with public property', () => {
let source = `
@Component({
selector: 'foobar',
template: '<div>{{ foo }}</div>
})
class Test {
foo: number;
}`;
assertSuccess('templates-use-public', source);
});

it('should succeed with non-existing property', () => {
let source = `
@Component({
selector: 'foobar',
template: '<div>{{ foo }}</div>
})
class Test {
}`;
assertSuccess('templates-use-public', source);
});

it('should succeed on public method', () => {
let source = `
@Component({
selector: 'foobar',
template: '<div>{{ foo() }}</div>
})
class Test {
public foo() {}
}`;
assertSuccess('templates-use-public', source);
});
});
});

0 comments on commit c849808

Please sign in to comment.