feat(compiler): make interpolation symbols configurable (`@Component` config) (#9367)
closes #9158
This commit is contained in:
parent
6fd52dfb38
commit
1b28cf71f5
|
@ -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.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -603,15 +603,18 @@ export class CompileTemplateMetadata {
|
|||
styleUrls: string[];
|
||||
animations: CompileAnimationEntryMetadata[];
|
||||
ngContentSelectors: string[];
|
||||
interpolation: [string, string];
|
||||
constructor(
|
||||
{encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors}: {
|
||||
{encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors,
|
||||
interpolation}: {
|
||||
encapsulation?: ViewEncapsulation,
|
||||
template?: string,
|
||||
templateUrl?: string,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
ngContentSelectors?: string[],
|
||||
animations?: CompileAnimationEntryMetadata[]
|
||||
animations?: CompileAnimationEntryMetadata[],
|
||||
interpolation?: [string, string]
|
||||
} = {}) {
|
||||
this.encapsulation = encapsulation;
|
||||
this.template = template;
|
||||
|
@ -620,6 +623,10 @@ export class CompileTemplateMetadata {
|
|||
this.styleUrls = isPresent(styleUrls) ? styleUrls : [];
|
||||
this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : [];
|
||||
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 {
|
||||
|
@ -634,7 +641,8 @@ export class CompileTemplateMetadata {
|
|||
styles: data['styles'],
|
||||
styleUrls: data['styleUrls'],
|
||||
animations: animations,
|
||||
ngContentSelectors: data['ngContentSelectors']
|
||||
ngContentSelectors: data['ngContentSelectors'],
|
||||
interpolation: data['interpolation']
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -647,7 +655,8 @@ export class CompileTemplateMetadata {
|
|||
'styles': this.styles,
|
||||
'styleUrls': this.styleUrls,
|
||||
'animations': _objToJson(this.animations),
|
||||
'ngContentSelectors': this.ngContentSelectors
|
||||
'ngContentSelectors': this.ngContentSelectors,
|
||||
'interpolation': this.interpolation
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,7 +104,8 @@ export class DirectiveNormalizer {
|
|||
styles: allResolvedStyles,
|
||||
styleUrls: allStyleAbsUrls,
|
||||
ngContentSelectors: visitor.ngContentSelectors,
|
||||
animations: templateMeta.animations
|
||||
animations: templateMeta.animations,
|
||||
interpolation: templateMeta.interpolation
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,14 @@ import {Injectable} from '@angular/core';
|
|||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
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 {$COLON, $COMMA, $LBRACE, $LBRACKET, $LPAREN, $PERIOD, $RBRACE, $RBRACKET, $RPAREN, $SEMICOLON, $SLASH, EOF, Lexer, Token, isIdentifier, isQuote} from './lexer';
|
||||
|
||||
|
||||
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 {
|
||||
constructor(message: string, input: string, errLocation: string, ctxLocation?: any) {
|
||||
|
@ -26,25 +25,36 @@ export class TemplateBindingParseResult {
|
|||
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()
|
||||
export class Parser {
|
||||
constructor(/** @internal */
|
||||
public _lexer: Lexer) {}
|
||||
|
||||
parseAction(input: string, location: any): ASTWithSource {
|
||||
this._checkNoInterpolation(input, location);
|
||||
parseAction(
|
||||
input: string, location: any,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
|
||||
this._checkNoInterpolation(input, location, interpolationConfig);
|
||||
var tokens = this._lexer.tokenize(this._stripComments(input));
|
||||
var ast = new _ParseAST(input, location, tokens, true).parseChain();
|
||||
return new ASTWithSource(ast, input, location);
|
||||
}
|
||||
|
||||
parseBinding(input: string, location: any): ASTWithSource {
|
||||
var ast = this._parseBindingAst(input, location);
|
||||
parseBinding(
|
||||
input: string, location: any,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
|
||||
var ast = this._parseBindingAst(input, location, interpolationConfig);
|
||||
return new ASTWithSource(ast, input, location);
|
||||
}
|
||||
|
||||
parseSimpleBinding(input: string, location: string): ASTWithSource {
|
||||
var ast = this._parseBindingAst(input, location);
|
||||
parseSimpleBinding(
|
||||
input: string, location: string,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
|
||||
var ast = this._parseBindingAst(input, location, interpolationConfig);
|
||||
if (!SimpleExpressionChecker.check(ast)) {
|
||||
throw new ParseException(
|
||||
'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);
|
||||
}
|
||||
|
||||
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
|
||||
// our lexer or parser for that, so we check for that ahead of time.
|
||||
var quote = this._parseQuote(input, location);
|
||||
|
@ -61,7 +72,7 @@ export class Parser {
|
|||
return quote;
|
||||
}
|
||||
|
||||
this._checkNoInterpolation(input, location);
|
||||
this._checkNoInterpolation(input, location, interpolationConfig);
|
||||
var tokens = this._lexer.tokenize(this._stripComments(input));
|
||||
return new _ParseAST(input, location, tokens, false).parseChain();
|
||||
}
|
||||
|
@ -81,8 +92,10 @@ export class Parser {
|
|||
return new _ParseAST(input, location, tokens, false).parseTemplateBindings();
|
||||
}
|
||||
|
||||
parseInterpolation(input: string, location: any): ASTWithSource {
|
||||
let split = this.splitInterpolation(input, location);
|
||||
parseInterpolation(
|
||||
input: string, location: any,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
|
||||
let split = this.splitInterpolation(input, location, interpolationConfig);
|
||||
if (split == null) return null;
|
||||
|
||||
let expressions: AST[] = [];
|
||||
|
@ -96,8 +109,11 @@ export class Parser {
|
|||
return new ASTWithSource(new Interpolation(split.strings, expressions), input, location);
|
||||
}
|
||||
|
||||
splitInterpolation(input: string, location: string): SplitInterpolation {
|
||||
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
|
||||
splitInterpolation(
|
||||
input: string, location: string,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): SplitInterpolation {
|
||||
const regexp = _createInterpolateRegExp(interpolationConfig);
|
||||
const parts = StringWrapper.split(input, regexp);
|
||||
if (parts.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
@ -114,7 +130,8 @@ export class Parser {
|
|||
} else {
|
||||
throw new ParseException(
|
||||
'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);
|
||||
|
@ -146,19 +163,26 @@ export class Parser {
|
|||
return null;
|
||||
}
|
||||
|
||||
private _checkNoInterpolation(input: string, location: any): void {
|
||||
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
|
||||
private _checkNoInterpolation(
|
||||
input: string, location: any, interpolationConfig: InterpolationConfig): void {
|
||||
var regexp = _createInterpolateRegExp(interpolationConfig);
|
||||
var parts = StringWrapper.split(input, regexp);
|
||||
if (parts.length > 1) {
|
||||
throw new ParseException(
|
||||
'Got interpolation ({{}}) where expression was expected', input,
|
||||
`at column ${this._findInterpolationErrorColumn(parts, 1)} in`, location);
|
||||
`Got interpolation (${interpolationConfig.start}${interpolationConfig.end}) where expression was expected`,
|
||||
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 = '';
|
||||
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;
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as chars from './chars';
|
|||
import {ListWrapper} from './facade/collection';
|
||||
import {NumberWrapper, StringWrapper, isBlank, isPresent} from './facade/lang';
|
||||
import {HtmlTagContentType, NAMED_ENTITIES, getHtmlTagDefinition} from './html_tags';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
|
||||
import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
||||
|
||||
export enum HtmlTokenType {
|
||||
|
@ -43,9 +44,11 @@ export class HtmlTokenizeResult {
|
|||
}
|
||||
|
||||
export function tokenizeHtml(
|
||||
sourceContent: string, sourceUrl: string,
|
||||
tokenizeExpansionForms: boolean = false): HtmlTokenizeResult {
|
||||
return new _HtmlTokenizer(new ParseSourceFile(sourceContent, sourceUrl), tokenizeExpansionForms)
|
||||
sourceContent: string, sourceUrl: string, tokenizeExpansionForms: boolean = false,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): HtmlTokenizeResult {
|
||||
return new _HtmlTokenizer(
|
||||
new ParseSourceFile(sourceContent, sourceUrl), tokenizeExpansionForms,
|
||||
interpolationConfig)
|
||||
.tokenize();
|
||||
}
|
||||
|
||||
|
@ -81,7 +84,9 @@ class _HtmlTokenizer {
|
|||
tokens: HtmlToken[] = [];
|
||||
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._length = file.content.length;
|
||||
this._advance();
|
||||
|
@ -114,7 +119,8 @@ class _HtmlTokenizer {
|
|||
this._consumeTagOpen(start);
|
||||
}
|
||||
} else if (
|
||||
isExpansionFormStart(this._peek, this._nextPeek) && this.tokenizeExpansionForms) {
|
||||
isExpansionFormStart(this._input, this._index, this.interpolationConfig.start) &&
|
||||
this.tokenizeExpansionForms) {
|
||||
this._consumeExpansionFormStart();
|
||||
|
||||
} else if (
|
||||
|
@ -232,16 +238,12 @@ class _HtmlTokenizer {
|
|||
}
|
||||
|
||||
private _attemptStr(chars: string): boolean {
|
||||
var indexBeforeAttempt = this._index;
|
||||
var columnBeforeAttempt = this._column;
|
||||
var lineBeforeAttempt = this._line;
|
||||
const initialPosition = this._savePosition();
|
||||
for (var i = 0; i < chars.length; i++) {
|
||||
if (!this._attemptCharCode(StringWrapper.charCodeAt(chars, i))) {
|
||||
// If attempting to parse the string fails, we want to reset the parser
|
||||
// to where it was before the attempt
|
||||
this._index = indexBeforeAttempt;
|
||||
this._column = columnBeforeAttempt;
|
||||
this._line = lineBeforeAttempt;
|
||||
this._restorePosition(initialPosition);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -558,35 +560,38 @@ class _HtmlTokenizer {
|
|||
var parts: string[] = [];
|
||||
let interpolation = false;
|
||||
|
||||
if (this._peek === chars.$LBRACE && this._nextPeek === chars.$LBRACE) {
|
||||
parts.push(this._readChar(true));
|
||||
parts.push(this._readChar(true));
|
||||
interpolation = true;
|
||||
} else {
|
||||
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));
|
||||
do {
|
||||
const savedPos = this._savePosition();
|
||||
// _attemptStr advances the position when it is true.
|
||||
// To push interpolation symbols, we have to reset it.
|
||||
if (this._attemptStr(this.interpolationConfig.start)) {
|
||||
this._restorePosition(savedPos);
|
||||
for (let i = 0; i < this.interpolationConfig.start.length; i++) {
|
||||
parts.push(this._readChar(true));
|
||||
}
|
||||
interpolation = true;
|
||||
} else if (
|
||||
this._peek === chars.$RBRACE && this._nextPeek === chars.$RBRACE && interpolation) {
|
||||
parts.push(this._readChar(true));
|
||||
parts.push(this._readChar(true));
|
||||
} else if (this._attemptStr(this.interpolationConfig.end) && interpolation) {
|
||||
this._restorePosition(savedPos);
|
||||
for (let i = 0; i < this.interpolationConfig.end.length; i++) {
|
||||
parts.push(this._readChar(true));
|
||||
}
|
||||
interpolation = false;
|
||||
} else {
|
||||
this._restorePosition(savedPos);
|
||||
parts.push(this._readChar(true));
|
||||
}
|
||||
}
|
||||
} while (!this._isTextEnd(interpolation));
|
||||
|
||||
this._endToken([this._processCarriageReturns(parts.join(''))]);
|
||||
}
|
||||
|
||||
private _isTextEnd(interpolation: boolean): boolean {
|
||||
if (this._peek === chars.$LT || this._peek === chars.$EOF) return true;
|
||||
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 &&
|
||||
(this._isInExpansionCase() || this._isInExpansionForm()))
|
||||
return true;
|
||||
|
@ -655,8 +660,11 @@ function isNamedEntityEnd(code: number): boolean {
|
|||
return code == chars.$SEMICOLON || code == chars.$EOF || !isAsciiLetter(code);
|
||||
}
|
||||
|
||||
function isExpansionFormStart(peek: number, nextPeek: number): boolean {
|
||||
return peek === chars.$LBRACE && nextPeek != chars.$LBRACE;
|
||||
function isExpansionFormStart(input: string, offset: number, interpolationStart: string): boolean {
|
||||
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 {
|
||||
|
|
|
@ -4,6 +4,7 @@ import {BaseException} from '../facade/exceptions';
|
|||
import {NumberWrapper, RegExpWrapper, isPresent} from '../facade/lang';
|
||||
import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from '../html_ast';
|
||||
import {HtmlParseTreeResult, HtmlParser} from '../html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../interpolation_config';
|
||||
import {ParseError, ParseSourceSpan} from '../parse_util';
|
||||
|
||||
import {expandNodes} from './expander';
|
||||
|
@ -96,15 +97,19 @@ let _PLACEHOLDER_EXPANDED_REGEXP = /<ph(\s)+name=("(\w)+")><\/ph>/gi;
|
|||
*/
|
||||
export class I18nHtmlParser implements HtmlParser {
|
||||
errors: ParseError[];
|
||||
private _interpolationConfig: InterpolationConfig;
|
||||
|
||||
constructor(
|
||||
private _htmlParser: HtmlParser, private _parser: Parser, private _messagesContent: string,
|
||||
private _messages: {[key: string]: HtmlAst[]}, private _implicitTags: 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 {
|
||||
this.errors = [];
|
||||
this._interpolationConfig = interpolationConfig;
|
||||
|
||||
let res = this._htmlParser.parse(sourceContent, sourceUrl, true);
|
||||
|
||||
|
@ -134,7 +139,7 @@ export class I18nHtmlParser implements HtmlParser {
|
|||
}
|
||||
|
||||
private _mergeI18Part(part: Part): HtmlAst[] {
|
||||
let message = part.createMessage(this._parser);
|
||||
let message = part.createMessage(this._parser, this._interpolationConfig);
|
||||
let messageId = id(message);
|
||||
if (!StringMapWrapper.contains(this._messages, messageId)) {
|
||||
throw new I18nError(
|
||||
|
@ -240,8 +245,8 @@ export class I18nHtmlParser implements HtmlParser {
|
|||
}
|
||||
|
||||
private _mergeTextInterpolation(t: HtmlElementAst, originalNode: HtmlTextAst): HtmlTextAst {
|
||||
let split =
|
||||
this._parser.splitInterpolation(originalNode.value, originalNode.sourceSpan.toString());
|
||||
let split = this._parser.splitInterpolation(
|
||||
originalNode.value, originalNode.sourceSpan.toString(), this._interpolationConfig);
|
||||
let exps = isPresent(split) ? split.expressions : [];
|
||||
|
||||
let messageSubstring =
|
||||
|
@ -277,9 +282,9 @@ export class I18nHtmlParser implements HtmlParser {
|
|||
res.push(attr);
|
||||
return;
|
||||
}
|
||||
message = messageFromAttribute(this._parser, attr);
|
||||
message = messageFromAttribute(this._parser, this._interpolationConfig, attr);
|
||||
} else {
|
||||
message = messageFromI18nAttribute(this._parser, el, i18ns[0]);
|
||||
message = messageFromI18nAttribute(this._parser, this._interpolationConfig, el, i18ns[0]);
|
||||
}
|
||||
|
||||
let messageId = id(message);
|
||||
|
@ -298,7 +303,8 @@ export class I18nHtmlParser implements HtmlParser {
|
|||
}
|
||||
|
||||
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 first = msg[0];
|
||||
|
@ -336,7 +342,7 @@ export class I18nHtmlParser implements HtmlParser {
|
|||
private _convertIntoExpression(
|
||||
name: string, expMap: Map<string, string>, sourceSpan: ParseSourceSpan) {
|
||||
if (expMap.has(name)) {
|
||||
return `{{${expMap.get(name)}}}`;
|
||||
return `${this._interpolationConfig.start}${expMap.get(name)}${this._interpolationConfig.end}`;
|
||||
} else {
|
||||
throw new I18nError(sourceSpan, `Invalid interpolation name '${name}'`);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import {StringMapWrapper} from '../facade/collection';
|
|||
import {isPresent} from '../facade/lang';
|
||||
import {HtmlAst, HtmlElementAst} from '../html_ast';
|
||||
import {HtmlParser} from '../html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../interpolation_config';
|
||||
import {ParseError} from '../parse_util';
|
||||
|
||||
import {expandNodes} from './expander';
|
||||
|
@ -92,14 +93,18 @@ export function removeDuplicates(messages: Message[]): Message[] {
|
|||
export class MessageExtractor {
|
||||
private _messages: Message[];
|
||||
private _errors: ParseError[];
|
||||
private _interpolationConfig: InterpolationConfig;
|
||||
|
||||
constructor(
|
||||
private _htmlParser: HtmlParser, private _parser: Parser, private _implicitTags: 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._errors = [];
|
||||
this._interpolationConfig = interpolationConfig;
|
||||
|
||||
let res = this._htmlParser.parse(template, sourceUrl, true);
|
||||
if (res.errors.length > 0) {
|
||||
|
@ -113,7 +118,7 @@ export class MessageExtractor {
|
|||
|
||||
private _extractMessagesFromPart(part: Part): void {
|
||||
if (part.hasI18n) {
|
||||
this._messages.push(part.createMessage(this._parser));
|
||||
this._messages.push(part.createMessage(this._parser, this._interpolationConfig));
|
||||
this._recurseToExtractMessagesFromAttributes(part.children);
|
||||
} else {
|
||||
this._recurse(part.children);
|
||||
|
@ -148,7 +153,8 @@ export class MessageExtractor {
|
|||
p.attrs.filter(attr => attr.name.startsWith(I18N_ATTR_PREFIX)).forEach(attr => {
|
||||
try {
|
||||
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) {
|
||||
if (e instanceof I18nError) {
|
||||
this._errors.push(e);
|
||||
|
@ -161,6 +167,8 @@ export class MessageExtractor {
|
|||
p.attrs.filter(attr => !attr.name.startsWith(I18N_ATTR_PREFIX))
|
||||
.filter(attr => explicitAttrs.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)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {Parser} from '../expression_parser/parser';
|
||||
import {StringWrapper, isBlank, isPresent} from '../facade/lang';
|
||||
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 {Message} from './message';
|
||||
|
||||
|
@ -61,9 +62,10 @@ export class Part {
|
|||
return this.children[0].sourceSpan;
|
||||
}
|
||||
|
||||
createMessage(parser: Parser): Message {
|
||||
createMessage(parser: Parser, interpolationConfig: InterpolationConfig): 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
|
||||
*/
|
||||
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 attr = p.attrs.find(a => a.name == expectedName);
|
||||
|
||||
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}'.`);
|
||||
}
|
||||
|
||||
export function messageFromAttribute(
|
||||
parser: Parser, attr: HtmlAttrAst, meaning: string = null,
|
||||
description: string = null): Message {
|
||||
let value = removeInterpolation(attr.value, attr.sourceSpan, parser);
|
||||
parser: Parser, interpolationConfig: InterpolationConfig, attr: HtmlAttrAst,
|
||||
meaning: string = null, description: string = null): Message {
|
||||
let value = removeInterpolation(attr.value, attr.sourceSpan, parser, interpolationConfig);
|
||||
return new Message(value, meaning, description);
|
||||
}
|
||||
|
||||
export function removeInterpolation(
|
||||
value: string, source: ParseSourceSpan, parser: Parser): string {
|
||||
value: string, source: ParseSourceSpan, parser: Parser,
|
||||
interpolationConfig: InterpolationConfig): string {
|
||||
try {
|
||||
let parsed = parser.splitInterpolation(value, source.toString());
|
||||
let parsed = parser.splitInterpolation(value, source.toString(), interpolationConfig);
|
||||
let usedNames = new Map<string, number>();
|
||||
if (isPresent(parsed)) {
|
||||
let res = '';
|
||||
|
@ -160,14 +165,15 @@ export function dedupePhName(usedNames: Map<string, number>, name: string): stri
|
|||
}
|
||||
}
|
||||
|
||||
export function stringifyNodes(nodes: HtmlAst[], parser: Parser): string {
|
||||
let visitor = new _StringifyVisitor(parser);
|
||||
export function stringifyNodes(
|
||||
nodes: HtmlAst[], parser: Parser, interpolationConfig: InterpolationConfig): string {
|
||||
let visitor = new _StringifyVisitor(parser, interpolationConfig);
|
||||
return htmlVisitAll(visitor, nodes).join('');
|
||||
}
|
||||
|
||||
class _StringifyVisitor implements HtmlAstVisitor {
|
||||
private _index: number = 0;
|
||||
constructor(private _parser: Parser) {}
|
||||
constructor(private _parser: Parser, private _interpolationConfig: InterpolationConfig) {}
|
||||
|
||||
visitElement(ast: HtmlElementAst, context: any): any {
|
||||
let name = this._index++;
|
||||
|
@ -179,7 +185,8 @@ class _StringifyVisitor implements HtmlAstVisitor {
|
|||
|
||||
visitText(ast: HtmlTextAst, context: any): any {
|
||||
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) {
|
||||
return `<ph name="t${index}">${noInterpolation}</ph>`;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export interface InterpolationConfig {
|
||||
start: string;
|
||||
end: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_INTERPOLATION_CONFIG: InterpolationConfig = {
|
||||
start: '{{',
|
||||
end: '}}'
|
||||
};
|
|
@ -5,7 +5,7 @@ import {StringMapWrapper} from '../src/facade/collection';
|
|||
import {BaseException} from '../src/facade/exceptions';
|
||||
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 {CompilerConfig} from './config';
|
||||
import {hasLifecycleHook} from './directive_lifecycle_reflector';
|
||||
|
@ -96,6 +96,7 @@ export class CompileMetadataResolver {
|
|||
var cmpMeta = <ComponentMetadata>dirMeta;
|
||||
var viewMeta = this._viewResolver.resolve(directiveType);
|
||||
assertArrayOfStrings('styles', viewMeta.styles);
|
||||
assertInterpolationSymbols('interpolation', viewMeta.interpolation);
|
||||
var animations = isPresent(viewMeta.animations) ?
|
||||
viewMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
|
||||
null;
|
||||
|
@ -106,7 +107,8 @@ export class CompileMetadataResolver {
|
|||
templateUrl: viewMeta.templateUrl,
|
||||
styles: viewMeta.styles,
|
||||
styleUrls: viewMeta.styleUrls,
|
||||
animations: animations
|
||||
animations: animations,
|
||||
interpolation: viewMeta.interpolation
|
||||
});
|
||||
changeDetectionStrategy = cmpMeta.changeDetection;
|
||||
if (isPresent(dirMeta.viewProviders)) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import {CompileDirectiveMetadata, CompilePipeMetadata, CompileMetadataWithType,}
|
|||
import {HtmlParser} from './html_parser';
|
||||
import {splitNsName, mergeNsAndName} from './html_tags';
|
||||
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 {CssSelector, SelectorMatcher} from './selector';
|
||||
|
@ -151,12 +152,20 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
|||
directivesIndex = new Map<CompileDirectiveMetadata, number>();
|
||||
ngContentCount: number = 0;
|
||||
pipesByName: Map<string, CompilePipeMetadata>;
|
||||
private _interpolationConfig: InterpolationConfig;
|
||||
|
||||
constructor(
|
||||
public providerViewContext: ProviderViewContext, directives: CompileDirectiveMetadata[],
|
||||
pipes: CompilePipeMetadata[], private _exprParser: Parser,
|
||||
private _schemaRegistry: ElementSchemaRegistry) {
|
||||
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(
|
||||
directives, (directive: CompileDirectiveMetadata, index: number) => {
|
||||
var selector = CssSelector.parse(directive.selector);
|
||||
|
@ -176,7 +185,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
|||
private _parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
var sourceInfo = sourceSpan.start.toString();
|
||||
try {
|
||||
var ast = this._exprParser.parseInterpolation(value, sourceInfo);
|
||||
var ast = this._exprParser.parseInterpolation(value, sourceInfo, this._interpolationConfig);
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
if (isPresent(ast) &&
|
||||
(<Interpolation>ast.ast).expressions.length > MAX_INTERPOLATION_VALUES) {
|
||||
|
@ -193,7 +202,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
|||
private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
var sourceInfo = sourceSpan.start.toString();
|
||||
try {
|
||||
var ast = this._exprParser.parseAction(value, sourceInfo);
|
||||
var ast = this._exprParser.parseAction(value, sourceInfo, this._interpolationConfig);
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
return ast;
|
||||
} catch (e) {
|
||||
|
@ -205,7 +214,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
|||
private _parseBinding(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
var sourceInfo = sourceSpan.start.toString();
|
||||
try {
|
||||
var ast = this._exprParser.parseBinding(value, sourceInfo);
|
||||
var ast = this._exprParser.parseBinding(value, sourceInfo, this._interpolationConfig);
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
return ast;
|
||||
} catch (e) {
|
||||
|
|
|
@ -51,7 +51,8 @@ export class ViewResolver {
|
|||
encapsulation: compMeta.encapsulation,
|
||||
styles: compMeta.styles,
|
||||
styleUrls: compMeta.styleUrls,
|
||||
animations: compMeta.animations
|
||||
animations: compMeta.animations,
|
||||
interpolation: compMeta.interpolation
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -49,7 +49,8 @@ export function main() {
|
|||
new CompileAnimationAnimateMetadata(
|
||||
1000, new CompileAnimationStyleMetadata(0, [{'opacity': 1}]))
|
||||
]))])],
|
||||
ngContentSelectors: ['*']
|
||||
ngContentSelectors: ['*'],
|
||||
interpolation: ['{{', '}}']
|
||||
});
|
||||
fullDirectiveMeta = CompileDirectiveMetadata.create({
|
||||
selector: 'someSelector',
|
||||
|
@ -145,6 +146,11 @@ export function main() {
|
|||
var empty = new CompileTemplateMetadata();
|
||||
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', () => {
|
||||
|
|
|
@ -467,6 +467,14 @@ export function main() {
|
|||
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', () => {
|
||||
it('should ignore comments in interpolation expressions',
|
||||
() => { checkInterpolation('{{a //comment}}', '{{ a }}'); });
|
||||
|
|
|
@ -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 {StringWrapper, isPresent, isString} from '../../src/facade/lang';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/interpolation_config';
|
||||
|
||||
export class Unparser implements AstVisitor {
|
||||
private static _quoteRegExp = /"/g;
|
||||
private _expression: string;
|
||||
private _interpolationConfig: InterpolationConfig;
|
||||
|
||||
unparse(ast: AST) {
|
||||
unparse(ast: AST, interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
|
||||
this._expression = '';
|
||||
this._interpolationConfig = interpolationConfig;
|
||||
this._visit(ast);
|
||||
return this._expression;
|
||||
}
|
||||
|
@ -74,9 +77,9 @@ export class Unparser implements AstVisitor {
|
|||
for (let i = 0; i < ast.strings.length; i++) {
|
||||
this._expression += ast.strings[i];
|
||||
if (i < ast.expressions.length) {
|
||||
this._expression += '{{ ';
|
||||
this._expression += `${this._interpolationConfig.start} `;
|
||||
this._visit(ast.expressions[i]);
|
||||
this._expression += ' }}';
|
||||
this._expression += ` ${this._interpolationConfig.end}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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 {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', () => {
|
||||
expect(tokenizeAndHumanizeParts('t\ne\rs\r\nt')).toEqual([
|
||||
[HtmlTokenType.TEXT, 't\ne\ns\nt'], [HtmlTokenType.EOF]
|
||||
|
@ -577,8 +588,9 @@ export function main() {
|
|||
}
|
||||
|
||||
function tokenizeWithoutErrors(
|
||||
input: string, tokenizeExpansionForms: boolean = false): HtmlToken[] {
|
||||
var tokenizeResult = tokenizeHtml(input, 'someUrl', tokenizeExpansionForms);
|
||||
input: string, tokenizeExpansionForms: boolean = false,
|
||||
interpolationConfig?: InterpolationConfig): HtmlToken[] {
|
||||
var tokenizeResult = tokenizeHtml(input, 'someUrl', tokenizeExpansionForms, interpolationConfig);
|
||||
if (tokenizeResult.errors.length > 0) {
|
||||
var errorString = tokenizeResult.errors.join('\n');
|
||||
throw new BaseException(`Unexpected parse errors:\n${errorString}`);
|
||||
|
@ -586,8 +598,10 @@ function tokenizeWithoutErrors(
|
|||
return tokenizeResult.tokens;
|
||||
}
|
||||
|
||||
function tokenizeAndHumanizeParts(input: string, tokenizeExpansionForms: boolean = false): any[] {
|
||||
return tokenizeWithoutErrors(input, tokenizeExpansionForms)
|
||||
function tokenizeAndHumanizeParts(
|
||||
input: string, tokenizeExpansionForms: boolean = false,
|
||||
interpolationConfig?: InterpolationConfig): any[] {
|
||||
return tokenizeWithoutErrors(input, tokenizeExpansionForms, interpolationConfig)
|
||||
.map(token => [<any>token.type].concat(token.parts));
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import {HtmlParseTreeResult, HtmlParser} from '@angular/compiler/src/html_parser
|
|||
import {I18nHtmlParser} from '@angular/compiler/src/i18n/i18n_html_parser';
|
||||
import {Message, id} from '@angular/compiler/src/i18n/message';
|
||||
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 {humanizeDom} from '@angular/compiler/test/html_ast_spec_utils';
|
||||
import {ddescribe, describe, expect, iit, it} from '@angular/core/testing/testing_internal';
|
||||
|
@ -15,7 +16,8 @@ export function main() {
|
|||
describe('I18nHtmlParser', () => {
|
||||
function parse(
|
||||
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());
|
||||
let htmlParser = new HtmlParser();
|
||||
|
||||
|
@ -26,7 +28,7 @@ export function main() {
|
|||
|
||||
return new I18nHtmlParser(
|
||||
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', () => {
|
||||
|
@ -63,6 +65,17 @@ export function main() {
|
|||
.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', () => {
|
||||
let translations: {[key: string]: string} = {};
|
||||
translations[id(new Message('<ph name="FIRST"/> and <ph name="SECOND"/>', null, null))] =
|
||||
|
|
|
@ -35,6 +35,7 @@ export function main() {
|
|||
expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
|
||||
expect(meta.template.template).toEqual('someTemplate');
|
||||
expect(meta.template.templateUrl).toEqual('someTemplateUrl');
|
||||
expect(meta.template.interpolation).toEqual(['{{', '}}']);
|
||||
}));
|
||||
|
||||
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: (?).`);
|
||||
}
|
||||
}));
|
||||
|
||||
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', () => {
|
||||
|
@ -120,7 +131,8 @@ class ComponentWithoutModuleId {
|
|||
encapsulation: ViewEncapsulation.Emulated,
|
||||
styles: ['someStyle'],
|
||||
styleUrls: ['someStyleUrl'],
|
||||
directives: [SomeDirective]
|
||||
directives: [SomeDirective],
|
||||
interpolation: ['{{', '}}']
|
||||
})
|
||||
class ComponentWithEverything implements OnChanges,
|
||||
OnInit, DoCheck, OnDestroy, AfterContentInit, AfterContentChecked, AfterViewInit,
|
||||
|
@ -139,3 +151,15 @@ class ComponentWithEverything implements OnChanges,
|
|||
class MyBrokenComp1 {
|
||||
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 {
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect,
|
|||
|
||||
import {SecurityContext} from '../core_private';
|
||||
import {Identifiers, identifierToken} from '../src/identifiers';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../src/interpolation_config';
|
||||
|
||||
import {Unparser} from './expression_parser/unparser';
|
||||
import {TEST_PROVIDERS} from './test_bindings';
|
||||
|
@ -152,6 +153,20 @@ export function main() {
|
|||
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', () => {
|
||||
|
||||
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[] {
|
||||
var humanizer = new TemplateHumanizer(false);
|
||||
function humanizeTplAst(
|
||||
templateAsts: TemplateAst[], interpolationConfig?: InterpolationConfig): any[] {
|
||||
const humanizer = new TemplateHumanizer(false, interpolationConfig);
|
||||
templateVisitAll(humanizer, templateAsts);
|
||||
return humanizer.result;
|
||||
}
|
||||
|
||||
function humanizeTplAstSourceSpans(templateAsts: TemplateAst[]): any[] {
|
||||
var humanizer = new TemplateHumanizer(true);
|
||||
function humanizeTplAstSourceSpans(
|
||||
templateAsts: TemplateAst[], interpolationConfig?: InterpolationConfig): any[] {
|
||||
const humanizer = new TemplateHumanizer(true, interpolationConfig);
|
||||
templateVisitAll(humanizer, templateAsts);
|
||||
return humanizer.result;
|
||||
}
|
||||
|
@ -1421,7 +1438,9 @@ function humanizeTplAstSourceSpans(templateAsts: TemplateAst[]): any[] {
|
|||
class TemplateHumanizer implements TemplateAstVisitor {
|
||||
result: any[] = [];
|
||||
|
||||
constructor(private includeSourceSpan: boolean){};
|
||||
constructor(
|
||||
private includeSourceSpan: boolean,
|
||||
private interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG){};
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any {
|
||||
var res = [NgContentAst];
|
||||
|
@ -1461,13 +1480,17 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
|||
return null;
|
||||
}
|
||||
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));
|
||||
return null;
|
||||
}
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {
|
||||
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));
|
||||
return null;
|
||||
|
@ -1478,7 +1501,7 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
|||
return null;
|
||||
}
|
||||
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));
|
||||
return null;
|
||||
}
|
||||
|
@ -1496,7 +1519,10 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
|||
return null;
|
||||
}
|
||||
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));
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -111,7 +111,8 @@ export class MockViewResolver extends ViewResolver {
|
|||
styles: view.styles,
|
||||
styleUrls: view.styleUrls,
|
||||
pipes: view.pipes,
|
||||
encapsulation: view.encapsulation
|
||||
encapsulation: view.encapsulation,
|
||||
interpolation: view.interpolation
|
||||
});
|
||||
|
||||
this._viewCache.set(component, view);
|
||||
|
|
|
@ -42,7 +42,8 @@ export interface ComponentDecorator extends TypeDecorator {
|
|||
renderer?: string,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
animations?: AnimationEntryMetadata[]
|
||||
animations?: AnimationEntryMetadata[],
|
||||
interpolation?: [string, string]
|
||||
}): ViewDecorator;
|
||||
}
|
||||
|
||||
|
@ -63,7 +64,8 @@ export interface ViewDecorator extends TypeDecorator {
|
|||
renderer?: string,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
animations?: AnimationEntryMetadata[]
|
||||
animations?: AnimationEntryMetadata[],
|
||||
interpolation?: [string, string]
|
||||
}): ViewDecorator;
|
||||
}
|
||||
|
||||
|
@ -175,7 +177,8 @@ export interface ComponentMetadataFactory {
|
|||
animations?: AnimationEntryMetadata[],
|
||||
directives?: Array<Type|any[]>,
|
||||
pipes?: Array<Type|any[]>,
|
||||
encapsulation?: ViewEncapsulation
|
||||
encapsulation?: ViewEncapsulation,
|
||||
interpolation?: [string, string]
|
||||
}): ComponentDecorator;
|
||||
new (obj: {
|
||||
selector?: string,
|
||||
|
@ -197,7 +200,8 @@ export interface ComponentMetadataFactory {
|
|||
animations?: AnimationEntryMetadata[],
|
||||
directives?: Array<Type|any[]>,
|
||||
pipes?: Array<Type|any[]>,
|
||||
encapsulation?: ViewEncapsulation
|
||||
encapsulation?: ViewEncapsulation,
|
||||
interpolation?: [string, string]
|
||||
}): ComponentMetadata;
|
||||
}
|
||||
|
||||
|
@ -252,7 +256,8 @@ export interface ViewMetadataFactory {
|
|||
encapsulation?: ViewEncapsulation,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
animations?: AnimationEntryMetadata[]
|
||||
animations?: AnimationEntryMetadata[],
|
||||
interpolation?: [string, string]
|
||||
}): ViewDecorator;
|
||||
new (obj: {
|
||||
templateUrl?: string,
|
||||
|
@ -262,7 +267,8 @@ export interface ViewMetadataFactory {
|
|||
encapsulation?: ViewEncapsulation,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
animations?: AnimationEntryMetadata[]
|
||||
animations?: AnimationEntryMetadata[],
|
||||
interpolation?: [string, string]
|
||||
}): ViewMetadata;
|
||||
}
|
||||
|
||||
|
|
|
@ -954,6 +954,8 @@ export class ComponentMetadata extends DirectiveMetadata {
|
|||
|
||||
encapsulation: ViewEncapsulation;
|
||||
|
||||
interpolation: [string, string];
|
||||
|
||||
constructor({selector,
|
||||
inputs,
|
||||
outputs,
|
||||
|
@ -973,7 +975,8 @@ export class ComponentMetadata extends DirectiveMetadata {
|
|||
animations,
|
||||
directives,
|
||||
pipes,
|
||||
encapsulation}: {
|
||||
encapsulation,
|
||||
interpolation}: {
|
||||
selector?: string,
|
||||
inputs?: string[],
|
||||
outputs?: string[],
|
||||
|
@ -993,7 +996,8 @@ export class ComponentMetadata extends DirectiveMetadata {
|
|||
animations?: AnimationEntryMetadata[],
|
||||
directives?: Array<Type|any[]>,
|
||||
pipes?: Array<Type|any[]>,
|
||||
encapsulation?: ViewEncapsulation
|
||||
encapsulation?: ViewEncapsulation,
|
||||
interpolation?: [string, string]
|
||||
} = {}) {
|
||||
super({
|
||||
selector: selector,
|
||||
|
@ -1018,6 +1022,7 @@ export class ComponentMetadata extends DirectiveMetadata {
|
|||
this.encapsulation = encapsulation;
|
||||
this.moduleId = moduleId;
|
||||
this.animations = animations;
|
||||
this.interpolation = interpolation;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -128,8 +128,11 @@ export class ViewMetadata {
|
|||
|
||||
animations: AnimationEntryMetadata[];
|
||||
|
||||
interpolation: [string, string];
|
||||
|
||||
constructor(
|
||||
{templateUrl, template, directives, pipes, encapsulation, styles, styleUrls, animations}: {
|
||||
{templateUrl, template, directives, pipes, encapsulation, styles, styleUrls, animations,
|
||||
interpolation}: {
|
||||
templateUrl?: string,
|
||||
template?: string,
|
||||
directives?: Array<Type|any[]>,
|
||||
|
@ -137,7 +140,8 @@ export class ViewMetadata {
|
|||
encapsulation?: ViewEncapsulation,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
animations?: AnimationEntryMetadata[]
|
||||
animations?: AnimationEntryMetadata[],
|
||||
interpolation?: [string, string]
|
||||
} = {}) {
|
||||
this.templateUrl = templateUrl;
|
||||
this.template = template;
|
||||
|
@ -147,5 +151,6 @@ export class ViewMetadata {
|
|||
this.pipes = pipes;
|
||||
this.encapsulation = encapsulation;
|
||||
this.animations = animations;
|
||||
this.interpolation = interpolation;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1300,6 +1300,31 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||
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', () => {
|
||||
|
@ -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()
|
||||
class MyService {
|
||||
greeting: string;
|
||||
|
|
|
@ -367,3 +367,7 @@ bool hasConstructor(Object value, Type type) {
|
|||
String escape(String s) {
|
||||
return Uri.encodeComponent(s);
|
||||
}
|
||||
|
||||
String escapeRegExp(String s) {
|
||||
return s.replaceAllMapped(new RegExp(r'([.*+?^=!:${}()|[\]\/\\])'), (Match m) => '\\${m[1]}');
|
||||
}
|
||||
|
|
|
@ -466,3 +466,7 @@ export function hasConstructor(value: Object, type: Type): boolean {
|
|||
export function escape(s: string): string {
|
||||
return _global.encodeURI(s);
|
||||
}
|
||||
|
||||
export function escapeRegExp(s: string): string {
|
||||
return s.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@ const CORE = [
|
|||
'CollectionChangeRecord.toString():string',
|
||||
'CollectionChangeRecord.trackById:any',
|
||||
'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.constructor(selector:string, _viewFactory:Function, _componentType:Type)',
|
||||
'ComponentFactory.create(injector:Injector, projectableNodes:any[][]=null, rootSelectorOrNode:string|any=null):ComponentRef<C>',
|
||||
|
@ -140,9 +140,10 @@ const CORE = [
|
|||
'ComponentMetadata',
|
||||
'ComponentMetadata.animations:AnimationEntryMetadata[]',
|
||||
'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.encapsulation:ViewEncapsulation',
|
||||
'ComponentMetadata.interpolation:[string, string]',
|
||||
'ComponentMetadata.moduleId:string',
|
||||
'ComponentMetadata.pipes:Array<Type|any[]>',
|
||||
'ComponentMetadata.styles:string[]',
|
||||
|
@ -592,16 +593,17 @@ const CORE = [
|
|||
'ViewContainerRef.parentInjector:Injector',
|
||||
'ViewContainerRef.remove(index?:number):void',
|
||||
'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.Emulated',
|
||||
'ViewEncapsulation.Native',
|
||||
'ViewEncapsulation.None',
|
||||
'ViewMetadata',
|
||||
'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.encapsulation:ViewEncapsulation',
|
||||
'ViewMetadata.interpolation:[string, string]',
|
||||
'ViewMetadata.pipes:Array<Type|any[]>',
|
||||
'ViewMetadata.styles:string[]',
|
||||
'ViewMetadata.styleUrls:string[]',
|
||||
|
@ -1229,9 +1231,10 @@ const COMPILER = [
|
|||
'CompilerConfig.useJit:boolean',
|
||||
'CompileTemplateMetadata',
|
||||
'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.fromJson(data:{[key:string]:any}):CompileTemplateMetadata',
|
||||
'CompileTemplateMetadata.interpolation:[string, string]',
|
||||
'CompileTemplateMetadata.ngContentSelectors:string[]',
|
||||
'CompileTemplateMetadata.styles:string[]',
|
||||
'CompileTemplateMetadata.styleUrls:string[]',
|
||||
|
|
Loading…
Reference in New Issue