feat(compiler): implement support for v-pre
This commit is contained in:
parent
08df965e3c
commit
5dfb271551
@ -1278,6 +1278,88 @@ describe('compiler: parse', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('v-pre', () => {
|
||||
const ast = parse(
|
||||
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
|
||||
`<div :id="foo"><Comp/>{{ bar }}</div>`
|
||||
)
|
||||
|
||||
const divWithPre = ast.children[0] as ElementNode
|
||||
expect(divWithPre.props).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.ATTRIBUTE,
|
||||
name: `:id`,
|
||||
value: {
|
||||
type: NodeTypes.TEXT,
|
||||
content: `foo`
|
||||
},
|
||||
loc: {
|
||||
source: `:id="foo"`,
|
||||
start: {
|
||||
line: 1,
|
||||
column: 12
|
||||
},
|
||||
end: {
|
||||
line: 1,
|
||||
column: 21
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
expect(divWithPre.children[0]).toMatchObject({
|
||||
type: NodeTypes.ELEMENT,
|
||||
tagType: ElementTypes.ELEMENT,
|
||||
tag: `Comp`
|
||||
})
|
||||
expect(divWithPre.children[1]).toMatchObject({
|
||||
type: NodeTypes.TEXT,
|
||||
content: `{{ bar }}`
|
||||
})
|
||||
|
||||
// should not affect siblings after it
|
||||
const divWithoutPre = ast.children[1] as ElementNode
|
||||
expect(divWithoutPre.props).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.DIRECTIVE,
|
||||
name: `bind`,
|
||||
arg: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
isStatic: true,
|
||||
content: `id`
|
||||
},
|
||||
exp: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
isStatic: false,
|
||||
content: `foo`
|
||||
},
|
||||
loc: {
|
||||
source: `:id="foo"`,
|
||||
start: {
|
||||
line: 2,
|
||||
column: 6
|
||||
},
|
||||
end: {
|
||||
line: 2,
|
||||
column: 15
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
expect(divWithoutPre.children[0]).toMatchObject({
|
||||
type: NodeTypes.ELEMENT,
|
||||
tagType: ElementTypes.COMPONENT,
|
||||
tag: `Comp`
|
||||
})
|
||||
expect(divWithoutPre.children[1]).toMatchObject({
|
||||
type: NodeTypes.INTERPOLATION,
|
||||
content: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `bar`,
|
||||
isStatic: false
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('end tags are case-insensitive.', () => {
|
||||
const ast = parse('<div>hello</DIV>after')
|
||||
const element = ast.children[0] as ElementNode
|
||||
|
@ -26,6 +26,7 @@ import {
|
||||
TemplateChildNode,
|
||||
InterpolationNode
|
||||
} from './ast'
|
||||
import { extend } from '@vue/shared'
|
||||
|
||||
export interface ParserOptions {
|
||||
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
|
||||
@ -74,6 +75,7 @@ interface ParserContext {
|
||||
line: number
|
||||
column: number
|
||||
maxCRNameLength: number
|
||||
inPre: boolean
|
||||
}
|
||||
|
||||
export function parse(content: string, options: ParserOptions = {}): RootNode {
|
||||
@ -109,7 +111,8 @@ function createParserContext(
|
||||
maxCRNameLength: Object.keys(
|
||||
options.namedCharacterReferences ||
|
||||
defaultParserOptions.namedCharacterReferences
|
||||
).reduce((max, name) => Math.max(max, name.length), 0)
|
||||
).reduce((max, name) => Math.max(max, name.length), 0),
|
||||
inPre: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +130,7 @@ function parseChildren(
|
||||
const s = context.source
|
||||
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
|
||||
|
||||
if (startsWith(s, context.options.delimiters[0])) {
|
||||
if (!context.inPre && startsWith(s, context.options.delimiters[0])) {
|
||||
// '{{'
|
||||
node = parseInterpolation(context, mode)
|
||||
} else if (mode === TextModes.DATA && s[0] === '<') {
|
||||
@ -325,8 +328,10 @@ function parseElement(
|
||||
__DEV__ && assert(/^<[a-z]/i.test(context.source))
|
||||
|
||||
// Start tag.
|
||||
const wasInPre = context.inPre
|
||||
const parent = last(ancestors)
|
||||
const element = parseTag(context, TagType.Start, parent)
|
||||
const isPreBoundary = context.inPre && !wasInPre
|
||||
|
||||
if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
|
||||
return element
|
||||
@ -354,6 +359,10 @@ function parseElement(
|
||||
}
|
||||
|
||||
element.loc = getSelection(context, element.loc.start)
|
||||
|
||||
if (isPreBoundary) {
|
||||
context.inPre = false
|
||||
}
|
||||
return element
|
||||
}
|
||||
|
||||
@ -380,18 +389,68 @@ function parseTag(
|
||||
const start = getCursor(context)
|
||||
const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
|
||||
const tag = match[1]
|
||||
const props = []
|
||||
const ns = context.options.getNamespace(tag, parent)
|
||||
|
||||
let tagType = ElementTypes.ELEMENT
|
||||
if (tag === 'slot') tagType = ElementTypes.SLOT
|
||||
else if (tag === 'template') tagType = ElementTypes.TEMPLATE
|
||||
else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
|
||||
|
||||
advanceBy(context, match[0].length)
|
||||
advanceSpaces(context)
|
||||
|
||||
// save current state in case we need to re-parse attributes with v-pre
|
||||
const cursor = getCursor(context)
|
||||
const currentSource = context.source
|
||||
|
||||
// Attributes.
|
||||
let props = parseAttributes(context, type)
|
||||
|
||||
// check v-pre
|
||||
if (
|
||||
!context.inPre &&
|
||||
props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
|
||||
) {
|
||||
context.inPre = true
|
||||
// reset context
|
||||
extend(context, cursor)
|
||||
context.source = currentSource
|
||||
// re-parse attrs and filter out v-pre itself
|
||||
props = parseAttributes(context, type).filter(p => p.name !== 'v-pre')
|
||||
}
|
||||
|
||||
// Tag close.
|
||||
let isSelfClosing = false
|
||||
if (context.source.length === 0) {
|
||||
emitError(context, ErrorCodes.EOF_IN_TAG)
|
||||
} else {
|
||||
isSelfClosing = startsWith(context.source, '/>')
|
||||
if (type === TagType.End && isSelfClosing) {
|
||||
emitError(context, ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS)
|
||||
}
|
||||
advanceBy(context, isSelfClosing ? 2 : 1)
|
||||
}
|
||||
|
||||
let tagType = ElementTypes.ELEMENT
|
||||
if (!context.inPre) {
|
||||
if (tag === 'slot') tagType = ElementTypes.SLOT
|
||||
else if (tag === 'template') tagType = ElementTypes.TEMPLATE
|
||||
else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
|
||||
}
|
||||
|
||||
return {
|
||||
type: NodeTypes.ELEMENT,
|
||||
ns,
|
||||
tag,
|
||||
tagType,
|
||||
props,
|
||||
isSelfClosing,
|
||||
children: [],
|
||||
loc: getSelection(context, start),
|
||||
codegenNode: undefined // to be created during transform phase
|
||||
}
|
||||
}
|
||||
|
||||
function parseAttributes(
|
||||
context: ParserContext,
|
||||
type: TagType
|
||||
): (AttributeNode | DirectiveNode)[] {
|
||||
const props = []
|
||||
const attributeNames = new Set<string>()
|
||||
while (
|
||||
context.source.length > 0 &&
|
||||
@ -418,30 +477,7 @@ function parseTag(
|
||||
}
|
||||
advanceSpaces(context)
|
||||
}
|
||||
|
||||
// Tag close.
|
||||
let isSelfClosing = false
|
||||
if (context.source.length === 0) {
|
||||
emitError(context, ErrorCodes.EOF_IN_TAG)
|
||||
} else {
|
||||
isSelfClosing = startsWith(context.source, '/>')
|
||||
if (type === TagType.End && isSelfClosing) {
|
||||
emitError(context, ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS)
|
||||
}
|
||||
advanceBy(context, isSelfClosing ? 2 : 1)
|
||||
}
|
||||
|
||||
return {
|
||||
type: NodeTypes.ELEMENT,
|
||||
ns,
|
||||
tag,
|
||||
tagType,
|
||||
props,
|
||||
isSelfClosing,
|
||||
children: [],
|
||||
loc: getSelection(context, start),
|
||||
codegenNode: undefined // to be created during transform phase
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
function parseAttribute(
|
||||
@ -497,7 +533,7 @@ function parseAttribute(
|
||||
}
|
||||
const loc = getSelection(context, start)
|
||||
|
||||
if (/^(v-|:|@|#)/.test(name)) {
|
||||
if (!context.inPre && /^(v-|:|@|#)/.test(name)) {
|
||||
const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)([^\.]+))?(.+)?$/i.exec(
|
||||
name
|
||||
)!
|
||||
|
Loading…
Reference in New Issue
Block a user