Skip to content

Commit 17c8ec8

Browse files
committed
feat(html_parser): change HtmlElementAst to store both the start and the end positions
1 parent a1880c3 commit 17c8ec8

File tree

5 files changed

+104
-76
lines changed

5 files changed

+104
-76
lines changed

modules/angular2/src/compiler/html_ast.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export class HtmlAttrAst implements HtmlAst {
1919

2020
export class HtmlElementAst implements HtmlAst {
2121
constructor(public name: string, public attrs: HtmlAttrAst[], public children: HtmlAst[],
22-
public sourceSpan: ParseSourceSpan) {}
22+
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
23+
public endSourceSpan: ParseSourceSpan) {}
2324
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitElement(this, context); }
2425
}
2526

modules/angular2/src/compiler/html_parser.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,12 @@ class TreeBuilder {
154154
selfClosing = false;
155155
}
156156
var end = this.peek.sourceSpan.start;
157-
var el = new HtmlElementAst(fullName, attrs, [],
158-
new ParseSourceSpan(startTagToken.sourceSpan.start, end));
157+
let span = new ParseSourceSpan(startTagToken.sourceSpan.start, end);
158+
var el = new HtmlElementAst(fullName, attrs, [], span, span, null);
159159
this._pushElement(el);
160160
if (selfClosing) {
161161
this._popElement(fullName);
162+
el.endSourceSpan = span;
162163
}
163164
}
164165

@@ -173,7 +174,8 @@ class TreeBuilder {
173174
var tagDef = getHtmlTagDefinition(el.name);
174175
var parentEl = this._getParentElement();
175176
if (tagDef.requireExtraParent(isPresent(parentEl) ? parentEl.name : null)) {
176-
var newParent = new HtmlElementAst(tagDef.parentToAdd, [], [el], el.sourceSpan);
177+
var newParent = new HtmlElementAst(tagDef.parentToAdd, [], [el], el.sourceSpan,
178+
el.startSourceSpan, el.endSourceSpan);
177179
this._addToParent(newParent);
178180
this.elementStack.push(newParent);
179181
this.elementStack.push(el);
@@ -187,6 +189,8 @@ class TreeBuilder {
187189
var fullName =
188190
getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getParentElement());
189191

192+
this._getParentElement().endSourceSpan = endTagToken.sourceSpan;
193+
190194
if (getHtmlTagDefinition(fullName).isVoid) {
191195
this.errors.push(
192196
HtmlTreeError.create(fullName, endTagToken.sourceSpan,

modules/angular2/src/compiler/legacy_template.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ export class LegacyHtmlAstTransformer implements HtmlAstVisitor {
5050
this.visitingTemplateEl = ast.name.toLowerCase() == 'template';
5151
let attrs = ast.attrs.map(attr => attr.visit(this, null));
5252
let children = ast.children.map(child => child.visit(this, null));
53-
return new HtmlElementAst(ast.name, attrs, children, ast.sourceSpan);
53+
return new HtmlElementAst(ast.name, attrs, children, ast.sourceSpan, ast.startSourceSpan,
54+
ast.endSourceSpan);
5455
}
5556

5657
visitAttr(originalAst: HtmlAttrAst, context: any): HtmlAttrAst {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {HtmlParser, HtmlParseTreeResult, HtmlTreeError} from 'angular2/src/compiler/html_parser';
2+
import {
3+
HtmlAst,
4+
HtmlAstVisitor,
5+
HtmlElementAst,
6+
HtmlAttrAst,
7+
HtmlTextAst,
8+
HtmlCommentAst,
9+
htmlVisitAll
10+
} from 'angular2/src/compiler/html_ast';
11+
import {ParseError, ParseLocation} from 'angular2/src/compiler/parse_util';
12+
import {BaseException} from 'angular2/src/facade/exceptions';
13+
14+
export function humanizeDom(parseResult: HtmlParseTreeResult): any[] {
15+
if (parseResult.errors.length > 0) {
16+
var errorString = parseResult.errors.join('\n');
17+
throw new BaseException(`Unexpected parse errors:\n${errorString}`);
18+
}
19+
20+
var humanizer = new _Humanizer(false);
21+
htmlVisitAll(humanizer, parseResult.rootNodes);
22+
return humanizer.result;
23+
}
24+
25+
export function humanizeDomSourceSpans(parseResult: HtmlParseTreeResult): any[] {
26+
if (parseResult.errors.length > 0) {
27+
var errorString = parseResult.errors.join('\n');
28+
throw new BaseException(`Unexpected parse errors:\n${errorString}`);
29+
}
30+
31+
var humanizer = new _Humanizer(true);
32+
htmlVisitAll(humanizer, parseResult.rootNodes);
33+
return humanizer.result;
34+
}
35+
36+
export function humanizeLineColumn(location: ParseLocation): string {
37+
return `${location.line}:${location.col}`;
38+
}
39+
40+
class _Humanizer implements HtmlAstVisitor {
41+
result: any[] = [];
42+
elDepth: number = 0;
43+
44+
constructor(private includeSourceSpan: boolean){};
45+
46+
visitElement(ast: HtmlElementAst, context: any): any {
47+
var res = this._appendContext(ast, [HtmlElementAst, ast.name, this.elDepth++]);
48+
this.result.push(res);
49+
htmlVisitAll(this, ast.attrs);
50+
htmlVisitAll(this, ast.children);
51+
this.elDepth--;
52+
return null;
53+
}
54+
55+
visitAttr(ast: HtmlAttrAst, context: any): any {
56+
var res = this._appendContext(ast, [HtmlAttrAst, ast.name, ast.value]);
57+
this.result.push(res);
58+
return null;
59+
}
60+
61+
visitText(ast: HtmlTextAst, context: any): any {
62+
var res = this._appendContext(ast, [HtmlTextAst, ast.value, this.elDepth]);
63+
this.result.push(res);
64+
return null;
65+
}
66+
67+
visitComment(ast: HtmlCommentAst, context: any): any {
68+
var res = this._appendContext(ast, [HtmlCommentAst, ast.value, this.elDepth]);
69+
this.result.push(res);
70+
return null;
71+
}
72+
73+
private _appendContext(ast: HtmlAst, input: any[]): any[] {
74+
if (!this.includeSourceSpan) return input;
75+
input.push(ast.sourceSpan.toString());
76+
return input;
77+
}
78+
}

modules/angular2/test/compiler/html_parser_spec.ts

Lines changed: 15 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@ import {
2020
HtmlCommentAst,
2121
htmlVisitAll
2222
} from 'angular2/src/compiler/html_ast';
23-
import {ParseError, ParseLocation, ParseSourceSpan} from 'angular2/src/compiler/parse_util';
24-
25-
import {BaseException} from 'angular2/src/facade/exceptions';
23+
import {ParseError, ParseLocation} from 'angular2/src/compiler/parse_util';
24+
import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './html_ast_spec_utils';
2625

2726
export function main() {
2827
describe('HtmlParser', () => {
@@ -51,6 +50,7 @@ export function main() {
5150
});
5251
});
5352

53+
5454
describe('elements', () => {
5555
it('should parse root level elements', () => {
5656
expect(humanizeDom(parser.parse('<div></div>', 'TestComp')))
@@ -253,6 +253,16 @@ export function main() {
253253
[HtmlTextAst, '\na\n', 1, '\na\n'],
254254
]);
255255
});
256+
257+
it('should set the start and end source spans', () => {
258+
let node = <HtmlElementAst>parser.parse('<div>a</div>', 'TestComp').rootNodes[0];
259+
260+
expect(node.startSourceSpan.start.offset).toEqual(0);
261+
expect(node.startSourceSpan.end.offset).toEqual(5);
262+
263+
expect(node.endSourceSpan.start.offset).toEqual(6);
264+
expect(node.endSourceSpan.end.offset).toEqual(12);
265+
});
256266
});
257267

258268
describe('errors', () => {
@@ -299,33 +309,7 @@ export function main() {
299309
});
300310
}
301311

302-
function humanizeDom(parseResult: HtmlParseTreeResult): any[] {
303-
if (parseResult.errors.length > 0) {
304-
var errorString = parseResult.errors.join('\n');
305-
throw new BaseException(`Unexpected parse errors:\n${errorString}`);
306-
}
307-
308-
var humanizer = new Humanizer(false);
309-
htmlVisitAll(humanizer, parseResult.rootNodes);
310-
return humanizer.result;
311-
}
312-
313-
function humanizeDomSourceSpans(parseResult: HtmlParseTreeResult): any[] {
314-
if (parseResult.errors.length > 0) {
315-
var errorString = parseResult.errors.join('\n');
316-
throw new BaseException(`Unexpected parse errors:\n${errorString}`);
317-
}
318-
319-
var humanizer = new Humanizer(true);
320-
htmlVisitAll(humanizer, parseResult.rootNodes);
321-
return humanizer.result;
322-
}
323-
324-
function humanizeLineColumn(location: ParseLocation): string {
325-
return `${location.line}:${location.col}`;
326-
}
327-
328-
function humanizeErrors(errors: ParseError[]): any[] {
312+
export function humanizeErrors(errors: ParseError[]): any[] {
329313
return errors.map(error => {
330314
if (error instanceof HtmlTreeError) {
331315
// Parser errors
@@ -334,44 +318,4 @@ function humanizeErrors(errors: ParseError[]): any[] {
334318
// Tokenizer errors
335319
return [(<any>error).tokenType, error.msg, humanizeLineColumn(error.span.start)];
336320
});
337-
}
338-
339-
class Humanizer implements HtmlAstVisitor {
340-
result: any[] = [];
341-
elDepth: number = 0;
342-
343-
constructor(private includeSourceSpan: boolean){};
344-
345-
visitElement(ast: HtmlElementAst, context: any): any {
346-
var res = this._appendContext(ast, [HtmlElementAst, ast.name, this.elDepth++]);
347-
this.result.push(res);
348-
htmlVisitAll(this, ast.attrs);
349-
htmlVisitAll(this, ast.children);
350-
this.elDepth--;
351-
return null;
352-
}
353-
354-
visitAttr(ast: HtmlAttrAst, context: any): any {
355-
var res = this._appendContext(ast, [HtmlAttrAst, ast.name, ast.value]);
356-
this.result.push(res);
357-
return null;
358-
}
359-
360-
visitText(ast: HtmlTextAst, context: any): any {
361-
var res = this._appendContext(ast, [HtmlTextAst, ast.value, this.elDepth]);
362-
this.result.push(res);
363-
return null;
364-
}
365-
366-
visitComment(ast: HtmlCommentAst, context: any): any {
367-
var res = this._appendContext(ast, [HtmlCommentAst, ast.value, this.elDepth]);
368-
this.result.push(res);
369-
return null;
370-
}
371-
372-
private _appendContext(ast: HtmlAst, input: any[]): any[] {
373-
if (!this.includeSourceSpan) return input;
374-
input.push(ast.sourceSpan.toString());
375-
return input;
376-
}
377-
}
321+
}

0 commit comments

Comments
 (0)