mirror of https://github.com/vuejs/core.git
wip: parse error tests
This commit is contained in:
parent
b6886a80b1
commit
59227d4124
File diff suppressed because it is too large
Load Diff
|
@ -33,14 +33,23 @@ describe('compiler: parse', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.skip('simple text with invalid end tag', () => {
|
test('simple text with invalid end tag', () => {
|
||||||
const onError = vi.fn()
|
const onError = vi.fn()
|
||||||
const ast = baseParse('some text</div>', {
|
const ast = baseParse('some text</div>', { onError })
|
||||||
onError
|
|
||||||
})
|
|
||||||
const text = ast.children[0] as TextNode
|
const text = ast.children[0] as TextNode
|
||||||
|
|
||||||
expect(onError).toBeCalled()
|
expect(onError.mock.calls).toMatchObject([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
code: ErrorCodes.X_INVALID_END_TAG,
|
||||||
|
loc: {
|
||||||
|
start: { column: 10, line: 1, offset: 9 },
|
||||||
|
end: { column: 10, line: 1, offset: 9 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
expect(text).toStrictEqual({
|
expect(text).toStrictEqual({
|
||||||
type: NodeTypes.TEXT,
|
type: NodeTypes.TEXT,
|
||||||
content: 'some text',
|
content: 'some text',
|
||||||
|
@ -1276,7 +1285,7 @@ describe('compiler: parse', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.skip('directive with no name', () => {
|
test('directive with no name', () => {
|
||||||
let errorCode = -1
|
let errorCode = -1
|
||||||
const ast = baseParse('<div v-/>', {
|
const ast = baseParse('<div v-/>', {
|
||||||
onError: err => {
|
onError: err => {
|
||||||
|
@ -1293,6 +1302,10 @@ describe('compiler: parse', () => {
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 5, line: 1, column: 6 },
|
start: { offset: 5, line: 1, column: 6 },
|
||||||
end: { offset: 7, line: 1, column: 8 }
|
end: { offset: 7, line: 1, column: 8 }
|
||||||
|
},
|
||||||
|
nameLoc: {
|
||||||
|
start: { offset: 5, line: 1, column: 6 },
|
||||||
|
end: { offset: 7, line: 1, column: 8 }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1734,7 +1747,7 @@ describe('compiler: parse', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.skip('invalid html', () => {
|
test('invalid html', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
baseParse(`<div>\n<span>\n</div>\n</span>`)
|
baseParse(`<div>\n<span>\n</div>\n</span>`)
|
||||||
}).toThrow('Element is missing end tag.')
|
}).toThrow('Element is missing end tag.')
|
||||||
|
@ -2031,30 +2044,30 @@ describe('compiler: parse', () => {
|
||||||
options?: Partial<ParserOptions>
|
options?: Partial<ParserOptions>
|
||||||
}>
|
}>
|
||||||
} = {
|
} = {
|
||||||
ABRUPT_CLOSING_OF_EMPTY_COMMENT: [
|
// ABRUPT_CLOSING_OF_EMPTY_COMMENT: [
|
||||||
{
|
// {
|
||||||
code: '<template><!--></template>',
|
// code: '<template><!--></template>',
|
||||||
errors: [
|
// errors: [
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT,
|
// type: ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT,
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
// loc: { offset: 10, line: 1, column: 11 }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
code: '<template><!---></template>',
|
// code: '<template><!---></template>',
|
||||||
errors: [
|
// errors: [
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT,
|
// type: ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT,
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
// loc: { offset: 10, line: 1, column: 11 }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
code: '<template><!----></template>',
|
// code: '<template><!----></template>',
|
||||||
errors: []
|
// errors: []
|
||||||
}
|
// }
|
||||||
],
|
// ],
|
||||||
CDATA_IN_HTML_CONTENT: [
|
CDATA_IN_HTML_CONTENT: [
|
||||||
{
|
{
|
||||||
code: '<template><![CDATA[cdata]]></template>',
|
code: '<template><![CDATA[cdata]]></template>',
|
||||||
|
@ -2081,28 +2094,28 @@ describe('compiler: parse', () => {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
END_TAG_WITH_ATTRIBUTES: [
|
// END_TAG_WITH_ATTRIBUTES: [
|
||||||
{
|
// {
|
||||||
code: '<template><div></div id=""></template>',
|
// code: '<template><div></div id=""></template>',
|
||||||
errors: [
|
// errors: [
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.END_TAG_WITH_ATTRIBUTES,
|
// type: ErrorCodes.END_TAG_WITH_ATTRIBUTES,
|
||||||
loc: { offset: 21, line: 1, column: 22 }
|
// loc: { offset: 21, line: 1, column: 22 }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
}
|
// }
|
||||||
],
|
// ],
|
||||||
END_TAG_WITH_TRAILING_SOLIDUS: [
|
// END_TAG_WITH_TRAILING_SOLIDUS: [
|
||||||
{
|
// {
|
||||||
code: '<template><div></div/></template>',
|
// code: '<template><div></div/></template>',
|
||||||
errors: [
|
// errors: [
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS,
|
// type: ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS,
|
||||||
loc: { offset: 20, line: 1, column: 21 }
|
// loc: { offset: 20, line: 1, column: 21 }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
}
|
// }
|
||||||
],
|
// ],
|
||||||
EOF_BEFORE_TAG_NAME: [
|
EOF_BEFORE_TAG_NAME: [
|
||||||
{
|
{
|
||||||
code: '<template><',
|
code: '<template><',
|
||||||
|
@ -2193,73 +2206,73 @@ describe('compiler: parse', () => {
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
// Bogus comments don't throw eof-in-comment error.
|
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#bogus-comment-state
|
|
||||||
{
|
|
||||||
code: '<template><!',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: '<template><!-',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: '<template><!abc',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT: [
|
|
||||||
{
|
|
||||||
code: "<script><!--console.log('hello')",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT,
|
|
||||||
loc: { offset: 32, line: 1, column: 33 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "<script>console.log('hello')",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
// // Bogus comments don't throw eof-in-comment error.
|
||||||
|
// // https://html.spec.whatwg.org/multipage/parsing.html#bogus-comment-state
|
||||||
|
// {
|
||||||
|
// code: '<template><!',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
||||||
|
// loc: { offset: 10, line: 1, column: 11 }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
|
// loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// code: '<template><!-',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
||||||
|
// loc: { offset: 10, line: 1, column: 11 }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
|
// loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// code: '<template><!abc',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
||||||
|
// loc: { offset: 10, line: 1, column: 11 }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
|
// loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
],
|
],
|
||||||
|
// EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT: [
|
||||||
|
// {
|
||||||
|
// code: "<script><!--console.log('hello')",
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
|
// loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT,
|
||||||
|
// loc: { offset: 32, line: 1, column: 33 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// code: "<script>console.log('hello')",
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
|
// loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
EOF_IN_TAG: [
|
EOF_IN_TAG: [
|
||||||
{
|
{
|
||||||
code: '<template><div',
|
code: '<template><div',
|
||||||
|
@ -2268,10 +2281,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 14, line: 1, column: 15 }
|
loc: { offset: 14, line: 1, column: 15 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2285,10 +2294,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 15, line: 1, column: 16 }
|
loc: { offset: 15, line: 1, column: 16 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2302,10 +2307,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 17, line: 1, column: 18 }
|
loc: { offset: 17, line: 1, column: 18 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2319,10 +2320,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 18, line: 1, column: 19 }
|
loc: { offset: 18, line: 1, column: 19 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2332,18 +2329,14 @@ describe('compiler: parse', () => {
|
||||||
{
|
{
|
||||||
code: '<template><div id =',
|
code: '<template><div id =',
|
||||||
errors: [
|
errors: [
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.MISSING_ATTRIBUTE_VALUE,
|
// type: ErrorCodes.MISSING_ATTRIBUTE_VALUE,
|
||||||
loc: { offset: 19, line: 1, column: 20 }
|
// loc: { offset: 19, line: 1, column: 20 }
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 19, line: 1, column: 20 }
|
loc: { offset: 19, line: 1, column: 20 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2357,10 +2350,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 22, line: 1, column: 23 }
|
loc: { offset: 22, line: 1, column: 23 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2374,10 +2363,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 22, line: 1, column: 23 }
|
loc: { offset: 22, line: 1, column: 23 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2391,10 +2376,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 23, line: 1, column: 24 }
|
loc: { offset: 23, line: 1, column: 24 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2408,10 +2389,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 23, line: 1, column: 24 }
|
loc: { offset: 23, line: 1, column: 24 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2425,10 +2402,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 21, line: 1, column: 22 }
|
loc: { offset: 21, line: 1, column: 22 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2446,10 +2419,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 24, line: 1, column: 25 }
|
loc: { offset: 24, line: 1, column: 25 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2467,10 +2436,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 24, line: 1, column: 25 }
|
loc: { offset: 24, line: 1, column: 25 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2488,10 +2453,6 @@ describe('compiler: parse', () => {
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 23, line: 1, column: 24 }
|
loc: { offset: 23, line: 1, column: 24 }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
@ -2504,102 +2465,106 @@ describe('compiler: parse', () => {
|
||||||
{
|
{
|
||||||
type: ErrorCodes.EOF_IN_TAG,
|
type: ErrorCodes.EOF_IN_TAG,
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
INCORRECTLY_CLOSED_COMMENT: [
|
|
||||||
{
|
|
||||||
code: '<template><!--comment--!></template>',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INCORRECTLY_CLOSED_COMMENT,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
INCORRECTLY_OPENED_COMMENT: [
|
|
||||||
{
|
|
||||||
code: '<template><!></template>',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: '<template><!-></template>',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: '<template><!ELEMENT br EMPTY></template>',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// Just ignore doctype.
|
|
||||||
{
|
|
||||||
code: '<!DOCTYPE html>',
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
INVALID_FIRST_CHARACTER_OF_TAG_NAME: [
|
|
||||||
{
|
|
||||||
code: '<template>a < b</template>',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
|
||||||
loc: { offset: 13, line: 1, column: 14 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: '<template><<3C>></template>',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
|
||||||
loc: { offset: 11, line: 1, column: 12 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: '<template>a </ b</template>',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
|
||||||
loc: { offset: 14, line: 1, column: 15 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
code: '<template></<2F>></template>',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
|
||||||
loc: { offset: 12, line: 1, column: 13 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// Don't throw invalid-first-character-of-tag-name in interpolation
|
|
||||||
{
|
|
||||||
code: '<template>{{a < b}}</template>',
|
|
||||||
errors: []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
// INCORRECTLY_CLOSED_COMMENT: [
|
||||||
|
// {
|
||||||
|
// code: '<template><!--comment--!></template>',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INCORRECTLY_CLOSED_COMMENT,
|
||||||
|
// loc: { offset: 10, line: 1, column: 11 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// INCORRECTLY_OPENED_COMMENT: [
|
||||||
|
// {
|
||||||
|
// code: '<template><!></template>',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
||||||
|
// loc: { offset: 10, line: 1, column: 11 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// code: '<template><!-></template>',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
||||||
|
// loc: { offset: 10, line: 1, column: 11 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// code: '<template><!ELEMENT br EMPTY></template>',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
|
||||||
|
// loc: { offset: 10, line: 1, column: 11 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// // Just ignore doctype.
|
||||||
|
// {
|
||||||
|
// code: '<!DOCTYPE html>',
|
||||||
|
// errors: []
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// INVALID_FIRST_CHARACTER_OF_TAG_NAME: [
|
||||||
|
// {
|
||||||
|
// code: '<template>a < b</template>',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
||||||
|
// loc: { offset: 13, line: 1, column: 14 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// code: '<template><<3C>></template>',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
||||||
|
// loc: { offset: 11, line: 1, column: 12 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// code: '<template>a </ b</template>',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
||||||
|
// loc: { offset: 14, line: 1, column: 15 }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
|
// loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// code: '<template></<2F>></template>',
|
||||||
|
// errors: [
|
||||||
|
// {
|
||||||
|
// type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
||||||
|
// loc: { offset: 12, line: 1, column: 13 }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// // Don't throw invalid-first-character-of-tag-name in interpolation
|
||||||
|
// {
|
||||||
|
// code: '<template>{{a < b}}</template>',
|
||||||
|
// errors: []
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
MISSING_ATTRIBUTE_VALUE: [
|
MISSING_ATTRIBUTE_VALUE: [
|
||||||
{
|
{
|
||||||
code: '<template><div id=></div></template>',
|
code: '<template><div id=></div></template>',
|
||||||
|
@ -2635,73 +2600,73 @@ describe('compiler: parse', () => {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
MISSING_WHITESPACE_BETWEEN_ATTRIBUTES: [
|
// MISSING_WHITESPACE_BETWEEN_ATTRIBUTES: [
|
||||||
{
|
// {
|
||||||
code: '<template><div id="foo"class="bar"></div></template>',
|
// code: '<template><div id="foo"class="bar"></div></template>',
|
||||||
errors: [
|
// errors: [
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES,
|
// type: ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES,
|
||||||
loc: { offset: 23, line: 1, column: 24 }
|
// loc: { offset: 23, line: 1, column: 24 }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
// CR doesn't appear in tokenization phase, but all CR are removed in preprocessing.
|
// // CR doesn't appear in tokenization phase, but all CR are removed in preprocessing.
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#preprocessing-the-input-stream
|
// // https://html.spec.whatwg.org/multipage/parsing.html#preprocessing-the-input-stream
|
||||||
{
|
// {
|
||||||
code: '<template><div id="foo"\r\nclass="bar"></div></template>',
|
// code: '<template><div id="foo"\r\nclass="bar"></div></template>',
|
||||||
errors: []
|
// errors: []
|
||||||
}
|
// }
|
||||||
],
|
// ],
|
||||||
NESTED_COMMENT: [
|
// NESTED_COMMENT: [
|
||||||
{
|
// {
|
||||||
code: '<template><!--a<!--b--></template>',
|
// code: '<template><!--a<!--b--></template>',
|
||||||
errors: [
|
// errors: [
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.NESTED_COMMENT,
|
// type: ErrorCodes.NESTED_COMMENT,
|
||||||
loc: { offset: 15, line: 1, column: 16 }
|
// loc: { offset: 15, line: 1, column: 16 }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
code: '<template><!--a<!--b<!--c--></template>',
|
// code: '<template><!--a<!--b<!--c--></template>',
|
||||||
errors: [
|
// errors: [
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.NESTED_COMMENT,
|
// type: ErrorCodes.NESTED_COMMENT,
|
||||||
loc: { offset: 15, line: 1, column: 16 }
|
// loc: { offset: 15, line: 1, column: 16 }
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.NESTED_COMMENT,
|
// type: ErrorCodes.NESTED_COMMENT,
|
||||||
loc: { offset: 20, line: 1, column: 21 }
|
// loc: { offset: 20, line: 1, column: 21 }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
code: '<template><!--a<!--b<!----></template>',
|
// code: '<template><!--a<!--b<!----></template>',
|
||||||
errors: [
|
// errors: [
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.NESTED_COMMENT,
|
// type: ErrorCodes.NESTED_COMMENT,
|
||||||
loc: { offset: 15, line: 1, column: 16 }
|
// loc: { offset: 15, line: 1, column: 16 }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
code: '<template><!--a<!--></template>',
|
// code: '<template><!--a<!--></template>',
|
||||||
errors: []
|
// errors: []
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
code: '<template><!--a<!--',
|
// code: '<template><!--a<!--',
|
||||||
errors: [
|
// errors: [
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.EOF_IN_COMMENT,
|
// type: ErrorCodes.EOF_IN_COMMENT,
|
||||||
loc: { offset: 19, line: 1, column: 20 }
|
// loc: { offset: 19, line: 1, column: 20 }
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
// type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 0, line: 1, column: 1 }
|
// loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
}
|
// }
|
||||||
],
|
// ],
|
||||||
UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME: [
|
UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME: [
|
||||||
{
|
{
|
||||||
code: "<template><div a\"bc=''></div></template>",
|
code: "<template><div a\"bc=''></div></template>",
|
||||||
|
@ -2843,6 +2808,19 @@ describe('compiler: parse', () => {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
code: '<template>a </ b</template>',
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
type: ErrorCodes.X_INVALID_END_TAG,
|
||||||
|
loc: { offset: 12, line: 1, column: 13 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
code: "<template>{{'</div>'}}</template>",
|
code: "<template>{{'</div>'}}</template>",
|
||||||
errors: []
|
errors: []
|
||||||
|
@ -2903,6 +2881,19 @@ describe('compiler: parse', () => {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
code: '<div>{{ foo</div>',
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
type: ErrorCodes.X_MISSING_INTERPOLATION_END,
|
||||||
|
loc: { offset: 5, line: 1, column: 6 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
code: '{{}}',
|
code: '{{}}',
|
||||||
errors: []
|
errors: []
|
||||||
|
@ -2924,7 +2915,7 @@ describe('compiler: parse', () => {
|
||||||
for (const key of Object.keys(patterns)) {
|
for (const key of Object.keys(patterns)) {
|
||||||
describe(key, () => {
|
describe(key, () => {
|
||||||
for (const { code, errors, options } of patterns[key]) {
|
for (const { code, errors, options } of patterns[key]) {
|
||||||
test.skip(
|
test(
|
||||||
code.replace(
|
code.replace(
|
||||||
/[\r\n]/g,
|
/[\r\n]/g,
|
||||||
c => `\\x0${c.codePointAt(0)!.toString(16)};`
|
c => `\\x0${c.codePointAt(0)!.toString(16)};`
|
||||||
|
@ -2933,6 +2924,8 @@ describe('compiler: parse', () => {
|
||||||
const spy = vi.fn()
|
const spy = vi.fn()
|
||||||
const ast = baseParse(code, {
|
const ast = baseParse(code, {
|
||||||
parseMode: 'html',
|
parseMode: 'html',
|
||||||
|
getNamespace: tag =>
|
||||||
|
tag === 'svg' ? Namespaces.SVG : Namespaces.HTML,
|
||||||
...options,
|
...options,
|
||||||
onError: spy
|
onError: spy
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,6 +22,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
IN THE SOFTWARE.
|
IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ErrorCodes } from '../errors'
|
||||||
import { ElementNode, Position } from '../ast'
|
import { ElementNode, Position } from '../ast'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,6 +55,7 @@ export const enum CharCodes {
|
||||||
Amp = 0x26, // "&"
|
Amp = 0x26, // "&"
|
||||||
SingleQuote = 0x27, // "'"
|
SingleQuote = 0x27, // "'"
|
||||||
DoubleQuote = 0x22, // '"'
|
DoubleQuote = 0x22, // '"'
|
||||||
|
GraveAccent = 96, // "`"
|
||||||
Dash = 0x2d, // "-"
|
Dash = 0x2d, // "-"
|
||||||
Slash = 0x2f, // "/"
|
Slash = 0x2f, // "/"
|
||||||
Zero = 0x30, // "0"
|
Zero = 0x30, // "0"
|
||||||
|
@ -83,7 +85,7 @@ const defaultDelimitersOpen = new Uint8Array([123, 123]) // "{{"
|
||||||
const defaultDelimitersClose = new Uint8Array([125, 125]) // "}}"
|
const defaultDelimitersClose = new Uint8Array([125, 125]) // "}}"
|
||||||
|
|
||||||
/** All the states the tokenizer can be in. */
|
/** All the states the tokenizer can be in. */
|
||||||
const enum State {
|
export const enum State {
|
||||||
Text = 1,
|
Text = 1,
|
||||||
|
|
||||||
// interpolation
|
// interpolation
|
||||||
|
@ -200,9 +202,10 @@ export interface Callbacks {
|
||||||
oncomment(start: number, endIndex: number): void
|
oncomment(start: number, endIndex: number): void
|
||||||
oncdata(start: number, endIndex: number): void
|
oncdata(start: number, endIndex: number): void
|
||||||
|
|
||||||
// onprocessinginstruction(start: number, endIndex: number): void
|
onprocessinginstruction(start: number, endIndex: number): void
|
||||||
// ondeclaration(start: number, endIndex: number): void
|
// ondeclaration(start: number, endIndex: number): void
|
||||||
onend(): void
|
onend(): void
|
||||||
|
onerr(code: ErrorCodes, index: number): void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -211,7 +214,7 @@ export interface Callbacks {
|
||||||
* We don't have `Script`, `Style`, or `Title` here. Instead, we re-use the *End
|
* We don't have `Script`, `Style`, or `Title` here. Instead, we re-use the *End
|
||||||
* sequences with an increased offset.
|
* sequences with an increased offset.
|
||||||
*/
|
*/
|
||||||
const Sequences = {
|
export const Sequences = {
|
||||||
Cdata: new Uint8Array([0x43, 0x44, 0x41, 0x54, 0x41, 0x5b]), // CDATA[
|
Cdata: new Uint8Array([0x43, 0x44, 0x41, 0x54, 0x41, 0x5b]), // CDATA[
|
||||||
CdataEnd: new Uint8Array([0x5d, 0x5d, 0x3e]), // ]]>
|
CdataEnd: new Uint8Array([0x5d, 0x5d, 0x3e]), // ]]>
|
||||||
CommentEnd: new Uint8Array([0x2d, 0x2d, 0x3e]), // `-->`
|
CommentEnd: new Uint8Array([0x2d, 0x2d, 0x3e]), // `-->`
|
||||||
|
@ -225,11 +228,11 @@ const Sequences = {
|
||||||
|
|
||||||
export default class Tokenizer {
|
export default class Tokenizer {
|
||||||
/** The current state the tokenizer is in. */
|
/** The current state the tokenizer is in. */
|
||||||
private state = State.Text
|
public state = State.Text
|
||||||
/** The read buffer. */
|
/** The read buffer. */
|
||||||
private buffer = ''
|
private buffer = ''
|
||||||
/** The beginning of the section that is currently being read. */
|
/** The beginning of the section that is currently being read. */
|
||||||
private sectionStart = 0
|
public sectionStart = 0
|
||||||
/** The index within the buffer that we are currently looking at. */
|
/** The index within the buffer that we are currently looking at. */
|
||||||
private index = 0
|
private index = 0
|
||||||
/** The start of the last entity. */
|
/** The start of the last entity. */
|
||||||
|
@ -366,7 +369,7 @@ export default class Tokenizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private currentSequence: Uint8Array = undefined!
|
public currentSequence: Uint8Array = undefined!
|
||||||
private sequenceIndex = 0
|
private sequenceIndex = 0
|
||||||
private stateSpecialStartSequence(c: number): void {
|
private stateSpecialStartSequence(c: number): void {
|
||||||
const isEnd = this.sequenceIndex === this.currentSequence.length
|
const isEnd = this.sequenceIndex === this.currentSequence.length
|
||||||
|
@ -581,7 +584,12 @@ export default class Tokenizer {
|
||||||
if (isWhitespace(c)) {
|
if (isWhitespace(c)) {
|
||||||
// Ignore
|
// Ignore
|
||||||
} else if (c === CharCodes.Gt) {
|
} else if (c === CharCodes.Gt) {
|
||||||
|
if (__DEV__ || !__BROWSER__) {
|
||||||
|
this.cbs.onerr(ErrorCodes.MISSING_END_TAG_NAME, this.index)
|
||||||
|
}
|
||||||
this.state = State.Text
|
this.state = State.Text
|
||||||
|
// Ignore
|
||||||
|
this.sectionStart = this.index + 1
|
||||||
} else {
|
} else {
|
||||||
this.state = isTagStartChar(c)
|
this.state = isTagStartChar(c)
|
||||||
? State.InClosingTagName
|
? State.InClosingTagName
|
||||||
|
@ -599,7 +607,7 @@ export default class Tokenizer {
|
||||||
}
|
}
|
||||||
private stateAfterClosingTagName(c: number): void {
|
private stateAfterClosingTagName(c: number): void {
|
||||||
// Skip everything until ">"
|
// Skip everything until ">"
|
||||||
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
|
if (c === CharCodes.Gt) {
|
||||||
this.state = State.Text
|
this.state = State.Text
|
||||||
this.sectionStart = this.index + 1
|
this.sectionStart = this.index + 1
|
||||||
}
|
}
|
||||||
|
@ -615,7 +623,19 @@ export default class Tokenizer {
|
||||||
this.sectionStart = this.index + 1
|
this.sectionStart = this.index + 1
|
||||||
} else if (c === CharCodes.Slash) {
|
} else if (c === CharCodes.Slash) {
|
||||||
this.state = State.InSelfClosingTag
|
this.state = State.InSelfClosingTag
|
||||||
|
if (
|
||||||
|
(__DEV__ || !__BROWSER__) &&
|
||||||
|
this.buffer.charCodeAt(this.index + 1) !== CharCodes.Gt
|
||||||
|
) {
|
||||||
|
this.cbs.onerr(ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG, this.index)
|
||||||
|
}
|
||||||
} else if (!isWhitespace(c)) {
|
} else if (!isWhitespace(c)) {
|
||||||
|
if ((__DEV__ || !__BROWSER__) && c === CharCodes.Eq) {
|
||||||
|
this.cbs.onerr(
|
||||||
|
ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME,
|
||||||
|
this.index
|
||||||
|
)
|
||||||
|
}
|
||||||
this.handleAttributeStart(c)
|
this.handleAttributeStart(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -655,6 +675,16 @@ export default class Tokenizer {
|
||||||
if (c === CharCodes.Eq || isEndOfTagSection(c)) {
|
if (c === CharCodes.Eq || isEndOfTagSection(c)) {
|
||||||
this.cbs.onattribname(this.sectionStart, this.index)
|
this.cbs.onattribname(this.sectionStart, this.index)
|
||||||
this.handleAttributeNameEnd(c)
|
this.handleAttributeNameEnd(c)
|
||||||
|
} else if (
|
||||||
|
(__DEV__ || !__BROWSER__) &&
|
||||||
|
(c === CharCodes.DoubleQuote ||
|
||||||
|
c === CharCodes.SingleQuote ||
|
||||||
|
c === CharCodes.Lt)
|
||||||
|
) {
|
||||||
|
this.cbs.onerr(
|
||||||
|
ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
|
||||||
|
this.index
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private stateInDirectiveName(c: number): void {
|
private stateInDirectiveName(c: number): void {
|
||||||
|
@ -687,7 +717,14 @@ export default class Tokenizer {
|
||||||
if (c === CharCodes.RightSquare) {
|
if (c === CharCodes.RightSquare) {
|
||||||
this.state = State.InDirectiveArg
|
this.state = State.InDirectiveArg
|
||||||
} else if (c === CharCodes.Eq || isEndOfTagSection(c)) {
|
} else if (c === CharCodes.Eq || isEndOfTagSection(c)) {
|
||||||
// TODO emit error
|
this.cbs.ondirarg(this.sectionStart, this.index + 1)
|
||||||
|
this.handleAttributeNameEnd(c)
|
||||||
|
if (__DEV__ || !__BROWSER__) {
|
||||||
|
this.cbs.onerr(
|
||||||
|
ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END,
|
||||||
|
this.index
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private stateInDirectiveModifier(c: number): void {
|
private stateInDirectiveModifier(c: number): void {
|
||||||
|
@ -757,6 +794,17 @@ export default class Tokenizer {
|
||||||
this.cbs.onattribend(QuoteType.Unquoted, this.index)
|
this.cbs.onattribend(QuoteType.Unquoted, this.index)
|
||||||
this.state = State.BeforeAttributeName
|
this.state = State.BeforeAttributeName
|
||||||
this.stateBeforeAttributeName(c)
|
this.stateBeforeAttributeName(c)
|
||||||
|
} else if (
|
||||||
|
((__DEV__ || !__BROWSER__) && c === CharCodes.DoubleQuote) ||
|
||||||
|
c === CharCodes.SingleQuote ||
|
||||||
|
c === CharCodes.Lt ||
|
||||||
|
c === CharCodes.Eq ||
|
||||||
|
c === CharCodes.GraveAccent
|
||||||
|
) {
|
||||||
|
this.cbs.onerr(
|
||||||
|
ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
|
||||||
|
this.index
|
||||||
|
)
|
||||||
} else if (!__BROWSER__ && c === CharCodes.Amp) {
|
} else if (!__BROWSER__ && c === CharCodes.Amp) {
|
||||||
this.startEntity()
|
this.startEntity()
|
||||||
}
|
}
|
||||||
|
@ -779,7 +827,7 @@ export default class Tokenizer {
|
||||||
}
|
}
|
||||||
private stateInProcessingInstruction(c: number): void {
|
private stateInProcessingInstruction(c: number): void {
|
||||||
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
|
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
|
||||||
// this.cbs.onprocessinginstruction(this.sectionStart, this.index)
|
this.cbs.onprocessinginstruction(this.sectionStart, this.index)
|
||||||
this.state = State.Text
|
this.state = State.Text
|
||||||
this.sectionStart = this.index + 1
|
this.sectionStart = this.index + 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ import Tokenizer, {
|
||||||
CharCodes,
|
CharCodes,
|
||||||
ParseMode,
|
ParseMode,
|
||||||
QuoteType,
|
QuoteType,
|
||||||
|
Sequences,
|
||||||
|
State,
|
||||||
isWhitespace,
|
isWhitespace,
|
||||||
toCharCodes
|
toCharCodes
|
||||||
} from './Tokenizer'
|
} from './Tokenizer'
|
||||||
|
@ -72,6 +74,8 @@ let currentVPreBoundary: ElementNode | null = null
|
||||||
const stack: ElementNode[] = []
|
const stack: ElementNode[] = []
|
||||||
|
|
||||||
const tokenizer = new Tokenizer(stack, {
|
const tokenizer = new Tokenizer(stack, {
|
||||||
|
onerr: emitError,
|
||||||
|
|
||||||
ontext(start, end) {
|
ontext(start, end) {
|
||||||
onText(getSlice(start, end), start, end)
|
onText(getSlice(start, end), start, end)
|
||||||
},
|
},
|
||||||
|
@ -121,7 +125,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
tagType: ElementTypes.ELEMENT, // will be refined on tag close
|
tagType: ElementTypes.ELEMENT, // will be refined on tag close
|
||||||
props: [],
|
props: [],
|
||||||
children: [],
|
children: [],
|
||||||
loc: getLoc(startIndex),
|
loc: getLoc(startIndex, end),
|
||||||
codegenNode: undefined
|
codegenNode: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -133,15 +137,24 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
onclosetag(start, end) {
|
onclosetag(start, end) {
|
||||||
const name = getSlice(start, end)
|
const name = getSlice(start, end)
|
||||||
if (!currentOptions.isVoidTag(name)) {
|
if (!currentOptions.isVoidTag(name)) {
|
||||||
|
let found = false
|
||||||
for (let i = 0; i < stack.length; i++) {
|
for (let i = 0; i < stack.length; i++) {
|
||||||
const e = stack[i]
|
const e = stack[i]
|
||||||
if (e.tag.toLowerCase() === name.toLowerCase()) {
|
if (e.tag.toLowerCase() === name.toLowerCase()) {
|
||||||
|
found = true
|
||||||
|
if (i > 0) {
|
||||||
|
emitError(ErrorCodes.X_MISSING_END_TAG, stack[0].loc.start.offset)
|
||||||
|
}
|
||||||
for (let j = 0; j <= i; j++) {
|
for (let j = 0; j <= i; j++) {
|
||||||
onCloseTag(stack.shift()!, end)
|
const el = stack.shift()!
|
||||||
|
onCloseTag(el, end, j < i)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!found) {
|
||||||
|
emitError(ErrorCodes.X_INVALID_END_TAG, backTrack(start, CharCodes.Lt))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -166,15 +179,6 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
|
|
||||||
ondirname(start, end) {
|
ondirname(start, end) {
|
||||||
const raw = getSlice(start, end)
|
const raw = getSlice(start, end)
|
||||||
if (inVPre) {
|
|
||||||
currentProp = {
|
|
||||||
type: NodeTypes.ATTRIBUTE,
|
|
||||||
name: raw,
|
|
||||||
nameLoc: getLoc(start, end),
|
|
||||||
value: undefined,
|
|
||||||
loc: getLoc(start)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const name =
|
const name =
|
||||||
raw === '.' || raw === ':'
|
raw === '.' || raw === ':'
|
||||||
? 'bind'
|
? 'bind'
|
||||||
|
@ -183,6 +187,20 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
: raw === '#'
|
: raw === '#'
|
||||||
? 'slot'
|
? 'slot'
|
||||||
: raw.slice(2)
|
: raw.slice(2)
|
||||||
|
|
||||||
|
if (!inVPre && name === '') {
|
||||||
|
emitError(ErrorCodes.X_MISSING_DIRECTIVE_NAME, start)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inVPre || name === '') {
|
||||||
|
currentProp = {
|
||||||
|
type: NodeTypes.ATTRIBUTE,
|
||||||
|
name: raw,
|
||||||
|
nameLoc: getLoc(start, end),
|
||||||
|
value: undefined,
|
||||||
|
loc: getLoc(start)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
currentProp = {
|
currentProp = {
|
||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name,
|
name,
|
||||||
|
@ -265,7 +283,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
p => (p.type === NodeTypes.DIRECTIVE ? p.rawName : p.name) === name
|
p => (p.type === NodeTypes.DIRECTIVE ? p.rawName : p.name) === name
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// TODO duplicate
|
emitError(ErrorCodes.DUPLICATE_ATTRIBUTE, start)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -287,6 +305,10 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
currentAttrValue = condense(currentAttrValue).trim()
|
currentAttrValue = condense(currentAttrValue).trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (quote === QuoteType.Unquoted && !currentAttrValue) {
|
||||||
|
emitError(ErrorCodes.MISSING_ATTRIBUTE_VALUE, end)
|
||||||
|
}
|
||||||
|
|
||||||
currentProp!.value = {
|
currentProp!.value = {
|
||||||
type: NodeTypes.TEXT,
|
type: NodeTypes.TEXT,
|
||||||
content: currentAttrValue,
|
content: currentAttrValue,
|
||||||
|
@ -342,16 +364,52 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
},
|
},
|
||||||
|
|
||||||
onend() {
|
onend() {
|
||||||
if (stack.length > 0) {
|
const end = currentInput.length
|
||||||
// has unclosed tag
|
// EOF ERRORS
|
||||||
currentOptions.onError(
|
if ((__DEV__ || !__BROWSER__) && tokenizer.state !== State.Text) {
|
||||||
// TODO loc info
|
switch (tokenizer.state) {
|
||||||
createCompilerError(ErrorCodes.MISSING_END_TAG_NAME)
|
case State.BeforeTagName:
|
||||||
|
case State.BeforeClosingTagName:
|
||||||
|
emitError(ErrorCodes.EOF_BEFORE_TAG_NAME, end)
|
||||||
|
break
|
||||||
|
case State.Interpolation:
|
||||||
|
case State.InterpolationClose:
|
||||||
|
emitError(
|
||||||
|
ErrorCodes.X_MISSING_INTERPOLATION_END,
|
||||||
|
tokenizer.sectionStart
|
||||||
)
|
)
|
||||||
|
break
|
||||||
|
case State.InCommentLike:
|
||||||
|
if (tokenizer.currentSequence === Sequences.CdataEnd) {
|
||||||
|
emitError(ErrorCodes.EOF_IN_CDATA, end)
|
||||||
|
} else {
|
||||||
|
emitError(ErrorCodes.EOF_IN_COMMENT, end)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case State.InTagName:
|
||||||
|
case State.InSelfClosingTag:
|
||||||
|
case State.InClosingTagName:
|
||||||
|
case State.BeforeAttributeName:
|
||||||
|
case State.InAttributeName:
|
||||||
|
case State.InDirectiveName:
|
||||||
|
case State.InDirectiveArg:
|
||||||
|
case State.InDirectiveDynamicArg:
|
||||||
|
case State.InDirectiveModifier:
|
||||||
|
case State.AfterAttributeName:
|
||||||
|
case State.BeforeAttributeValue:
|
||||||
|
case State.InAttributeValueDq: // "
|
||||||
|
case State.InAttributeValueSq: // '
|
||||||
|
case State.InAttributeValueNq:
|
||||||
|
emitError(ErrorCodes.EOF_IN_TAG, end)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// console.log(tokenizer.state)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const end = currentInput.length - 1
|
|
||||||
for (let index = 0; index < stack.length; index++) {
|
for (let index = 0; index < stack.length; index++) {
|
||||||
onCloseTag(stack[index], end)
|
onCloseTag(stack[index], end - 1)
|
||||||
|
emitError(ErrorCodes.X_MISSING_END_TAG, stack[index].loc.start.offset)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -359,7 +417,17 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
if (stack[0].ns !== Namespaces.HTML) {
|
if (stack[0].ns !== Namespaces.HTML) {
|
||||||
onText(getSlice(start, end), start, end)
|
onText(getSlice(start, end), start, end)
|
||||||
} else {
|
} else {
|
||||||
// TODO throw error if ns is html
|
emitError(ErrorCodes.CDATA_IN_HTML_CONTENT, start - 9)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onprocessinginstruction(start) {
|
||||||
|
// ignore as we do not have runtime handling for this, only check error
|
||||||
|
if ((stack[0] ? stack[0].ns : currentOptions.ns) === Namespaces.HTML) {
|
||||||
|
emitError(
|
||||||
|
ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
|
||||||
|
start - 1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -453,10 +521,12 @@ function endOpenTag(end: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onText(content: string, start: number, end: number) {
|
function onText(content: string, start: number, end: number) {
|
||||||
if (__BROWSER__ && content.includes('&')) {
|
if (__BROWSER__) {
|
||||||
// TODO do not do this in <script> or <style>
|
const tag = stack[0]?.tag
|
||||||
|
if (tag !== 'script' && tag !== 'style' && content.includes('&')) {
|
||||||
content = currentOptions.decodeEntities!(content, false)
|
content = currentOptions.decodeEntities!(content, false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const parent = stack[0] || currentRoot
|
const parent = stack[0] || currentRoot
|
||||||
const lastNode = parent.children[parent.children.length - 1]
|
const lastNode = parent.children[parent.children.length - 1]
|
||||||
if (lastNode?.type === NodeTypes.TEXT) {
|
if (lastNode?.type === NodeTypes.TEXT) {
|
||||||
|
@ -472,7 +542,7 @@ function onText(content: string, start: number, end: number) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCloseTag(el: ElementNode, end: number) {
|
function onCloseTag(el: ElementNode, end: number, isImplied = false) {
|
||||||
// attach end position
|
// attach end position
|
||||||
if (tokenizer.inSFCRoot) {
|
if (tokenizer.inSFCRoot) {
|
||||||
// SFC root tag, end position should be inner end
|
// SFC root tag, end position should be inner end
|
||||||
|
@ -481,6 +551,9 @@ function onCloseTag(el: ElementNode, end: number) {
|
||||||
} else {
|
} else {
|
||||||
el.loc.end = extend({}, el.loc.start)
|
el.loc.end = extend({}, el.loc.start)
|
||||||
}
|
}
|
||||||
|
} else if (isImplied) {
|
||||||
|
// implied close, end should be backtracked to close
|
||||||
|
el.loc.end = tokenizer.getPos(backTrack(end, CharCodes.Lt))
|
||||||
} else {
|
} else {
|
||||||
el.loc.end = tokenizer.getPos(end + fastForward(end, CharCodes.Gt) + 1)
|
el.loc.end = tokenizer.getPos(end + fastForward(end, CharCodes.Gt) + 1)
|
||||||
}
|
}
|
||||||
|
@ -527,6 +600,12 @@ function fastForward(start: number, c: number) {
|
||||||
return offset
|
return offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function backTrack(index: number, c: number) {
|
||||||
|
let i = index
|
||||||
|
while (currentInput.charCodeAt(i) !== c && i >= 0) i--
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
const specialTemplateDir = new Set(['if', 'else', 'else-if', 'for', 'slot'])
|
const specialTemplateDir = new Set(['if', 'else', 'else-if', 'for', 'slot'])
|
||||||
function isFragmentTemplate({ tag, props }: ElementNode): boolean {
|
function isFragmentTemplate({ tag, props }: ElementNode): boolean {
|
||||||
if (tag === 'template') {
|
if (tag === 'template') {
|
||||||
|
@ -734,6 +813,10 @@ function dirToAttr(dir: DirectiveNode): AttributeNode {
|
||||||
return attr
|
return attr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emitError(code: ErrorCodes, index: number) {
|
||||||
|
currentOptions.onError(createCompilerError(code, getLoc(index, index)))
|
||||||
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
tokenizer.reset()
|
tokenizer.reset()
|
||||||
currentElement = null
|
currentElement = null
|
||||||
|
|
Loading…
Reference in New Issue