feat(html_parser): change HtmlElementAst to store both the start and the end positions

This commit is contained in:
vsavkin 2016-03-23 13:43:28 -07:00 committed by Victor Savkin
parent a1880c3576
commit 17c8ec8a5d
5 changed files with 104 additions and 76 deletions

View File

@ -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); }
}

View File

@ -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,

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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('<div></div>', '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 = <HtmlElementAst>parser.parse('<div>a</div>', '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 [(<any>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;
}
}
}