feat(compiler): make interpolation symbols configurable (@Component config) (#9367)

closes #9158
This commit is contained in:
Victor Berchet 2016-06-20 09:52:41 -07:00 committed by GitHub
parent 6fd52dfb38
commit 1b28cf71f5
27 changed files with 403 additions and 125 deletions

View File

@ -16,3 +16,24 @@ export function assertArrayOfStrings(identifier: string, value: any) {
} }
} }
} }
const INTERPOLATION_BLACKLIST_REGEXPS = [
/^\s*$/g, // empty
/[<>]/g, // html tag
/^[\{\}]$/g, // i18n expansion
];
export function assertInterpolationSymbols(identifier: string, value: any): void {
if (isDevMode() && !isBlank(value) && (!isArray(value) || value.length != 2)) {
throw new BaseException(`Expected '${identifier}' to be an array, [start, end].`);
} else if (isDevMode() && !isBlank(value)) {
const start = value[0] as string;
const end = value[1] as string;
// black list checking
INTERPOLATION_BLACKLIST_REGEXPS.forEach(regexp => {
if (regexp.test(start) || regexp.test(end)) {
throw new BaseException(`['${start}', '${end}'] contains unusable interpolation symbol.`);
}
});
}
}

View File

@ -603,15 +603,18 @@ export class CompileTemplateMetadata {
styleUrls: string[]; styleUrls: string[];
animations: CompileAnimationEntryMetadata[]; animations: CompileAnimationEntryMetadata[];
ngContentSelectors: string[]; ngContentSelectors: string[];
interpolation: [string, string];
constructor( constructor(
{encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors}: { {encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors,
interpolation}: {
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
template?: string, template?: string,
templateUrl?: string, templateUrl?: string,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
ngContentSelectors?: string[], ngContentSelectors?: string[],
animations?: CompileAnimationEntryMetadata[] animations?: CompileAnimationEntryMetadata[],
interpolation?: [string, string]
} = {}) { } = {}) {
this.encapsulation = encapsulation; this.encapsulation = encapsulation;
this.template = template; this.template = template;
@ -620,6 +623,10 @@ export class CompileTemplateMetadata {
this.styleUrls = isPresent(styleUrls) ? styleUrls : []; this.styleUrls = isPresent(styleUrls) ? styleUrls : [];
this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : []; this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : [];
this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : []; this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
if (isPresent(interpolation) && interpolation.length != 2) {
throw new BaseException(`'interpolation' should have a start and an end symbol.`);
}
this.interpolation = interpolation;
} }
static fromJson(data: {[key: string]: any}): CompileTemplateMetadata { static fromJson(data: {[key: string]: any}): CompileTemplateMetadata {
@ -634,7 +641,8 @@ export class CompileTemplateMetadata {
styles: data['styles'], styles: data['styles'],
styleUrls: data['styleUrls'], styleUrls: data['styleUrls'],
animations: animations, animations: animations,
ngContentSelectors: data['ngContentSelectors'] ngContentSelectors: data['ngContentSelectors'],
interpolation: data['interpolation']
}); });
} }
@ -647,7 +655,8 @@ export class CompileTemplateMetadata {
'styles': this.styles, 'styles': this.styles,
'styleUrls': this.styleUrls, 'styleUrls': this.styleUrls,
'animations': _objToJson(this.animations), 'animations': _objToJson(this.animations),
'ngContentSelectors': this.ngContentSelectors 'ngContentSelectors': this.ngContentSelectors,
'interpolation': this.interpolation
}; };
} }
} }

View File

@ -104,7 +104,8 @@ export class DirectiveNormalizer {
styles: allResolvedStyles, styles: allResolvedStyles,
styleUrls: allStyleAbsUrls, styleUrls: allStyleAbsUrls,
ngContentSelectors: visitor.ngContentSelectors, ngContentSelectors: visitor.ngContentSelectors,
animations: templateMeta.animations animations: templateMeta.animations,
interpolation: templateMeta.interpolation
}); });
} }
} }

View File

@ -2,15 +2,14 @@ import {Injectable} from '@angular/core';
import {ListWrapper} from '../facade/collection'; import {ListWrapper} from '../facade/collection';
import {BaseException} from '../facade/exceptions'; import {BaseException} from '../facade/exceptions';
import {StringWrapper, isBlank, isPresent} from '../facade/lang'; import {RegExpWrapper, StringWrapper, escapeRegExp, isBlank, isPresent} from '../facade/lang';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../interpolation_config';
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast'; import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
import {$COLON, $COMMA, $LBRACE, $LBRACKET, $LPAREN, $PERIOD, $RBRACE, $RBRACKET, $RPAREN, $SEMICOLON, $SLASH, EOF, Lexer, Token, isIdentifier, isQuote} from './lexer'; import {$COLON, $COMMA, $LBRACE, $LBRACKET, $LPAREN, $PERIOD, $RBRACE, $RBRACKET, $RPAREN, $SEMICOLON, $SLASH, EOF, Lexer, Token, isIdentifier, isQuote} from './lexer';
var _implicitReceiver = new ImplicitReceiver(); var _implicitReceiver = new ImplicitReceiver();
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var INTERPOLATION_REGEXP = /\{\{([\s\S]*?)\}\}/g;
class ParseException extends BaseException { class ParseException extends BaseException {
constructor(message: string, input: string, errLocation: string, ctxLocation?: any) { constructor(message: string, input: string, errLocation: string, ctxLocation?: any) {
@ -26,25 +25,36 @@ export class TemplateBindingParseResult {
constructor(public templateBindings: TemplateBinding[], public warnings: string[]) {} constructor(public templateBindings: TemplateBinding[], public warnings: string[]) {}
} }
function _createInterpolateRegExp(config: InterpolationConfig): RegExp {
const regexp = escapeRegExp(config.start) + '([\\s\\S]*?)' + escapeRegExp(config.end);
return RegExpWrapper.create(regexp, 'g');
}
@Injectable() @Injectable()
export class Parser { export class Parser {
constructor(/** @internal */ constructor(/** @internal */
public _lexer: Lexer) {} public _lexer: Lexer) {}
parseAction(input: string, location: any): ASTWithSource { parseAction(
this._checkNoInterpolation(input, location); input: string, location: any,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
this._checkNoInterpolation(input, location, interpolationConfig);
var tokens = this._lexer.tokenize(this._stripComments(input)); var tokens = this._lexer.tokenize(this._stripComments(input));
var ast = new _ParseAST(input, location, tokens, true).parseChain(); var ast = new _ParseAST(input, location, tokens, true).parseChain();
return new ASTWithSource(ast, input, location); return new ASTWithSource(ast, input, location);
} }
parseBinding(input: string, location: any): ASTWithSource { parseBinding(
var ast = this._parseBindingAst(input, location); input: string, location: any,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
var ast = this._parseBindingAst(input, location, interpolationConfig);
return new ASTWithSource(ast, input, location); return new ASTWithSource(ast, input, location);
} }
parseSimpleBinding(input: string, location: string): ASTWithSource { parseSimpleBinding(
var ast = this._parseBindingAst(input, location); input: string, location: string,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
var ast = this._parseBindingAst(input, location, interpolationConfig);
if (!SimpleExpressionChecker.check(ast)) { if (!SimpleExpressionChecker.check(ast)) {
throw new ParseException( throw new ParseException(
'Host binding expression can only contain field access and constants', input, location); 'Host binding expression can only contain field access and constants', input, location);
@ -52,7 +62,8 @@ export class Parser {
return new ASTWithSource(ast, input, location); return new ASTWithSource(ast, input, location);
} }
private _parseBindingAst(input: string, location: string): AST { private _parseBindingAst(
input: string, location: string, interpolationConfig: InterpolationConfig): AST {
// Quotes expressions use 3rd-party expression language. We don't want to use // Quotes expressions use 3rd-party expression language. We don't want to use
// our lexer or parser for that, so we check for that ahead of time. // our lexer or parser for that, so we check for that ahead of time.
var quote = this._parseQuote(input, location); var quote = this._parseQuote(input, location);
@ -61,7 +72,7 @@ export class Parser {
return quote; return quote;
} }
this._checkNoInterpolation(input, location); this._checkNoInterpolation(input, location, interpolationConfig);
var tokens = this._lexer.tokenize(this._stripComments(input)); var tokens = this._lexer.tokenize(this._stripComments(input));
return new _ParseAST(input, location, tokens, false).parseChain(); return new _ParseAST(input, location, tokens, false).parseChain();
} }
@ -81,8 +92,10 @@ export class Parser {
return new _ParseAST(input, location, tokens, false).parseTemplateBindings(); return new _ParseAST(input, location, tokens, false).parseTemplateBindings();
} }
parseInterpolation(input: string, location: any): ASTWithSource { parseInterpolation(
let split = this.splitInterpolation(input, location); input: string, location: any,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
let split = this.splitInterpolation(input, location, interpolationConfig);
if (split == null) return null; if (split == null) return null;
let expressions: AST[] = []; let expressions: AST[] = [];
@ -96,8 +109,11 @@ export class Parser {
return new ASTWithSource(new Interpolation(split.strings, expressions), input, location); return new ASTWithSource(new Interpolation(split.strings, expressions), input, location);
} }
splitInterpolation(input: string, location: string): SplitInterpolation { splitInterpolation(
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP); input: string, location: string,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): SplitInterpolation {
const regexp = _createInterpolateRegExp(interpolationConfig);
const parts = StringWrapper.split(input, regexp);
if (parts.length <= 1) { if (parts.length <= 1) {
return null; return null;
} }
@ -114,7 +130,8 @@ export class Parser {
} else { } else {
throw new ParseException( throw new ParseException(
'Blank expressions are not allowed in interpolated strings', input, 'Blank expressions are not allowed in interpolated strings', input,
`at column ${this._findInterpolationErrorColumn(parts, i)} in`, location); `at column ${this._findInterpolationErrorColumn(parts, i, interpolationConfig)} in`,
location);
} }
} }
return new SplitInterpolation(strings, expressions); return new SplitInterpolation(strings, expressions);
@ -146,19 +163,26 @@ export class Parser {
return null; return null;
} }
private _checkNoInterpolation(input: string, location: any): void { private _checkNoInterpolation(
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP); input: string, location: any, interpolationConfig: InterpolationConfig): void {
var regexp = _createInterpolateRegExp(interpolationConfig);
var parts = StringWrapper.split(input, regexp);
if (parts.length > 1) { if (parts.length > 1) {
throw new ParseException( throw new ParseException(
'Got interpolation ({{}}) where expression was expected', input, `Got interpolation (${interpolationConfig.start}${interpolationConfig.end}) where expression was expected`,
`at column ${this._findInterpolationErrorColumn(parts, 1)} in`, location); input,
`at column ${this._findInterpolationErrorColumn(parts, 1, interpolationConfig)} in`,
location);
} }
} }
private _findInterpolationErrorColumn(parts: string[], partInErrIdx: number): number { private _findInterpolationErrorColumn(
parts: string[], partInErrIdx: number, interpolationConfig: InterpolationConfig): number {
var errLocation = ''; var errLocation = '';
for (var j = 0; j < partInErrIdx; j++) { for (var j = 0; j < partInErrIdx; j++) {
errLocation += j % 2 === 0 ? parts[j] : `{{${parts[j]}}}`; errLocation += j % 2 === 0 ?
parts[j] :
`${interpolationConfig.start}${parts[j]}${interpolationConfig.end}`;
} }
return errLocation.length; return errLocation.length;

View File

@ -2,6 +2,7 @@ import * as chars from './chars';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from './facade/collection';
import {NumberWrapper, StringWrapper, isBlank, isPresent} from './facade/lang'; import {NumberWrapper, StringWrapper, isBlank, isPresent} from './facade/lang';
import {HtmlTagContentType, NAMED_ENTITIES, getHtmlTagDefinition} from './html_tags'; import {HtmlTagContentType, NAMED_ENTITIES, getHtmlTagDefinition} from './html_tags';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util'; import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
export enum HtmlTokenType { export enum HtmlTokenType {
@ -43,9 +44,11 @@ export class HtmlTokenizeResult {
} }
export function tokenizeHtml( export function tokenizeHtml(
sourceContent: string, sourceUrl: string, sourceContent: string, sourceUrl: string, tokenizeExpansionForms: boolean = false,
tokenizeExpansionForms: boolean = false): HtmlTokenizeResult { interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): HtmlTokenizeResult {
return new _HtmlTokenizer(new ParseSourceFile(sourceContent, sourceUrl), tokenizeExpansionForms) return new _HtmlTokenizer(
new ParseSourceFile(sourceContent, sourceUrl), tokenizeExpansionForms,
interpolationConfig)
.tokenize(); .tokenize();
} }
@ -81,7 +84,9 @@ class _HtmlTokenizer {
tokens: HtmlToken[] = []; tokens: HtmlToken[] = [];
errors: HtmlTokenError[] = []; errors: HtmlTokenError[] = [];
constructor(private file: ParseSourceFile, private tokenizeExpansionForms: boolean) { constructor(
private file: ParseSourceFile, private tokenizeExpansionForms: boolean,
private interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
this._input = file.content; this._input = file.content;
this._length = file.content.length; this._length = file.content.length;
this._advance(); this._advance();
@ -114,7 +119,8 @@ class _HtmlTokenizer {
this._consumeTagOpen(start); this._consumeTagOpen(start);
} }
} else if ( } else if (
isExpansionFormStart(this._peek, this._nextPeek) && this.tokenizeExpansionForms) { isExpansionFormStart(this._input, this._index, this.interpolationConfig.start) &&
this.tokenizeExpansionForms) {
this._consumeExpansionFormStart(); this._consumeExpansionFormStart();
} else if ( } else if (
@ -232,16 +238,12 @@ class _HtmlTokenizer {
} }
private _attemptStr(chars: string): boolean { private _attemptStr(chars: string): boolean {
var indexBeforeAttempt = this._index; const initialPosition = this._savePosition();
var columnBeforeAttempt = this._column;
var lineBeforeAttempt = this._line;
for (var i = 0; i < chars.length; i++) { for (var i = 0; i < chars.length; i++) {
if (!this._attemptCharCode(StringWrapper.charCodeAt(chars, i))) { if (!this._attemptCharCode(StringWrapper.charCodeAt(chars, i))) {
// If attempting to parse the string fails, we want to reset the parser // If attempting to parse the string fails, we want to reset the parser
// to where it was before the attempt // to where it was before the attempt
this._index = indexBeforeAttempt; this._restorePosition(initialPosition);
this._column = columnBeforeAttempt;
this._line = lineBeforeAttempt;
return false; return false;
} }
} }
@ -558,35 +560,38 @@ class _HtmlTokenizer {
var parts: string[] = []; var parts: string[] = [];
let interpolation = false; let interpolation = false;
if (this._peek === chars.$LBRACE && this._nextPeek === chars.$LBRACE) { do {
parts.push(this._readChar(true)); const savedPos = this._savePosition();
parts.push(this._readChar(true)); // _attemptStr advances the position when it is true.
interpolation = true; // To push interpolation symbols, we have to reset it.
} else { if (this._attemptStr(this.interpolationConfig.start)) {
this._restorePosition(savedPos);
for (let i = 0; i < this.interpolationConfig.start.length; i++) {
parts.push(this._readChar(true)); parts.push(this._readChar(true));
} }
while (!this._isTextEnd(interpolation)) {
if (this._peek === chars.$LBRACE && this._nextPeek === chars.$LBRACE) {
parts.push(this._readChar(true));
parts.push(this._readChar(true));
interpolation = true; interpolation = true;
} else if ( } else if (this._attemptStr(this.interpolationConfig.end) && interpolation) {
this._peek === chars.$RBRACE && this._nextPeek === chars.$RBRACE && interpolation) { this._restorePosition(savedPos);
parts.push(this._readChar(true)); for (let i = 0; i < this.interpolationConfig.end.length; i++) {
parts.push(this._readChar(true)); parts.push(this._readChar(true));
}
interpolation = false; interpolation = false;
} else { } else {
this._restorePosition(savedPos);
parts.push(this._readChar(true)); parts.push(this._readChar(true));
} }
} } while (!this._isTextEnd(interpolation));
this._endToken([this._processCarriageReturns(parts.join(''))]); this._endToken([this._processCarriageReturns(parts.join(''))]);
} }
private _isTextEnd(interpolation: boolean): boolean { private _isTextEnd(interpolation: boolean): boolean {
if (this._peek === chars.$LT || this._peek === chars.$EOF) return true; if (this._peek === chars.$LT || this._peek === chars.$EOF) return true;
if (this.tokenizeExpansionForms) { if (this.tokenizeExpansionForms) {
if (isExpansionFormStart(this._peek, this._nextPeek)) return true; const savedPos = this._savePosition();
if (isExpansionFormStart(this._input, this._index, this.interpolationConfig.start))
return true;
this._restorePosition(savedPos);
if (this._peek === chars.$RBRACE && !interpolation && if (this._peek === chars.$RBRACE && !interpolation &&
(this._isInExpansionCase() || this._isInExpansionForm())) (this._isInExpansionCase() || this._isInExpansionForm()))
return true; return true;
@ -655,8 +660,11 @@ function isNamedEntityEnd(code: number): boolean {
return code == chars.$SEMICOLON || code == chars.$EOF || !isAsciiLetter(code); return code == chars.$SEMICOLON || code == chars.$EOF || !isAsciiLetter(code);
} }
function isExpansionFormStart(peek: number, nextPeek: number): boolean { function isExpansionFormStart(input: string, offset: number, interpolationStart: string): boolean {
return peek === chars.$LBRACE && nextPeek != chars.$LBRACE; const substr = input.substring(offset);
return StringWrapper.charCodeAt(substr, 0) === chars.$LBRACE &&
StringWrapper.charCodeAt(substr, 1) !== chars.$LBRACE &&
!substr.startsWith(interpolationStart);
} }
function isExpansionCaseStart(peek: number): boolean { function isExpansionCaseStart(peek: number): boolean {

View File

@ -4,6 +4,7 @@ import {BaseException} from '../facade/exceptions';
import {NumberWrapper, RegExpWrapper, isPresent} from '../facade/lang'; import {NumberWrapper, RegExpWrapper, isPresent} from '../facade/lang';
import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from '../html_ast'; import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from '../html_ast';
import {HtmlParseTreeResult, HtmlParser} from '../html_parser'; import {HtmlParseTreeResult, HtmlParser} from '../html_parser';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../interpolation_config';
import {ParseError, ParseSourceSpan} from '../parse_util'; import {ParseError, ParseSourceSpan} from '../parse_util';
import {expandNodes} from './expander'; import {expandNodes} from './expander';
@ -96,15 +97,19 @@ let _PLACEHOLDER_EXPANDED_REGEXP = /<ph(\s)+name=("(\w)+")><\/ph>/gi;
*/ */
export class I18nHtmlParser implements HtmlParser { export class I18nHtmlParser implements HtmlParser {
errors: ParseError[]; errors: ParseError[];
private _interpolationConfig: InterpolationConfig;
constructor( constructor(
private _htmlParser: HtmlParser, private _parser: Parser, private _messagesContent: string, private _htmlParser: HtmlParser, private _parser: Parser, private _messagesContent: string,
private _messages: {[key: string]: HtmlAst[]}, private _implicitTags: string[], private _messages: {[key: string]: HtmlAst[]}, private _implicitTags: string[],
private _implicitAttrs: {[k: string]: string[]}) {} private _implicitAttrs: {[k: string]: string[]}) {}
parse(sourceContent: string, sourceUrl: string, parseExpansionForms: boolean = false): parse(
sourceContent: string, sourceUrl: string, parseExpansionForms: boolean = false,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG):
HtmlParseTreeResult { HtmlParseTreeResult {
this.errors = []; this.errors = [];
this._interpolationConfig = interpolationConfig;
let res = this._htmlParser.parse(sourceContent, sourceUrl, true); let res = this._htmlParser.parse(sourceContent, sourceUrl, true);
@ -134,7 +139,7 @@ export class I18nHtmlParser implements HtmlParser {
} }
private _mergeI18Part(part: Part): HtmlAst[] { private _mergeI18Part(part: Part): HtmlAst[] {
let message = part.createMessage(this._parser); let message = part.createMessage(this._parser, this._interpolationConfig);
let messageId = id(message); let messageId = id(message);
if (!StringMapWrapper.contains(this._messages, messageId)) { if (!StringMapWrapper.contains(this._messages, messageId)) {
throw new I18nError( throw new I18nError(
@ -240,8 +245,8 @@ export class I18nHtmlParser implements HtmlParser {
} }
private _mergeTextInterpolation(t: HtmlElementAst, originalNode: HtmlTextAst): HtmlTextAst { private _mergeTextInterpolation(t: HtmlElementAst, originalNode: HtmlTextAst): HtmlTextAst {
let split = let split = this._parser.splitInterpolation(
this._parser.splitInterpolation(originalNode.value, originalNode.sourceSpan.toString()); originalNode.value, originalNode.sourceSpan.toString(), this._interpolationConfig);
let exps = isPresent(split) ? split.expressions : []; let exps = isPresent(split) ? split.expressions : [];
let messageSubstring = let messageSubstring =
@ -277,9 +282,9 @@ export class I18nHtmlParser implements HtmlParser {
res.push(attr); res.push(attr);
return; return;
} }
message = messageFromAttribute(this._parser, attr); message = messageFromAttribute(this._parser, this._interpolationConfig, attr);
} else { } else {
message = messageFromI18nAttribute(this._parser, el, i18ns[0]); message = messageFromI18nAttribute(this._parser, this._interpolationConfig, el, i18ns[0]);
} }
let messageId = id(message); let messageId = id(message);
@ -298,7 +303,8 @@ export class I18nHtmlParser implements HtmlParser {
} }
private _replaceInterpolationInAttr(attr: HtmlAttrAst, msg: HtmlAst[]): string { private _replaceInterpolationInAttr(attr: HtmlAttrAst, msg: HtmlAst[]): string {
let split = this._parser.splitInterpolation(attr.value, attr.sourceSpan.toString()); let split = this._parser.splitInterpolation(
attr.value, attr.sourceSpan.toString(), this._interpolationConfig);
let exps = isPresent(split) ? split.expressions : []; let exps = isPresent(split) ? split.expressions : [];
let first = msg[0]; let first = msg[0];
@ -336,7 +342,7 @@ export class I18nHtmlParser implements HtmlParser {
private _convertIntoExpression( private _convertIntoExpression(
name: string, expMap: Map<string, string>, sourceSpan: ParseSourceSpan) { name: string, expMap: Map<string, string>, sourceSpan: ParseSourceSpan) {
if (expMap.has(name)) { if (expMap.has(name)) {
return `{{${expMap.get(name)}}}`; return `${this._interpolationConfig.start}${expMap.get(name)}${this._interpolationConfig.end}`;
} else { } else {
throw new I18nError(sourceSpan, `Invalid interpolation name '${name}'`); throw new I18nError(sourceSpan, `Invalid interpolation name '${name}'`);
} }

View File

@ -3,6 +3,7 @@ import {StringMapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {HtmlAst, HtmlElementAst} from '../html_ast'; import {HtmlAst, HtmlElementAst} from '../html_ast';
import {HtmlParser} from '../html_parser'; import {HtmlParser} from '../html_parser';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../interpolation_config';
import {ParseError} from '../parse_util'; import {ParseError} from '../parse_util';
import {expandNodes} from './expander'; import {expandNodes} from './expander';
@ -92,14 +93,18 @@ export function removeDuplicates(messages: Message[]): Message[] {
export class MessageExtractor { export class MessageExtractor {
private _messages: Message[]; private _messages: Message[];
private _errors: ParseError[]; private _errors: ParseError[];
private _interpolationConfig: InterpolationConfig;
constructor( constructor(
private _htmlParser: HtmlParser, private _parser: Parser, private _implicitTags: string[], private _htmlParser: HtmlParser, private _parser: Parser, private _implicitTags: string[],
private _implicitAttrs: {[k: string]: string[]}) {} private _implicitAttrs: {[k: string]: string[]}) {}
extract(template: string, sourceUrl: string): ExtractionResult { extract(
template: string, sourceUrl: string,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ExtractionResult {
this._messages = []; this._messages = [];
this._errors = []; this._errors = [];
this._interpolationConfig = interpolationConfig;
let res = this._htmlParser.parse(template, sourceUrl, true); let res = this._htmlParser.parse(template, sourceUrl, true);
if (res.errors.length > 0) { if (res.errors.length > 0) {
@ -113,7 +118,7 @@ export class MessageExtractor {
private _extractMessagesFromPart(part: Part): void { private _extractMessagesFromPart(part: Part): void {
if (part.hasI18n) { if (part.hasI18n) {
this._messages.push(part.createMessage(this._parser)); this._messages.push(part.createMessage(this._parser, this._interpolationConfig));
this._recurseToExtractMessagesFromAttributes(part.children); this._recurseToExtractMessagesFromAttributes(part.children);
} else { } else {
this._recurse(part.children); this._recurse(part.children);
@ -148,7 +153,8 @@ export class MessageExtractor {
p.attrs.filter(attr => attr.name.startsWith(I18N_ATTR_PREFIX)).forEach(attr => { p.attrs.filter(attr => attr.name.startsWith(I18N_ATTR_PREFIX)).forEach(attr => {
try { try {
explicitAttrs.push(attr.name.substring(I18N_ATTR_PREFIX.length)); explicitAttrs.push(attr.name.substring(I18N_ATTR_PREFIX.length));
this._messages.push(messageFromI18nAttribute(this._parser, p, attr)); this._messages.push(
messageFromI18nAttribute(this._parser, this._interpolationConfig, p, attr));
} catch (e) { } catch (e) {
if (e instanceof I18nError) { if (e instanceof I18nError) {
this._errors.push(e); this._errors.push(e);
@ -161,6 +167,8 @@ export class MessageExtractor {
p.attrs.filter(attr => !attr.name.startsWith(I18N_ATTR_PREFIX)) p.attrs.filter(attr => !attr.name.startsWith(I18N_ATTR_PREFIX))
.filter(attr => explicitAttrs.indexOf(attr.name) == -1) .filter(attr => explicitAttrs.indexOf(attr.name) == -1)
.filter(attr => transAttrs.indexOf(attr.name) > -1) .filter(attr => transAttrs.indexOf(attr.name) > -1)
.forEach(attr => this._messages.push(messageFromAttribute(this._parser, attr))); .forEach(
attr => this._messages.push(
messageFromAttribute(this._parser, this._interpolationConfig, attr)));
} }
} }

View File

@ -1,6 +1,7 @@
import {Parser} from '../expression_parser/parser'; import {Parser} from '../expression_parser/parser';
import {StringWrapper, isBlank, isPresent} from '../facade/lang'; import {StringWrapper, isBlank, isPresent} from '../facade/lang';
import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from '../html_ast'; import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from '../html_ast';
import {InterpolationConfig} from '../interpolation_config';
import {ParseError, ParseSourceSpan} from '../parse_util'; import {ParseError, ParseSourceSpan} from '../parse_util';
import {Message} from './message'; import {Message} from './message';
@ -61,9 +62,10 @@ export class Part {
return this.children[0].sourceSpan; return this.children[0].sourceSpan;
} }
createMessage(parser: Parser): Message { createMessage(parser: Parser, interpolationConfig: InterpolationConfig): Message {
return new Message( return new Message(
stringifyNodes(this.children, parser), meaning(this.i18n), description(this.i18n)); stringifyNodes(this.children, parser, interpolationConfig), meaning(this.i18n),
description(this.i18n));
} }
} }
@ -102,28 +104,31 @@ export function description(i18n: string): string {
* @internal * @internal
*/ */
export function messageFromI18nAttribute( export function messageFromI18nAttribute(
parser: Parser, p: HtmlElementAst, i18nAttr: HtmlAttrAst): Message { parser: Parser, interpolationConfig: InterpolationConfig, p: HtmlElementAst,
i18nAttr: HtmlAttrAst): Message {
let expectedName = i18nAttr.name.substring(5); let expectedName = i18nAttr.name.substring(5);
let attr = p.attrs.find(a => a.name == expectedName); let attr = p.attrs.find(a => a.name == expectedName);
if (attr) { if (attr) {
return messageFromAttribute(parser, attr, meaning(i18nAttr.value), description(i18nAttr.value)); return messageFromAttribute(
parser, interpolationConfig, attr, meaning(i18nAttr.value), description(i18nAttr.value));
} }
throw new I18nError(p.sourceSpan, `Missing attribute '${expectedName}'.`); throw new I18nError(p.sourceSpan, `Missing attribute '${expectedName}'.`);
} }
export function messageFromAttribute( export function messageFromAttribute(
parser: Parser, attr: HtmlAttrAst, meaning: string = null, parser: Parser, interpolationConfig: InterpolationConfig, attr: HtmlAttrAst,
description: string = null): Message { meaning: string = null, description: string = null): Message {
let value = removeInterpolation(attr.value, attr.sourceSpan, parser); let value = removeInterpolation(attr.value, attr.sourceSpan, parser, interpolationConfig);
return new Message(value, meaning, description); return new Message(value, meaning, description);
} }
export function removeInterpolation( export function removeInterpolation(
value: string, source: ParseSourceSpan, parser: Parser): string { value: string, source: ParseSourceSpan, parser: Parser,
interpolationConfig: InterpolationConfig): string {
try { try {
let parsed = parser.splitInterpolation(value, source.toString()); let parsed = parser.splitInterpolation(value, source.toString(), interpolationConfig);
let usedNames = new Map<string, number>(); let usedNames = new Map<string, number>();
if (isPresent(parsed)) { if (isPresent(parsed)) {
let res = ''; let res = '';
@ -160,14 +165,15 @@ export function dedupePhName(usedNames: Map<string, number>, name: string): stri
} }
} }
export function stringifyNodes(nodes: HtmlAst[], parser: Parser): string { export function stringifyNodes(
let visitor = new _StringifyVisitor(parser); nodes: HtmlAst[], parser: Parser, interpolationConfig: InterpolationConfig): string {
let visitor = new _StringifyVisitor(parser, interpolationConfig);
return htmlVisitAll(visitor, nodes).join(''); return htmlVisitAll(visitor, nodes).join('');
} }
class _StringifyVisitor implements HtmlAstVisitor { class _StringifyVisitor implements HtmlAstVisitor {
private _index: number = 0; private _index: number = 0;
constructor(private _parser: Parser) {} constructor(private _parser: Parser, private _interpolationConfig: InterpolationConfig) {}
visitElement(ast: HtmlElementAst, context: any): any { visitElement(ast: HtmlElementAst, context: any): any {
let name = this._index++; let name = this._index++;
@ -179,7 +185,8 @@ class _StringifyVisitor implements HtmlAstVisitor {
visitText(ast: HtmlTextAst, context: any): any { visitText(ast: HtmlTextAst, context: any): any {
let index = this._index++; let index = this._index++;
let noInterpolation = removeInterpolation(ast.value, ast.sourceSpan, this._parser); let noInterpolation =
removeInterpolation(ast.value, ast.sourceSpan, this._parser, this._interpolationConfig);
if (noInterpolation != ast.value) { if (noInterpolation != ast.value) {
return `<ph name="t${index}">${noInterpolation}</ph>`; return `<ph name="t${index}">${noInterpolation}</ph>`;
} }

View File

@ -0,0 +1,9 @@
export interface InterpolationConfig {
start: string;
end: string;
}
export const DEFAULT_INTERPOLATION_CONFIG: InterpolationConfig = {
start: '{{',
end: '}}'
};

View File

@ -5,7 +5,7 @@ import {StringMapWrapper} from '../src/facade/collection';
import {BaseException} from '../src/facade/exceptions'; import {BaseException} from '../src/facade/exceptions';
import {Type, isArray, isBlank, isPresent, isString, isStringMap, stringify} from '../src/facade/lang'; import {Type, isArray, isBlank, isPresent, isString, isStringMap, stringify} from '../src/facade/lang';
import {assertArrayOfStrings} from './assertions'; import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata'; import * as cpl from './compile_metadata';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {hasLifecycleHook} from './directive_lifecycle_reflector'; import {hasLifecycleHook} from './directive_lifecycle_reflector';
@ -96,6 +96,7 @@ export class CompileMetadataResolver {
var cmpMeta = <ComponentMetadata>dirMeta; var cmpMeta = <ComponentMetadata>dirMeta;
var viewMeta = this._viewResolver.resolve(directiveType); var viewMeta = this._viewResolver.resolve(directiveType);
assertArrayOfStrings('styles', viewMeta.styles); assertArrayOfStrings('styles', viewMeta.styles);
assertInterpolationSymbols('interpolation', viewMeta.interpolation);
var animations = isPresent(viewMeta.animations) ? var animations = isPresent(viewMeta.animations) ?
viewMeta.animations.map(e => this.getAnimationEntryMetadata(e)) : viewMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null; null;
@ -106,7 +107,8 @@ export class CompileMetadataResolver {
templateUrl: viewMeta.templateUrl, templateUrl: viewMeta.templateUrl,
styles: viewMeta.styles, styles: viewMeta.styles,
styleUrls: viewMeta.styleUrls, styleUrls: viewMeta.styleUrls,
animations: animations animations: animations,
interpolation: viewMeta.interpolation
}); });
changeDetectionStrategy = cmpMeta.changeDetection; changeDetectionStrategy = cmpMeta.changeDetection;
if (isPresent(dirMeta.viewProviders)) { if (isPresent(dirMeta.viewProviders)) {

View File

@ -11,6 +11,7 @@ import {CompileDirectiveMetadata, CompilePipeMetadata, CompileMetadataWithType,}
import {HtmlParser} from './html_parser'; import {HtmlParser} from './html_parser';
import {splitNsName, mergeNsAndName} from './html_tags'; import {splitNsName, mergeNsAndName} from './html_tags';
import {ParseSourceSpan, ParseError, ParseLocation, ParseErrorLevel} from './parse_util'; import {ParseSourceSpan, ParseError, ParseLocation, ParseErrorLevel} from './parse_util';
import {InterpolationConfig} from './interpolation_config';
import {ElementAst, BoundElementPropertyAst, BoundEventAst, ReferenceAst, TemplateAst, TemplateAstVisitor, templateVisitAll, TextAst, BoundTextAst, EmbeddedTemplateAst, AttrAst, NgContentAst, PropertyBindingType, DirectiveAst, BoundDirectivePropertyAst, ProviderAst, ProviderAstType, VariableAst} from './template_ast'; import {ElementAst, BoundElementPropertyAst, BoundEventAst, ReferenceAst, TemplateAst, TemplateAstVisitor, templateVisitAll, TextAst, BoundTextAst, EmbeddedTemplateAst, AttrAst, NgContentAst, PropertyBindingType, DirectiveAst, BoundDirectivePropertyAst, ProviderAst, ProviderAstType, VariableAst} from './template_ast';
import {CssSelector, SelectorMatcher} from './selector'; import {CssSelector, SelectorMatcher} from './selector';
@ -151,12 +152,20 @@ class TemplateParseVisitor implements HtmlAstVisitor {
directivesIndex = new Map<CompileDirectiveMetadata, number>(); directivesIndex = new Map<CompileDirectiveMetadata, number>();
ngContentCount: number = 0; ngContentCount: number = 0;
pipesByName: Map<string, CompilePipeMetadata>; pipesByName: Map<string, CompilePipeMetadata>;
private _interpolationConfig: InterpolationConfig;
constructor( constructor(
public providerViewContext: ProviderViewContext, directives: CompileDirectiveMetadata[], public providerViewContext: ProviderViewContext, directives: CompileDirectiveMetadata[],
pipes: CompilePipeMetadata[], private _exprParser: Parser, pipes: CompilePipeMetadata[], private _exprParser: Parser,
private _schemaRegistry: ElementSchemaRegistry) { private _schemaRegistry: ElementSchemaRegistry) {
this.selectorMatcher = new SelectorMatcher(); this.selectorMatcher = new SelectorMatcher();
const tempMeta = providerViewContext.component.template;
if (isPresent(tempMeta) && isPresent(tempMeta.interpolation)) {
this._interpolationConfig = {
start: tempMeta.interpolation[0],
end: tempMeta.interpolation[1]
};
}
ListWrapper.forEachWithIndex( ListWrapper.forEachWithIndex(
directives, (directive: CompileDirectiveMetadata, index: number) => { directives, (directive: CompileDirectiveMetadata, index: number) => {
var selector = CssSelector.parse(directive.selector); var selector = CssSelector.parse(directive.selector);
@ -176,7 +185,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
private _parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource { private _parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
var sourceInfo = sourceSpan.start.toString(); var sourceInfo = sourceSpan.start.toString();
try { try {
var ast = this._exprParser.parseInterpolation(value, sourceInfo); var ast = this._exprParser.parseInterpolation(value, sourceInfo, this._interpolationConfig);
this._checkPipes(ast, sourceSpan); this._checkPipes(ast, sourceSpan);
if (isPresent(ast) && if (isPresent(ast) &&
(<Interpolation>ast.ast).expressions.length > MAX_INTERPOLATION_VALUES) { (<Interpolation>ast.ast).expressions.length > MAX_INTERPOLATION_VALUES) {
@ -193,7 +202,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource { private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
var sourceInfo = sourceSpan.start.toString(); var sourceInfo = sourceSpan.start.toString();
try { try {
var ast = this._exprParser.parseAction(value, sourceInfo); var ast = this._exprParser.parseAction(value, sourceInfo, this._interpolationConfig);
this._checkPipes(ast, sourceSpan); this._checkPipes(ast, sourceSpan);
return ast; return ast;
} catch (e) { } catch (e) {
@ -205,7 +214,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
private _parseBinding(value: string, sourceSpan: ParseSourceSpan): ASTWithSource { private _parseBinding(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
var sourceInfo = sourceSpan.start.toString(); var sourceInfo = sourceSpan.start.toString();
try { try {
var ast = this._exprParser.parseBinding(value, sourceInfo); var ast = this._exprParser.parseBinding(value, sourceInfo, this._interpolationConfig);
this._checkPipes(ast, sourceSpan); this._checkPipes(ast, sourceSpan);
return ast; return ast;
} catch (e) { } catch (e) {

View File

@ -51,7 +51,8 @@ export class ViewResolver {
encapsulation: compMeta.encapsulation, encapsulation: compMeta.encapsulation,
styles: compMeta.styles, styles: compMeta.styles,
styleUrls: compMeta.styleUrls, styleUrls: compMeta.styleUrls,
animations: compMeta.animations animations: compMeta.animations,
interpolation: compMeta.interpolation
}); });
} }
} else { } else {

View File

@ -49,7 +49,8 @@ export function main() {
new CompileAnimationAnimateMetadata( new CompileAnimationAnimateMetadata(
1000, new CompileAnimationStyleMetadata(0, [{'opacity': 1}])) 1000, new CompileAnimationStyleMetadata(0, [{'opacity': 1}]))
]))])], ]))])],
ngContentSelectors: ['*'] ngContentSelectors: ['*'],
interpolation: ['{{', '}}']
}); });
fullDirectiveMeta = CompileDirectiveMetadata.create({ fullDirectiveMeta = CompileDirectiveMetadata.create({
selector: 'someSelector', selector: 'someSelector',
@ -145,6 +146,11 @@ export function main() {
var empty = new CompileTemplateMetadata(); var empty = new CompileTemplateMetadata();
expect(CompileTemplateMetadata.fromJson(empty.toJson())).toEqual(empty); expect(CompileTemplateMetadata.fromJson(empty.toJson())).toEqual(empty);
}); });
it('should throw an error with invalid interpolation symbols', () => {
expect(() => new CompileTemplateMetadata(<any>{interpolation: ['{{']}))
.toThrowError(`'interpolation' should have a start and an end symbol.`);
});
}); });
describe('CompileAnimationStyleMetadata', () => { describe('CompileAnimationStyleMetadata', () => {

View File

@ -467,6 +467,14 @@ export function main() {
checkInterpolation(`{{ 'foo' +\n 'bar' +\r 'baz' }}`, `{{ "foo" + "bar" + "baz" }}`); checkInterpolation(`{{ 'foo' +\n 'bar' +\r 'baz' }}`, `{{ "foo" + "bar" + "baz" }}`);
}); });
it('should support custom interpolation', () => {
const parser = new Parser(new Lexer());
const ast = parser.parseInterpolation('{% a %}', null, {start: '{%', end: '%}'}).ast as any;
expect(ast.strings).toEqual(['', '']);
expect(ast.expressions.length).toEqual(1);
expect(ast.expressions[0].name).toEqual('a');
});
describe('comments', () => { describe('comments', () => {
it('should ignore comments in interpolation expressions', it('should ignore comments in interpolation expressions',
() => { checkInterpolation('{{a //comment}}', '{{ a }}'); }); () => { checkInterpolation('{{a //comment}}', '{{ a }}'); });

View File

@ -1,12 +1,15 @@
import {AST, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '../../src/expression_parser/ast'; import {AST, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '../../src/expression_parser/ast';
import {StringWrapper, isPresent, isString} from '../../src/facade/lang'; import {StringWrapper, isPresent, isString} from '../../src/facade/lang';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/interpolation_config';
export class Unparser implements AstVisitor { export class Unparser implements AstVisitor {
private static _quoteRegExp = /"/g; private static _quoteRegExp = /"/g;
private _expression: string; private _expression: string;
private _interpolationConfig: InterpolationConfig;
unparse(ast: AST) { unparse(ast: AST, interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
this._expression = ''; this._expression = '';
this._interpolationConfig = interpolationConfig;
this._visit(ast); this._visit(ast);
return this._expression; return this._expression;
} }
@ -74,9 +77,9 @@ export class Unparser implements AstVisitor {
for (let i = 0; i < ast.strings.length; i++) { for (let i = 0; i < ast.strings.length; i++) {
this._expression += ast.strings[i]; this._expression += ast.strings[i];
if (i < ast.expressions.length) { if (i < ast.expressions.length) {
this._expression += '{{ '; this._expression += `${this._interpolationConfig.start} `;
this._visit(ast.expressions[i]); this._visit(ast.expressions[i]);
this._expression += ' }}'; this._expression += ` ${this._interpolationConfig.end}`;
} }
} }
} }

View File

@ -1,4 +1,5 @@
import {HtmlToken, HtmlTokenError, HtmlTokenType, tokenizeHtml} from '@angular/compiler/src/html_lexer'; import {HtmlToken, HtmlTokenError, HtmlTokenType, tokenizeHtml} from '@angular/compiler/src/html_lexer';
import {InterpolationConfig} from '@angular/compiler/src/interpolation_config';
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler/src/parse_util'; import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler/src/parse_util';
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal'; import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
@ -345,6 +346,16 @@ export function main() {
]); ]);
}); });
it('should parse interpolation', () => {
expect(tokenizeAndHumanizeParts('{{ a }}')).toEqual([
[HtmlTokenType.TEXT, '{{ a }}'], [HtmlTokenType.EOF]
]);
expect(tokenizeAndHumanizeParts('{% a %}', null, {start: '{%', end: '%}'})).toEqual([
[HtmlTokenType.TEXT, '{% a %}'], [HtmlTokenType.EOF]
]);
});
it('should handle CR & LF', () => { it('should handle CR & LF', () => {
expect(tokenizeAndHumanizeParts('t\ne\rs\r\nt')).toEqual([ expect(tokenizeAndHumanizeParts('t\ne\rs\r\nt')).toEqual([
[HtmlTokenType.TEXT, 't\ne\ns\nt'], [HtmlTokenType.EOF] [HtmlTokenType.TEXT, 't\ne\ns\nt'], [HtmlTokenType.EOF]
@ -577,8 +588,9 @@ export function main() {
} }
function tokenizeWithoutErrors( function tokenizeWithoutErrors(
input: string, tokenizeExpansionForms: boolean = false): HtmlToken[] { input: string, tokenizeExpansionForms: boolean = false,
var tokenizeResult = tokenizeHtml(input, 'someUrl', tokenizeExpansionForms); interpolationConfig?: InterpolationConfig): HtmlToken[] {
var tokenizeResult = tokenizeHtml(input, 'someUrl', tokenizeExpansionForms, interpolationConfig);
if (tokenizeResult.errors.length > 0) { if (tokenizeResult.errors.length > 0) {
var errorString = tokenizeResult.errors.join('\n'); var errorString = tokenizeResult.errors.join('\n');
throw new BaseException(`Unexpected parse errors:\n${errorString}`); throw new BaseException(`Unexpected parse errors:\n${errorString}`);
@ -586,8 +598,10 @@ function tokenizeWithoutErrors(
return tokenizeResult.tokens; return tokenizeResult.tokens;
} }
function tokenizeAndHumanizeParts(input: string, tokenizeExpansionForms: boolean = false): any[] { function tokenizeAndHumanizeParts(
return tokenizeWithoutErrors(input, tokenizeExpansionForms) input: string, tokenizeExpansionForms: boolean = false,
interpolationConfig?: InterpolationConfig): any[] {
return tokenizeWithoutErrors(input, tokenizeExpansionForms, interpolationConfig)
.map(token => [<any>token.type].concat(token.parts)); .map(token => [<any>token.type].concat(token.parts));
} }

View File

@ -5,6 +5,7 @@ import {HtmlParseTreeResult, HtmlParser} from '@angular/compiler/src/html_parser
import {I18nHtmlParser} from '@angular/compiler/src/i18n/i18n_html_parser'; import {I18nHtmlParser} from '@angular/compiler/src/i18n/i18n_html_parser';
import {Message, id} from '@angular/compiler/src/i18n/message'; import {Message, id} from '@angular/compiler/src/i18n/message';
import {deserializeXmb} from '@angular/compiler/src/i18n/xmb_serializer'; import {deserializeXmb} from '@angular/compiler/src/i18n/xmb_serializer';
import {InterpolationConfig} from '@angular/compiler/src/interpolation_config';
import {ParseError} from '@angular/compiler/src/parse_util'; import {ParseError} from '@angular/compiler/src/parse_util';
import {humanizeDom} from '@angular/compiler/test/html_ast_spec_utils'; import {humanizeDom} from '@angular/compiler/test/html_ast_spec_utils';
import {ddescribe, describe, expect, iit, it} from '@angular/core/testing/testing_internal'; import {ddescribe, describe, expect, iit, it} from '@angular/core/testing/testing_internal';
@ -15,7 +16,8 @@ export function main() {
describe('I18nHtmlParser', () => { describe('I18nHtmlParser', () => {
function parse( function parse(
template: string, messages: {[key: string]: string}, implicitTags: string[] = [], template: string, messages: {[key: string]: string}, implicitTags: string[] = [],
implicitAttrs: {[k: string]: string[]} = {}): HtmlParseTreeResult { implicitAttrs: {[k: string]: string[]} = {},
interpolation?: InterpolationConfig): HtmlParseTreeResult {
var parser = new Parser(new Lexer()); var parser = new Parser(new Lexer());
let htmlParser = new HtmlParser(); let htmlParser = new HtmlParser();
@ -26,7 +28,7 @@ export function main() {
return new I18nHtmlParser( return new I18nHtmlParser(
htmlParser, parser, res.content, res.messages, implicitTags, implicitAttrs) htmlParser, parser, res.content, res.messages, implicitTags, implicitAttrs)
.parse(template, 'someurl', true); .parse(template, 'someurl', true, interpolation);
} }
it('should delegate to the provided parser when no i18n', () => { it('should delegate to the provided parser when no i18n', () => {
@ -63,6 +65,17 @@ export function main() {
.toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', '{{b}} or {{a}}']]); .toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', '{{b}} or {{a}}']]);
}); });
it('should handle interpolation with config', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('<ph name="0"/> and <ph name="1"/>', null, null))] =
'<ph name="1"/> or <ph name="0"/>';
expect(humanizeDom(parse(
'<div value=\'{%a%} and {%b%}\' i18n-value></div>', translations, [], {},
{start: '{%', end: '%}'})))
.toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', '{%b%} or {%a%}']]);
});
it('should handle interpolation with custom placeholder names', () => { it('should handle interpolation with custom placeholder names', () => {
let translations: {[key: string]: string} = {}; let translations: {[key: string]: string} = {};
translations[id(new Message('<ph name="FIRST"/> and <ph name="SECOND"/>', null, null))] = translations[id(new Message('<ph name="FIRST"/> and <ph name="SECOND"/>', null, null))] =

View File

@ -35,6 +35,7 @@ export function main() {
expect(meta.template.styleUrls).toEqual(['someStyleUrl']); expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
expect(meta.template.template).toEqual('someTemplate'); expect(meta.template.template).toEqual('someTemplate');
expect(meta.template.templateUrl).toEqual('someTemplateUrl'); expect(meta.template.templateUrl).toEqual('someTemplateUrl');
expect(meta.template.interpolation).toEqual(['{{', '}}']);
})); }));
it('should use the moduleUrl from the reflector if none is given', it('should use the moduleUrl from the reflector if none is given',
@ -61,6 +62,16 @@ export function main() {
.toThrowError(`Can't resolve all parameters for MyBrokenComp1: (?).`); .toThrowError(`Can't resolve all parameters for MyBrokenComp1: (?).`);
} }
})); }));
it('should throw an error when the interpolation config has invalid symbols',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation1))
.toThrowError(`[' ', ' '] contains unusable interpolation symbol.`);
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation2))
.toThrowError(`['{', '}'] contains unusable interpolation symbol.`);
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation3))
.toThrowError(`['<%', '%>'] contains unusable interpolation symbol.`);
}));
}); });
describe('getViewDirectivesMetadata', () => { describe('getViewDirectivesMetadata', () => {
@ -120,7 +131,8 @@ class ComponentWithoutModuleId {
encapsulation: ViewEncapsulation.Emulated, encapsulation: ViewEncapsulation.Emulated,
styles: ['someStyle'], styles: ['someStyle'],
styleUrls: ['someStyleUrl'], styleUrls: ['someStyleUrl'],
directives: [SomeDirective] directives: [SomeDirective],
interpolation: ['{{', '}}']
}) })
class ComponentWithEverything implements OnChanges, class ComponentWithEverything implements OnChanges,
OnInit, DoCheck, OnDestroy, AfterContentInit, AfterContentChecked, AfterViewInit, OnInit, DoCheck, OnDestroy, AfterContentInit, AfterContentChecked, AfterViewInit,
@ -139,3 +151,15 @@ class ComponentWithEverything implements OnChanges,
class MyBrokenComp1 { class MyBrokenComp1 {
constructor(public dependency: any) {} constructor(public dependency: any) {}
} }
@Component({selector: 'someSelector', template: '', interpolation: [' ', ' ']})
class ComponentWithInvalidInterpolation1 {
}
@Component({selector: 'someSelector', template: '', interpolation: ['{', '}']})
class ComponentWithInvalidInterpolation2 {
}
@Component({selector: 'someSelector', template: '', interpolation: ['<%', '%>']})
class ComponentWithInvalidInterpolation3 {
}

View File

@ -9,6 +9,7 @@ import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect,
import {SecurityContext} from '../core_private'; import {SecurityContext} from '../core_private';
import {Identifiers, identifierToken} from '../src/identifiers'; import {Identifiers, identifierToken} from '../src/identifiers';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../src/interpolation_config';
import {Unparser} from './expression_parser/unparser'; import {Unparser} from './expression_parser/unparser';
import {TEST_PROVIDERS} from './test_bindings'; import {TEST_PROVIDERS} from './test_bindings';
@ -152,6 +153,20 @@ export function main() {
expect(humanizeTplAst(parse('{{a}}', []))).toEqual([[BoundTextAst, '{{ a }}']]); expect(humanizeTplAst(parse('{{a}}', []))).toEqual([[BoundTextAst, '{{ a }}']]);
}); });
it('should parse with custom interpolation config',
inject([TemplateParser], (parser: TemplateParser) => {
const component = CompileDirectiveMetadata.create({
selector: 'test',
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'Test'}),
isComponent: true,
template: new CompileTemplateMetadata({interpolation: ['{%', '%}']})
});
expect(humanizeTplAst(parser.parse(component, '{%a%}', [], [], 'TestComp'), {
start: '{%',
end: '%}'
})).toEqual([[BoundTextAst, '{% a %}']]);
}));
describe('bound properties', () => { describe('bound properties', () => {
it('should parse mixed case bound properties', () => { it('should parse mixed case bound properties', () => {
@ -1406,14 +1421,16 @@ The pipe 'test' could not be found ("[ERROR ->]{{a | test}}"): TestComp@0:0`);
}); });
} }
function humanizeTplAst(templateAsts: TemplateAst[]): any[] { function humanizeTplAst(
var humanizer = new TemplateHumanizer(false); templateAsts: TemplateAst[], interpolationConfig?: InterpolationConfig): any[] {
const humanizer = new TemplateHumanizer(false, interpolationConfig);
templateVisitAll(humanizer, templateAsts); templateVisitAll(humanizer, templateAsts);
return humanizer.result; return humanizer.result;
} }
function humanizeTplAstSourceSpans(templateAsts: TemplateAst[]): any[] { function humanizeTplAstSourceSpans(
var humanizer = new TemplateHumanizer(true); templateAsts: TemplateAst[], interpolationConfig?: InterpolationConfig): any[] {
const humanizer = new TemplateHumanizer(true, interpolationConfig);
templateVisitAll(humanizer, templateAsts); templateVisitAll(humanizer, templateAsts);
return humanizer.result; return humanizer.result;
} }
@ -1421,7 +1438,9 @@ function humanizeTplAstSourceSpans(templateAsts: TemplateAst[]): any[] {
class TemplateHumanizer implements TemplateAstVisitor { class TemplateHumanizer implements TemplateAstVisitor {
result: any[] = []; result: any[] = [];
constructor(private includeSourceSpan: boolean){}; constructor(
private includeSourceSpan: boolean,
private interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG){};
visitNgContent(ast: NgContentAst, context: any): any { visitNgContent(ast: NgContentAst, context: any): any {
var res = [NgContentAst]; var res = [NgContentAst];
@ -1461,13 +1480,17 @@ class TemplateHumanizer implements TemplateAstVisitor {
return null; return null;
} }
visitEvent(ast: BoundEventAst, context: any): any { visitEvent(ast: BoundEventAst, context: any): any {
var res = [BoundEventAst, ast.name, ast.target, expressionUnparser.unparse(ast.handler)]; var res = [
BoundEventAst, ast.name, ast.target,
expressionUnparser.unparse(ast.handler, this.interpolationConfig)
];
this.result.push(this._appendContext(ast, res)); this.result.push(this._appendContext(ast, res));
return null; return null;
} }
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { visitElementProperty(ast: BoundElementPropertyAst, context: any): any {
var res = [ var res = [
BoundElementPropertyAst, ast.type, ast.name, expressionUnparser.unparse(ast.value), ast.unit BoundElementPropertyAst, ast.type, ast.name,
expressionUnparser.unparse(ast.value, this.interpolationConfig), ast.unit
]; ];
this.result.push(this._appendContext(ast, res)); this.result.push(this._appendContext(ast, res));
return null; return null;
@ -1478,7 +1501,7 @@ class TemplateHumanizer implements TemplateAstVisitor {
return null; return null;
} }
visitBoundText(ast: BoundTextAst, context: any): any { visitBoundText(ast: BoundTextAst, context: any): any {
var res = [BoundTextAst, expressionUnparser.unparse(ast.value)]; var res = [BoundTextAst, expressionUnparser.unparse(ast.value, this.interpolationConfig)];
this.result.push(this._appendContext(ast, res)); this.result.push(this._appendContext(ast, res));
return null; return null;
} }
@ -1496,7 +1519,10 @@ class TemplateHumanizer implements TemplateAstVisitor {
return null; return null;
} }
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {
var res = [BoundDirectivePropertyAst, ast.directiveName, expressionUnparser.unparse(ast.value)]; var res = [
BoundDirectivePropertyAst, ast.directiveName,
expressionUnparser.unparse(ast.value, this.interpolationConfig)
];
this.result.push(this._appendContext(ast, res)); this.result.push(this._appendContext(ast, res));
return null; return null;
} }

View File

@ -111,7 +111,8 @@ export class MockViewResolver extends ViewResolver {
styles: view.styles, styles: view.styles,
styleUrls: view.styleUrls, styleUrls: view.styleUrls,
pipes: view.pipes, pipes: view.pipes,
encapsulation: view.encapsulation encapsulation: view.encapsulation,
interpolation: view.interpolation
}); });
this._viewCache.set(component, view); this._viewCache.set(component, view);

View File

@ -42,7 +42,8 @@ export interface ComponentDecorator extends TypeDecorator {
renderer?: string, renderer?: string,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
animations?: AnimationEntryMetadata[] animations?: AnimationEntryMetadata[],
interpolation?: [string, string]
}): ViewDecorator; }): ViewDecorator;
} }
@ -63,7 +64,8 @@ export interface ViewDecorator extends TypeDecorator {
renderer?: string, renderer?: string,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
animations?: AnimationEntryMetadata[] animations?: AnimationEntryMetadata[],
interpolation?: [string, string]
}): ViewDecorator; }): ViewDecorator;
} }
@ -175,7 +177,8 @@ export interface ComponentMetadataFactory {
animations?: AnimationEntryMetadata[], animations?: AnimationEntryMetadata[],
directives?: Array<Type|any[]>, directives?: Array<Type|any[]>,
pipes?: Array<Type|any[]>, pipes?: Array<Type|any[]>,
encapsulation?: ViewEncapsulation encapsulation?: ViewEncapsulation,
interpolation?: [string, string]
}): ComponentDecorator; }): ComponentDecorator;
new (obj: { new (obj: {
selector?: string, selector?: string,
@ -197,7 +200,8 @@ export interface ComponentMetadataFactory {
animations?: AnimationEntryMetadata[], animations?: AnimationEntryMetadata[],
directives?: Array<Type|any[]>, directives?: Array<Type|any[]>,
pipes?: Array<Type|any[]>, pipes?: Array<Type|any[]>,
encapsulation?: ViewEncapsulation encapsulation?: ViewEncapsulation,
interpolation?: [string, string]
}): ComponentMetadata; }): ComponentMetadata;
} }
@ -252,7 +256,8 @@ export interface ViewMetadataFactory {
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
animations?: AnimationEntryMetadata[] animations?: AnimationEntryMetadata[],
interpolation?: [string, string]
}): ViewDecorator; }): ViewDecorator;
new (obj: { new (obj: {
templateUrl?: string, templateUrl?: string,
@ -262,7 +267,8 @@ export interface ViewMetadataFactory {
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
animations?: AnimationEntryMetadata[] animations?: AnimationEntryMetadata[],
interpolation?: [string, string]
}): ViewMetadata; }): ViewMetadata;
} }

View File

@ -954,6 +954,8 @@ export class ComponentMetadata extends DirectiveMetadata {
encapsulation: ViewEncapsulation; encapsulation: ViewEncapsulation;
interpolation: [string, string];
constructor({selector, constructor({selector,
inputs, inputs,
outputs, outputs,
@ -973,7 +975,8 @@ export class ComponentMetadata extends DirectiveMetadata {
animations, animations,
directives, directives,
pipes, pipes,
encapsulation}: { encapsulation,
interpolation}: {
selector?: string, selector?: string,
inputs?: string[], inputs?: string[],
outputs?: string[], outputs?: string[],
@ -993,7 +996,8 @@ export class ComponentMetadata extends DirectiveMetadata {
animations?: AnimationEntryMetadata[], animations?: AnimationEntryMetadata[],
directives?: Array<Type|any[]>, directives?: Array<Type|any[]>,
pipes?: Array<Type|any[]>, pipes?: Array<Type|any[]>,
encapsulation?: ViewEncapsulation encapsulation?: ViewEncapsulation,
interpolation?: [string, string]
} = {}) { } = {}) {
super({ super({
selector: selector, selector: selector,
@ -1018,6 +1022,7 @@ export class ComponentMetadata extends DirectiveMetadata {
this.encapsulation = encapsulation; this.encapsulation = encapsulation;
this.moduleId = moduleId; this.moduleId = moduleId;
this.animations = animations; this.animations = animations;
this.interpolation = interpolation;
} }
} }

View File

@ -128,8 +128,11 @@ export class ViewMetadata {
animations: AnimationEntryMetadata[]; animations: AnimationEntryMetadata[];
interpolation: [string, string];
constructor( constructor(
{templateUrl, template, directives, pipes, encapsulation, styles, styleUrls, animations}: { {templateUrl, template, directives, pipes, encapsulation, styles, styleUrls, animations,
interpolation}: {
templateUrl?: string, templateUrl?: string,
template?: string, template?: string,
directives?: Array<Type|any[]>, directives?: Array<Type|any[]>,
@ -137,7 +140,8 @@ export class ViewMetadata {
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
animations?: AnimationEntryMetadata[] animations?: AnimationEntryMetadata[],
interpolation?: [string, string]
} = {}) { } = {}) {
this.templateUrl = templateUrl; this.templateUrl = templateUrl;
this.template = template; this.template = template;
@ -147,5 +151,6 @@ export class ViewMetadata {
this.pipes = pipes; this.pipes = pipes;
this.encapsulation = encapsulation; this.encapsulation = encapsulation;
this.animations = animations; this.animations = animations;
this.interpolation = interpolation;
} }
} }

View File

@ -1300,6 +1300,31 @@ function declareTests({useJit}: {useJit: boolean}) {
async.done(); async.done();
}); });
})); }));
it('should support custom interpolation',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideView(
MyComp, new ViewMetadata({
template: `<div>{{ctxProp}}</div>
<cmp-with-custom-interpolation-a></cmp-with-custom-interpolation-a>
<cmp-with-custom-interpolation-b></cmp-with-custom-interpolation-b>`,
directives: [
ComponentWithCustomInterpolationA, ComponentWithCustomInterpolationB
]
}))
.createAsync(MyComp)
.then((fixture) => {
fixture.debugElement.componentInstance.ctxProp = 'Default Interpolation';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText(
'Default Interpolation\nCustom Interpolation A\nCustom Interpolation B (Default Interpolation)');
async.done();
});
}));
}); });
describe('dependency injection', () => { describe('dependency injection', () => {
@ -2023,6 +2048,32 @@ function declareTests({useJit}: {useJit: boolean}) {
}); });
} }
@Component({selector: 'cmp-with-default-interpolation', template: `{{text}}`})
class ComponentWithDefaultInterpolation {
text = 'Default Interpolation';
}
@Component({
selector: 'cmp-with-custom-interpolation-a',
template: `<div>{%text%}</div>`,
interpolation: ['{%', '%}']
})
class ComponentWithCustomInterpolationA {
text = 'Custom Interpolation A';
}
@Component({
selector: 'cmp-with-custom-interpolation-b',
template:
`<div>{**text%}</div> (<cmp-with-default-interpolation></cmp-with-default-interpolation>)`,
interpolation: ['{**', '%}'],
directives: [ComponentWithDefaultInterpolation]
})
class ComponentWithCustomInterpolationB {
text = 'Custom Interpolation B';
}
@Injectable() @Injectable()
class MyService { class MyService {
greeting: string; greeting: string;

View File

@ -367,3 +367,7 @@ bool hasConstructor(Object value, Type type) {
String escape(String s) { String escape(String s) {
return Uri.encodeComponent(s); return Uri.encodeComponent(s);
} }
String escapeRegExp(String s) {
return s.replaceAllMapped(new RegExp(r'([.*+?^=!:${}()|[\]\/\\])'), (Match m) => '\\${m[1]}');
}

View File

@ -466,3 +466,7 @@ export function hasConstructor(value: Object, type: Type): boolean {
export function escape(s: string): string { export function escape(s: string): string {
return _global.encodeURI(s); return _global.encodeURI(s);
} }
export function escapeRegExp(s: string): string {
return s.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
}

View File

@ -131,7 +131,7 @@ const CORE = [
'CollectionChangeRecord.toString():string', 'CollectionChangeRecord.toString():string',
'CollectionChangeRecord.trackById:any', 'CollectionChangeRecord.trackById:any',
'ComponentDecorator', 'ComponentDecorator',
'ComponentDecorator.View(obj:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, renderer?:string, styles?:string[], styleUrls?:string[], animations?:AnimationEntryMetadata[]}):ViewDecorator', 'ComponentDecorator.View(obj:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, renderer?:string, styles?:string[], styleUrls?:string[], animations?:AnimationEntryMetadata[], interpolation?:[string, string]}):ViewDecorator',
'ComponentFactory.componentType:Type', 'ComponentFactory.componentType:Type',
'ComponentFactory.constructor(selector:string, _viewFactory:Function, _componentType:Type)', 'ComponentFactory.constructor(selector:string, _viewFactory:Function, _componentType:Type)',
'ComponentFactory.create(injector:Injector, projectableNodes:any[][]=null, rootSelectorOrNode:string|any=null):ComponentRef<C>', 'ComponentFactory.create(injector:Injector, projectableNodes:any[][]=null, rootSelectorOrNode:string|any=null):ComponentRef<C>',
@ -140,9 +140,10 @@ const CORE = [
'ComponentMetadata', 'ComponentMetadata',
'ComponentMetadata.animations:AnimationEntryMetadata[]', 'ComponentMetadata.animations:AnimationEntryMetadata[]',
'ComponentMetadata.changeDetection:ChangeDetectionStrategy', 'ComponentMetadata.changeDetection:ChangeDetectionStrategy',
'ComponentMetadata.constructor({selector,inputs,outputs,properties,events,host,exportAs,moduleId,providers,viewProviders,changeDetection=ChangeDetectionStrategy.Default,queries,templateUrl,template,styleUrls,styles,animations,directives,pipes,encapsulation}:{selector?:string, inputs?:string[], outputs?:string[], properties?:string[], events?:string[], host?:{[key:string]:string}, providers?:any[], exportAs?:string, moduleId?:string, viewProviders?:any[], queries?:{[key:string]:any}, changeDetection?:ChangeDetectionStrategy, templateUrl?:string, template?:string, styleUrls?:string[], styles?:string[], animations?:AnimationEntryMetadata[], directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, encapsulation?:ViewEncapsulation}={})', 'ComponentMetadata.constructor({selector,inputs,outputs,properties,events,host,exportAs,moduleId,providers,viewProviders,changeDetection=ChangeDetectionStrategy.Default,queries,templateUrl,template,styleUrls,styles,animations,directives,pipes,encapsulation,interpolation}:{selector?:string, inputs?:string[], outputs?:string[], properties?:string[], events?:string[], host?:{[key:string]:string}, providers?:any[], exportAs?:string, moduleId?:string, viewProviders?:any[], queries?:{[key:string]:any}, changeDetection?:ChangeDetectionStrategy, templateUrl?:string, template?:string, styleUrls?:string[], styles?:string[], animations?:AnimationEntryMetadata[], directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, encapsulation?:ViewEncapsulation, interpolation?:[string, string]}={})',
'ComponentMetadata.directives:Array<Type|any[]>', 'ComponentMetadata.directives:Array<Type|any[]>',
'ComponentMetadata.encapsulation:ViewEncapsulation', 'ComponentMetadata.encapsulation:ViewEncapsulation',
'ComponentMetadata.interpolation:[string, string]',
'ComponentMetadata.moduleId:string', 'ComponentMetadata.moduleId:string',
'ComponentMetadata.pipes:Array<Type|any[]>', 'ComponentMetadata.pipes:Array<Type|any[]>',
'ComponentMetadata.styles:string[]', 'ComponentMetadata.styles:string[]',
@ -592,16 +593,17 @@ const CORE = [
'ViewContainerRef.parentInjector:Injector', 'ViewContainerRef.parentInjector:Injector',
'ViewContainerRef.remove(index?:number):void', 'ViewContainerRef.remove(index?:number):void',
'ViewDecorator', 'ViewDecorator',
'ViewDecorator.View(obj:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, renderer?:string, styles?:string[], styleUrls?:string[], animations?:AnimationEntryMetadata[]}):ViewDecorator', 'ViewDecorator.View(obj:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, renderer?:string, styles?:string[], styleUrls?:string[], animations?:AnimationEntryMetadata[], interpolation?:[string, string]}):ViewDecorator',
'ViewEncapsulation', 'ViewEncapsulation',
'ViewEncapsulation.Emulated', 'ViewEncapsulation.Emulated',
'ViewEncapsulation.Native', 'ViewEncapsulation.Native',
'ViewEncapsulation.None', 'ViewEncapsulation.None',
'ViewMetadata', 'ViewMetadata',
'ViewMetadata.animations:AnimationEntryMetadata[]', 'ViewMetadata.animations:AnimationEntryMetadata[]',
'ViewMetadata.constructor({templateUrl,template,directives,pipes,encapsulation,styles,styleUrls,animations}:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, encapsulation?:ViewEncapsulation, styles?:string[], styleUrls?:string[], animations?:AnimationEntryMetadata[]}={})', 'ViewMetadata.constructor({templateUrl,template,directives,pipes,encapsulation,styles,styleUrls,animations,interpolation}:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, encapsulation?:ViewEncapsulation, styles?:string[], styleUrls?:string[], animations?:AnimationEntryMetadata[], interpolation?:[string, string]}={})',
'ViewMetadata.directives:Array<Type|any[]>', 'ViewMetadata.directives:Array<Type|any[]>',
'ViewMetadata.encapsulation:ViewEncapsulation', 'ViewMetadata.encapsulation:ViewEncapsulation',
'ViewMetadata.interpolation:[string, string]',
'ViewMetadata.pipes:Array<Type|any[]>', 'ViewMetadata.pipes:Array<Type|any[]>',
'ViewMetadata.styles:string[]', 'ViewMetadata.styles:string[]',
'ViewMetadata.styleUrls:string[]', 'ViewMetadata.styleUrls:string[]',
@ -1229,9 +1231,10 @@ const COMPILER = [
'CompilerConfig.useJit:boolean', 'CompilerConfig.useJit:boolean',
'CompileTemplateMetadata', 'CompileTemplateMetadata',
'CompileTemplateMetadata.animations:CompileAnimationEntryMetadata[]', 'CompileTemplateMetadata.animations:CompileAnimationEntryMetadata[]',
'CompileTemplateMetadata.constructor({encapsulation,template,templateUrl,styles,styleUrls,animations,ngContentSelectors}:{encapsulation?:ViewEncapsulation, template?:string, templateUrl?:string, styles?:string[], styleUrls?:string[], ngContentSelectors?:string[], animations?:CompileAnimationEntryMetadata[]}={})', 'CompileTemplateMetadata.constructor({encapsulation,template,templateUrl,styles,styleUrls,animations,ngContentSelectors,interpolation}:{encapsulation?:ViewEncapsulation, template?:string, templateUrl?:string, styles?:string[], styleUrls?:string[], ngContentSelectors?:string[], animations?:CompileAnimationEntryMetadata[], interpolation?:[string, string]}={})',
'CompileTemplateMetadata.encapsulation:ViewEncapsulation', 'CompileTemplateMetadata.encapsulation:ViewEncapsulation',
'CompileTemplateMetadata.fromJson(data:{[key:string]:any}):CompileTemplateMetadata', 'CompileTemplateMetadata.fromJson(data:{[key:string]:any}):CompileTemplateMetadata',
'CompileTemplateMetadata.interpolation:[string, string]',
'CompileTemplateMetadata.ngContentSelectors:string[]', 'CompileTemplateMetadata.ngContentSelectors:string[]',
'CompileTemplateMetadata.styles:string[]', 'CompileTemplateMetadata.styles:string[]',
'CompileTemplateMetadata.styleUrls:string[]', 'CompileTemplateMetadata.styleUrls:string[]',