feat(parser): allows users install custom AST transformers
Closes #5382
This commit is contained in:
parent
125fa3885e
commit
a43ed79ee7
|
@ -13,7 +13,8 @@ import {
|
|||
assertionsEnabled,
|
||||
isBlank
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {Injectable, Inject, Injector, OpaqueToken, Optional} from 'angular2/core';
|
||||
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
import {Parser, AST, ASTWithSource} from 'angular2/src/core/change_detection/change_detection';
|
||||
import {TemplateBinding} from 'angular2/src/core/change_detection/parser/ast';
|
||||
|
@ -28,6 +29,8 @@ import {
|
|||
BoundEventAst,
|
||||
VariableAst,
|
||||
TemplateAst,
|
||||
TemplateAstVisitor,
|
||||
templateVisitAll,
|
||||
TextAst,
|
||||
BoundTextAst,
|
||||
EmbeddedTemplateAst,
|
||||
|
@ -78,6 +81,8 @@ const STYLE_PREFIX = 'style';
|
|||
|
||||
var TEXT_CSS_SELECTOR = CssSelector.parse('*')[0];
|
||||
|
||||
export const TEMPLATE_TRANSFORMS = CONST_EXPR(new OpaqueToken('TemplateTransforms'));
|
||||
|
||||
export class TemplateParseError extends ParseError {
|
||||
constructor(message: string, location: ParseLocation) { super(location, message); }
|
||||
}
|
||||
|
@ -85,7 +90,8 @@ export class TemplateParseError extends ParseError {
|
|||
@Injectable()
|
||||
export class TemplateParser {
|
||||
constructor(private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry,
|
||||
private _htmlParser: HtmlParser) {}
|
||||
private _htmlParser: HtmlParser,
|
||||
@Optional() @Inject(TEMPLATE_TRANSFORMS) public transforms: TemplateAstVisitor[]) {}
|
||||
|
||||
parse(template: string, directives: CompileDirectiveMetadata[],
|
||||
templateUrl: string): TemplateAst[] {
|
||||
|
@ -97,6 +103,10 @@ export class TemplateParser {
|
|||
var errorString = errors.join('\n');
|
||||
throw new BaseException(`Template parse errors:\n${errorString}`);
|
||||
}
|
||||
if (isPresent(this.transforms)) {
|
||||
this.transforms.forEach(
|
||||
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,6 @@ export class KeyedWrite extends AST {
|
|||
|
||||
export class BindingPipe extends AST {
|
||||
constructor(public exp: AST, public name: string, public args: any[]) { super(); }
|
||||
|
||||
visit(visitor: AstVisitor): any { return visitor.visitPipe(this); }
|
||||
}
|
||||
|
||||
|
@ -79,7 +78,7 @@ export class LiteralMap extends AST {
|
|||
|
||||
export class Interpolation extends AST {
|
||||
constructor(public strings: any[], public expressions: any[]) { super(); }
|
||||
visit(visitor: AstVisitor) { visitor.visitInterpolation(this); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitInterpolation(this); }
|
||||
}
|
||||
|
||||
export class Binary extends AST {
|
||||
|
@ -214,68 +213,66 @@ export class RecursiveAstVisitor implements AstVisitor {
|
|||
}
|
||||
|
||||
export class AstTransformer implements AstVisitor {
|
||||
visitImplicitReceiver(ast: ImplicitReceiver): ImplicitReceiver { return ast; }
|
||||
visitImplicitReceiver(ast: ImplicitReceiver): AST { return ast; }
|
||||
|
||||
visitInterpolation(ast: Interpolation): Interpolation {
|
||||
visitInterpolation(ast: Interpolation): AST {
|
||||
return new Interpolation(ast.strings, this.visitAll(ast.expressions));
|
||||
}
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive): LiteralPrimitive {
|
||||
return new LiteralPrimitive(ast.value);
|
||||
}
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive): AST { return new LiteralPrimitive(ast.value); }
|
||||
|
||||
visitPropertyRead(ast: PropertyRead): PropertyRead {
|
||||
visitPropertyRead(ast: PropertyRead): AST {
|
||||
return new PropertyRead(ast.receiver.visit(this), ast.name, ast.getter);
|
||||
}
|
||||
|
||||
visitPropertyWrite(ast: PropertyWrite): PropertyWrite {
|
||||
visitPropertyWrite(ast: PropertyWrite): AST {
|
||||
return new PropertyWrite(ast.receiver.visit(this), ast.name, ast.setter, ast.value);
|
||||
}
|
||||
|
||||
visitSafePropertyRead(ast: SafePropertyRead): SafePropertyRead {
|
||||
visitSafePropertyRead(ast: SafePropertyRead): AST {
|
||||
return new SafePropertyRead(ast.receiver.visit(this), ast.name, ast.getter);
|
||||
}
|
||||
|
||||
visitMethodCall(ast: MethodCall): MethodCall {
|
||||
visitMethodCall(ast: MethodCall): AST {
|
||||
return new MethodCall(ast.receiver.visit(this), ast.name, ast.fn, this.visitAll(ast.args));
|
||||
}
|
||||
|
||||
visitSafeMethodCall(ast: SafeMethodCall): SafeMethodCall {
|
||||
visitSafeMethodCall(ast: SafeMethodCall): AST {
|
||||
return new SafeMethodCall(ast.receiver.visit(this), ast.name, ast.fn, this.visitAll(ast.args));
|
||||
}
|
||||
|
||||
visitFunctionCall(ast: FunctionCall): FunctionCall {
|
||||
visitFunctionCall(ast: FunctionCall): AST {
|
||||
return new FunctionCall(ast.target.visit(this), this.visitAll(ast.args));
|
||||
}
|
||||
|
||||
visitLiteralArray(ast: LiteralArray): LiteralArray {
|
||||
visitLiteralArray(ast: LiteralArray): AST {
|
||||
return new LiteralArray(this.visitAll(ast.expressions));
|
||||
}
|
||||
|
||||
visitLiteralMap(ast: LiteralMap): LiteralMap {
|
||||
visitLiteralMap(ast: LiteralMap): AST {
|
||||
return new LiteralMap(ast.keys, this.visitAll(ast.values));
|
||||
}
|
||||
|
||||
visitBinary(ast: Binary): Binary {
|
||||
visitBinary(ast: Binary): AST {
|
||||
return new Binary(ast.operation, ast.left.visit(this), ast.right.visit(this));
|
||||
}
|
||||
|
||||
visitPrefixNot(ast: PrefixNot): PrefixNot { return new PrefixNot(ast.expression.visit(this)); }
|
||||
visitPrefixNot(ast: PrefixNot): AST { return new PrefixNot(ast.expression.visit(this)); }
|
||||
|
||||
visitConditional(ast: Conditional): Conditional {
|
||||
visitConditional(ast: Conditional): AST {
|
||||
return new Conditional(ast.condition.visit(this), ast.trueExp.visit(this),
|
||||
ast.falseExp.visit(this));
|
||||
}
|
||||
|
||||
visitPipe(ast: BindingPipe): BindingPipe {
|
||||
visitPipe(ast: BindingPipe): AST {
|
||||
return new BindingPipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args));
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: KeyedRead): KeyedRead {
|
||||
visitKeyedRead(ast: KeyedRead): AST {
|
||||
return new KeyedRead(ast.obj.visit(this), ast.key.visit(this));
|
||||
}
|
||||
|
||||
visitKeyedWrite(ast: KeyedWrite): KeyedWrite {
|
||||
visitKeyedWrite(ast: KeyedWrite): AST {
|
||||
return new KeyedWrite(ast.obj.visit(this), ast.key.visit(this), ast.value.visit(this));
|
||||
}
|
||||
|
||||
|
@ -287,5 +284,5 @@ export class AstTransformer implements AstVisitor {
|
|||
return res;
|
||||
}
|
||||
|
||||
visitChain(ast: Chain): Chain { return new Chain(this.visitAll(ast.expressions)); }
|
||||
visitChain(ast: Chain): AST { return new Chain(this.visitAll(ast.expressions)); }
|
||||
}
|
||||
|
|
|
@ -14,7 +14,11 @@ import {provide} from 'angular2/src/core/di';
|
|||
|
||||
import {TEST_PROVIDERS} from './test_bindings';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {TemplateParser, splitClasses} from 'angular2/src/compiler/template_parser';
|
||||
import {
|
||||
TemplateParser,
|
||||
splitClasses,
|
||||
TEMPLATE_TRANSFORMS
|
||||
} from 'angular2/src/compiler/template_parser';
|
||||
import {
|
||||
CompileDirectiveMetadata,
|
||||
CompileTypeMetadata,
|
||||
|
@ -69,6 +73,22 @@ export function main() {
|
|||
return parser.parse(template, directives, 'TestComp');
|
||||
}
|
||||
|
||||
describe('template transform', () => {
|
||||
beforeEachProviders(
|
||||
() => [provide(TEMPLATE_TRANSFORMS, {useValue: new FooAstTransformer(), multi: true})]);
|
||||
|
||||
it('should transform TemplateAST',
|
||||
() => { expect(humanizeTplAst(parse('<div>', []))).toEqual([[ElementAst, 'foo']]); });
|
||||
|
||||
describe('multiple', () => {
|
||||
beforeEachProviders(
|
||||
() => [provide(TEMPLATE_TRANSFORMS, {useValue: new BarAstTransformer(), multi: true})]);
|
||||
|
||||
it('should compose transformers',
|
||||
() => { expect(humanizeTplAst(parse('<div>', []))).toEqual([[ElementAst, 'bar']]); });
|
||||
});
|
||||
});
|
||||
|
||||
describe('parse', () => {
|
||||
describe('nodes without bindings', () => {
|
||||
|
||||
|
@ -1068,3 +1088,29 @@ class TemplateContentProjectionHumanizer implements TemplateAstVisitor {
|
|||
visitDirective(ast: DirectiveAst, context: any): any { return null; }
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
|
||||
}
|
||||
|
||||
class FooAstTransformer implements TemplateAstVisitor {
|
||||
visitNgContent(ast: NgContentAst, context: any): any { throw 'not implemented'; }
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { throw 'not implemented'; }
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
if (ast.name != 'div') return ast;
|
||||
return new ElementAst('foo', [], [], [], [], [], [], ast.ngContentIndex, ast.sourceSpan);
|
||||
}
|
||||
visitVariable(ast: VariableAst, context: any): any { throw 'not implemented'; }
|
||||
visitEvent(ast: BoundEventAst, context: any): any { throw 'not implemented'; }
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { throw 'not implemented'; }
|
||||
visitAttr(ast: AttrAst, context: any): any { throw 'not implemented'; }
|
||||
visitBoundText(ast: BoundTextAst, context: any): any { throw 'not implemented'; }
|
||||
visitText(ast: TextAst, context: any): any { throw 'not implemented'; }
|
||||
visitDirective(ast: DirectiveAst, context: any): any { throw 'not implemented'; }
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {
|
||||
throw 'not implemented';
|
||||
}
|
||||
}
|
||||
|
||||
class BarAstTransformer extends FooAstTransformer {
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
if (ast.name != 'foo') return ast;
|
||||
return new ElementAst('bar', [], [], [], [], [], [], ast.ngContentIndex, ast.sourceSpan);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,9 @@ TemplateCompiler createTemplateCompiler(AssetReader reader,
|
|||
var _htmlParser = new HtmlParser();
|
||||
var _urlResolver = const TransformerUrlResolver();
|
||||
|
||||
// TODO(yjbanov): add router AST transformer when ready
|
||||
var templateParser = new TemplateParser(new ng.Parser(new ng.Lexer()),
|
||||
new DomElementSchemaRegistry(), _htmlParser);
|
||||
new DomElementSchemaRegistry(), _htmlParser, null);
|
||||
|
||||
var cdCompiler = changeDetectionConfig != null
|
||||
? new ChangeDetectionCompiler(changeDetectionConfig)
|
||||
|
|
Loading…
Reference in New Issue