feat(host): limits host properties to renames
This commit is contained in:
parent
c1a494bc37
commit
92ffc465d6
|
@ -45,7 +45,8 @@ import {
|
|||
SafeMethodCall,
|
||||
FunctionCall,
|
||||
TemplateBinding,
|
||||
ASTWithSource
|
||||
ASTWithSource,
|
||||
AstVisitor
|
||||
} from './ast';
|
||||
|
||||
|
||||
|
@ -73,6 +74,12 @@ export class Parser {
|
|||
return new ASTWithSource(ast, input, location);
|
||||
}
|
||||
|
||||
parseSimpleBinding(input: string, location: string): ASTWithSource {
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseSimpleBinding();
|
||||
return new ASTWithSource(ast, input, location);
|
||||
}
|
||||
|
||||
parseTemplateBindings(input: string, location: any): List<TemplateBinding> {
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();
|
||||
|
@ -202,6 +209,14 @@ class _ParseAST {
|
|||
return new Chain(exprs);
|
||||
}
|
||||
|
||||
parseSimpleBinding(): AST {
|
||||
var ast = this.parseChain();
|
||||
if (!SimpleExpressionChecker.check(ast)) {
|
||||
this.error(`Simple binding expression can only contain field access and constants'`);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
parsePipe() {
|
||||
var result = this.parseExpression();
|
||||
if (this.optionalOperator("|")) {
|
||||
|
@ -590,3 +605,57 @@ class _ParseAST {
|
|||
`Parser Error: ${message} ${location} [${this.input}] in ${this.location}`);
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleExpressionChecker implements AstVisitor {
|
||||
static check(ast: AST) {
|
||||
var s = new SimpleExpressionChecker();
|
||||
ast.visit(s);
|
||||
return s.simple;
|
||||
}
|
||||
|
||||
simple = true;
|
||||
|
||||
visitImplicitReceiver(ast: ImplicitReceiver) {}
|
||||
|
||||
visitInterpolation(ast: Interpolation) { this.simple = false; }
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive) {}
|
||||
|
||||
visitAccessMember(ast: AccessMember) {}
|
||||
|
||||
visitSafeAccessMember(ast: SafeAccessMember) { this.simple = false; }
|
||||
|
||||
visitMethodCall(ast: MethodCall) { this.simple = false; }
|
||||
|
||||
visitSafeMethodCall(ast: SafeMethodCall) { this.simple = false; }
|
||||
|
||||
visitFunctionCall(ast: FunctionCall) { this.simple = false; }
|
||||
|
||||
visitLiteralArray(ast: LiteralArray) { this.visitAll(ast.expressions); }
|
||||
|
||||
visitLiteralMap(ast: LiteralMap) { this.visitAll(ast.values); }
|
||||
|
||||
visitBinary(ast: Binary) { this.simple = false; }
|
||||
|
||||
visitPrefixNot(ast: PrefixNot) { this.simple = false; }
|
||||
|
||||
visitConditional(ast: Conditional) { this.simple = false; }
|
||||
|
||||
visitPipe(ast: BindingPipe) { this.simple = false; }
|
||||
|
||||
visitKeyedAccess(ast: KeyedAccess) { this.simple = false; }
|
||||
|
||||
visitAll(asts: List<any>) {
|
||||
var res = ListWrapper.createFixedSize(asts.length);
|
||||
for (var i = 0; i < asts.length; ++i) {
|
||||
res[i] = asts[i].visit(this);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
visitChain(ast: Chain) { this.simple = false; }
|
||||
|
||||
visitAssignment(ast: Assignment) { this.simple = false; }
|
||||
|
||||
visitIf(ast: If) { this.simple = false; }
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {Directive, Renderer, ElementRef} from 'angular2/angular2';
|
||||
import {NgControl} from './ng_control';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {setProperty} from './shared';
|
||||
|
||||
/**
|
||||
|
@ -20,12 +21,12 @@ import {setProperty} from './shared';
|
|||
'(change)': 'onChange($event.target.checked)',
|
||||
'(blur)': 'onTouched()',
|
||||
'[checked]': 'checked',
|
||||
'[class.ng-untouched]': 'cd.control?.untouched == true',
|
||||
'[class.ng-touched]': 'cd.control?.touched == true',
|
||||
'[class.ng-pristine]': 'cd.control?.pristine == true',
|
||||
'[class.ng-dirty]': 'cd.control?.dirty == true',
|
||||
'[class.ng-valid]': 'cd.control?.valid == true',
|
||||
'[class.ng-invalid]': 'cd.control?.valid == false'
|
||||
'[class.ng-untouched]': 'ngClassUntouched',
|
||||
'[class.ng-touched]': 'ngClassTouched',
|
||||
'[class.ng-pristine]': 'ngClassPristine',
|
||||
'[class.ng-dirty]': 'ngClassDirty',
|
||||
'[class.ng-valid]': 'ngClassValid',
|
||||
'[class.ng-invalid]': 'ngClassInvalid'
|
||||
}
|
||||
})
|
||||
export class CheckboxControlValueAccessor implements ControlValueAccessor {
|
||||
|
@ -44,6 +45,21 @@ export class CheckboxControlValueAccessor implements ControlValueAccessor {
|
|||
setProperty(this.renderer, this.elementRef, "checked", value);
|
||||
}
|
||||
|
||||
get ngClassUntouched(): boolean {
|
||||
return isPresent(this.cd.control) ? this.cd.control.untouched : false;
|
||||
}
|
||||
get ngClassTouched(): boolean {
|
||||
return isPresent(this.cd.control) ? this.cd.control.touched : false;
|
||||
}
|
||||
get ngClassPristine(): boolean {
|
||||
return isPresent(this.cd.control) ? this.cd.control.pristine : false;
|
||||
}
|
||||
get ngClassDirty(): boolean { return isPresent(this.cd.control) ? this.cd.control.dirty : false; }
|
||||
get ngClassValid(): boolean { return isPresent(this.cd.control) ? this.cd.control.valid : false; }
|
||||
get ngClassInvalid(): boolean {
|
||||
return isPresent(this.cd.control) ? !this.cd.control.valid : false;
|
||||
}
|
||||
|
||||
registerOnChange(fn): void { this.onChange = fn; }
|
||||
registerOnTouched(fn): void { this.onTouched = fn; }
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {Directive, Renderer, ElementRef} from 'angular2/angular2';
|
||||
import {NgControl} from './ng_control';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {isBlank} from 'angular2/src/facade/lang';
|
||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {setProperty} from './shared';
|
||||
|
||||
/**
|
||||
|
@ -23,16 +23,17 @@ import {setProperty} from './shared';
|
|||
'(input)': 'onChange($event.target.value)',
|
||||
'(blur)': 'onTouched()',
|
||||
'[value]': 'value',
|
||||
'[class.ng-untouched]': 'cd.control?.untouched == true',
|
||||
'[class.ng-touched]': 'cd.control?.touched == true',
|
||||
'[class.ng-pristine]': 'cd.control?.pristine == true',
|
||||
'[class.ng-dirty]': 'cd.control?.dirty == true',
|
||||
'[class.ng-valid]': 'cd.control?.valid == true',
|
||||
'[class.ng-invalid]': 'cd.control?.valid == false'
|
||||
'[class.ng-untouched]': 'ngClassUntouched',
|
||||
'[class.ng-touched]': 'ngClassTouched',
|
||||
'[class.ng-pristine]': 'ngClassPristine',
|
||||
'[class.ng-dirty]': 'ngClassDirty',
|
||||
'[class.ng-valid]': 'ngClassValid',
|
||||
'[class.ng-invalid]': 'ngClassInvalid'
|
||||
}
|
||||
})
|
||||
export class DefaultValueAccessor implements ControlValueAccessor {
|
||||
value: string = null;
|
||||
|
||||
onChange = (_) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
|
@ -47,6 +48,21 @@ export class DefaultValueAccessor implements ControlValueAccessor {
|
|||
setProperty(this.renderer, this.elementRef, 'value', this.value);
|
||||
}
|
||||
|
||||
get ngClassUntouched(): boolean {
|
||||
return isPresent(this.cd.control) ? this.cd.control.untouched : false;
|
||||
}
|
||||
get ngClassTouched(): boolean {
|
||||
return isPresent(this.cd.control) ? this.cd.control.touched : false;
|
||||
}
|
||||
get ngClassPristine(): boolean {
|
||||
return isPresent(this.cd.control) ? this.cd.control.pristine : false;
|
||||
}
|
||||
get ngClassDirty(): boolean { return isPresent(this.cd.control) ? this.cd.control.dirty : false; }
|
||||
get ngClassValid(): boolean { return isPresent(this.cd.control) ? this.cd.control.valid : false; }
|
||||
get ngClassInvalid(): boolean {
|
||||
return isPresent(this.cd.control) ? !this.cd.control.valid : false;
|
||||
}
|
||||
|
||||
registerOnChange(fn): void { this.onChange = fn; }
|
||||
|
||||
registerOnTouched(fn): void { this.onTouched = fn; }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {Directive, Query, QueryList, Renderer, ElementRef} from 'angular2/angular2';
|
||||
import {NgControl} from './ng_control';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {setProperty} from './shared';
|
||||
|
||||
/**
|
||||
|
@ -30,12 +31,12 @@ export class NgSelectOption {
|
|||
'(input)': 'onChange($event.target.value)',
|
||||
'(blur)': 'onTouched()',
|
||||
'[value]': 'value',
|
||||
'[class.ng-untouched]': 'cd.control?.untouched == true',
|
||||
'[class.ng-touched]': 'cd.control?.touched == true',
|
||||
'[class.ng-pristine]': 'cd.control?.pristine == true',
|
||||
'[class.ng-dirty]': 'cd.control?.dirty == true',
|
||||
'[class.ng-valid]': 'cd.control?.valid == true',
|
||||
'[class.ng-invalid]': 'cd.control?.valid == false'
|
||||
'[class.ng-untouched]': 'ngClassUntouched',
|
||||
'[class.ng-touched]': 'ngClassTouched',
|
||||
'[class.ng-pristine]': 'ngClassPristine',
|
||||
'[class.ng-dirty]': 'ngClassDirty',
|
||||
'[class.ng-valid]': 'ngClassValid',
|
||||
'[class.ng-invalid]': 'ngClassInvalid'
|
||||
}
|
||||
})
|
||||
export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
|
@ -57,6 +58,21 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
|
|||
setProperty(this.renderer, this.elementRef, "value", value);
|
||||
}
|
||||
|
||||
get ngClassUntouched(): boolean {
|
||||
return isPresent(this.cd.control) ? this.cd.control.untouched : false;
|
||||
}
|
||||
get ngClassTouched(): boolean {
|
||||
return isPresent(this.cd.control) ? this.cd.control.touched : false;
|
||||
}
|
||||
get ngClassPristine(): boolean {
|
||||
return isPresent(this.cd.control) ? this.cd.control.pristine : false;
|
||||
}
|
||||
get ngClassDirty(): boolean { return isPresent(this.cd.control) ? this.cd.control.dirty : false; }
|
||||
get ngClassValid(): boolean { return isPresent(this.cd.control) ? this.cd.control.valid : false; }
|
||||
get ngClassInvalid(): boolean {
|
||||
return isPresent(this.cd.control) ? !this.cd.control.valid : false;
|
||||
}
|
||||
|
||||
registerOnChange(fn): void { this.onChange = fn; }
|
||||
registerOnTouched(fn): void { this.onTouched = fn; }
|
||||
|
||||
|
|
|
@ -168,8 +168,8 @@ export class DirectiveParser implements CompileStep {
|
|||
}
|
||||
|
||||
_bindHostProperty(hostPropertyName, expression, compileElement, directiveBinderBuilder) {
|
||||
var ast = this._parser.parseBinding(expression,
|
||||
`hostProperties of ${compileElement.elementDescription}`);
|
||||
var ast = this._parser.parseSimpleBinding(
|
||||
expression, `hostProperties of ${compileElement.elementDescription}`);
|
||||
directiveBinderBuilder.bindHostProperty(hostPropertyName, ast);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import {Parser} from 'angular2/src/change_detection/parser/parser';
|
|||
import {Unparser} from './unparser';
|
||||
import {Lexer} from 'angular2/src/change_detection/parser/lexer';
|
||||
import {Locals} from 'angular2/src/change_detection/parser/locals';
|
||||
import {BindingPipe, LiteralPrimitive} from 'angular2/src/change_detection/parser/ast';
|
||||
import {BindingPipe, LiteralPrimitive, AST} from 'angular2/src/change_detection/parser/ast';
|
||||
|
||||
class TestData {
|
||||
constructor(public a?: any, public b?: any, public fnReturnValue?: any) {}
|
||||
|
@ -39,6 +39,12 @@ export function main() {
|
|||
return createParser().parseInterpolation(text, location);
|
||||
}
|
||||
|
||||
function parseSimpleBinding(text, location = null): any {
|
||||
return createParser().parseSimpleBinding(text, location);
|
||||
}
|
||||
|
||||
function unparse(ast: AST): string { return new Unparser().unparse(ast); }
|
||||
|
||||
function emptyLocals() { return new Locals(null, new Map()); }
|
||||
|
||||
function evalAction(text, passedInContext = null, passedInLocals = null) {
|
||||
|
@ -620,6 +626,24 @@ export function main() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("parseSimpleBinding", () => {
|
||||
it("should parse a field access", () => {
|
||||
var p = parseSimpleBinding("name");
|
||||
expect(unparse(p)).toEqual("name");
|
||||
});
|
||||
|
||||
it("should parse a constant", () => {
|
||||
var p = parseSimpleBinding("[1, 2]");
|
||||
expect(unparse(p)).toEqual("[1, 2]");
|
||||
});
|
||||
|
||||
it("should throw when the given expression is not just a field name", () => {
|
||||
expect(() => parseSimpleBinding("name + 1"))
|
||||
.toThrowError(new RegExp(
|
||||
'Simple binding expression can only contain field access and constants'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrapLiteralPrimitive', () => {
|
||||
it('should wrap a literal primitive', () => {
|
||||
expect(createParser().wrapLiteralPrimitive("foo", null).eval(null, emptyLocals()))
|
||||
|
|
|
@ -670,7 +670,7 @@ export function main() {
|
|||
|
||||
var input = view.querySelector("input");
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-untouched", "ng-pristine", "ng-invalid"]);
|
||||
.toEqual(['ng-binding', 'ng-untouched', 'ng-pristine', 'ng-invalid']);
|
||||
|
||||
dispatchEvent(input, "blur");
|
||||
view.detectChanges();
|
||||
|
|
|
@ -24,6 +24,7 @@ export function main() {
|
|||
decoratorWithMultipleAttrs,
|
||||
someDirectiveWithProps,
|
||||
someDirectiveWithHostProperties,
|
||||
someDirectiveWithInvalidHostProperties,
|
||||
someDirectiveWithHostAttributes,
|
||||
someDirectiveWithEvents,
|
||||
someDirectiveWithGlobalEvents,
|
||||
|
@ -103,6 +104,12 @@ export function main() {
|
|||
expect(ast.source).toEqual('dirProp');
|
||||
});
|
||||
|
||||
it('should throw when parsing invalid host properties', () => {
|
||||
expect(() => process(el('<input some-decor-with-invalid-host-props>')))
|
||||
.toThrowError(
|
||||
new RegExp('Simple binding expression can only contain field access and constants'));
|
||||
});
|
||||
|
||||
it('should set host element attributes', () => {
|
||||
var element = el('<input some-decor-with-host-attrs>');
|
||||
var results = process(element);
|
||||
|
@ -235,6 +242,11 @@ var someDirectiveWithHostProperties = DirectiveMetadata.create({
|
|||
host: MapWrapper.createFromStringMap({'[hostProp]': 'dirProp'})
|
||||
});
|
||||
|
||||
var someDirectiveWithInvalidHostProperties = DirectiveMetadata.create({
|
||||
selector: '[some-decor-with-invalid-host-props]',
|
||||
host: MapWrapper.createFromStringMap({'[hostProp]': 'dirProp + dirProp2'})
|
||||
});
|
||||
|
||||
var someDirectiveWithHostAttributes = DirectiveMetadata.create({
|
||||
selector: '[some-decor-with-host-attrs]',
|
||||
host: MapWrapper.createFromStringMap({'attr_name': 'attr_val', 'class': 'foo bar'})
|
||||
|
|
Loading…
Reference in New Issue