mirror of https://github.com/vuejs/core.git
wip: pass all compiler-sfc tests
This commit is contained in:
parent
dda4fd526e
commit
059caafc13
|
@ -491,6 +491,27 @@ describe('compiler: parse', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('self-closing void element', () => {
|
||||
const ast = baseParse('<img/>after', {
|
||||
isVoidTag: tag => tag === 'img'
|
||||
})
|
||||
const element = ast.children[0] as ElementNode
|
||||
|
||||
expect(element).toStrictEqual({
|
||||
type: NodeTypes.ELEMENT,
|
||||
ns: Namespaces.HTML,
|
||||
tag: 'img',
|
||||
tagType: ElementTypes.ELEMENT,
|
||||
codegenNode: undefined,
|
||||
props: [],
|
||||
children: [],
|
||||
loc: {
|
||||
start: { offset: 0, line: 1, column: 1 },
|
||||
end: { offset: 6, line: 1, column: 7 }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('template element with directives', () => {
|
||||
const ast = baseParse('<template v-if="ok"></template>')
|
||||
const element = ast.children[0]
|
||||
|
|
|
@ -861,6 +861,9 @@ export default class Tokenizer {
|
|||
this.buffer = input
|
||||
while (this.index < this.buffer.length) {
|
||||
const c = this.buffer.charCodeAt(this.index)
|
||||
if (c === CharCodes.NewLine) {
|
||||
this.newlines.push(this.index)
|
||||
}
|
||||
switch (this.state) {
|
||||
case State.Text: {
|
||||
this.stateText(c)
|
||||
|
@ -999,9 +1002,6 @@ export default class Tokenizer {
|
|||
break
|
||||
}
|
||||
}
|
||||
if (c === CharCodes.NewLine) {
|
||||
this.newlines.push(this.index)
|
||||
}
|
||||
this.index++
|
||||
}
|
||||
this.cleanup()
|
||||
|
|
|
@ -24,7 +24,12 @@ import Tokenizer, {
|
|||
} from './Tokenizer'
|
||||
import { CompilerCompatOptions } from '../compat/compatConfig'
|
||||
import { NO, extend } from '@vue/shared'
|
||||
import { defaultOnError, defaultOnWarn } from '../errors'
|
||||
import {
|
||||
ErrorCodes,
|
||||
createCompilerError,
|
||||
defaultOnError,
|
||||
defaultOnWarn
|
||||
} from '../errors'
|
||||
import { forAliasRE, isCoreComponent } from '../utils'
|
||||
import { decodeHTML } from 'entities/lib/decode.js'
|
||||
|
||||
|
@ -105,6 +110,11 @@ const tokenizer = new Tokenizer(stack, {
|
|||
|
||||
onopentagname(start, end) {
|
||||
const name = getSlice(start, end)
|
||||
// in SFC mode, root-level tags locations are for its inner content.
|
||||
const startIndex =
|
||||
tokenizer.mode === ParseMode.SFC && stack.length === 0
|
||||
? end + fastForward(end, CharCodes.Gt) + 1
|
||||
: start - 1
|
||||
currentElement = {
|
||||
type: NodeTypes.ELEMENT,
|
||||
tag: name,
|
||||
|
@ -112,7 +122,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
tagType: ElementTypes.ELEMENT, // will be refined on tag close
|
||||
props: [],
|
||||
children: [],
|
||||
loc: getLoc(start - 1),
|
||||
loc: getLoc(startIndex),
|
||||
codegenNode: undefined
|
||||
}
|
||||
},
|
||||
|
@ -137,7 +147,11 @@ const tokenizer = new Tokenizer(stack, {
|
|||
},
|
||||
|
||||
onselfclosingtag(end) {
|
||||
closeCurrentTag(end)
|
||||
const name = currentElement!.tag
|
||||
endOpenTag(end)
|
||||
if (stack[0]?.tag === name) {
|
||||
onCloseTag(stack.shift()!, end)
|
||||
}
|
||||
},
|
||||
|
||||
onattribname(start, end) {
|
||||
|
@ -318,6 +332,13 @@ const tokenizer = new Tokenizer(stack, {
|
|||
},
|
||||
|
||||
onend() {
|
||||
if (stack.length > 0) {
|
||||
// has unclosed tag
|
||||
currentOptions.onError(
|
||||
// TODO loc info
|
||||
createCompilerError(ErrorCodes.MISSING_END_TAG_NAME)
|
||||
)
|
||||
}
|
||||
const end = currentInput.length - 1
|
||||
for (let index = 0; index < stack.length; index++) {
|
||||
onCloseTag(stack[index], end)
|
||||
|
@ -421,14 +442,6 @@ function endOpenTag(end: number) {
|
|||
currentElement = null
|
||||
}
|
||||
|
||||
function closeCurrentTag(end: number) {
|
||||
const name = currentElement!.tag
|
||||
endOpenTag(end)
|
||||
if (stack[0].tag === name) {
|
||||
onCloseTag(stack.shift()!, end)
|
||||
}
|
||||
}
|
||||
|
||||
function onText(content: string, start: number, end: number) {
|
||||
if (__BROWSER__ && content.includes('&')) {
|
||||
// TODO do not do this in <script> or <style>
|
||||
|
@ -451,14 +464,16 @@ function onText(content: string, start: number, end: number) {
|
|||
|
||||
function onCloseTag(el: ElementNode, end: number) {
|
||||
// attach end position
|
||||
let offset = 0
|
||||
while (
|
||||
currentInput.charCodeAt(end + offset) !== CharCodes.Gt &&
|
||||
end + offset < currentInput.length
|
||||
) {
|
||||
offset++
|
||||
if (tokenizer.mode === ParseMode.SFC && stack.length === 0) {
|
||||
// SFC root tag, end position should be inner end
|
||||
if (el.children.length) {
|
||||
el.loc.end = extend({}, el.children[el.children.length - 1].loc.end)
|
||||
} else {
|
||||
el.loc.end = extend({}, el.loc.start)
|
||||
}
|
||||
} else {
|
||||
el.loc.end = tokenizer.getPos(end + fastForward(end, CharCodes.Gt) + 1)
|
||||
}
|
||||
el.loc.end = tokenizer.getPos(end + offset + 1)
|
||||
|
||||
// refine element type
|
||||
const { tag, ns } = el
|
||||
|
@ -491,6 +506,17 @@ function onCloseTag(el: ElementNode, end: number) {
|
|||
}
|
||||
}
|
||||
|
||||
function fastForward(start: number, c: number) {
|
||||
let offset = 0
|
||||
while (
|
||||
currentInput.charCodeAt(start + offset) !== CharCodes.Gt &&
|
||||
start + offset < currentInput.length
|
||||
) {
|
||||
offset++
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
const specialTemplateDir = new Set(['if', 'else', 'else-if', 'for', 'slot'])
|
||||
function isFragmentTemplate({ tag, props }: ElementNode): boolean {
|
||||
if (tag === 'template') {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { parse } from '../src'
|
||||
import { baseParse, baseCompile } from '@vue/compiler-core'
|
||||
import { baseCompile, createRoot } from '@vue/compiler-core'
|
||||
import { SourceMapConsumer } from 'source-map-js'
|
||||
|
||||
describe('compiler:sfc', () => {
|
||||
|
@ -122,8 +122,7 @@ h1 { color: red }
|
|||
line: 3,
|
||||
column: 1,
|
||||
offset: 10 + content.length
|
||||
},
|
||||
source: content
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -132,9 +131,8 @@ h1 { color: red }
|
|||
expect(descriptor.template).toBeTruthy()
|
||||
expect(descriptor.template!.content).toBeFalsy()
|
||||
expect(descriptor.template!.loc).toMatchObject({
|
||||
start: { line: 1, column: 1, offset: 0 },
|
||||
end: { line: 1, column: 1, offset: 0 },
|
||||
source: ''
|
||||
start: { line: 1, column: 12, offset: 11 },
|
||||
end: { line: 1, column: 12, offset: 11 }
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -144,8 +142,7 @@ h1 { color: red }
|
|||
expect(descriptor.template!.content).toBeFalsy()
|
||||
expect(descriptor.template!.loc).toMatchObject({
|
||||
start: { line: 1, column: 11, offset: 10 },
|
||||
end: { line: 1, column: 11, offset: 10 },
|
||||
source: ''
|
||||
end: { line: 1, column: 11, offset: 10 }
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -176,14 +173,12 @@ h1 { color: red }
|
|||
)
|
||||
expect(descriptor.script).toBeTruthy()
|
||||
expect(descriptor.script!.loc).toMatchObject({
|
||||
source: '',
|
||||
start: { line: 1, column: 9, offset: 8 },
|
||||
end: { line: 1, column: 9, offset: 8 }
|
||||
})
|
||||
|
||||
expect(descriptor.scriptSetup).toBeTruthy()
|
||||
expect(descriptor.scriptSetup!.loc).toMatchObject({
|
||||
source: '\n',
|
||||
start: { line: 2, column: 15, offset: 32 },
|
||||
end: { line: 3, column: 1, offset: 33 }
|
||||
})
|
||||
|
@ -260,11 +255,18 @@ h1 { color: red }
|
|||
test('custom compiler', () => {
|
||||
const { errors } = parse(`<template><input></template>`, {
|
||||
compiler: {
|
||||
parse: baseParse,
|
||||
parse: (_, options) => {
|
||||
options.onError!(new Error('foo') as any)
|
||||
return createRoot([])
|
||||
},
|
||||
compile: baseCompile
|
||||
}
|
||||
})
|
||||
expect(errors.length).toBe(1)
|
||||
expect(errors.length).toBe(2)
|
||||
// error thrown by the custom parse
|
||||
expect(errors[0].message).toBe('foo')
|
||||
// error thrown based on the returned root
|
||||
expect(errors[1].message).toMatch('At least one')
|
||||
})
|
||||
|
||||
test('treat custom blocks as raw text', () => {
|
||||
|
|
|
@ -136,7 +136,8 @@ export function parse(
|
|||
if (node.type !== NodeTypes.ELEMENT) {
|
||||
return
|
||||
}
|
||||
// we only want to keep the nodes that are not empty (when the tag is not a template)
|
||||
// we only want to keep the nodes that are not empty
|
||||
// (when the tag is not a template)
|
||||
if (
|
||||
ignoreEmpty &&
|
||||
node.tag !== 'template' &&
|
||||
|
@ -284,33 +285,11 @@ function createBlock(
|
|||
pad: SFCParseOptions['pad']
|
||||
): SFCBlock {
|
||||
const type = node.tag
|
||||
let { start, end } = node.loc
|
||||
let content = ''
|
||||
if (node.children.length) {
|
||||
start = node.children[0].loc.start
|
||||
end = node.children[node.children.length - 1].loc.end
|
||||
content = source.slice(start.offset, end.offset)
|
||||
} else {
|
||||
const offset = source.indexOf(`</`, start.offset)
|
||||
if (offset > -1) {
|
||||
start = {
|
||||
line: start.line,
|
||||
column: start.column + offset,
|
||||
offset: start.offset + offset
|
||||
}
|
||||
}
|
||||
end = { ...start }
|
||||
}
|
||||
const loc = {
|
||||
source: content,
|
||||
start,
|
||||
end
|
||||
}
|
||||
const attrs: Record<string, string | true> = {}
|
||||
const block: SFCBlock = {
|
||||
type,
|
||||
content,
|
||||
loc,
|
||||
content: source.slice(node.loc.start.offset, node.loc.end.offset),
|
||||
loc: node.loc,
|
||||
attrs
|
||||
}
|
||||
if (pad) {
|
||||
|
|
Loading…
Reference in New Issue