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 = parser.parse('
a
', 'TestComp').rootNodes[0]; + + expect(node.startSourceSpan.start.offset).toEqual(0); + expect(node.startSourceSpan.end.offset).toEqual(5); + + expect(node.endSourceSpan.start.offset).toEqual(6); + expect(node.endSourceSpan.end.offset).toEqual(12); + }); }); describe('errors', () => { @@ -299,33 +309,7 @@ export function main() { }); } -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; -} - -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[] { +export function humanizeErrors(errors: ParseError[]): any[] { return errors.map(error => { if (error instanceof HtmlTreeError) { // Parser errors @@ -334,44 +318,4 @@ function humanizeErrors(errors: ParseError[]): any[] { // Tokenizer errors return [(error).tokenType, error.msg, humanizeLineColumn(error.span.start)]; }); -} - -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; - } -} +} \ No newline at end of file