diff --git a/modules/angular2/src/compiler/html_lexer.ts b/modules/angular2/src/compiler/html_lexer.ts index 827010d6b3..5b56a9b94d 100644 --- a/modules/angular2/src/compiler/html_lexer.ts +++ b/modules/angular2/src/compiler/html_lexer.ts @@ -6,6 +6,7 @@ import { CONST_EXPR, serializeEnum } from 'angular2/src/facade/lang'; +import {BaseException} from 'angular2/src/facade/exceptions'; import {ParseLocation, ParseError, ParseSourceFile, ParseSourceSpan} from './parse_util'; import {getHtmlTagDefinition, HtmlTagContentType, NAMED_ENTITIES} from './html_tags'; @@ -49,7 +50,6 @@ export function tokenizeHtml(sourceContent: string, sourceUrl: string): HtmlToke const $EOF = 0; const $TAB = 9; const $LF = 10; -const $FF = 12; const $CR = 13; const $SPACE = 32; @@ -247,22 +247,17 @@ class _HtmlTokenizer { } } - private _readChar(decodeEntities: boolean, extraNotCharRef: number = null): string { + private _readChar(decodeEntities: boolean): string { if (decodeEntities && this.peek === $AMPERSAND) { var start = this._getLocation(); + this._attemptUntilChar($SEMICOLON); this._advance(); - if (isCharRefStart(this.peek, extraNotCharRef)) { - this._attemptUntilChar($SEMICOLON); - this._advance(); - var entitySrc = this.input.substring(start.offset + 1, this.index - 1); - var decodedEntity = decodeEntity(entitySrc); - if (isPresent(decodedEntity)) { - return decodedEntity; - } else { - throw this._createError(unknownEntityErrorMsg(entitySrc), start); - } + var entitySrc = this.input.substring(start.offset + 1, this.index - 1); + var decodedEntity = decodeEntity(entitySrc); + if (isPresent(decodedEntity)) { + return decodedEntity; } else { - return '&'; + throw this._createError(unknownEntityErrorMsg(entitySrc), start); } } else { var index = this.index; @@ -394,7 +389,7 @@ class _HtmlTokenizer { this._advance(); var parts = []; while (this.peek !== quoteChar) { - parts.push(this._readChar(true, quoteChar)); + parts.push(this._readChar(true)); } value = parts.join(''); this._advance(); @@ -445,13 +440,7 @@ function isWhitespace(code: number): boolean { function isNameEnd(code: number): boolean { return isWhitespace(code) || code === $GT || code === $SLASH || code === $SQ || code === $DQ || - code === $EQ; -} - -// http://www.w3.org/TR/html5/syntax.html#consume-a-character-reference -function isCharRefStart(code: number, extraNotCharRef: number): boolean { - return code != $TAB && code != $LF && code != $FF && code != $SPACE && code != $LT && - code != $AMPERSAND && code != $EOF && code !== extraNotCharRef; + code === $EQ } function isPrefixEnd(code: number): boolean { diff --git a/modules/angular2/src/compiler/html_parser.ts b/modules/angular2/src/compiler/html_parser.ts index 698ada573d..ea303e71da 100644 --- a/modules/angular2/src/compiler/html_parser.ts +++ b/modules/angular2/src/compiler/html_parser.ts @@ -9,22 +9,34 @@ import { serializeEnum, CONST_EXPR } from 'angular2/src/facade/lang'; - +import {DOM} from 'angular2/src/core/dom/dom_adapter'; import {ListWrapper} from 'angular2/src/facade/collection'; import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlElementAst} from './html_ast'; +import {escapeDoubleQuoteString} from './util'; import {Injectable} from 'angular2/src/core/di'; import {HtmlToken, HtmlTokenType, tokenizeHtml} from './html_lexer'; import {ParseError, ParseLocation, ParseSourceSpan} from './parse_util'; import {HtmlTagDefinition, getHtmlTagDefinition} from './html_tags'; +// TODO: remove this, just provide a plain error message! +export enum HtmlTreeErrorType { + UnexpectedClosingTag +} + +const HTML_ERROR_TYPE_MSGS = CONST_EXPR(['Unexpected closing tag']); + + export class HtmlTreeError extends ParseError { - static create(elementName: string, location: ParseLocation, msg: string): HtmlTreeError { - return new HtmlTreeError(elementName, location, msg); + static create(type: HtmlTreeErrorType, elementName: string, + location: ParseLocation): HtmlTreeError { + return new HtmlTreeError(type, HTML_ERROR_TYPE_MSGS[serializeEnum(type)], elementName, + location); } - constructor(public elementName: string, location: ParseLocation, msg: string) { + constructor(public type: HtmlTreeErrorType, msg: string, public elementName: string, + location: ParseLocation) { super(location, msg); } } @@ -43,8 +55,11 @@ export class HtmlParser { } } +var NS_PREFIX_RE = /^@[^:]+/g; + class TreeBuilder { private index: number = -1; + private length: number; private peek: HtmlToken; private rootNodes: HtmlAst[] = []; @@ -114,7 +129,7 @@ class TreeBuilder { while (this.peek.type === HtmlTokenType.ATTR_NAME) { attrs.push(this._consumeAttr(this._advance())); } - var fullName = getElementFullName(prefix, name, this._getParentElement()); + var fullName = elementName(prefix, name, this._getParentElement()); var voidElement = false; // Note: There could have been a tokenizer error // so that we don't get a token for the end tag... @@ -135,13 +150,15 @@ class TreeBuilder { } private _pushElement(el: HtmlElementAst) { - for (var stackIndex = this.elementStack.length - 1; stackIndex >= 0; stackIndex--) { + var stackIndex = this.elementStack.length - 1; + while (stackIndex >= 0) { var parentEl = this.elementStack[stackIndex]; - if (getHtmlTagDefinition(parentEl.name).isClosedByChild(el.name)) { - ListWrapper.splice(this.elementStack, stackIndex, this.elementStack.length - stackIndex); + if (!getHtmlTagDefinition(parentEl.name).isClosedByChild(el.name)) { break; } + stackIndex--; } + this.elementStack.splice(stackIndex, this.elementStack.length - 1 - stackIndex); var tagDef = getHtmlTagDefinition(el.name); var parentEl = this._getParentElement(); @@ -158,29 +175,35 @@ class TreeBuilder { private _consumeEndTag(endTagToken: HtmlToken) { var fullName = - getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getParentElement()); + elementName(endTagToken.parts[0], endTagToken.parts[1], this._getParentElement()); if (!this._popElement(fullName)) { - this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan.start, - `Unexpected closing tag "${endTagToken.parts[1]}"`)); + this.errors.push(HtmlTreeError.create(HtmlTreeErrorType.UnexpectedClosingTag, fullName, + endTagToken.sourceSpan.start)); } } private _popElement(fullName: string): boolean { - for (let stackIndex = this.elementStack.length - 1; stackIndex >= 0; stackIndex--) { + var stackIndex = this.elementStack.length - 1; + var hasError = false; + while (stackIndex >= 0) { var el = this.elementStack[stackIndex]; - if (el.name.toLowerCase() == fullName.toLowerCase()) { - ListWrapper.splice(this.elementStack, stackIndex, this.elementStack.length - stackIndex); - return true; + if (el.name == fullName) { + break; } if (!getHtmlTagDefinition(el.name).closedByParent) { - return false; + hasError = true; + break; } + stackIndex--; } - return false; + if (!hasError) { + this.elementStack.splice(stackIndex, this.elementStack.length - stackIndex); + } + return !hasError; } private _consumeAttr(attrName: HtmlToken): HtmlAttrAst { - var fullName = mergeNsAndName(attrName.parts[0], attrName.parts[1]); + var fullName = elementName(attrName.parts[0], attrName.parts[1], null); var end = attrName.sourceSpan.end; var value = ''; if (this.peek.type === HtmlTokenType.ATTR_VALUE) { @@ -205,24 +228,20 @@ class TreeBuilder { } } -function mergeNsAndName(prefix: string, localName: string): string { - return isPresent(prefix) ? `@${prefix}:${localName}` : localName; -} - -function getElementFullName(prefix: string, localName: string, - parentElement: HtmlElementAst): string { +function elementName(prefix: string, localName: string, parentElement: HtmlElementAst) { if (isBlank(prefix)) { prefix = getHtmlTagDefinition(localName).implicitNamespacePrefix; - if (isBlank(prefix) && isPresent(parentElement)) { - prefix = namespacePrefix(parentElement.name); - } } - - return mergeNsAndName(prefix, localName); + if (isBlank(prefix) && isPresent(parentElement)) { + prefix = namespacePrefix(parentElement.name); + } + if (isPresent(prefix)) { + return `@${prefix}:${localName}`; + } else { + return localName; + } } -var NS_PREFIX_RE = /^@([^:]+)/g; - function namespacePrefix(elementName: string): string { var match = RegExpWrapper.firstMatch(NS_PREFIX_RE, elementName); return isBlank(match) ? null : match[1]; diff --git a/modules/angular2/src/compiler/html_tags.ts b/modules/angular2/src/compiler/html_tags.ts index b104cbbbc6..a88bc363e4 100644 --- a/modules/angular2/src/compiler/html_tags.ts +++ b/modules/angular2/src/compiler/html_tags.ts @@ -1,61 +1,7 @@ import {isPresent, isBlank, normalizeBool, CONST_EXPR} from 'angular2/src/facade/lang'; -// see http://www.w3.org/TR/html51/syntax.html#named-character-references -// see https://html.spec.whatwg.org/multipage/entities.json -// This list is not exhaustive to keep the compiler footprint low. -// The `{` / `ƫ` syntax should be used when the named character reference does not exist. -export const NAMED_ENTITIES = CONST_EXPR({ - 'lt': '<', - 'gt': '>', - 'nbsp': '\u00A0', - 'amp': '&', - 'Aacute': '\u00C1', - 'Acirc': '\u00C2', - 'Agrave': '\u00C0', - 'Atilde': '\u00C3', - 'Auml': '\u00C4', - 'Ccedil': '\u00C7', - 'Eacute': '\u00C9', - 'Ecirc': '\u00CA', - 'Egrave': '\u00C8', - 'Euml': '\u00CB', - 'Iacute': '\u00CD', - 'Icirc': '\u00CE', - 'Igrave': '\u00CC', - 'Iuml': '\u00CF', - 'Oacute': '\u00D3', - 'Ocirc': '\u00D4', - 'Ograve': '\u00D2', - 'Otilde': '\u00D5', - 'Ouml': '\u00D6', - 'Uacute': '\u00DA', - 'Ucirc': '\u00DB', - 'Ugrave': '\u00D9', - 'Uuml': '\u00DC', - 'aacute': '\u00E1', - 'acirc': '\u00E2', - 'agrave': '\u00E0', - 'atilde': '\u00E3', - 'auml': '\u00E4', - 'ccedil': '\u00E7', - 'eacute': '\u00E9', - 'ecirc': '\u00EA', - 'egrave': '\u00E8', - 'euml': '\u00EB', - 'iacute': '\u00ED', - 'icirc': '\u00EE', - 'igrave': '\u00EC', - 'iuml': '\u00EF', - 'oacute': '\u00F3', - 'ocirc': '\u00F4', - 'ograve': '\u00F2', - 'otilde': '\u00F5', - 'ouml': '\u00F6', - 'uacute': '\u00FA', - 'ucirc': '\u00FB', - 'ugrave': '\u00F9', - 'uuml': '\u00FC', -}); +// TODO: fill this! +export const NAMED_ENTITIES: {[key: string]: string} = CONST_EXPR({'amp': '&'}); export enum HtmlTagContentType { RAW_TEXT, @@ -65,72 +11,54 @@ export enum HtmlTagContentType { export class HtmlTagDefinition { private closedByChildren: {[key: string]: boolean} = {}; - public closedByParent: boolean = false; + public closedByParent: boolean; public requiredParent: string; public implicitNamespacePrefix: string; public contentType: HtmlTagContentType; constructor({closedByChildren, requiredParent, implicitNamespacePrefix, contentType}: { - closedByChildren?: string, + closedByChildren?: string[], requiredParent?: string, implicitNamespacePrefix?: string, contentType?: HtmlTagContentType } = {}) { - if (isPresent(closedByChildren) && closedByChildren.length > 0) { - closedByChildren.split(',').forEach(tagName => this.closedByChildren[tagName.trim()] = true); - this.closedByParent = true; + if (isPresent(closedByChildren)) { + closedByChildren.forEach(tagName => this.closedByChildren[tagName] = true); } + this.closedByParent = isPresent(closedByChildren) && closedByChildren.length > 0; this.requiredParent = requiredParent; this.implicitNamespacePrefix = implicitNamespacePrefix; this.contentType = isPresent(contentType) ? contentType : HtmlTagContentType.PARSABLE_DATA; } - requireExtraParent(currentParent: string): boolean { + requireExtraParent(currentParent: string) { return isPresent(this.requiredParent) && - (isBlank(currentParent) || this.requiredParent != currentParent.toLowerCase()); + (isBlank(currentParent) || this.requiredParent != currentParent.toLocaleLowerCase()); } - isClosedByChild(name: string): boolean { + isClosedByChild(name: string) { return normalizeBool(this.closedByChildren['*']) || normalizeBool(this.closedByChildren[name.toLowerCase()]); } } -// see http://www.w3.org/TR/html51/syntax.html#optional-tags -// This implementation does not fully conform to the HTML5 spec. +// TODO: Fill this table using +// https://github.com/greim/html-tokenizer/blob/master/parser.js +// and http://www.w3.org/TR/html51/syntax.html#optional-tags var TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = { - 'link': new HtmlTagDefinition({closedByChildren: '*'}), - 'ng-content': new HtmlTagDefinition({closedByChildren: '*'}), - 'img': new HtmlTagDefinition({closedByChildren: '*'}), - 'input': new HtmlTagDefinition({closedByChildren: '*'}), - 'hr': new HtmlTagDefinition({closedByChildren: '*'}), - 'br': new HtmlTagDefinition({closedByChildren: '*'}), - 'wbr': new HtmlTagDefinition({closedByChildren: '*'}), - 'p': new HtmlTagDefinition({ - closedByChildren: - 'address,article,aside,blockquote,div,dl,fieldset,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,main,nav,ol,p,pre,section,table,ul' - }), - 'thead': new HtmlTagDefinition({closedByChildren: 'tbody,tfoot'}), - 'tbody': new HtmlTagDefinition({closedByChildren: 'tbody,tfoot'}), - 'tfoot': new HtmlTagDefinition({closedByChildren: 'tbody'}), - 'tr': new HtmlTagDefinition({closedByChildren: 'tr', requiredParent: 'tbody'}), - 'td': new HtmlTagDefinition({closedByChildren: 'td,th'}), - 'th': new HtmlTagDefinition({closedByChildren: 'td,th'}), - 'col': new HtmlTagDefinition({closedByChildren: 'col', requiredParent: 'colgroup'}), + 'link': new HtmlTagDefinition({closedByChildren: ['*']}), + 'ng-content': new HtmlTagDefinition({closedByChildren: ['*']}), + 'img': new HtmlTagDefinition({closedByChildren: ['*']}), + 'input': new HtmlTagDefinition({closedByChildren: ['*']}), + 'p': new HtmlTagDefinition({closedByChildren: ['p']}), + 'tr': new HtmlTagDefinition({closedByChildren: ['tr'], requiredParent: 'tbody'}), + 'col': new HtmlTagDefinition({closedByChildren: ['col'], requiredParent: 'colgroup'}), 'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}), 'math': new HtmlTagDefinition({implicitNamespacePrefix: 'math'}), - 'li': new HtmlTagDefinition({closedByChildren: 'li'}), - 'dt': new HtmlTagDefinition({closedByChildren: 'dt,dd'}), - 'dd': new HtmlTagDefinition({closedByChildren: 'dt,dd'}), - 'rb': new HtmlTagDefinition({closedByChildren: 'rb,rt,rtc,rp'}), - 'rt': new HtmlTagDefinition({closedByChildren: 'rb,rt,rtc,rp'}), - 'rtc': new HtmlTagDefinition({closedByChildren: 'rb,rtc,rp'}), - 'rp': new HtmlTagDefinition({closedByChildren: 'rb,rt,rtc,rp'}), - 'optgroup': new HtmlTagDefinition({closedByChildren: 'optgroup'}), 'style': new HtmlTagDefinition({contentType: HtmlTagContentType.RAW_TEXT}), 'script': new HtmlTagDefinition({contentType: HtmlTagContentType.RAW_TEXT}), 'title': new HtmlTagDefinition({contentType: HtmlTagContentType.ESCAPABLE_RAW_TEXT}), - 'textarea': new HtmlTagDefinition({contentType: HtmlTagContentType.ESCAPABLE_RAW_TEXT}), + 'textarea': new HtmlTagDefinition({contentType: HtmlTagContentType.ESCAPABLE_RAW_TEXT}) }; var DEFAULT_TAG_DEFINITION = new HtmlTagDefinition(); diff --git a/modules/angular2/src/compiler/parse_util.ts b/modules/angular2/src/compiler/parse_util.ts index 7e1da4dc43..4c9413cce8 100644 --- a/modules/angular2/src/compiler/parse_util.ts +++ b/modules/angular2/src/compiler/parse_util.ts @@ -1,8 +1,10 @@ +import {Math} from 'angular2/src/facade/math'; + export class ParseLocation { constructor(public file: ParseSourceFile, public offset: number, public line: number, public col: number) {} - toString(): string { return `${this.file.url}@${this.line}:${this.col}`; } + toString() { return `${this.file.url}@${this.line}:${this.col}`; } } export class ParseSourceFile { @@ -14,33 +16,9 @@ export abstract class ParseError { toString(): string { var source = this.location.file.content; - var ctxStart = this.location.offset; - var ctxEnd = this.location.offset; - var ctxLen = 0; - var ctxLines = 0; - - while (ctxLen < 100 && ctxStart > 0) { - ctxStart--; - ctxLen++; - if (source[ctxStart] == "\n") { - if (++ctxLines == 3) { - break; - } - } - } - - ctxLen = 0; - ctxLines = 0; - while (ctxLen < 100 && ctxEnd < source.length - 1) { - ctxEnd++; - ctxLen++; - if (source[ctxEnd] == "\n") { - if (++ctxLines == 3) { - break; - } - } - } - return `${this.msg} ("${source.substring(ctxStart, ctxEnd + 1)}"): ${this.location}`; + var ctxStart = Math.max(this.location.offset - 10, 0); + var ctxEnd = Math.min(this.location.offset + 10, source.length); + return `${this.msg} (${source.substring(ctxStart, ctxEnd)}): ${this.location}`; } } diff --git a/modules/angular2/src/compiler/template_parser.ts b/modules/angular2/src/compiler/template_parser.ts index 173e356dac..40d279b4b9 100644 --- a/modules/angular2/src/compiler/template_parser.ts +++ b/modules/angular2/src/compiler/template_parser.ts @@ -64,7 +64,7 @@ import {dashCaseToCamelCase, camelCaseToDashCase, splitAtColon} from './util'; // Group 7 = idenitifer inside [] // Group 8 = identifier inside () var BIND_NAME_REGEXP = - /^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/ig; + /^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g; const TEMPLATE_ELEMENT = 'template'; const TEMPLATE_ATTR = 'template'; @@ -218,7 +218,7 @@ class TemplateParseVisitor implements HtmlAstVisitor { } }); - var isTemplateElement = nodeName.toLowerCase() == TEMPLATE_ELEMENT; + var isTemplateElement = nodeName == TEMPLATE_ELEMENT; var elementCssSelector = createElementCssSelector(nodeName, matchableAttrs); var directives = this._createDirectiveAsts( element.name, this._parseDirectives(this.selectorMatcher, elementCssSelector), @@ -266,7 +266,7 @@ class TemplateParseVisitor implements HtmlAstVisitor { targetProps: BoundElementOrDirectiveProperty[], targetVars: VariableAst[]): boolean { var templateBindingsSource = null; - if (attr.name.toLowerCase() == TEMPLATE_ATTR) { + if (attr.name == TEMPLATE_ATTR) { templateBindingsSource = attr.value; } else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) { var key = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star @@ -347,7 +347,7 @@ class TemplateParseVisitor implements HtmlAstVisitor { } private _normalizeAttributeName(attrName: string): string { - return attrName.toLowerCase().startsWith('data-') ? attrName.substring(5) : attrName; + return attrName.startsWith('data-') ? attrName.substring(5) : attrName; } private _parseVariable(identifier: string, value: string, sourceSpan: ParseSourceSpan, @@ -542,25 +542,21 @@ class TemplateParseVisitor implements HtmlAstVisitor { `Can't bind to '${boundPropertyName}' since it isn't a known native property`, sourceSpan); } + } else if (parts[0] == ATTRIBUTE_PREFIX) { + boundPropertyName = dashCaseToCamelCase(parts[1]); + bindingType = PropertyBindingType.Attribute; + } else if (parts[0] == CLASS_PREFIX) { + // keep original case! + boundPropertyName = parts[1]; + bindingType = PropertyBindingType.Class; + } else if (parts[0] == STYLE_PREFIX) { + unit = parts.length > 2 ? parts[2] : null; + boundPropertyName = dashCaseToCamelCase(parts[1]); + bindingType = PropertyBindingType.Style; } else { - let lcPrefix = parts[0].toLowerCase(); - if (lcPrefix == ATTRIBUTE_PREFIX) { - boundPropertyName = dashCaseToCamelCase(parts[1]); - bindingType = PropertyBindingType.Attribute; - } else if (lcPrefix == CLASS_PREFIX) { - // keep original case! - boundPropertyName = parts[1]; - bindingType = PropertyBindingType.Class; - } else if (lcPrefix == STYLE_PREFIX) { - unit = parts.length > 2 ? parts[2] : null; - boundPropertyName = dashCaseToCamelCase(parts[1]); - bindingType = PropertyBindingType.Style; - } else { - this._reportError(`Invalid property name ${name}`, sourceSpan); - bindingType = null; - } + this._reportError(`Invalid property name ${name}`, sourceSpan); + bindingType = null; } - return new BoundElementPropertyAst(boundPropertyName, bindingType, ast, unit, sourceSpan); } diff --git a/modules/angular2/src/compiler/template_preparser.ts b/modules/angular2/src/compiler/template_preparser.ts index 25a8eb287c..28640b9fe0 100644 --- a/modules/angular2/src/compiler/template_preparser.ts +++ b/modules/angular2/src/compiler/template_preparser.ts @@ -17,19 +17,18 @@ export function preparseElement(ast: HtmlElementAst): PreparsedElement { var relAttr = null; var nonBindable = false; ast.attrs.forEach(attr => { - let attrName = attr.name.toLowerCase(); - if (attrName == NG_CONTENT_SELECT_ATTR) { + if (attr.name == NG_CONTENT_SELECT_ATTR) { selectAttr = attr.value; - } else if (attrName == LINK_STYLE_HREF_ATTR) { + } else if (attr.name == LINK_STYLE_HREF_ATTR) { hrefAttr = attr.value; - } else if (attrName == LINK_STYLE_REL_ATTR) { + } else if (attr.name == LINK_STYLE_REL_ATTR) { relAttr = attr.value; - } else if (attrName == NG_NON_BINDABLE_ATTR) { + } else if (attr.name == NG_NON_BINDABLE_ATTR) { nonBindable = true; } }); selectAttr = normalizeNgContentSelect(selectAttr); - var nodeName = ast.name.toLowerCase(); + var nodeName = ast.name; var type = PreparsedElementType.OTHER; if (nodeName == NG_CONTENT_ELEMENT) { type = PreparsedElementType.NG_CONTENT; diff --git a/modules/angular2/src/core/render/dom/dom_renderer.ts b/modules/angular2/src/core/render/dom/dom_renderer.ts index 371f65e798..633a2cb2d9 100644 --- a/modules/angular2/src/core/render/dom/dom_renderer.ts +++ b/modules/angular2/src/core/render/dom/dom_renderer.ts @@ -35,8 +35,94 @@ import {DefaultRenderView, DefaultRenderFragmentRef, DefaultProtoViewRef} from ' import {camelCaseToDashCase} from './util'; import {ViewEncapsulation} from 'angular2/src/core/metadata'; -const NAMESPACE_URIS = - CONST_EXPR({'xlink': 'http://www.w3.org/1999/xlink', 'svg': 'http://www.w3.org/2000/svg'}); +// TODO(tbosch): solve SVG properly once https://github.com/angular/angular/issues/4417 is done +const XLINK_NAMESPACE = 'http://www.w3.org/1999/xlink'; +const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; +const SVG_ELEMENT_NAMES = CONST_EXPR({ + 'altGlyph': true, + 'altGlyphDef': true, + 'altGlyphItem': true, + 'animate': true, + 'animateColor': true, + 'animateMotion': true, + 'animateTransform': true, + 'circle': true, + 'clipPath': true, + 'color-profile': true, + 'cursor': true, + 'defs': true, + 'desc': true, + 'ellipse': true, + 'feBlend': true, + 'feColorMatrix': true, + 'feComponentTransfer': true, + 'feComposite': true, + 'feConvolveMatrix': true, + 'feDiffuseLighting': true, + 'feDisplacementMap': true, + 'feDistantLight': true, + 'feFlood': true, + 'feFuncA': true, + 'feFuncB': true, + 'feFuncG': true, + 'feFuncR': true, + 'feGaussianBlur': true, + 'feImage': true, + 'feMerge': true, + 'feMergeNode': true, + 'feMorphology': true, + 'feOffset': true, + 'fePointLight': true, + 'feSpecularLighting': true, + 'feSpotLight': true, + 'feTile': true, + 'feTurbulence': true, + 'filter': true, + 'font': true, + 'font-face': true, + 'font-face-format': true, + 'font-face-name': true, + 'font-face-src': true, + 'font-face-uri': true, + 'foreignObject': true, + 'g': true, + // TODO(tbosch): this needs to be disabled + // because of an internal project. + // We will fix SVG soon, so this will go away... + // 'glyph': true, + 'glyphRef': true, + 'hkern': true, + 'image': true, + 'line': true, + 'linearGradient': true, + 'marker': true, + 'mask': true, + 'metadata': true, + 'missing-glyph': true, + 'mpath': true, + 'path': true, + 'pattern': true, + 'polygon': true, + 'polyline': true, + 'radialGradient': true, + 'rect': true, + 'set': true, + 'stop': true, + 'style': true, + 'svg': true, + 'switch': true, + 'symbol': true, + 'text': true, + 'textPath': true, + 'title': true, + 'tref': true, + 'tspan': true, + 'use': true, + 'view': true, + 'vkern': true +}); + +const SVG_ATTR_NAMESPACES = CONST_EXPR({'href': XLINK_NAMESPACE, 'xlink:href': XLINK_NAMESPACE}); export abstract class DomRenderer extends Renderer implements NodeFactory { abstract registerComponentTemplate(template: RenderComponentTemplate); @@ -278,31 +364,24 @@ export class DomRenderer_ extends DomRenderer { wtfLeave(s); } createElement(name: string, attrNameAndValues: string[]): Node { - var nsAndName = splitNamespace(name); - var el = isPresent(nsAndName[0]) ? - DOM.createElementNS(NAMESPACE_URIS[nsAndName[0]], nsAndName[1]) : - DOM.createElement(nsAndName[1]); - this._setAttributes(el, attrNameAndValues); + var isSvg = SVG_ELEMENT_NAMES[name] == true; + var el = isSvg ? DOM.createElementNS(SVG_NAMESPACE, name) : DOM.createElement(name); + this._setAttributes(el, attrNameAndValues, isSvg); return el; } mergeElement(existing: Node, attrNameAndValues: string[]) { DOM.clearNodes(existing); - this._setAttributes(existing, attrNameAndValues); + this._setAttributes(existing, attrNameAndValues, false); } - private _setAttributes(node: Node, attrNameAndValues: string[]) { + private _setAttributes(node: Node, attrNameAndValues: string[], isSvg: boolean) { for (var attrIdx = 0; attrIdx < attrNameAndValues.length; attrIdx += 2) { - var attrNs; var attrName = attrNameAndValues[attrIdx]; - var nsAndName = splitNamespace(attrName); - if (isPresent(nsAndName[0])) { - attrName = nsAndName[0] + ':' + nsAndName[1]; - attrNs = NAMESPACE_URIS[nsAndName[0]]; - } var attrValue = attrNameAndValues[attrIdx + 1]; + var attrNs = isSvg ? SVG_ATTR_NAMESPACES[attrName] : null; if (isPresent(attrNs)) { - DOM.setAttributeNS(node, attrNs, attrName, attrValue); + DOM.setAttributeNS(node, XLINK_NAMESPACE, attrName, attrValue); } else { - DOM.setAttribute(node, nsAndName[1], attrValue); + DOM.setAttribute(node, attrName, attrValue); } } } @@ -353,13 +432,3 @@ function decoratePreventDefault(eventHandler: Function): Function { } }; } - -var NS_PREFIX_RE = /^@([^:]+):(.+)/g; - -function splitNamespace(name: string): string[] { - if (name[0] != '@') { - return [null, name]; - } - let match = RegExpWrapper.firstMatch(NS_PREFIX_RE, name); - return [match[1], match[2]]; -} diff --git a/modules/angular2/test/compiler/html_lexer_spec.ts b/modules/angular2/test/compiler/html_lexer_spec.ts index 815a9da532..8c7d74d9d2 100644 --- a/modules/angular2/test/compiler/html_lexer_spec.ts +++ b/modules/angular2/test/compiler/html_lexer_spec.ts @@ -1,22 +1,8 @@ -import { - ddescribe, - describe, - it, - iit, - xit, - expect, - beforeEach, - afterEach -} from 'angular2/testing_internal'; -import {BaseException} from 'angular2/src/facade/exceptions'; +import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from '../../test_lib'; +import {BaseException} from '../../src/facade/exceptions'; -import { - tokenizeHtml, - HtmlToken, - HtmlTokenType, - HtmlTokenError -} from 'angular2/src/compiler/html_lexer'; -import {ParseSourceSpan, ParseLocation, ParseSourceFile} from 'angular2/src/compiler/parse_util'; +import {tokenizeHtml, HtmlToken, HtmlTokenType} from '../../src/compiler/html_lexer'; +import {ParseSourceSpan, ParseLocation} from '../../src/compiler/parse_util'; export function main() { describe('HtmlLexer', () => { @@ -277,17 +263,6 @@ export function main() { ]); }); - it('should parse attributes with "&" in values', () => { - expect(tokenizeAndHumanizeParts('')) - .toEqual([ - [HtmlTokenType.TAG_OPEN_START, null, 't'], - [HtmlTokenType.ATTR_NAME, null, 'a'], - [HtmlTokenType.ATTR_VALUE, 'b && c &'], - [HtmlTokenType.TAG_OPEN_END], - [HtmlTokenType.EOF] - ]); - }); - it('should store the locations', () => { expect(tokenizeAndHumanizeSourceSpans('')) .toEqual([ @@ -389,11 +364,6 @@ export function main() { .toEqual([[HtmlTokenType.TEXT, 'a&b'], [HtmlTokenType.EOF]]); }); - it('should parse text starting with "&"', () => { - expect(tokenizeAndHumanizeParts('a && b &')) - .toEqual([[HtmlTokenType.TEXT, 'a && b &'], [HtmlTokenType.EOF]]); - }); - it('should store the locations', () => { expect(tokenizeAndHumanizeSourceSpans('a')) .toEqual([[HtmlTokenType.TEXT, 'a'], [HtmlTokenType.EOF, '']]); @@ -516,17 +486,6 @@ export function main() { }); - describe('errors', () => { - it('should include 2 lines of context in message', () => { - let src = "111\n222\n333\nE\n444\n555\n666\n"; - let file = new ParseSourceFile(src, 'file://'); - let location = new ParseLocation(file, 12, 123, 456); - let error = new HtmlTokenError('**ERROR**', null, location); - expect(error.toString()) - .toEqual(`**ERROR** ("\n222\n333\nE\n444\n555\n"): file://@123:456`); - }); - }); - }); } diff --git a/modules/angular2/test/compiler/html_parser_spec.ts b/modules/angular2/test/compiler/html_parser_spec.ts index 68feda373d..21618378b2 100644 --- a/modules/angular2/test/compiler/html_parser_spec.ts +++ b/modules/angular2/test/compiler/html_parser_spec.ts @@ -9,8 +9,8 @@ import { afterEach } from 'angular2/testing_internal'; -import {HtmlTokenType} from 'angular2/src/compiler/html_lexer'; -import {HtmlParser, HtmlParseTreeResult, HtmlTreeError} from 'angular2/src/compiler/html_parser'; + +import {HtmlParser, HtmlParseTreeResult} from 'angular2/src/compiler/html_parser'; import { HtmlAst, HtmlAstVisitor, @@ -19,15 +19,17 @@ import { HtmlTextAst, htmlVisitAll } from 'angular2/src/compiler/html_ast'; -import {ParseError, ParseLocation, ParseSourceSpan} from 'angular2/src/compiler/parse_util'; - -import {BaseException} from 'angular2/src/facade/exceptions'; export function main() { describe('HtmlParser', () => { var parser: HtmlParser; beforeEach(() => { parser = new HtmlParser(); }); + // TODO: add more test cases + // TODO: separate tests for source spans from tests for tree parsing + // TODO: find a better way to assert the tree structure! + // -> maybe with arrays and object hashes!! + describe('parse', () => { describe('text nodes', () => { it('should parse root level text nodes', () => { @@ -36,91 +38,37 @@ export function main() { it('should parse text nodes inside regular elements', () => { expect(humanizeDom(parser.parse('
a
', 'TestComp'))) - .toEqual([[HtmlElementAst, 'div', 0], [HtmlTextAst, 'a']]); + .toEqual([[HtmlElementAst, 'div'], [HtmlTextAst, 'a']]); }); it('should parse text nodes inside template elements', () => { expect(humanizeDom(parser.parse('', 'TestComp'))) - .toEqual([[HtmlElementAst, 'template', 0], [HtmlTextAst, 'a']]); - }); - - it('should parse CDATA', () => { - expect(humanizeDom(parser.parse('', 'TestComp'))) - .toEqual([[HtmlTextAst, 'text']]); + .toEqual([[HtmlElementAst, 'template'], [HtmlTextAst, 'a']]); }); }); describe('elements', () => { it('should parse root level elements', () => { expect(humanizeDom(parser.parse('
', 'TestComp'))) - .toEqual([[HtmlElementAst, 'div', 0]]); + .toEqual([[HtmlElementAst, 'div']]); }); it('should parse elements inside of regular elements', () => { expect(humanizeDom(parser.parse('
', 'TestComp'))) - .toEqual([[HtmlElementAst, 'div', 0], [HtmlElementAst, 'span', 1]]); + .toEqual([[HtmlElementAst, 'div'], [HtmlElementAst, 'span']]); }); it('should parse elements inside of template elements', () => { expect(humanizeDom(parser.parse('', 'TestComp'))) - .toEqual([[HtmlElementAst, 'template', 0], [HtmlElementAst, 'span', 1]]); - }); - - it('should support void elements', () => { - expect(humanizeDom(parser.parse('', 'TestComp'))) - .toEqual([ - [HtmlElementAst, 'link', 0], - [HtmlAttrAst, 'rel', 'author license'], - [HtmlAttrAst, 'href', '/about'], - ]); - }); - - it('should support optional end tags', () => { - expect(humanizeDom(parser.parse('

1

2

', 'TestComp'))) - .toEqual([ - [HtmlElementAst, 'div', 0], - [HtmlElementAst, 'p', 1], - [HtmlTextAst, '1'], - [HtmlElementAst, 'p', 1], - [HtmlTextAst, '2'], - ]); - }); - - it('should add the requiredParent', () => { - expect(humanizeDom(parser.parse('
', 'TestComp'))) - .toEqual([ - [HtmlElementAst, 'table', 0], - [HtmlElementAst, 'tbody', 1], - [HtmlElementAst, 'tr', 2], - ]); - }); - - it('should support explicit mamespace', () => { - expect(humanizeDom(parser.parse('', 'TestComp'))) - .toEqual([[HtmlElementAst, '@myns:div', 0]]); - }); - - it('should support implicit mamespace', () => { - expect(humanizeDom(parser.parse('', 'TestComp'))) - .toEqual([[HtmlElementAst, '@svg:svg', 0]]); - }); - - it('should propagate the namespace', () => { - expect(humanizeDom(parser.parse('

', 'TestComp'))) - .toEqual([[HtmlElementAst, '@myns:div', 0], [HtmlElementAst, '@myns:p', 1]]); - }); - - it('should match closing tags case insensitive', () => { - expect(humanizeDom(parser.parse('

', 'TestComp'))) - .toEqual([[HtmlElementAst, 'DiV', 0], [HtmlElementAst, 'P', 1]]); + .toEqual([[HtmlElementAst, 'template'], [HtmlElementAst, 'span']]); }); }); describe('attributes', () => { - it('should parse attributes on regular elements case sensitive', () => { + it('should parse attributes on regular elements', () => { expect(humanizeDom(parser.parse('
', 'TestComp'))) .toEqual([ - [HtmlElementAst, 'div', 0], + [HtmlElementAst, 'div'], [HtmlAttrAst, 'kEy', 'v'], [HtmlAttrAst, 'key2', 'v2'], ]); @@ -128,135 +76,51 @@ export function main() { it('should parse attributes without values', () => { expect(humanizeDom(parser.parse('
', 'TestComp'))) - .toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'k', '']]); + .toEqual([[HtmlElementAst, 'div'], [HtmlAttrAst, 'k', '']]); }); it('should parse attributes on svg elements case sensitive', () => { expect(humanizeDom(parser.parse('', 'TestComp'))) - .toEqual([[HtmlElementAst, '@svg:svg', 0], [HtmlAttrAst, 'viewBox', '0']]); + .toEqual([[HtmlElementAst, '@svg:svg'], [HtmlAttrAst, 'viewBox', '0']]); }); it('should parse attributes on template elements', () => { expect(humanizeDom(parser.parse('', 'TestComp'))) - .toEqual([[HtmlElementAst, 'template', 0], [HtmlAttrAst, 'k', 'v']]); + .toEqual([[HtmlElementAst, 'template'], [HtmlAttrAst, 'k', 'v']]); }); - it('should support mamespace', () => { - expect(humanizeDom(parser.parse('', 'TestComp'))) - .toEqual([[HtmlElementAst, 'use', 0], [HtmlAttrAst, '@xlink:href', 'Port']]); - }); - }); - - describe('comments', () => { - it('should ignore comments', () => { - expect(humanizeDom(parser.parse('
', 'TestComp'))) - .toEqual([[HtmlElementAst, 'div', 0]]); - }); - }); - - describe('source spans', () => { - it('should store the location', () => { - expect(humanizeDomSourceSpans(parser.parse( - '
\na\n
', 'TestComp'))) - .toEqual([ - [HtmlElementAst, 'div', 0, '
'], - [HtmlAttrAst, '[prop]', 'v1', '[prop]="v1"'], - [HtmlAttrAst, '(e)', 'do()', '(e)="do()"'], - [HtmlAttrAst, 'attr', 'v2', 'attr="v2"'], - [HtmlAttrAst, 'noValue', '', 'noValue'], - [HtmlTextAst, '\na\n', '\na\n'], - ]); - }); - }); - - describe('errors', () => { - it('should report unexpected closing tags', () => { - let errors = parser.parse('

', 'TestComp').errors; - expect(errors.length).toEqual(1); - expect(humanizeErrors(errors)).toEqual([['p', 'Unexpected closing tag "p"', '0:5']]); - }); - - it('should also report lexer errors', () => { - let errors = parser.parse('

', 'TestComp').errors; - expect(errors.length).toEqual(2); - expect(humanizeErrors(errors)) - .toEqual([ - [HtmlTokenType.COMMENT_START, 'Unexpected character "e"', '0:3'], - ['p', 'Unexpected closing tag "p"', '0:14'] - ]); - }); }); }); }); } function humanizeDom(parseResult: HtmlParseTreeResult): any[] { + // TODO: humanize errors as well! if (parseResult.errors.length > 0) { - var errorString = parseResult.errors.join('\n'); - throw new BaseException(`Unexpected parse errors:\n${errorString}`); + throw parseResult.errors; } - - var humanizer = new Humanizer(false); + var humanizer = new Humanizer(); htmlVisitAll(humanizer, parseResult.rootNodes); return humanizer.result; } -function humanizeDomSourceSpans(parseResult: HtmlParseTreeResult): any[] { - if (parseResult.errors.length > 0) { - var errorString = parseResult.errors.join('\n'); - throw new BaseException(`Unexpected parse errors:\n${errorString}`); - } - - var humanizer = new Humanizer(true); - htmlVisitAll(humanizer, parseResult.rootNodes); - return humanizer.result; -} - -function humanizeLineColumn(location: ParseLocation): string { - return `${location.line}:${location.col}`; -} - -function humanizeErrors(errors: ParseError[]): any[] { - return errors.map(error => { - if (error instanceof HtmlTreeError) { - // Parser errors - return [error.elementName, error.msg, humanizeLineColumn(error.location)]; - } - // Tokenizer errors - return [(error).tokenType, error.msg, humanizeLineColumn(error.location)]; - }); -} - class Humanizer implements HtmlAstVisitor { result: any[] = []; - elDepth: number = 0; - - constructor(private includeSourceSpan: boolean){}; visitElement(ast: HtmlElementAst, context: any): any { - var res = this._appendContext(ast, [HtmlElementAst, ast.name, this.elDepth++]); - this.result.push(res); + this.result.push([HtmlElementAst, ast.name]); htmlVisitAll(this, ast.attrs); htmlVisitAll(this, ast.children); - this.elDepth--; return null; } visitAttr(ast: HtmlAttrAst, context: any): any { - var res = this._appendContext(ast, [HtmlAttrAst, ast.name, ast.value]); - this.result.push(res); + this.result.push([HtmlAttrAst, ast.name, ast.value]); return null; } visitText(ast: HtmlTextAst, context: any): any { - var res = this._appendContext(ast, [HtmlTextAst, ast.value]); - this.result.push(res); + this.result.push([HtmlTextAst, ast.value]); return null; } - - private _appendContext(ast: HtmlAst, input: any[]): any[] { - if (!this.includeSourceSpan) return input; - input.push(ast.sourceSpan.toString()); - return input; - } } diff --git a/modules/angular2/test/compiler/template_parser_spec.ts b/modules/angular2/test/compiler/template_parser_spec.ts index de2e2df116..9e288b65f2 100644 --- a/modules/angular2/test/compiler/template_parser_spec.ts +++ b/modules/angular2/test/compiler/template_parser_spec.ts @@ -45,6 +45,9 @@ import {Unparser} from '../core/change_detection/parser/unparser'; var expressionUnparser = new Unparser(); +// TODO(tbosch): add tests for checking that we +// keep the correct sourceSpans! + export function main() { describe('TemplateParser', () => { beforeEachProviders(() => [ @@ -73,35 +76,27 @@ export function main() { describe('nodes without bindings', () => { it('should parse text nodes', - () => { expect(humanizeTplAst(parse('a', []))).toEqual([[TextAst, 'a']]); }); + () => { expect(humanizeTemplateAsts(parse('a', []))).toEqual([[TextAst, 'a']]); }); it('should parse elements with attributes', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([[ElementAst, 'div'], [AttrAst, 'a', 'b']]); }); }); it('should parse ngContent', () => { var parsed = parse('', []); - expect(humanizeTplAst(parsed)).toEqual([[NgContentAst]]); + expect(humanizeTemplateAsts(parsed)).toEqual([[NgContentAst]]); }); it('should parse bound text nodes', () => { - expect(humanizeTplAst(parse('{{a}}', []))).toEqual([[BoundTextAst, '{{ a }}']]); + expect(humanizeTemplateAsts(parse('{{a}}', []))).toEqual([[BoundTextAst, '{{ a }}']]); }); describe('bound properties', () => { - it('should parse mixed case bound properties', () => { - expect(humanizeTplAst(parse('
', []))) - .toEqual([ - [ElementAst, 'div'], - [BoundElementPropertyAst, PropertyBindingType.Property, 'someProp', 'v', null] - ]); - }); - it('should parse and camel case bound properties', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Property, 'someProp', 'v', null] @@ -109,71 +104,31 @@ export function main() { }); it('should normalize property names via the element schema', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Property, 'mappedProp', 'v', null] ]); }); - it('should parse mixed case bound attributes', () => { - expect(humanizeTplAst(parse('
', []))) - .toEqual([ - [ElementAst, 'div'], - [BoundElementPropertyAst, PropertyBindingType.Attribute, 'someAttr', 'v', null] - ]); - }); - it('should parse and camel case bound attributes', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Attribute, 'someAttr', 'v', null] ]); - expect(humanizeTplAst(parse('
', []))) - .toEqual([ - [ElementAst, 'div'], - [BoundElementPropertyAst, PropertyBindingType.Attribute, 'someAttr', 'v', null] - ]); - }); it('should parse and dash case bound classes', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Class, 'some-class', 'v', null] ]); - expect(humanizeTplAst(parse('
', []))) - .toEqual([ - [ElementAst, 'div'], - [BoundElementPropertyAst, PropertyBindingType.Class, 'some-class', 'v', null] - ]); - }); - - it('should parse mixed case bound classes', () => { - expect(humanizeTplAst(parse('
', []))) - .toEqual([ - [ElementAst, 'div'], - [BoundElementPropertyAst, PropertyBindingType.Class, 'someClass', 'v', null] - ]); }); it('should parse and camel case bound styles', () => { - expect(humanizeTplAst(parse('
', []))) - .toEqual([ - [ElementAst, 'div'], - [BoundElementPropertyAst, PropertyBindingType.Style, 'someStyle', 'v', null] - ]); - expect(humanizeTplAst(parse('
', []))) - .toEqual([ - [ElementAst, 'div'], - [BoundElementPropertyAst, PropertyBindingType.Style, 'someStyle', 'v', null] - ]); - }); - - it('should parse and mixed case bound styles', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Style, 'someStyle', 'v', null] @@ -181,7 +136,7 @@ export function main() { }); it('should parse bound properties via [...] and not report them as attributes', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null] @@ -189,12 +144,7 @@ export function main() { }); it('should parse bound properties via bind- and not report them as attributes', () => { - expect(humanizeTplAst(parse('
', []))) - .toEqual([ - [ElementAst, 'div'], - [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null] - ]); - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null] @@ -202,7 +152,7 @@ export function main() { }); it('should parse bound properties via {{...}} and not report them as attributes', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', '{{ v }}', null] @@ -214,29 +164,22 @@ export function main() { describe('events', () => { it('should parse bound events with a target', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([[ElementAst, 'div'], [BoundEventAst, 'event', 'window', 'v']]); }); it('should parse bound events via (...) and not report them as attributes', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([[ElementAst, 'div'], [BoundEventAst, 'event', null, 'v']]); }); - it('should parse and camel case event names', () => { - expect(humanizeTplAst(parse('
', []))) - .toEqual([[ElementAst, 'div'], [BoundEventAst, 'someEvent', null, 'v']]); - }); - - it('should parse mixed case event names', () => { - expect(humanizeTplAst(parse('
', []))) + it('should camel case event names', () => { + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([[ElementAst, 'div'], [BoundEventAst, 'someEvent', null, 'v']]); }); it('should parse bound events via on- and not report them as attributes', () => { - expect(humanizeTplAst(parse('
', []))) - .toEqual([[ElementAst, 'div'], [BoundEventAst, 'event', null, 'v']]); - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([[ElementAst, 'div'], [BoundEventAst, 'event', null, 'v']]); }); @@ -247,7 +190,7 @@ export function main() { outputs: ['e'], type: new CompileTypeMetadata({name: 'DirA'}) }); - expect(humanizeTplAst(parse('', [dirA]))) + expect(humanizeTemplateAsts(parse('', [dirA]))) .toEqual([ [EmbeddedTemplateAst], [BoundEventAst, 'e', null, 'f'], @@ -259,7 +202,7 @@ export function main() { describe('bindon', () => { it('should parse bound events and properties via [(...)] and not report them as attributes', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null], @@ -269,13 +212,7 @@ export function main() { it('should parse bound events and properties via bindon- and not report them as attributes', () => { - expect(humanizeTplAst(parse('
', []))) - .toEqual([ - [ElementAst, 'div'], - [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null], - [BoundEventAst, 'propChange', null, 'v = $event'] - ]); - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null], @@ -300,7 +237,7 @@ export function main() { type: new CompileTypeMetadata({name: 'ZComp'}), template: new CompileTemplateMetadata({ngContentSelectors: []}) }); - expect(humanizeTplAst(parse('
', [dirA, dirB, dirC, comp]))) + expect(humanizeTemplateAsts(parse('
', [dirA, dirB, dirC, comp]))) .toEqual([ [ElementAst, 'div'], [AttrAst, 'a', ''], @@ -318,7 +255,7 @@ export function main() { {selector: '[a=b]', type: new CompileTypeMetadata({name: 'DirA'})}); var dirB = CompileDirectiveMetadata.create( {selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})}); - expect(humanizeTplAst(parse('
', [dirA, dirB]))) + expect(humanizeTemplateAsts(parse('
', [dirA, dirB]))) .toEqual([ [ElementAst, 'div'], [BoundElementPropertyAst, PropertyBindingType.Property, 'a', 'b', null], @@ -332,7 +269,7 @@ export function main() { type: new CompileTypeMetadata({name: 'DirA'}), host: {'[a]': 'expr'} }); - expect(humanizeTplAst(parse('
', [dirA]))) + expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div'], [DirectiveAst, dirA], @@ -346,7 +283,7 @@ export function main() { type: new CompileTypeMetadata({name: 'DirA'}), host: {'(a)': 'expr'} }); - expect(humanizeTplAst(parse('
', [dirA]))) + expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual( [[ElementAst, 'div'], [DirectiveAst, dirA], [BoundEventAst, 'a', null, 'expr']]); }); @@ -354,7 +291,7 @@ export function main() { it('should parse directive properties', () => { var dirA = CompileDirectiveMetadata.create( {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['aProp']}); - expect(humanizeTplAst(parse('
', [dirA]))) + expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div'], [DirectiveAst, dirA], @@ -365,7 +302,7 @@ export function main() { it('should parse renamed directive properties', () => { var dirA = CompileDirectiveMetadata.create( {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['b:a']}); - expect(humanizeTplAst(parse('
', [dirA]))) + expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div'], [DirectiveAst, dirA], @@ -376,7 +313,7 @@ export function main() { it('should parse literal directive properties', () => { var dirA = CompileDirectiveMetadata.create( {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['a']}); - expect(humanizeTplAst(parse('
', [dirA]))) + expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div'], [AttrAst, 'a', 'literal'], @@ -388,7 +325,7 @@ export function main() { it('should favor explicit bound properties over literal properties', () => { var dirA = CompileDirectiveMetadata.create( {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['a']}); - expect(humanizeTplAst(parse('
', [dirA]))) + expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div'], [AttrAst, 'a', 'literal'], @@ -400,7 +337,7 @@ export function main() { it('should support optional directive properties', () => { var dirA = CompileDirectiveMetadata.create( {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['a']}); - expect(humanizeTplAst(parse('
', [dirA]))) + expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([[ElementAst, 'div'], [DirectiveAst, dirA]]); }); @@ -409,31 +346,29 @@ export function main() { describe('variables', () => { it('should parse variables via #... and not report them as attributes', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([[ElementAst, 'div'], [VariableAst, 'a', '']]); }); it('should parse variables via var-... and not report them as attributes', () => { - expect(humanizeTplAst(parse('
', []))) - .toEqual([[ElementAst, 'div'], [VariableAst, 'a', '']]); - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([[ElementAst, 'div'], [VariableAst, 'a', '']]); }); it('should camel case variables', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([[ElementAst, 'div'], [VariableAst, 'someA', '']]); }); it('should assign variables with empty value to the element', () => { - expect(humanizeTplAst(parse('
', []))) + expect(humanizeTemplateAsts(parse('
', []))) .toEqual([[ElementAst, 'div'], [VariableAst, 'a', '']]); }); it('should assign variables to directives via exportAs', () => { var dirA = CompileDirectiveMetadata.create( {selector: '[a]', type: new CompileTypeMetadata({name: 'DirA'}), exportAs: 'dirA'}); - expect(humanizeTplAst(parse('
', [dirA]))) + expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div'], [AttrAst, 'a', ''], @@ -444,12 +379,12 @@ export function main() { it('should report variables with values that dont match a directive as errors', () => { expect(() => parse('
', [])).toThrowError(`Template parse errors: -There is no directive with "exportAs" set to "dirA" ("
"): TestComp@0:5`); +There is no directive with "exportAs" set to "dirA" (
): TestComp@0:5`); }); it('should allow variables with values that dont match a directive on embedded template elements', () => { - expect(humanizeTplAst(parse('', []))) + expect(humanizeTemplateAsts(parse('', []))) .toEqual([[EmbeddedTemplateAst], [VariableAst, 'a', 'b']]); }); @@ -461,7 +396,7 @@ There is no directive with "exportAs" set to "dirA" ("
"): T exportAs: 'dirA', template: new CompileTemplateMetadata({ngContentSelectors: []}) }); - expect(humanizeTplAst(parse('
', [dirA]))) + expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div'], [AttrAst, 'a', ''], @@ -475,23 +410,19 @@ There is no directive with "exportAs" set to "dirA" ("
"): T describe('explicit templates', () => { it('should create embedded templates for