diff --git a/modules/angular2/src/compiler/html_parser.ts b/modules/angular2/src/compiler/html_parser.ts index 7f2f35ae1d..b9333c0295 100644 --- a/modules/angular2/src/compiler/html_parser.ts +++ b/modules/angular2/src/compiler/html_parser.ts @@ -128,21 +128,21 @@ class TreeBuilder { attrs.push(this._consumeAttr(this._advance())); } var fullName = getElementFullName(prefix, name, this._getParentElement()); - var voidElement = false; + var selfClosing = false; // Note: There could have been a tokenizer error // so that we don't get a token for the end tag... if (this.peek.type === HtmlTokenType.TAG_OPEN_END_VOID) { this._advance(); - voidElement = true; + selfClosing = true; } else if (this.peek.type === HtmlTokenType.TAG_OPEN_END) { this._advance(); - voidElement = false; + selfClosing = false; } var end = this.peek.sourceSpan.start; var el = new HtmlElementAst(fullName, attrs, [], new ParseSourceSpan(startTagToken.sourceSpan.start, end)); this._pushElement(el); - if (voidElement) { + if (selfClosing) { this._popElement(fullName); } } @@ -172,18 +172,27 @@ class TreeBuilder { var fullName = getElementFullName(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]}"`)); + let msg; + + if (getHtmlTagDefinition(fullName).isVoid) { + msg = + `Void elements do not have end tags (they can not have content) "${endTagToken.parts[1]}"`; + } else { + msg = `Unexpected closing tag "${endTagToken.parts[1]}"`; + } + + this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan.start, msg)); } } private _popElement(fullName: string): boolean { for (let stackIndex = this.elementStack.length - 1; stackIndex >= 0; stackIndex--) { - var el = this.elementStack[stackIndex]; + let el = this.elementStack[stackIndex]; if (el.name.toLowerCase() == fullName.toLowerCase()) { ListWrapper.splice(this.elementStack, stackIndex, this.elementStack.length - stackIndex); return true; } + if (!getHtmlTagDefinition(el.name).closedByParent) { return false; } diff --git a/modules/angular2/test/compiler/html_parser_spec.ts b/modules/angular2/test/compiler/html_parser_spec.ts index 725f7a3c32..91400e20ac 100644 --- a/modules/angular2/test/compiler/html_parser_spec.ts +++ b/modules/angular2/test/compiler/html_parser_spec.ts @@ -85,6 +85,11 @@ export function main() { ]); }); + it('should tolerate end tags for void elements when they have no content', () => { + expect(humanizeDom(parser.parse('', 'TestComp'))) + .toEqual([[HtmlElementAst, 'ng-content', 0]]); + }); + it('should support optional end tags', () => { expect(humanizeDom(parser.parse('

1

2

', 'TestComp'))) .toEqual([ @@ -209,6 +214,32 @@ export function main() { expect(humanizeErrors(errors)).toEqual([['p', 'Unexpected closing tag "p"', '0:5']]); }); + it('should report text content in void elements', () => { + let errors = parser.parse('content', 'TestComp').errors; + expect(errors.length).toEqual(1); + expect(humanizeErrors(errors)) + .toEqual([ + [ + 'ng-content', + 'Void elements do not have end tags (they can not have content) "ng-content"', + '0:19' + ] + ]); + }); + + it('should report html content in void elements', () => { + let errors = parser.parse('

', 'TestComp').errors; + expect(errors.length).toEqual(1); + expect(humanizeErrors(errors)) + .toEqual([ + [ + 'ng-content', + 'Void elements do not have end tags (they can not have content) "ng-content"', + '0:19' + ] + ]); + }); + it('should also report lexer errors', () => { let errors = parser.parse('

', 'TestComp').errors; expect(errors.length).toEqual(2);