wip: v-pre handling

This commit is contained in:
Evan You 2023-11-16 01:31:52 +08:00
parent 1a1f680536
commit c342433cb5
3 changed files with 122 additions and 49 deletions

View File

@ -191,6 +191,7 @@ export interface DirectiveNode extends Node {
exp: ExpressionNode | undefined
arg: ExpressionNode | undefined
modifiers: string[]
raw?: string
/**
* optional property to cache the expression parse result for v-for
*/

View File

@ -282,6 +282,7 @@ export default class Tokenizer {
this.cbs.oninterpolation(this.sectionStart, this.index)
this.state = State.Text
this.sectionStart = this.index
this.stateText(this.buffer.charCodeAt(this.index))
}
}

View File

@ -8,6 +8,7 @@ import {
Namespaces,
NodeTypes,
RootNode,
SimpleExpressionNode,
SourceLocation,
TemplateChildNode,
createRoot
@ -67,7 +68,8 @@ let currentAttrStartIndex = -1
let currentAttrEndIndex = -1
let currentAttrs: Set<string> = new Set()
let inPre = 0
// let inVPre = 0
let inVPre = false
let currentElementIsVPreBoundary = false
const stack: ElementNode[] = []
const tokenizer = new Tokenizer({
@ -80,6 +82,9 @@ const tokenizer = new Tokenizer({
},
oninterpolation(start, end) {
if (inVPre) {
return onText(getSlice(start, end), start, end)
}
let innerStart = start + tokenizer.delimiterOpen.length
let innerEnd = end - tokenizer.delimiterClose.length
while (isWhitespace(currentInput.charCodeAt(innerStart))) {
@ -103,7 +108,24 @@ const tokenizer = new Tokenizer({
},
onopentagname(start, end) {
emitOpenTag(getSlice(start, end), start)
const name = getSlice(start, end)
currentElement = {
type: NodeTypes.ELEMENT,
tag: name,
ns: currentOptions.getNamespace(name, getParent()),
// TODO refine tag type
tagType: ElementTypes.ELEMENT,
props: [],
children: [],
loc: {
start: tokenizer.getPos(start - 1),
// @ts-expect-error to be attached on tag close
end: undefined,
source: ''
},
codegenNode: undefined
}
currentAttrs.clear()
},
onopentagend(end) {
@ -138,40 +160,72 @@ const tokenizer = new Tokenizer({
ondirname(start, end) {
const raw = getSlice(start, end)
const name =
raw === '.' || raw === ':'
? 'bind'
: raw === '@'
? 'on'
: raw === '#'
? 'slot'
: raw.slice(2)
currentProp = {
type: NodeTypes.DIRECTIVE,
name,
exp: undefined,
arg: undefined,
modifiers: [],
loc: getLoc(start)
if (inVPre) {
currentProp = {
type: NodeTypes.ATTRIBUTE,
name: raw,
value: undefined,
loc: getLoc(start)
}
} else {
const name =
raw === '.' || raw === ':'
? 'bind'
: raw === '@'
? 'on'
: raw === '#'
? 'slot'
: raw.slice(2)
currentProp = {
type: NodeTypes.DIRECTIVE,
name,
raw,
exp: undefined,
arg: undefined,
modifiers: [],
loc: getLoc(start)
}
if (name === 'pre') {
inVPre = true
currentElementIsVPreBoundary = true
// force current element type
currentElement!.tagType = ElementTypes.ELEMENT
// convert dirs before this one to attributes
const props = currentElement!.props
for (let i = 0; i < props.length; i++) {
if (props[i].type === NodeTypes.DIRECTIVE) {
props[i] = dirToAttr(props[i] as DirectiveNode)
}
}
}
}
},
ondirarg(start, end) {
const arg = getSlice(start, end)
const isStatic = arg[0] !== `[`
;(currentProp as DirectiveNode).arg = {
type: NodeTypes.SIMPLE_EXPRESSION,
content: arg,
isStatic,
constType: isStatic
? ConstantTypes.CAN_STRINGIFY
: ConstantTypes.NOT_CONSTANT,
loc: getLoc(start, end)
if (inVPre) {
;(currentProp as AttributeNode).name += arg
} else {
const isStatic = arg[0] !== `[`
;(currentProp as DirectiveNode).arg = {
type: NodeTypes.SIMPLE_EXPRESSION,
content: arg,
isStatic,
constType: isStatic
? ConstantTypes.CAN_STRINGIFY
: ConstantTypes.NOT_CONSTANT,
loc: getLoc(start, end)
}
}
},
ondirmodifier(start, end) {
;(currentProp as DirectiveNode).modifiers.push(getSlice(start, end))
const mod = getSlice(start, end)
if (inVPre) {
;(currentProp as AttributeNode).name += '.' + mod
} else {
;(currentProp as DirectiveNode).modifiers.push(mod)
}
},
onattribdata(start, end) {
@ -188,6 +242,9 @@ const tokenizer = new Tokenizer({
// check duplicate attrs
const start = currentProp!.loc.start.offset
const name = getSlice(start, end)
if (currentProp!.type === NodeTypes.DIRECTIVE) {
currentProp!.raw = name
}
if (currentAttrs.has(name)) {
currentProp = null
// TODO emit error DUPLICATE_ATTRIBUTE
@ -225,7 +282,12 @@ const tokenizer = new Tokenizer({
}
}
currentProp.loc.end = tokenizer.getPos(end)
currentElement.props.push(currentProp!)
if (
currentProp.type !== NodeTypes.DIRECTIVE ||
currentProp.name !== 'pre'
) {
currentElement.props.push(currentProp)
}
}
currentAttrValue = ''
currentAttrStartIndex = currentAttrEndIndex = -1
@ -251,26 +313,6 @@ function getSlice(start: number, end: number) {
return currentInput.slice(start, end)
}
function emitOpenTag(name: string, start: number) {
currentElement = {
type: NodeTypes.ELEMENT,
tag: name,
ns: currentOptions.getNamespace(name, getParent()),
// TODO refine tag type
tagType: ElementTypes.ELEMENT,
props: [],
children: [],
loc: {
start: tokenizer.getPos(start - 1),
// @ts-expect-error to be attached on tag close
end: undefined,
source: ''
},
codegenNode: undefined
}
currentAttrs.clear()
}
function endOpenTag(end: number) {
addNode(currentElement!)
const name = currentElement!.tag
@ -299,7 +341,7 @@ function onText(content: string, start: number, end: number) {
if (lastNode?.type === NodeTypes.TEXT) {
// merge
lastNode.content += content
// TODO update loc
lastNode.loc.end = tokenizer.getPos(end)
} else {
parent.children.push({
type: NodeTypes.TEXT,
@ -325,6 +367,10 @@ function onCloseTag(el: ElementNode, end: number) {
if (currentOptions.isPreTag(el.tag)) {
inPre--
}
if (currentElementIsVPreBoundary) {
inVPre = false
currentElementIsVPreBoundary = false
}
}
const windowsNewlineRE = /\r\n/g
@ -429,6 +475,31 @@ function getLoc(start: number, end?: number): SourceLocation {
}
}
function dirToAttr(dir: DirectiveNode): AttributeNode {
const attr: AttributeNode = {
type: NodeTypes.ATTRIBUTE,
name: dir.raw!,
value: undefined,
loc: dir.loc
}
if (dir.exp) {
// account for quotes
const loc = dir.exp.loc
if (loc.end.offset < dir.loc.end.offset) {
loc.start.offset--
loc.start.column--
loc.end.offset++
loc.end.column++
}
attr.value = {
type: NodeTypes.TEXT,
content: (dir.exp as SimpleExpressionNode).content,
loc
}
}
return attr
}
function reset() {
tokenizer.reset()
currentElement = null