diff --git a/modules/angular2/src/compiler/html_ast.ts b/modules/angular2/src/compiler/html_ast.ts index c18be31942..d1f18b47df 100644 --- a/modules/angular2/src/compiler/html_ast.ts +++ b/modules/angular2/src/compiler/html_ast.ts @@ -19,7 +19,8 @@ export class HtmlAttrAst implements HtmlAst { export class HtmlElementAst implements HtmlAst { constructor(public name: string, public attrs: HtmlAttrAst[], public children: HtmlAst[], - public sourceSpan: ParseSourceSpan) {} + public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan, + public endSourceSpan: ParseSourceSpan) {} visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitElement(this, context); } } diff --git a/modules/angular2/src/compiler/html_parser.ts b/modules/angular2/src/compiler/html_parser.ts index d1eb270c84..40385eb105 100644 --- a/modules/angular2/src/compiler/html_parser.ts +++ b/modules/angular2/src/compiler/html_parser.ts @@ -154,11 +154,12 @@ class TreeBuilder { selfClosing = false; } var end = this.peek.sourceSpan.start; - var el = new HtmlElementAst(fullName, attrs, [], - new ParseSourceSpan(startTagToken.sourceSpan.start, end)); + let span = new ParseSourceSpan(startTagToken.sourceSpan.start, end); + var el = new HtmlElementAst(fullName, attrs, [], span, span, null); this._pushElement(el); if (selfClosing) { this._popElement(fullName); + el.endSourceSpan = span; } } @@ -173,7 +174,8 @@ class TreeBuilder { var tagDef = getHtmlTagDefinition(el.name); var parentEl = this._getParentElement(); if (tagDef.requireExtraParent(isPresent(parentEl) ? parentEl.name : null)) { - var newParent = new HtmlElementAst(tagDef.parentToAdd, [], [el], el.sourceSpan); + var newParent = new HtmlElementAst(tagDef.parentToAdd, [], [el], el.sourceSpan, + el.startSourceSpan, el.endSourceSpan); this._addToParent(newParent); this.elementStack.push(newParent); this.elementStack.push(el); @@ -187,6 +189,8 @@ class TreeBuilder { var fullName = getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getParentElement()); + this._getParentElement().endSourceSpan = endTagToken.sourceSpan; + if (getHtmlTagDefinition(fullName).isVoid) { this.errors.push( HtmlTreeError.create(fullName, endTagToken.sourceSpan, diff --git a/modules/angular2/src/compiler/legacy_template.ts b/modules/angular2/src/compiler/legacy_template.ts index 009810c645..6adbf78a4a 100644 --- a/modules/angular2/src/compiler/legacy_template.ts +++ b/modules/angular2/src/compiler/legacy_template.ts @@ -50,7 +50,8 @@ export class LegacyHtmlAstTransformer implements HtmlAstVisitor { this.visitingTemplateEl = ast.name.toLowerCase() == 'template'; let attrs = ast.attrs.map(attr => attr.visit(this, null)); let children = ast.children.map(child => child.visit(this, null)); - return new HtmlElementAst(ast.name, attrs, children, ast.sourceSpan); + return new HtmlElementAst(ast.name, attrs, children, ast.sourceSpan, ast.startSourceSpan, + ast.endSourceSpan); } visitAttr(originalAst: HtmlAttrAst, context: any): HtmlAttrAst { diff --git a/modules/angular2/test/compiler/html_ast_spec_utils.ts b/modules/angular2/test/compiler/html_ast_spec_utils.ts new file mode 100644 index 0000000000..a59476917f --- /dev/null +++ b/modules/angular2/test/compiler/html_ast_spec_utils.ts @@ -0,0 +1,78 @@ +import {HtmlParser, HtmlParseTreeResult, HtmlTreeError} from 'angular2/src/compiler/html_parser'; +import { + HtmlAst, + HtmlAstVisitor, + HtmlElementAst, + HtmlAttrAst, + HtmlTextAst, + HtmlCommentAst, + htmlVisitAll +} from 'angular2/src/compiler/html_ast'; +import {ParseError, ParseLocation} from 'angular2/src/compiler/parse_util'; +import {BaseException} from 'angular2/src/facade/exceptions'; + +export function humanizeDom(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(false); + htmlVisitAll(humanizer, parseResult.rootNodes); + return humanizer.result; +} + +export 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; +} + +export function humanizeLineColumn(location: ParseLocation): string { + return `${location.line}:${location.col}`; +} + +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); + 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); + return null; + } + + visitText(ast: HtmlTextAst, context: any): any { + var res = this._appendContext(ast, [HtmlTextAst, ast.value, this.elDepth]); + this.result.push(res); + return null; + } + + visitComment(ast: HtmlCommentAst, context: any): any { + var res = this._appendContext(ast, [HtmlCommentAst, ast.value, this.elDepth]); + this.result.push(res); + 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/html_parser_spec.ts b/modules/angular2/test/compiler/html_parser_spec.ts index b87337c2ba..3af628f315 100644 --- a/modules/angular2/test/compiler/html_parser_spec.ts +++ b/modules/angular2/test/compiler/html_parser_spec.ts @@ -20,9 +20,8 @@ import { HtmlCommentAst, htmlVisitAll } from 'angular2/src/compiler/html_ast'; -import {ParseError, ParseLocation, ParseSourceSpan} from 'angular2/src/compiler/parse_util'; - -import {BaseException} from 'angular2/src/facade/exceptions'; +import {ParseError, ParseLocation} from 'angular2/src/compiler/parse_util'; +import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './html_ast_spec_utils'; export function main() { describe('HtmlParser', () => { @@ -51,6 +50,7 @@ export function main() { }); }); + describe('elements', () => { it('should parse root level elements', () => { expect(humanizeDom(parser.parse('
', 'TestComp'))) @@ -253,6 +253,16 @@ export function main() { [HtmlTextAst, '\na\n', 1, '\na\n'], ]); }); + + it('should set the start and end source spans', () => { + let node =