Skip to content

Commit 41ff68e

Browse files
committedNov 27, 2023
feat(compiler): improve parsing tolerance for language-tools
1 parent 0721daf commit 41ff68e

File tree

2 files changed

+36
-8
lines changed

2 files changed

+36
-8
lines changed
 

‎packages/compiler-core/__tests__/parse.spec.ts

+23
Original file line numberDiff line numberDiff line change
@@ -1918,6 +1918,29 @@ describe('compiler: parse', () => {
19181918
expect(baz.loc.end).toEqual({ line: 2, column: 28, offset })
19191919
})
19201920

1921+
// With standard HTML parsing, the following input would ignore the slash
1922+
// and treat "<" and "template" as attributes on the open tag of "Hello",
1923+
// causing `<template>` to fail to close, and `<script>` being parsed as its
1924+
// child. This is would never be intended in actual templates, but is a common
1925+
// intermediate state from user input when parsing for IDE support. We want
1926+
// the `<script>` to be at root-level to keep the SFC structure stable for
1927+
// Volar to do incremental computations.
1928+
test('tag termination handling for IDE', () => {
1929+
const spy = vi.fn()
1930+
const ast = baseParse(
1931+
`<template><Hello\n</template><script>console.log(1)</script>`,
1932+
{
1933+
onError: spy
1934+
}
1935+
)
1936+
//
1937+
expect(ast.children.length).toBe(2)
1938+
expect(ast.children[1]).toMatchObject({
1939+
type: NodeTypes.ELEMENT,
1940+
tag: 'script'
1941+
})
1942+
})
1943+
19211944
describe('decodeEntities option', () => {
19221945
test('use decode by default', () => {
19231946
const ast: any = baseParse('&gt;&lt;&amp;&apos;&quot;&foo;')

‎packages/compiler-core/src/tokenizer.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ export default class Tokenizer {
301301
}
302302
}
303303

304+
private peek() {
305+
return this.buffer.charCodeAt(this.index + 1)
306+
}
307+
304308
private stateText(c: number): void {
305309
if (c === CharCodes.Lt) {
306310
if (this.index > this.sectionStart) {
@@ -627,12 +631,16 @@ export default class Tokenizer {
627631
this.sectionStart = this.index + 1
628632
} else if (c === CharCodes.Slash) {
629633
this.state = State.InSelfClosingTag
630-
if (
631-
(__DEV__ || !__BROWSER__) &&
632-
this.buffer.charCodeAt(this.index + 1) !== CharCodes.Gt
633-
) {
634+
if ((__DEV__ || !__BROWSER__) && this.peek() !== CharCodes.Gt) {
634635
this.cbs.onerr(ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG, this.index)
635636
}
637+
} else if (c === CharCodes.Lt && this.peek() === CharCodes.Slash) {
638+
// special handling for </ appearing in open tag state
639+
// this is different from standard HTML parsing but makes practical sense
640+
// especially for parsing intermedaite input state in IDEs.
641+
this.cbs.onopentagend(this.index)
642+
this.state = State.BeforeTagName
643+
this.sectionStart = this.index
636644
} else if (!isWhitespace(c)) {
637645
if ((__DEV__ || !__BROWSER__) && c === CharCodes.Eq) {
638646
this.cbs.onerr(
@@ -644,10 +652,7 @@ export default class Tokenizer {
644652
}
645653
}
646654
private handleAttrStart(c: number) {
647-
if (
648-
c === CharCodes.LowerV &&
649-
this.buffer.charCodeAt(this.index + 1) === CharCodes.Dash
650-
) {
655+
if (c === CharCodes.LowerV && this.peek() === CharCodes.Dash) {
651656
this.state = State.InDirName
652657
this.sectionStart = this.index
653658
} else if (

0 commit comments

Comments
 (0)
Please sign in to comment.