2023-11-13 21:03:39 +08:00
|
|
|
import {
|
2023-11-14 18:03:00 +08:00
|
|
|
AttributeNode,
|
2023-11-14 20:39:08 +08:00
|
|
|
ConstantTypes,
|
2023-11-14 18:03:00 +08:00
|
|
|
DirectiveNode,
|
2023-11-13 21:03:39 +08:00
|
|
|
ElementNode,
|
|
|
|
ElementTypes,
|
2023-11-17 14:17:30 +08:00
|
|
|
ForParseResult,
|
2023-11-14 20:39:08 +08:00
|
|
|
Namespaces,
|
2023-11-13 21:03:39 +08:00
|
|
|
NodeTypes,
|
|
|
|
RootNode,
|
2023-11-16 01:31:52 +08:00
|
|
|
SimpleExpressionNode,
|
2023-11-15 17:45:42 +08:00
|
|
|
SourceLocation,
|
2023-11-13 21:03:39 +08:00
|
|
|
TemplateChildNode,
|
2023-11-17 14:17:30 +08:00
|
|
|
createRoot,
|
|
|
|
createSimpleExpression
|
2023-11-24 21:53:35 +08:00
|
|
|
} from './ast'
|
|
|
|
import { ParserOptions } from './options'
|
2023-11-17 09:22:12 +08:00
|
|
|
import Tokenizer, {
|
|
|
|
CharCodes,
|
|
|
|
ParseMode,
|
|
|
|
QuoteType,
|
2023-11-22 13:58:50 +08:00
|
|
|
Sequences,
|
|
|
|
State,
|
2023-11-17 09:22:12 +08:00
|
|
|
isWhitespace,
|
|
|
|
toCharCodes
|
2023-11-24 21:53:35 +08:00
|
|
|
} from './tokenizer'
|
2023-11-22 15:19:06 +08:00
|
|
|
import {
|
|
|
|
CompilerCompatOptions,
|
|
|
|
CompilerDeprecationTypes,
|
|
|
|
checkCompatEnabled,
|
|
|
|
isCompatEnabled,
|
|
|
|
warnDeprecation
|
2023-11-24 21:53:35 +08:00
|
|
|
} from './compat/compatConfig'
|
2023-11-14 21:55:16 +08:00
|
|
|
import { NO, extend } from '@vue/shared'
|
2023-11-20 17:38:00 +08:00
|
|
|
import {
|
|
|
|
ErrorCodes,
|
|
|
|
createCompilerError,
|
|
|
|
defaultOnError,
|
|
|
|
defaultOnWarn
|
2023-11-24 21:53:35 +08:00
|
|
|
} from './errors'
|
|
|
|
import { forAliasRE, isCoreComponent, isStaticArgOf } from './utils'
|
2023-11-19 10:39:11 +08:00
|
|
|
import { decodeHTML } from 'entities/lib/decode.js'
|
2023-11-13 21:03:39 +08:00
|
|
|
|
2023-11-14 20:39:08 +08:00
|
|
|
type OptionalOptions =
|
2023-11-18 21:39:31 +08:00
|
|
|
| 'decodeEntities'
|
2023-11-14 20:39:08 +08:00
|
|
|
| 'whitespace'
|
|
|
|
| 'isNativeTag'
|
|
|
|
| 'isBuiltInComponent'
|
|
|
|
| keyof CompilerCompatOptions
|
|
|
|
|
2023-11-22 15:19:06 +08:00
|
|
|
export type MergedParserOptions = Omit<
|
|
|
|
Required<ParserOptions>,
|
|
|
|
OptionalOptions
|
|
|
|
> &
|
2023-11-14 20:39:08 +08:00
|
|
|
Pick<ParserOptions, OptionalOptions>
|
|
|
|
|
|
|
|
export const defaultParserOptions: MergedParserOptions = {
|
2023-11-17 09:22:12 +08:00
|
|
|
parseMode: 'base',
|
2023-11-19 11:20:05 +08:00
|
|
|
ns: Namespaces.HTML,
|
2023-11-14 20:39:08 +08:00
|
|
|
delimiters: [`{{`, `}}`],
|
|
|
|
getNamespace: () => Namespaces.HTML,
|
|
|
|
isVoidTag: NO,
|
|
|
|
isPreTag: NO,
|
|
|
|
isCustomElement: NO,
|
|
|
|
onError: defaultOnError,
|
|
|
|
onWarn: defaultOnWarn,
|
|
|
|
comments: __DEV__
|
|
|
|
}
|
|
|
|
|
|
|
|
let currentOptions: MergedParserOptions = defaultParserOptions
|
2023-11-17 14:17:30 +08:00
|
|
|
let currentRoot: RootNode | null = null
|
2023-11-13 21:03:39 +08:00
|
|
|
|
|
|
|
// parser state
|
|
|
|
let currentInput = ''
|
2023-11-22 15:19:06 +08:00
|
|
|
let currentOpenTag: ElementNode | null = null
|
2023-11-14 18:03:00 +08:00
|
|
|
let currentProp: AttributeNode | DirectiveNode | null = null
|
|
|
|
let currentAttrValue = ''
|
2023-11-15 17:45:42 +08:00
|
|
|
let currentAttrStartIndex = -1
|
|
|
|
let currentAttrEndIndex = -1
|
2023-11-13 21:03:39 +08:00
|
|
|
let inPre = 0
|
2023-11-16 01:31:52 +08:00
|
|
|
let inVPre = false
|
2023-11-17 17:46:47 +08:00
|
|
|
let currentVPreBoundary: ElementNode | null = null
|
2023-11-14 18:03:00 +08:00
|
|
|
const stack: ElementNode[] = []
|
2023-11-13 21:03:39 +08:00
|
|
|
|
2023-11-17 09:22:12 +08:00
|
|
|
const tokenizer = new Tokenizer(stack, {
|
2023-11-22 13:58:50 +08:00
|
|
|
onerr: emitError,
|
|
|
|
|
2023-11-15 23:33:57 +08:00
|
|
|
ontext(start, end) {
|
|
|
|
onText(getSlice(start, end), start, end)
|
|
|
|
},
|
2023-11-13 21:03:39 +08:00
|
|
|
|
2023-11-19 10:39:11 +08:00
|
|
|
ontextentity(char, start, end) {
|
|
|
|
onText(char, start, end)
|
2023-11-15 23:33:57 +08:00
|
|
|
},
|
2023-11-15 19:36:05 +08:00
|
|
|
|
2023-11-15 23:33:57 +08:00
|
|
|
oninterpolation(start, end) {
|
2023-11-16 01:31:52 +08:00
|
|
|
if (inVPre) {
|
|
|
|
return onText(getSlice(start, end), start, end)
|
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
let innerStart = start + tokenizer.delimiterOpen.length
|
|
|
|
let innerEnd = end - tokenizer.delimiterClose.length
|
|
|
|
while (isWhitespace(currentInput.charCodeAt(innerStart))) {
|
|
|
|
innerStart++
|
|
|
|
}
|
|
|
|
while (isWhitespace(currentInput.charCodeAt(innerEnd - 1))) {
|
|
|
|
innerEnd--
|
|
|
|
}
|
2023-11-19 10:39:11 +08:00
|
|
|
let exp = getSlice(innerStart, innerEnd)
|
|
|
|
// decode entities for backwards compat
|
|
|
|
if (exp.includes('&')) {
|
|
|
|
if (__BROWSER__) {
|
|
|
|
exp = currentOptions.decodeEntities!(exp, false)
|
|
|
|
} else {
|
|
|
|
exp = decodeHTML(exp)
|
|
|
|
}
|
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
addNode({
|
|
|
|
type: NodeTypes.INTERPOLATION,
|
2023-11-19 10:39:11 +08:00
|
|
|
content: createSimpleExpression(exp, false, getLoc(innerStart, innerEnd)),
|
2023-11-15 23:33:57 +08:00
|
|
|
loc: getLoc(start, end)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
onopentagname(start, end) {
|
2023-11-16 01:31:52 +08:00
|
|
|
const name = getSlice(start, end)
|
2023-11-22 15:19:06 +08:00
|
|
|
currentOpenTag = {
|
2023-11-16 01:31:52 +08:00
|
|
|
type: NodeTypes.ELEMENT,
|
|
|
|
tag: name,
|
2023-11-19 11:20:05 +08:00
|
|
|
ns: currentOptions.getNamespace(name, stack[0], currentOptions.ns),
|
2023-11-16 10:54:54 +08:00
|
|
|
tagType: ElementTypes.ELEMENT, // will be refined on tag close
|
2023-11-16 01:31:52 +08:00
|
|
|
props: [],
|
|
|
|
children: [],
|
2023-11-25 18:07:17 +08:00
|
|
|
loc: getLoc(start - 1, end),
|
2023-11-16 01:31:52 +08:00
|
|
|
codegenNode: undefined
|
|
|
|
}
|
2023-11-25 18:07:17 +08:00
|
|
|
if (tokenizer.inSFCRoot) {
|
|
|
|
// in SFC mode, generate locations for root-level tags' inner content.
|
|
|
|
currentOpenTag.innerLoc = getLoc(
|
|
|
|
end + fastForward(end, CharCodes.Gt) + 1,
|
|
|
|
end
|
|
|
|
)
|
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
onopentagend(end) {
|
|
|
|
endOpenTag(end)
|
|
|
|
},
|
|
|
|
|
|
|
|
onclosetag(start, end) {
|
|
|
|
const name = getSlice(start, end)
|
|
|
|
if (!currentOptions.isVoidTag(name)) {
|
2023-11-22 13:58:50 +08:00
|
|
|
let found = false
|
2023-11-17 17:46:47 +08:00
|
|
|
for (let i = 0; i < stack.length; i++) {
|
|
|
|
const e = stack[i]
|
|
|
|
if (e.tag.toLowerCase() === name.toLowerCase()) {
|
2023-11-22 13:58:50 +08:00
|
|
|
found = true
|
|
|
|
if (i > 0) {
|
|
|
|
emitError(ErrorCodes.X_MISSING_END_TAG, stack[0].loc.start.offset)
|
|
|
|
}
|
2023-11-17 17:46:47 +08:00
|
|
|
for (let j = 0; j <= i; j++) {
|
2023-11-22 13:58:50 +08:00
|
|
|
const el = stack.shift()!
|
|
|
|
onCloseTag(el, end, j < i)
|
2023-11-17 17:46:47 +08:00
|
|
|
}
|
|
|
|
break
|
2023-11-15 23:33:57 +08:00
|
|
|
}
|
2023-11-15 19:36:05 +08:00
|
|
|
}
|
2023-11-22 13:58:50 +08:00
|
|
|
if (!found) {
|
|
|
|
emitError(ErrorCodes.X_INVALID_END_TAG, backTrack(start, CharCodes.Lt))
|
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onselfclosingtag(end) {
|
2023-11-22 15:19:06 +08:00
|
|
|
const name = currentOpenTag!.tag
|
2023-11-25 22:55:39 +08:00
|
|
|
currentOpenTag!.isSelfClosing = true
|
2023-11-20 17:38:00 +08:00
|
|
|
endOpenTag(end)
|
|
|
|
if (stack[0]?.tag === name) {
|
|
|
|
onCloseTag(stack.shift()!, end)
|
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
onattribname(start, end) {
|
|
|
|
// plain attribute
|
|
|
|
currentProp = {
|
|
|
|
type: NodeTypes.ATTRIBUTE,
|
|
|
|
name: getSlice(start, end),
|
2023-11-17 14:17:30 +08:00
|
|
|
nameLoc: getLoc(start, end),
|
2023-11-15 23:33:57 +08:00
|
|
|
value: undefined,
|
|
|
|
loc: getLoc(start)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
ondirname(start, end) {
|
|
|
|
const raw = getSlice(start, end)
|
2023-11-22 13:58:50 +08:00
|
|
|
const name =
|
|
|
|
raw === '.' || raw === ':'
|
|
|
|
? 'bind'
|
|
|
|
: raw === '@'
|
|
|
|
? 'on'
|
|
|
|
: raw === '#'
|
|
|
|
? 'slot'
|
|
|
|
: raw.slice(2)
|
|
|
|
|
|
|
|
if (!inVPre && name === '') {
|
|
|
|
emitError(ErrorCodes.X_MISSING_DIRECTIVE_NAME, start)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inVPre || name === '') {
|
2023-11-16 01:31:52 +08:00
|
|
|
currentProp = {
|
|
|
|
type: NodeTypes.ATTRIBUTE,
|
|
|
|
name: raw,
|
2023-11-17 14:17:30 +08:00
|
|
|
nameLoc: getLoc(start, end),
|
2023-11-16 01:31:52 +08:00
|
|
|
value: undefined,
|
|
|
|
loc: getLoc(start)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
currentProp = {
|
|
|
|
type: NodeTypes.DIRECTIVE,
|
|
|
|
name,
|
2023-11-17 14:17:30 +08:00
|
|
|
rawName: raw,
|
2023-11-16 01:31:52 +08:00
|
|
|
exp: undefined,
|
|
|
|
arg: undefined,
|
2023-11-17 17:46:47 +08:00
|
|
|
modifiers: raw === '.' ? ['prop'] : [],
|
2023-11-16 01:31:52 +08:00
|
|
|
loc: getLoc(start)
|
|
|
|
}
|
|
|
|
if (name === 'pre') {
|
|
|
|
inVPre = true
|
2023-11-22 15:19:06 +08:00
|
|
|
currentVPreBoundary = currentOpenTag
|
2023-11-16 01:31:52 +08:00
|
|
|
// convert dirs before this one to attributes
|
2023-11-22 15:19:06 +08:00
|
|
|
const props = currentOpenTag!.props
|
2023-11-16 01:31:52 +08:00
|
|
|
for (let i = 0; i < props.length; i++) {
|
|
|
|
if (props[i].type === NodeTypes.DIRECTIVE) {
|
|
|
|
props[i] = dirToAttr(props[i] as DirectiveNode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
ondirarg(start, end) {
|
|
|
|
const arg = getSlice(start, end)
|
2023-11-16 01:31:52 +08:00
|
|
|
if (inVPre) {
|
|
|
|
;(currentProp as AttributeNode).name += arg
|
2023-11-25 19:22:39 +08:00
|
|
|
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
2023-11-16 01:31:52 +08:00
|
|
|
} else {
|
|
|
|
const isStatic = arg[0] !== `[`
|
2023-11-17 14:17:30 +08:00
|
|
|
;(currentProp as DirectiveNode).arg = createSimpleExpression(
|
2023-11-17 17:46:47 +08:00
|
|
|
isStatic ? arg : arg.slice(1, -1),
|
2023-11-16 01:31:52 +08:00
|
|
|
isStatic,
|
2023-11-17 14:17:30 +08:00
|
|
|
getLoc(start, end),
|
|
|
|
isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
|
|
|
|
)
|
2023-11-15 23:33:57 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
ondirmodifier(start, end) {
|
2023-11-16 01:31:52 +08:00
|
|
|
const mod = getSlice(start, end)
|
|
|
|
if (inVPre) {
|
|
|
|
;(currentProp as AttributeNode).name += '.' + mod
|
2023-11-25 19:22:39 +08:00
|
|
|
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
2023-11-17 17:46:47 +08:00
|
|
|
} else if ((currentProp as DirectiveNode).name === 'slot') {
|
|
|
|
// slot has no modifiers, special case for edge cases like
|
|
|
|
// https://github.com/vuejs/language-tools/issues/2710
|
|
|
|
const arg = (currentProp as DirectiveNode).arg
|
|
|
|
if (arg) {
|
|
|
|
;(arg as SimpleExpressionNode).content += '.' + mod
|
2023-11-25 19:22:39 +08:00
|
|
|
setLocEnd(arg.loc, end)
|
2023-11-17 17:46:47 +08:00
|
|
|
}
|
2023-11-16 01:31:52 +08:00
|
|
|
} else {
|
|
|
|
;(currentProp as DirectiveNode).modifiers.push(mod)
|
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
onattribdata(start, end) {
|
|
|
|
currentAttrValue += getSlice(start, end)
|
|
|
|
if (currentAttrStartIndex < 0) currentAttrStartIndex = start
|
|
|
|
currentAttrEndIndex = end
|
|
|
|
},
|
|
|
|
|
2023-11-19 10:39:11 +08:00
|
|
|
onattribentity(char, start, end) {
|
2023-11-18 21:39:31 +08:00
|
|
|
currentAttrValue += char
|
2023-11-19 10:39:11 +08:00
|
|
|
if (currentAttrStartIndex < 0) currentAttrStartIndex = start
|
|
|
|
currentAttrEndIndex = end
|
2023-11-15 23:33:57 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
onattribnameend(end) {
|
|
|
|
const start = currentProp!.loc.start.offset
|
|
|
|
const name = getSlice(start, end)
|
2023-11-16 01:31:52 +08:00
|
|
|
if (currentProp!.type === NodeTypes.DIRECTIVE) {
|
2023-11-17 14:17:30 +08:00
|
|
|
currentProp!.rawName = name
|
2023-11-16 01:31:52 +08:00
|
|
|
}
|
2023-11-18 12:01:55 +08:00
|
|
|
// check duplicate attrs
|
|
|
|
if (
|
2023-11-22 15:19:06 +08:00
|
|
|
currentOpenTag!.props.some(
|
2023-11-18 12:01:55 +08:00
|
|
|
p => (p.type === NodeTypes.DIRECTIVE ? p.rawName : p.name) === name
|
|
|
|
)
|
|
|
|
) {
|
2023-11-22 13:58:50 +08:00
|
|
|
emitError(ErrorCodes.DUPLICATE_ATTRIBUTE, start)
|
2023-11-15 23:33:57 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onattribend(quote, end) {
|
2023-11-22 15:19:06 +08:00
|
|
|
if (currentOpenTag && currentProp) {
|
|
|
|
// finalize end pos
|
2023-11-25 19:22:39 +08:00
|
|
|
setLocEnd(currentProp.loc, end)
|
2023-11-22 15:19:06 +08:00
|
|
|
|
2023-11-17 17:46:47 +08:00
|
|
|
if (quote !== QuoteType.NoValue) {
|
2023-11-18 21:39:31 +08:00
|
|
|
if (__BROWSER__ && currentAttrValue.includes('&')) {
|
|
|
|
currentAttrValue = currentOptions.decodeEntities!(
|
|
|
|
currentAttrValue,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
}
|
2023-11-22 15:19:06 +08:00
|
|
|
|
2023-11-15 23:33:57 +08:00
|
|
|
if (currentProp.type === NodeTypes.ATTRIBUTE) {
|
|
|
|
// assign value
|
2023-11-17 17:46:47 +08:00
|
|
|
|
|
|
|
// condense whitespaces in class
|
|
|
|
if (currentProp!.name === 'class') {
|
|
|
|
currentAttrValue = condense(currentAttrValue).trim()
|
|
|
|
}
|
|
|
|
|
2023-11-22 13:58:50 +08:00
|
|
|
if (quote === QuoteType.Unquoted && !currentAttrValue) {
|
|
|
|
emitError(ErrorCodes.MISSING_ATTRIBUTE_VALUE, end)
|
|
|
|
}
|
|
|
|
|
2023-11-15 23:33:57 +08:00
|
|
|
currentProp!.value = {
|
|
|
|
type: NodeTypes.TEXT,
|
|
|
|
content: currentAttrValue,
|
|
|
|
loc:
|
|
|
|
quote === QuoteType.Unquoted
|
|
|
|
? getLoc(currentAttrStartIndex, currentAttrEndIndex)
|
|
|
|
: getLoc(currentAttrStartIndex - 1, currentAttrEndIndex + 1)
|
|
|
|
}
|
2023-11-20 22:49:16 +08:00
|
|
|
if (
|
|
|
|
tokenizer.inSFCRoot &&
|
2023-11-22 15:19:06 +08:00
|
|
|
currentOpenTag.tag === 'template' &&
|
2023-11-21 22:27:35 +08:00
|
|
|
currentProp.name === 'lang' &&
|
|
|
|
currentAttrValue &&
|
|
|
|
currentAttrValue !== 'html'
|
2023-11-20 22:49:16 +08:00
|
|
|
) {
|
|
|
|
// SFC root template with preprocessor lang, force tokenizer to
|
|
|
|
// RCDATA mode
|
|
|
|
tokenizer.enterRCDATA(toCharCodes(`</template`), 0)
|
|
|
|
}
|
2023-11-18 10:49:29 +08:00
|
|
|
} else {
|
2023-11-15 23:33:57 +08:00
|
|
|
// directive
|
2023-11-17 14:17:30 +08:00
|
|
|
currentProp.exp = createSimpleExpression(
|
|
|
|
currentAttrValue,
|
|
|
|
false,
|
|
|
|
getLoc(currentAttrStartIndex, currentAttrEndIndex)
|
|
|
|
)
|
|
|
|
if (currentProp.name === 'for') {
|
|
|
|
currentProp.forParseResult = parseForExpression(currentProp.exp)
|
2023-11-15 17:45:42 +08:00
|
|
|
}
|
2023-11-22 15:19:06 +08:00
|
|
|
// 2.x compat v-bind:foo.sync -> v-model:foo
|
|
|
|
let syncIndex = -1
|
|
|
|
if (
|
|
|
|
__COMPAT__ &&
|
|
|
|
currentProp.name === 'bind' &&
|
|
|
|
(syncIndex = currentProp.modifiers.indexOf('sync')) > -1 &&
|
|
|
|
checkCompatEnabled(
|
|
|
|
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
|
|
|
|
currentOptions,
|
|
|
|
currentProp.loc,
|
|
|
|
currentProp.rawName
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
currentProp.name = 'model'
|
|
|
|
currentProp.modifiers.splice(syncIndex, 1)
|
|
|
|
}
|
2023-11-15 17:45:42 +08:00
|
|
|
}
|
2023-11-13 21:03:39 +08:00
|
|
|
}
|
2023-11-16 01:31:52 +08:00
|
|
|
if (
|
|
|
|
currentProp.type !== NodeTypes.DIRECTIVE ||
|
|
|
|
currentProp.name !== 'pre'
|
|
|
|
) {
|
2023-11-22 15:19:06 +08:00
|
|
|
currentOpenTag.props.push(currentProp)
|
2023-11-16 01:31:52 +08:00
|
|
|
}
|
2023-11-13 21:03:39 +08:00
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
currentAttrValue = ''
|
|
|
|
currentAttrStartIndex = currentAttrEndIndex = -1
|
|
|
|
},
|
|
|
|
|
2023-11-16 11:05:31 +08:00
|
|
|
oncomment(start, end) {
|
|
|
|
if (currentOptions.comments) {
|
|
|
|
addNode({
|
|
|
|
type: NodeTypes.COMMENT,
|
|
|
|
content: getSlice(start, end),
|
|
|
|
loc: getLoc(start - 4, end + 3)
|
|
|
|
})
|
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
onend() {
|
2023-11-22 13:58:50 +08:00
|
|
|
const end = currentInput.length
|
|
|
|
// EOF ERRORS
|
|
|
|
if ((__DEV__ || !__BROWSER__) && tokenizer.state !== State.Text) {
|
|
|
|
switch (tokenizer.state) {
|
|
|
|
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:
|
2023-11-22 14:01:20 +08:00
|
|
|
case State.BeforeAttrName:
|
|
|
|
case State.InAttrName:
|
|
|
|
case State.InDirName:
|
|
|
|
case State.InDirArg:
|
|
|
|
case State.InDirDynamicArg:
|
|
|
|
case State.InDirModifier:
|
|
|
|
case State.AfterAttrName:
|
|
|
|
case State.BeforeAttrValue:
|
|
|
|
case State.InAttrValueDq: // "
|
|
|
|
case State.InAttrValueSq: // '
|
|
|
|
case State.InAttrValueNq:
|
2023-11-22 13:58:50 +08:00
|
|
|
emitError(ErrorCodes.EOF_IN_TAG, end)
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
// console.log(tokenizer.state)
|
|
|
|
break
|
|
|
|
}
|
2023-11-20 17:38:00 +08:00
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
for (let index = 0; index < stack.length; index++) {
|
2023-11-22 13:58:50 +08:00
|
|
|
onCloseTag(stack[index], end - 1)
|
|
|
|
emitError(ErrorCodes.X_MISSING_END_TAG, stack[index].loc.start.offset)
|
2023-11-15 23:33:57 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-11-16 11:05:31 +08:00
|
|
|
oncdata(start, end) {
|
2023-11-19 10:39:11 +08:00
|
|
|
if (stack[0].ns !== Namespaces.HTML) {
|
|
|
|
onText(getSlice(start, end), start, end)
|
|
|
|
} else {
|
2023-11-22 13:58:50 +08:00
|
|
|
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
|
|
|
|
)
|
2023-11-19 10:39:11 +08:00
|
|
|
}
|
2023-11-13 21:03:39 +08:00
|
|
|
}
|
2023-11-15 23:33:57 +08:00
|
|
|
})
|
2023-11-13 21:03:39 +08:00
|
|
|
|
2023-11-17 14:17:30 +08:00
|
|
|
// This regex doesn't cover the case if key or index aliases have destructuring,
|
|
|
|
// but those do not make sense in the first place, so this works in practice.
|
|
|
|
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
|
|
|
const stripParensRE = /^\(|\)$/g
|
|
|
|
|
|
|
|
function parseForExpression(
|
|
|
|
input: SimpleExpressionNode
|
|
|
|
): ForParseResult | undefined {
|
|
|
|
const loc = input.loc
|
|
|
|
const exp = input.content
|
|
|
|
const inMatch = exp.match(forAliasRE)
|
|
|
|
if (!inMatch) return
|
|
|
|
|
|
|
|
const [, LHS, RHS] = inMatch
|
|
|
|
|
|
|
|
const createAliasExpression = (content: string, offset: number) => {
|
|
|
|
const start = loc.start.offset + offset
|
|
|
|
const end = start + content.length
|
|
|
|
return createSimpleExpression(content, false, getLoc(start, end))
|
|
|
|
}
|
|
|
|
|
|
|
|
const result: ForParseResult = {
|
|
|
|
source: createAliasExpression(RHS.trim(), exp.indexOf(RHS, LHS.length)),
|
|
|
|
value: undefined,
|
|
|
|
key: undefined,
|
|
|
|
index: undefined,
|
|
|
|
finalized: false
|
|
|
|
}
|
|
|
|
|
|
|
|
let valueContent = LHS.trim().replace(stripParensRE, '').trim()
|
|
|
|
const trimmedOffset = LHS.indexOf(valueContent)
|
|
|
|
|
|
|
|
const iteratorMatch = valueContent.match(forIteratorRE)
|
|
|
|
if (iteratorMatch) {
|
|
|
|
valueContent = valueContent.replace(forIteratorRE, '').trim()
|
|
|
|
|
|
|
|
const keyContent = iteratorMatch[1].trim()
|
|
|
|
let keyOffset: number | undefined
|
|
|
|
if (keyContent) {
|
|
|
|
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
|
|
|
|
result.key = createAliasExpression(keyContent, keyOffset)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iteratorMatch[2]) {
|
|
|
|
const indexContent = iteratorMatch[2].trim()
|
|
|
|
|
|
|
|
if (indexContent) {
|
|
|
|
result.index = createAliasExpression(
|
|
|
|
indexContent,
|
|
|
|
exp.indexOf(
|
|
|
|
indexContent,
|
|
|
|
result.key
|
|
|
|
? keyOffset! + keyContent.length
|
|
|
|
: trimmedOffset + valueContent.length
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (valueContent) {
|
|
|
|
result.value = createAliasExpression(valueContent, trimmedOffset)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:03:39 +08:00
|
|
|
function getSlice(start: number, end: number) {
|
|
|
|
return currentInput.slice(start, end)
|
|
|
|
}
|
|
|
|
|
2023-11-14 20:39:08 +08:00
|
|
|
function endOpenTag(end: number) {
|
2023-11-22 15:19:06 +08:00
|
|
|
addNode(currentOpenTag!)
|
|
|
|
const { tag, ns } = currentOpenTag!
|
2023-11-19 11:46:44 +08:00
|
|
|
if (ns === Namespaces.HTML && currentOptions.isPreTag(tag)) {
|
2023-11-15 23:55:50 +08:00
|
|
|
inPre++
|
|
|
|
}
|
2023-11-19 11:46:44 +08:00
|
|
|
if (currentOptions.isVoidTag(tag)) {
|
2023-11-22 15:19:06 +08:00
|
|
|
onCloseTag(currentOpenTag!, end)
|
2023-11-15 23:55:50 +08:00
|
|
|
} else {
|
2023-11-22 15:19:06 +08:00
|
|
|
stack.unshift(currentOpenTag!)
|
2023-11-19 11:46:44 +08:00
|
|
|
if (ns === Namespaces.SVG || ns === Namespaces.MATH_ML) {
|
|
|
|
tokenizer.inXML = true
|
|
|
|
}
|
2023-11-13 21:03:39 +08:00
|
|
|
}
|
2023-11-22 15:19:06 +08:00
|
|
|
currentOpenTag = null
|
2023-11-13 21:03:39 +08:00
|
|
|
}
|
|
|
|
|
2023-11-14 01:14:33 +08:00
|
|
|
function onText(content: string, start: number, end: number) {
|
2023-11-22 13:58:50 +08:00
|
|
|
if (__BROWSER__) {
|
|
|
|
const tag = stack[0]?.tag
|
|
|
|
if (tag !== 'script' && tag !== 'style' && content.includes('&')) {
|
|
|
|
content = currentOptions.decodeEntities!(content, false)
|
|
|
|
}
|
2023-11-18 21:39:31 +08:00
|
|
|
}
|
2023-11-19 10:39:11 +08:00
|
|
|
const parent = stack[0] || currentRoot
|
2023-11-13 21:03:39 +08:00
|
|
|
const lastNode = parent.children[parent.children.length - 1]
|
|
|
|
if (lastNode?.type === NodeTypes.TEXT) {
|
|
|
|
// merge
|
|
|
|
lastNode.content += content
|
2023-11-25 19:22:39 +08:00
|
|
|
setLocEnd(lastNode.loc, end)
|
2023-11-13 21:03:39 +08:00
|
|
|
} else {
|
|
|
|
parent.children.push({
|
|
|
|
type: NodeTypes.TEXT,
|
|
|
|
content,
|
2023-11-17 14:17:30 +08:00
|
|
|
loc: getLoc(start, end)
|
2023-11-13 21:03:39 +08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-22 13:58:50 +08:00
|
|
|
function onCloseTag(el: ElementNode, end: number, isImplied = false) {
|
2023-11-14 18:03:00 +08:00
|
|
|
// attach end position
|
2023-11-25 18:07:17 +08:00
|
|
|
if (isImplied) {
|
2023-11-22 13:58:50 +08:00
|
|
|
// implied close, end should be backtracked to close
|
2023-11-25 19:22:39 +08:00
|
|
|
setLocEnd(el.loc, backTrack(end, CharCodes.Lt))
|
2023-11-20 17:38:00 +08:00
|
|
|
} else {
|
2023-11-25 19:22:39 +08:00
|
|
|
setLocEnd(el.loc, end + fastForward(end, CharCodes.Gt) + 1)
|
2023-11-13 21:03:39 +08:00
|
|
|
}
|
2023-11-16 10:54:54 +08:00
|
|
|
|
2023-11-25 18:07:17 +08:00
|
|
|
if (tokenizer.inSFCRoot) {
|
|
|
|
// SFC root tag, resolve inner end
|
|
|
|
if (el.children.length) {
|
|
|
|
el.innerLoc!.end = extend({}, el.children[el.children.length - 1].loc.end)
|
|
|
|
} else {
|
|
|
|
el.innerLoc!.end = extend({}, el.innerLoc!.start)
|
|
|
|
}
|
2023-11-25 19:22:39 +08:00
|
|
|
el.innerLoc!.source = getSlice(
|
|
|
|
el.innerLoc!.start.offset,
|
|
|
|
el.innerLoc!.end.offset
|
|
|
|
)
|
2023-11-25 18:07:17 +08:00
|
|
|
}
|
|
|
|
|
2023-11-16 10:54:54 +08:00
|
|
|
// refine element type
|
2023-11-19 11:46:44 +08:00
|
|
|
const { tag, ns } = el
|
2023-11-16 10:54:54 +08:00
|
|
|
if (!inVPre) {
|
|
|
|
if (tag === 'slot') {
|
|
|
|
el.tagType = ElementTypes.SLOT
|
|
|
|
} else if (isFragmentTemplate(el)) {
|
|
|
|
el.tagType = ElementTypes.TEMPLATE
|
|
|
|
} else if (isComponent(el)) {
|
|
|
|
el.tagType = ElementTypes.COMPONENT
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:03:39 +08:00
|
|
|
// whitepsace management
|
2023-11-17 17:46:47 +08:00
|
|
|
if (!tokenizer.inRCDATA) {
|
|
|
|
el.children = condenseWhitespace(el.children, el.tag)
|
|
|
|
}
|
2023-11-19 11:46:44 +08:00
|
|
|
if (ns === Namespaces.HTML && currentOptions.isPreTag(tag)) {
|
2023-11-15 23:55:50 +08:00
|
|
|
inPre--
|
|
|
|
}
|
2023-11-17 17:46:47 +08:00
|
|
|
if (currentVPreBoundary === el) {
|
2023-11-16 01:31:52 +08:00
|
|
|
inVPre = false
|
2023-11-17 17:46:47 +08:00
|
|
|
currentVPreBoundary = null
|
2023-11-16 01:31:52 +08:00
|
|
|
}
|
2023-11-19 11:46:44 +08:00
|
|
|
if (
|
|
|
|
tokenizer.inXML &&
|
|
|
|
(stack[0] ? stack[0].ns : currentOptions.ns) === Namespaces.HTML
|
|
|
|
) {
|
|
|
|
tokenizer.inXML = false
|
|
|
|
}
|
2023-11-22 15:19:06 +08:00
|
|
|
|
|
|
|
// 2.x compat / deprecation checks
|
|
|
|
if (__COMPAT__) {
|
|
|
|
const props = el.props
|
|
|
|
if (
|
|
|
|
__DEV__ &&
|
|
|
|
isCompatEnabled(
|
|
|
|
CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
|
|
|
|
currentOptions
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
let hasIf = false
|
|
|
|
let hasFor = false
|
|
|
|
for (let i = 0; i < props.length; i++) {
|
|
|
|
const p = props[i]
|
|
|
|
if (p.type === NodeTypes.DIRECTIVE) {
|
|
|
|
if (p.name === 'if') {
|
|
|
|
hasIf = true
|
|
|
|
} else if (p.name === 'for') {
|
|
|
|
hasFor = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (hasIf && hasFor) {
|
|
|
|
warnDeprecation(
|
|
|
|
CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
|
|
|
|
currentOptions,
|
|
|
|
el.loc
|
|
|
|
)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
isCompatEnabled(
|
|
|
|
CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
|
|
|
|
currentOptions
|
|
|
|
) &&
|
|
|
|
el.tag === 'template' &&
|
|
|
|
!isFragmentTemplate(el)
|
|
|
|
) {
|
|
|
|
__DEV__ &&
|
|
|
|
warnDeprecation(
|
|
|
|
CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
|
|
|
|
currentOptions,
|
|
|
|
el.loc
|
|
|
|
)
|
|
|
|
// unwrap
|
|
|
|
const parent = stack[0] || currentRoot
|
|
|
|
const index = parent.children.indexOf(el)
|
|
|
|
parent.children.splice(index, 1, ...el.children)
|
|
|
|
}
|
|
|
|
|
|
|
|
const inlineTemplateProp = props.find(
|
|
|
|
p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template'
|
|
|
|
) as AttributeNode
|
|
|
|
if (
|
|
|
|
inlineTemplateProp &&
|
|
|
|
checkCompatEnabled(
|
|
|
|
CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE,
|
|
|
|
currentOptions,
|
|
|
|
inlineTemplateProp.loc
|
|
|
|
) &&
|
|
|
|
el.children.length
|
|
|
|
) {
|
|
|
|
inlineTemplateProp.value = {
|
|
|
|
type: NodeTypes.TEXT,
|
|
|
|
content: getSlice(
|
|
|
|
el.children[0].loc.start.offset,
|
|
|
|
el.children[el.children.length - 1].loc.end.offset
|
|
|
|
),
|
|
|
|
loc: inlineTemplateProp.loc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-11-14 01:14:33 +08:00
|
|
|
}
|
|
|
|
|
2023-11-20 17:38:00 +08:00
|
|
|
function fastForward(start: number, c: number) {
|
|
|
|
let offset = 0
|
|
|
|
while (
|
|
|
|
currentInput.charCodeAt(start + offset) !== CharCodes.Gt &&
|
|
|
|
start + offset < currentInput.length
|
|
|
|
) {
|
|
|
|
offset++
|
|
|
|
}
|
|
|
|
return offset
|
|
|
|
}
|
|
|
|
|
2023-11-22 13:58:50 +08:00
|
|
|
function backTrack(index: number, c: number) {
|
|
|
|
let i = index
|
|
|
|
while (currentInput.charCodeAt(i) !== c && i >= 0) i--
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2023-11-16 10:54:54 +08:00
|
|
|
const specialTemplateDir = new Set(['if', 'else', 'else-if', 'for', 'slot'])
|
|
|
|
function isFragmentTemplate({ tag, props }: ElementNode): boolean {
|
|
|
|
if (tag === 'template') {
|
|
|
|
for (let i = 0; i < props.length; i++) {
|
|
|
|
if (
|
|
|
|
props[i].type === NodeTypes.DIRECTIVE &&
|
2023-11-17 14:17:30 +08:00
|
|
|
specialTemplateDir.has((props[i] as DirectiveNode).name)
|
2023-11-16 10:54:54 +08:00
|
|
|
) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2023-11-14 01:14:33 +08:00
|
|
|
|
2023-11-16 10:54:54 +08:00
|
|
|
function isComponent({ tag, props }: ElementNode): boolean {
|
|
|
|
if (currentOptions.isCustomElement(tag)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
tag === 'component' ||
|
|
|
|
isUpperCase(tag.charCodeAt(0)) ||
|
|
|
|
isCoreComponent(tag) ||
|
|
|
|
currentOptions.isBuiltInComponent?.(tag) ||
|
2023-11-17 17:46:47 +08:00
|
|
|
(currentOptions.isNativeTag && !currentOptions.isNativeTag(tag))
|
2023-11-16 10:54:54 +08:00
|
|
|
) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
// at this point the tag should be a native tag, but check for potential "is"
|
|
|
|
// casting
|
|
|
|
for (let i = 0; i < props.length; i++) {
|
|
|
|
const p = props[i]
|
|
|
|
if (p.type === NodeTypes.ATTRIBUTE) {
|
|
|
|
if (p.name === 'is' && p.value) {
|
|
|
|
if (p.value.content.startsWith('vue:')) {
|
|
|
|
return true
|
2023-11-22 15:19:06 +08:00
|
|
|
} else if (
|
|
|
|
__COMPAT__ &&
|
|
|
|
checkCompatEnabled(
|
|
|
|
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
|
|
|
currentOptions,
|
|
|
|
p.loc
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return true
|
2023-11-16 10:54:54 +08:00
|
|
|
}
|
|
|
|
}
|
2023-11-22 15:19:06 +08:00
|
|
|
} else if (
|
|
|
|
__COMPAT__ &&
|
|
|
|
// :is on plain element - only treat as component in compat mode
|
|
|
|
p.name === 'bind' &&
|
|
|
|
isStaticArgOf(p.arg, 'is') &&
|
|
|
|
checkCompatEnabled(
|
|
|
|
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
|
|
|
currentOptions,
|
|
|
|
p.loc
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return true
|
2023-11-16 10:54:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
function isUpperCase(c: number) {
|
|
|
|
return c > 64 && c < 91
|
|
|
|
}
|
|
|
|
|
|
|
|
const windowsNewlineRE = /\r\n/g
|
2023-11-17 17:46:47 +08:00
|
|
|
function condenseWhitespace(
|
|
|
|
nodes: TemplateChildNode[],
|
|
|
|
tag?: string
|
|
|
|
): TemplateChildNode[] {
|
2023-11-13 21:03:39 +08:00
|
|
|
const shouldCondense = currentOptions.whitespace !== 'preserve'
|
|
|
|
let removedWhitespace = false
|
|
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
|
|
const node = nodes[i]
|
|
|
|
if (node.type === NodeTypes.TEXT) {
|
|
|
|
if (!inPre) {
|
2023-11-14 21:55:16 +08:00
|
|
|
if (isAllWhitespace(node.content)) {
|
|
|
|
const prev = nodes[i - 1]?.type
|
|
|
|
const next = nodes[i + 1]?.type
|
2023-11-13 21:03:39 +08:00
|
|
|
// Remove if:
|
|
|
|
// - the whitespace is the first or last node, or:
|
2023-11-14 21:55:16 +08:00
|
|
|
// - (condense mode) the whitespace is between two comments, or:
|
2023-11-13 21:03:39 +08:00
|
|
|
// - (condense mode) the whitespace is between comment and element, or:
|
|
|
|
// - (condense mode) the whitespace is between two elements AND contains newline
|
|
|
|
if (
|
|
|
|
!prev ||
|
|
|
|
!next ||
|
|
|
|
(shouldCondense &&
|
2023-11-14 21:55:16 +08:00
|
|
|
((prev === NodeTypes.COMMENT &&
|
|
|
|
(next === NodeTypes.COMMENT || next === NodeTypes.ELEMENT)) ||
|
|
|
|
(prev === NodeTypes.ELEMENT &&
|
|
|
|
(next === NodeTypes.COMMENT ||
|
|
|
|
(next === NodeTypes.ELEMENT &&
|
|
|
|
hasNewlineChar(node.content))))))
|
2023-11-13 21:03:39 +08:00
|
|
|
) {
|
|
|
|
removedWhitespace = true
|
|
|
|
nodes[i] = null as any
|
|
|
|
} else {
|
|
|
|
// Otherwise, the whitespace is condensed into a single space
|
|
|
|
node.content = ' '
|
|
|
|
}
|
|
|
|
} else if (shouldCondense) {
|
|
|
|
// in condense mode, consecutive whitespaces in text are condensed
|
|
|
|
// down to a single space.
|
2023-11-14 21:55:16 +08:00
|
|
|
node.content = condense(node.content)
|
2023-11-13 21:03:39 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// #6410 normalize windows newlines in <pre>:
|
|
|
|
// in SSR, browsers normalize server-rendered \r\n into a single \n
|
|
|
|
// in the DOM
|
2023-11-14 01:14:33 +08:00
|
|
|
node.content = node.content.replace(windowsNewlineRE, '\n')
|
2023-11-13 21:03:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-11-17 17:46:47 +08:00
|
|
|
if (inPre && tag && currentOptions.isPreTag(tag)) {
|
|
|
|
// remove leading newline per html spec
|
|
|
|
// https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
|
|
|
|
const first = nodes[0]
|
|
|
|
if (first && first.type === NodeTypes.TEXT) {
|
|
|
|
first.content = first.content.replace(/^\r?\n/, '')
|
|
|
|
}
|
|
|
|
}
|
2023-11-14 01:14:33 +08:00
|
|
|
return removedWhitespace ? nodes.filter(Boolean) : nodes
|
2023-11-13 21:03:39 +08:00
|
|
|
}
|
|
|
|
|
2023-11-14 21:55:16 +08:00
|
|
|
function isAllWhitespace(str: string) {
|
|
|
|
for (let i = 0; i < str.length; i++) {
|
|
|
|
if (!isWhitespace(str.charCodeAt(i))) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasNewlineChar(str: string) {
|
|
|
|
for (let i = 0; i < str.length; i++) {
|
|
|
|
const c = str.charCodeAt(i)
|
|
|
|
if (c === CharCodes.NewLine || c === CharCodes.CarriageReturn) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
function condense(str: string) {
|
|
|
|
let ret = ''
|
|
|
|
let prevCharIsWhitespace = false
|
|
|
|
for (let i = 0; i < str.length; i++) {
|
|
|
|
if (isWhitespace(str.charCodeAt(i))) {
|
|
|
|
if (!prevCharIsWhitespace) {
|
|
|
|
ret += ' '
|
|
|
|
prevCharIsWhitespace = true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ret += str[i]
|
|
|
|
prevCharIsWhitespace = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:03:39 +08:00
|
|
|
function addNode(node: TemplateChildNode) {
|
2023-11-19 10:39:11 +08:00
|
|
|
;(stack[0] || currentRoot).children.push(node)
|
2023-11-13 21:03:39 +08:00
|
|
|
}
|
|
|
|
|
2023-11-15 17:45:42 +08:00
|
|
|
function getLoc(start: number, end?: number): SourceLocation {
|
|
|
|
return {
|
2023-11-15 19:36:05 +08:00
|
|
|
start: tokenizer.getPos(start),
|
2023-11-15 17:45:42 +08:00
|
|
|
// @ts-expect-error allow late attachment
|
2023-11-25 19:22:39 +08:00
|
|
|
end: end == null ? end : tokenizer.getPos(end),
|
|
|
|
// @ts-expect-error allow late attachment
|
|
|
|
source: end == null ? end : getSlice(start, end)
|
2023-11-14 21:55:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-25 19:22:39 +08:00
|
|
|
function setLocEnd(loc: SourceLocation, end: number) {
|
|
|
|
loc.end = tokenizer.getPos(end)
|
|
|
|
loc.source = getSlice(loc.start.offset, end)
|
|
|
|
}
|
|
|
|
|
2023-11-16 01:31:52 +08:00
|
|
|
function dirToAttr(dir: DirectiveNode): AttributeNode {
|
|
|
|
const attr: AttributeNode = {
|
|
|
|
type: NodeTypes.ATTRIBUTE,
|
2023-11-17 14:17:30 +08:00
|
|
|
name: dir.rawName!,
|
|
|
|
nameLoc: getLoc(
|
|
|
|
dir.loc.start.offset,
|
|
|
|
dir.loc.start.offset + dir.rawName!.length
|
|
|
|
),
|
2023-11-16 01:31:52 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-11-22 13:58:50 +08:00
|
|
|
function emitError(code: ErrorCodes, index: number) {
|
|
|
|
currentOptions.onError(createCompilerError(code, getLoc(index, index)))
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:03:39 +08:00
|
|
|
function reset() {
|
|
|
|
tokenizer.reset()
|
2023-11-22 15:19:06 +08:00
|
|
|
currentOpenTag = null
|
2023-11-14 18:03:00 +08:00
|
|
|
currentProp = null
|
|
|
|
currentAttrValue = ''
|
2023-11-15 17:45:42 +08:00
|
|
|
currentAttrStartIndex = -1
|
|
|
|
currentAttrEndIndex = -1
|
2023-11-13 21:03:39 +08:00
|
|
|
stack.length = 0
|
|
|
|
}
|
2023-11-12 21:42:27 +08:00
|
|
|
|
2023-11-14 20:39:08 +08:00
|
|
|
export function baseParse(input: string, options?: ParserOptions): RootNode {
|
2023-11-13 21:03:39 +08:00
|
|
|
reset()
|
2023-11-17 09:22:12 +08:00
|
|
|
currentInput = input
|
2023-11-22 15:19:06 +08:00
|
|
|
currentOptions = extend({}, defaultParserOptions)
|
|
|
|
|
|
|
|
if (options) {
|
|
|
|
let key: keyof ParserOptions
|
|
|
|
for (key in options) {
|
|
|
|
if (options[key] != null) {
|
|
|
|
// @ts-ignore
|
|
|
|
currentOptions[key] = options[key]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-11-17 09:22:12 +08:00
|
|
|
|
2023-11-18 21:39:31 +08:00
|
|
|
if (__DEV__) {
|
|
|
|
if (!__BROWSER__ && currentOptions.decodeEntities) {
|
|
|
|
console.warn(
|
|
|
|
`[@vue/compiler-core] decodeEntities option is passed but will be ` +
|
|
|
|
`ignored in non-browser builds.`
|
|
|
|
)
|
|
|
|
} else if (__BROWSER__ && !currentOptions.decodeEntities) {
|
|
|
|
throw new Error(
|
|
|
|
`[@vue/compiler-core] decodeEntities option is required in browser builds.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-17 09:22:12 +08:00
|
|
|
tokenizer.mode =
|
|
|
|
currentOptions.parseMode === 'html'
|
|
|
|
? ParseMode.HTML
|
|
|
|
: currentOptions.parseMode === 'sfc'
|
2023-11-21 22:27:35 +08:00
|
|
|
? ParseMode.SFC
|
|
|
|
: ParseMode.BASE
|
2023-11-17 09:22:12 +08:00
|
|
|
|
2023-11-15 23:33:57 +08:00
|
|
|
const delimiters = options?.delimiters
|
|
|
|
if (delimiters) {
|
|
|
|
tokenizer.delimiterOpen = toCharCodes(delimiters[0])
|
|
|
|
tokenizer.delimiterClose = toCharCodes(delimiters[1])
|
|
|
|
}
|
2023-11-17 09:22:12 +08:00
|
|
|
|
2023-11-17 14:17:30 +08:00
|
|
|
const root = (currentRoot = createRoot([], input))
|
2023-11-13 21:03:39 +08:00
|
|
|
tokenizer.parse(currentInput)
|
2023-11-18 10:49:29 +08:00
|
|
|
root.loc = getLoc(0, input.length)
|
2023-11-14 01:14:33 +08:00
|
|
|
root.children = condenseWhitespace(root.children)
|
2023-11-17 14:17:30 +08:00
|
|
|
currentRoot = null
|
2023-11-12 16:58:24 +08:00
|
|
|
return root
|
|
|
|
}
|