X Tutup
Skip to content

Commit 19a08f3

Browse files
chuckjazvikerman
authored andcommitted
feat(compiler): Added spans to HTML parser errors
Allows using the HTML parser in contexts errors are reported in a development tool such as an editor.
1 parent a3d7629 commit 19a08f3

File tree

6 files changed

+56
-43
lines changed

6 files changed

+56
-43
lines changed

modules/angular2/src/compiler/html_lexer.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ export class HtmlToken {
3434
}
3535

3636
export class HtmlTokenError extends ParseError {
37-
constructor(errorMsg: string, public tokenType: HtmlTokenType, location: ParseLocation) {
38-
super(location, errorMsg);
37+
constructor(errorMsg: string, public tokenType: HtmlTokenType, span: ParseSourceSpan) {
38+
super(span, errorMsg);
3939
}
4040
}
4141

@@ -125,7 +125,8 @@ class _HtmlTokenizer {
125125

126126
private _processCarriageReturns(content: string): string {
127127
// http://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream
128-
// In order to keep the original position in the source, we can not pre-process it.
128+
// In order to keep the original position in the source, we can not
129+
// pre-process it.
129130
// Instead CRs are processed right before instantiating the tokens.
130131
return StringWrapper.replaceAll(content, CR_OR_CRLF_REGEXP, '\n');
131132
}
@@ -168,6 +169,16 @@ class _HtmlTokenizer {
168169
return new ParseLocation(this.file, this.index, this.line, this.column);
169170
}
170171

172+
private _getSpan(start?: ParseLocation, end?: ParseLocation): ParseSourceSpan {
173+
if (isBlank(start)) {
174+
start = this._getLocation();
175+
}
176+
if (isBlank(end)) {
177+
end = this._getLocation();
178+
}
179+
return new ParseSourceSpan(start, end);
180+
}
181+
171182
private _beginToken(type: HtmlTokenType, start: ParseLocation = null) {
172183
if (isBlank(start)) {
173184
start = this._getLocation();
@@ -188,16 +199,16 @@ class _HtmlTokenizer {
188199
return token;
189200
}
190201

191-
private _createError(msg: string, position: ParseLocation): ControlFlowError {
192-
var error = new HtmlTokenError(msg, this.currentTokenType, position);
202+
private _createError(msg: string, span: ParseSourceSpan): ControlFlowError {
203+
var error = new HtmlTokenError(msg, this.currentTokenType, span);
193204
this.currentTokenStart = null;
194205
this.currentTokenType = null;
195206
return new ControlFlowError(error);
196207
}
197208

198209
private _advance() {
199210
if (this.index >= this.length) {
200-
throw this._createError(unexpectedCharacterErrorMsg($EOF), this._getLocation());
211+
throw this._createError(unexpectedCharacterErrorMsg($EOF), this._getSpan());
201212
}
202213
if (this.peek === $LF) {
203214
this.line++;
@@ -228,7 +239,8 @@ class _HtmlTokenizer {
228239
private _requireCharCode(charCode: number) {
229240
var location = this._getLocation();
230241
if (!this._attemptCharCode(charCode)) {
231-
throw this._createError(unexpectedCharacterErrorMsg(this.peek), location);
242+
throw this._createError(unexpectedCharacterErrorMsg(this.peek),
243+
this._getSpan(location, location));
232244
}
233245
}
234246

@@ -253,7 +265,7 @@ class _HtmlTokenizer {
253265
private _requireStr(chars: string) {
254266
var location = this._getLocation();
255267
if (!this._attemptStr(chars)) {
256-
throw this._createError(unexpectedCharacterErrorMsg(this.peek), location);
268+
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan(location));
257269
}
258270
}
259271

@@ -267,7 +279,7 @@ class _HtmlTokenizer {
267279
var start = this._getLocation();
268280
this._attemptCharCodeUntilFn(predicate);
269281
if (this.index - start.offset < len) {
270-
throw this._createError(unexpectedCharacterErrorMsg(this.peek), start);
282+
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan(start, start));
271283
}
272284
}
273285

@@ -295,7 +307,7 @@ class _HtmlTokenizer {
295307
let numberStart = this._getLocation().offset;
296308
this._attemptCharCodeUntilFn(isDigitEntityEnd);
297309
if (this.peek != $SEMICOLON) {
298-
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getLocation());
310+
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan());
299311
}
300312
this._advance();
301313
let strNum = this.input.substring(numberStart, this.index - 1);
@@ -304,7 +316,7 @@ class _HtmlTokenizer {
304316
return StringWrapper.fromCharCode(charCode);
305317
} catch (e) {
306318
let entity = this.input.substring(start.offset + 1, this.index - 1);
307-
throw this._createError(unknownEntityErrorMsg(entity), start);
319+
throw this._createError(unknownEntityErrorMsg(entity), this._getSpan(start));
308320
}
309321
} else {
310322
let startPosition = this._savePosition();
@@ -317,7 +329,7 @@ class _HtmlTokenizer {
317329
let name = this.input.substring(start.offset + 1, this.index - 1);
318330
let char = NAMED_ENTITIES[name];
319331
if (isBlank(char)) {
320-
throw this._createError(unknownEntityErrorMsg(name), start);
332+
throw this._createError(unknownEntityErrorMsg(name), this._getSpan(start));
321333
}
322334
return char;
323335
}
@@ -394,7 +406,7 @@ class _HtmlTokenizer {
394406
let lowercaseTagName;
395407
try {
396408
if (!isAsciiLetter(this.peek)) {
397-
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getLocation());
409+
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan());
398410
}
399411
var nameStart = this.index;
400412
this._consumeTagOpenStart(start);

modules/angular2/src/compiler/html_parser.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@ import {ParseError, ParseLocation, ParseSourceSpan} from './parse_util';
1919
import {HtmlTagDefinition, getHtmlTagDefinition, getNsPrefix, mergeNsAndName} from './html_tags';
2020

2121
export class HtmlTreeError extends ParseError {
22-
static create(elementName: string, location: ParseLocation, msg: string): HtmlTreeError {
23-
return new HtmlTreeError(elementName, location, msg);
22+
static create(elementName: string, span: ParseSourceSpan, msg: string): HtmlTreeError {
23+
return new HtmlTreeError(elementName, span, msg);
2424
}
2525

26-
constructor(public elementName: string, location: ParseLocation, msg: string) {
27-
super(location, msg);
28-
}
26+
constructor(public elementName: string, span: ParseSourceSpan, msg: string) { super(span, msg); }
2927
}
3028

3129
export class HtmlParseTreeResult {
@@ -146,7 +144,7 @@ class TreeBuilder {
146144
selfClosing = true;
147145
if (getNsPrefix(fullName) == null && !getHtmlTagDefinition(fullName).isVoid) {
148146
this.errors.push(HtmlTreeError.create(
149-
fullName, startTagToken.sourceSpan.start,
147+
fullName, startTagToken.sourceSpan,
150148
`Only void and foreign elements can be self closed "${startTagToken.parts[1]}"`));
151149
}
152150
} else if (this.peek.type === HtmlTokenType.TAG_OPEN_END) {
@@ -189,10 +187,10 @@ class TreeBuilder {
189187

190188
if (getHtmlTagDefinition(fullName).isVoid) {
191189
this.errors.push(
192-
HtmlTreeError.create(fullName, endTagToken.sourceSpan.start,
190+
HtmlTreeError.create(fullName, endTagToken.sourceSpan,
193191
`Void elements do not have end tags "${endTagToken.parts[1]}"`));
194192
} else if (!this._popElement(fullName)) {
195-
this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan.start,
193+
this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan,
196194
`Unexpected closing tag "${endTagToken.parts[1]}"`));
197195
}
198196
}

modules/angular2/src/compiler/parse_util.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,20 @@ export class ParseSourceFile {
99
constructor(public content: string, public url: string) {}
1010
}
1111

12+
export class ParseSourceSpan {
13+
constructor(public start: ParseLocation, public end: ParseLocation) {}
14+
15+
toString(): string {
16+
return this.start.file.content.substring(this.start.offset, this.end.offset);
17+
}
18+
}
19+
1220
export abstract class ParseError {
13-
constructor(public location: ParseLocation, public msg: string) {}
21+
constructor(public span: ParseSourceSpan, public msg: string) {}
1422

1523
toString(): string {
16-
var source = this.location.file.content;
17-
var ctxStart = this.location.offset;
24+
var source = this.span.start.file.content;
25+
var ctxStart = this.span.start.offset;
1826
if (ctxStart > source.length - 1) {
1927
ctxStart = source.length - 1;
2028
}
@@ -44,17 +52,9 @@ export abstract class ParseError {
4452
}
4553
}
4654

47-
let context = source.substring(ctxStart, this.location.offset) + '[ERROR ->]' +
48-
source.substring(this.location.offset, ctxEnd + 1);
55+
let context = source.substring(ctxStart, this.span.start.offset) + '[ERROR ->]' +
56+
source.substring(this.span.start.offset, ctxEnd + 1);
4957

50-
return `${this.msg} ("${context}"): ${this.location}`;
51-
}
52-
}
53-
54-
export class ParseSourceSpan {
55-
constructor(public start: ParseLocation, public end: ParseLocation) {}
56-
57-
toString(): string {
58-
return this.start.file.content.substring(this.start.offset, this.end.offset);
58+
return `${this.msg} ("${context}"): ${this.span.start}`;
5959
}
6060
}

modules/angular2/src/compiler/template_parser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ var TEXT_CSS_SELECTOR = CssSelector.parse('*')[0];
7979
export const TEMPLATE_TRANSFORMS = CONST_EXPR(new OpaqueToken('TemplateTransforms'));
8080

8181
export class TemplateParseError extends ParseError {
82-
constructor(message: string, location: ParseLocation) { super(location, message); }
82+
constructor(message: string, span: ParseSourceSpan) { super(span, message); }
8383
}
8484

8585
@Injectable()
@@ -128,7 +128,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
128128
}
129129

130130
private _reportError(message: string, sourceSpan: ParseSourceSpan) {
131-
this.errors.push(new TemplateParseError(message, sourceSpan.start));
131+
this.errors.push(new TemplateParseError(message, sourceSpan));
132132
}
133133

134134
private _parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {

modules/angular2/test/compiler/html_lexer_spec.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,8 @@ export function main() {
581581
let src = "111\n222\n333\nE\n444\n555\n666\n";
582582
let file = new ParseSourceFile(src, 'file://');
583583
let location = new ParseLocation(file, 12, 123, 456);
584-
let error = new HtmlTokenError('**ERROR**', null, location);
584+
let span = new ParseSourceSpan(location, location);
585+
let error = new HtmlTokenError('**ERROR**', null, span);
585586
expect(error.toString())
586587
.toEqual(`**ERROR** ("\n222\n333\n[ERROR ->]E\n444\n555\n"): file://@123:456`);
587588
});
@@ -631,7 +632,9 @@ function tokenizeAndHumanizeLineColumn(input: string): any[] {
631632

632633
function tokenizeAndHumanizeErrors(input: string): any[] {
633634
return tokenizeHtml(input, 'someUrl')
634-
.errors.map(
635-
tokenError =>
636-
[<any>tokenError.tokenType, tokenError.msg, humanizeLineColumn(tokenError.location)]);
635+
.errors.map(tokenError => [
636+
<any>tokenError.tokenType,
637+
tokenError.msg,
638+
humanizeLineColumn(tokenError.span.start)
639+
]);
637640
}

modules/angular2/test/compiler/html_parser_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,10 @@ function humanizeErrors(errors: ParseError[]): any[] {
328328
return errors.map(error => {
329329
if (error instanceof HtmlTreeError) {
330330
// Parser errors
331-
return [<any>error.elementName, error.msg, humanizeLineColumn(error.location)];
331+
return [<any>error.elementName, error.msg, humanizeLineColumn(error.span.start)];
332332
}
333333
// Tokenizer errors
334-
return [(<any>error).tokenType, error.msg, humanizeLineColumn(error.location)];
334+
return [(<any>error).tokenType, error.msg, humanizeLineColumn(error.span.start)];
335335
});
336336
}
337337

0 commit comments

Comments
 (0)
X Tutup