Skip to content

Commit 6cf8df9

Browse files
committed
feat($compile): display line numbers for errors in the parser
#6338
1 parent 048e940 commit 6cf8df9

File tree

3 files changed

+72
-3
lines changed

3 files changed

+72
-3
lines changed

Diff for: src/compiler/parser/html-parser.js

+23-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
1010
*/
1111

12-
import { makeMap, no } from 'shared/util'
12+
import { makeMap, no, splitLine } from 'shared/util'
1313
import { isNonPhrasingTag } from 'web/compiler/util'
1414

1515
// Regular Expressions for parsing tags and attributes
@@ -53,11 +53,30 @@ function decodeAttr (value, shouldDecodeNewlines) {
5353
return value.replace(re, match => decodingMap[match])
5454
}
5555

56+
function resolveLineNumbers (lines, start) {
57+
let l = 0
58+
let cur = 0
59+
for (let i = 0, len = lines.length; i < len; ++i) {
60+
const { line, linefeed = '' } = lines[i]
61+
if (cur + line.length < start) {
62+
cur += (line.length + linefeed.length)
63+
l++
64+
} else {
65+
break
66+
}
67+
}
68+
return {
69+
line: l,
70+
column: start - cur
71+
}
72+
}
73+
5674
export function parseHTML (html, options) {
5775
const stack = []
5876
const expectHTML = options.expectHTML
5977
const isUnaryTag = options.isUnaryTag || no
6078
const canBeLeftOpenTag = options.canBeLeftOpenTag || no
79+
const lines = splitLine(html)
6180
let index = 0
6281
let last, lastTag
6382
while (html) {
@@ -243,7 +262,7 @@ export function parseHTML (html, options) {
243262
}
244263

245264
if (!unary) {
246-
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })
265+
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start })
247266
lastTag = tagName
248267
}
249268

@@ -280,8 +299,9 @@ export function parseHTML (html, options) {
280299
(i > pos || !tagName) &&
281300
options.warn
282301
) {
302+
const { line, column } = resolveLineNumbers(lines, stack[i].start)
283303
options.warn(
284-
`tag <${stack[i].tag}> has no matching end tag.`
304+
`tag <${stack[i].tag}> has no matching end tag at line ${line} column ${column}.`
285305
)
286306
}
287307
if (options.end) {

Diff for: src/shared/util.js

+24
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,27 @@ export function once (fn: Function): Function {
304304
}
305305
}
306306
}
307+
308+
/**
309+
* split string with different linefeed.
310+
*/
311+
const linefeedRE = /\r\n|\n|\u2028|\u2029/
312+
export function splitLine (str: string): Array<Object> {
313+
const lines = []
314+
while (str) {
315+
const line = linefeedRE.exec(str)
316+
if (line) {
317+
const linefeed = line[0]
318+
const idx = line.index
319+
lines.push({
320+
line: str.substring(0, idx),
321+
linefeed
322+
})
323+
str = str.substring(idx + linefeed.length)
324+
} else {
325+
lines.push({ length: str })
326+
break
327+
}
328+
}
329+
return lines
330+
}

Diff for: test/unit/modules/compiler/parser.spec.js

+25
Original file line numberDiff line numberDiff line change
@@ -593,4 +593,29 @@ describe('parser', () => {
593593
expect(ast.children[1].isComment).toBe(true) // parse comment with ASTText
594594
expect(ast.children[1].text).toBe('comment here')
595595
})
596+
597+
it('should warn with line and column', () => {
598+
parse(`
599+
<div>
600+
<span>
601+
123
602+
</div>
603+
`, baseOptions)
604+
expect(`tag <span> has no matching end tag at line 2 column 8.`).toHaveBeenWarned()
605+
})
606+
607+
it('should warn with correct match', () => {
608+
parse(`
609+
<div>
610+
<div>
611+
123
612+
</div>
613+
`, baseOptions)
614+
expect(`tag <div> has no matching end tag at line 1 column 6.`).toHaveBeenWarned()
615+
})
616+
617+
it('should work with different linefeed', () => {
618+
parse('<div>\n <div>\r\n <span>\u2028 123\u2029 </span>\n </div>\n <li>\n</div>', baseOptions)
619+
expect(`tag <li> has no matching end tag at line 6 column 2.`).toHaveBeenWarned()
620+
})
596621
})

0 commit comments

Comments
 (0)