From a43ed79ee7d49ec55a0adea9b587ed67780c870c Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Thu, 19 Nov 2015 10:51:16 -0800 Subject: [PATCH] feat(parser): allows users install custom AST transformers Closes #5382 --- .../angular2/src/compiler/template_parser.ts | 14 +++++- .../src/core/change_detection/parser/ast.ts | 41 ++++++++-------- .../test/compiler/template_parser_spec.ts | 48 ++++++++++++++++++- .../lib/src/transform/common/ng_compiler.dart | 3 +- 4 files changed, 80 insertions(+), 26 deletions(-) diff --git a/modules/angular2/src/compiler/template_parser.ts b/modules/angular2/src/compiler/template_parser.ts index 173e356dac..2dc9353324 100644 --- a/modules/angular2/src/compiler/template_parser.ts +++ b/modules/angular2/src/compiler/template_parser.ts @@ -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; } } diff --git a/modules/angular2/src/core/change_detection/parser/ast.ts b/modules/angular2/src/core/change_detection/parser/ast.ts index fc0ed2e6e4..f3a382bd4f 100644 --- a/modules/angular2/src/core/change_detection/parser/ast.ts +++ b/modules/angular2/src/core/change_detection/parser/ast.ts @@ -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)); } } diff --git a/modules/angular2/test/compiler/template_parser_spec.ts b/modules/angular2/test/compiler/template_parser_spec.ts index 830cb09f8d..36fc5d6e72 100644 --- a/modules/angular2/test/compiler/template_parser_spec.ts +++ b/modules/angular2/test/compiler/template_parser_spec.ts @@ -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('
', []))).toEqual([[ElementAst, 'foo']]); }); + + describe('multiple', () => { + beforeEachProviders( + () => [provide(TEMPLATE_TRANSFORMS, {useValue: new BarAstTransformer(), multi: true})]); + + it('should compose transformers', + () => { expect(humanizeTplAst(parse('
', []))).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); + } +} diff --git a/modules_dart/transform/lib/src/transform/common/ng_compiler.dart b/modules_dart/transform/lib/src/transform/common/ng_compiler.dart index 57fac0e9c0..d6b55b8bc7 100644 --- a/modules_dart/transform/lib/src/transform/common/ng_compiler.dart +++ b/modules_dart/transform/lib/src/transform/common/ng_compiler.dart @@ -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)