index.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. import { fromCodePoint } from 'entities/lib/decode.js'
  2. import {
  3. AttributeNode,
  4. ConstantTypes,
  5. DirectiveNode,
  6. ElementNode,
  7. ElementTypes,
  8. ForParseResult,
  9. Namespaces,
  10. NodeTypes,
  11. RootNode,
  12. SimpleExpressionNode,
  13. SourceLocation,
  14. TemplateChildNode,
  15. createRoot,
  16. createSimpleExpression
  17. } from '../ast'
  18. import { ParserOptions } from '../options'
  19. import Tokenizer, {
  20. CharCodes,
  21. ParseMode,
  22. QuoteType,
  23. isWhitespace,
  24. toCharCodes
  25. } from './Tokenizer'
  26. import { CompilerCompatOptions } from '../compat/compatConfig'
  27. import { NO, extend } from '@vue/shared'
  28. import { defaultOnError, defaultOnWarn } from '../errors'
  29. import { forAliasRE, isCoreComponent } from '../utils'
  30. type OptionalOptions =
  31. | 'whitespace'
  32. | 'isNativeTag'
  33. | 'isBuiltInComponent'
  34. | keyof CompilerCompatOptions
  35. type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
  36. Pick<ParserOptions, OptionalOptions>
  37. // The default decoder only provides escapes for characters reserved as part of
  38. // the template syntax, and is only used if the custom renderer did not provide
  39. // a platform-specific decoder.
  40. const decodeRE = /&(gt|lt|amp|apos|quot);/g
  41. const decodeMap: Record<string, string> = {
  42. gt: '>',
  43. lt: '<',
  44. amp: '&',
  45. apos: "'",
  46. quot: '"'
  47. }
  48. export const defaultParserOptions: MergedParserOptions = {
  49. parseMode: 'base',
  50. delimiters: [`{{`, `}}`],
  51. getNamespace: () => Namespaces.HTML,
  52. isVoidTag: NO,
  53. isPreTag: NO,
  54. isCustomElement: NO,
  55. // TODO handle entities
  56. decodeEntities: (rawText: string): string =>
  57. rawText.replace(decodeRE, (_, p1) => decodeMap[p1]),
  58. onError: defaultOnError,
  59. onWarn: defaultOnWarn,
  60. comments: __DEV__
  61. }
  62. let currentOptions: MergedParserOptions = defaultParserOptions
  63. let currentRoot: RootNode | null = null
  64. // parser state
  65. let currentInput = ''
  66. let currentElement: ElementNode | null = null
  67. let currentProp: AttributeNode | DirectiveNode | null = null
  68. let currentAttrValue = ''
  69. let currentAttrStartIndex = -1
  70. let currentAttrEndIndex = -1
  71. let currentAttrs: Set<string> = new Set()
  72. let inPre = 0
  73. let inVPre = false
  74. let currentVPreBoundary: ElementNode | null = null
  75. const stack: ElementNode[] = []
  76. const tokenizer = new Tokenizer(stack, {
  77. ontext(start, end) {
  78. onText(getSlice(start, end), start, end)
  79. },
  80. ontextentity(cp, end) {
  81. onText(fromCodePoint(cp), end - 1, end)
  82. },
  83. oninterpolation(start, end) {
  84. if (inVPre) {
  85. return onText(getSlice(start, end), start, end)
  86. }
  87. let innerStart = start + tokenizer.delimiterOpen.length
  88. let innerEnd = end - tokenizer.delimiterClose.length
  89. while (isWhitespace(currentInput.charCodeAt(innerStart))) {
  90. innerStart++
  91. }
  92. while (isWhitespace(currentInput.charCodeAt(innerEnd - 1))) {
  93. innerEnd--
  94. }
  95. addNode({
  96. type: NodeTypes.INTERPOLATION,
  97. content: createSimpleExpression(
  98. getSlice(innerStart, innerEnd),
  99. false,
  100. getLoc(innerStart, innerEnd)
  101. ),
  102. loc: getLoc(start, end)
  103. })
  104. },
  105. onopentagname(start, end) {
  106. const name = getSlice(start, end)
  107. currentElement = {
  108. type: NodeTypes.ELEMENT,
  109. tag: name,
  110. ns: currentOptions.getNamespace(name, getParent()),
  111. tagType: ElementTypes.ELEMENT, // will be refined on tag close
  112. props: [],
  113. children: [],
  114. loc: getLoc(start - 1),
  115. codegenNode: undefined
  116. }
  117. currentAttrs.clear()
  118. },
  119. onopentagend(end) {
  120. endOpenTag(end)
  121. },
  122. onclosetag(start, end) {
  123. const name = getSlice(start, end)
  124. if (!currentOptions.isVoidTag(name)) {
  125. for (let i = 0; i < stack.length; i++) {
  126. const e = stack[i]
  127. if (e.tag.toLowerCase() === name.toLowerCase()) {
  128. for (let j = 0; j <= i; j++) {
  129. onCloseTag(stack.shift()!, end)
  130. }
  131. break
  132. }
  133. }
  134. }
  135. },
  136. onselfclosingtag(end) {
  137. closeCurrentTag(end)
  138. },
  139. onattribname(start, end) {
  140. // plain attribute
  141. currentProp = {
  142. type: NodeTypes.ATTRIBUTE,
  143. name: getSlice(start, end),
  144. nameLoc: getLoc(start, end),
  145. value: undefined,
  146. loc: getLoc(start)
  147. }
  148. },
  149. ondirname(start, end) {
  150. const raw = getSlice(start, end)
  151. if (inVPre) {
  152. currentProp = {
  153. type: NodeTypes.ATTRIBUTE,
  154. name: raw,
  155. nameLoc: getLoc(start, end),
  156. value: undefined,
  157. loc: getLoc(start)
  158. }
  159. } else {
  160. const name =
  161. raw === '.' || raw === ':'
  162. ? 'bind'
  163. : raw === '@'
  164. ? 'on'
  165. : raw === '#'
  166. ? 'slot'
  167. : raw.slice(2)
  168. currentProp = {
  169. type: NodeTypes.DIRECTIVE,
  170. name,
  171. rawName: raw,
  172. exp: undefined,
  173. rawExp: undefined,
  174. arg: undefined,
  175. modifiers: raw === '.' ? ['prop'] : [],
  176. loc: getLoc(start)
  177. }
  178. if (name === 'pre') {
  179. inVPre = true
  180. currentVPreBoundary = currentElement
  181. // convert dirs before this one to attributes
  182. const props = currentElement!.props
  183. for (let i = 0; i < props.length; i++) {
  184. if (props[i].type === NodeTypes.DIRECTIVE) {
  185. props[i] = dirToAttr(props[i] as DirectiveNode)
  186. }
  187. }
  188. }
  189. }
  190. },
  191. ondirarg(start, end) {
  192. const arg = getSlice(start, end)
  193. if (inVPre) {
  194. ;(currentProp as AttributeNode).name += arg
  195. ;(currentProp as AttributeNode).nameLoc.end = tokenizer.getPos(end)
  196. } else {
  197. const isStatic = arg[0] !== `[`
  198. ;(currentProp as DirectiveNode).arg = createSimpleExpression(
  199. isStatic ? arg : arg.slice(1, -1),
  200. isStatic,
  201. getLoc(start, end),
  202. isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
  203. )
  204. }
  205. },
  206. ondirmodifier(start, end) {
  207. const mod = getSlice(start, end)
  208. if (inVPre) {
  209. ;(currentProp as AttributeNode).name += '.' + mod
  210. ;(currentProp as AttributeNode).nameLoc.end = tokenizer.getPos(end)
  211. } else if ((currentProp as DirectiveNode).name === 'slot') {
  212. // slot has no modifiers, special case for edge cases like
  213. // https://github.com/vuejs/language-tools/issues/2710
  214. const arg = (currentProp as DirectiveNode).arg
  215. if (arg) {
  216. ;(arg as SimpleExpressionNode).content += '.' + mod
  217. arg.loc.end = tokenizer.getPos(end)
  218. }
  219. } else {
  220. ;(currentProp as DirectiveNode).modifiers.push(mod)
  221. }
  222. },
  223. onattribdata(start, end) {
  224. currentAttrValue += getSlice(start, end)
  225. if (currentAttrStartIndex < 0) currentAttrStartIndex = start
  226. currentAttrEndIndex = end
  227. },
  228. onattribentity(codepoint) {
  229. currentAttrValue += fromCodePoint(codepoint)
  230. },
  231. onattribnameend(end) {
  232. // check duplicate attrs
  233. const start = currentProp!.loc.start.offset
  234. const name = getSlice(start, end)
  235. if (currentProp!.type === NodeTypes.DIRECTIVE) {
  236. currentProp!.rawName = name
  237. }
  238. if (currentAttrs.has(name)) {
  239. currentProp = null
  240. // TODO emit error DUPLICATE_ATTRIBUTE
  241. throw new Error(`duplicate attr ${name}`)
  242. } else {
  243. currentAttrs.add(name)
  244. }
  245. },
  246. onattribend(quote, end) {
  247. if (currentElement && currentProp) {
  248. if (quote !== QuoteType.NoValue) {
  249. if (currentProp.type === NodeTypes.ATTRIBUTE) {
  250. // assign value
  251. // condense whitespaces in class
  252. if (currentProp!.name === 'class') {
  253. currentAttrValue = condense(currentAttrValue).trim()
  254. }
  255. currentProp!.value = {
  256. type: NodeTypes.TEXT,
  257. content: currentAttrValue,
  258. loc:
  259. quote === QuoteType.Unquoted
  260. ? getLoc(currentAttrStartIndex, currentAttrEndIndex)
  261. : getLoc(currentAttrStartIndex - 1, currentAttrEndIndex + 1)
  262. }
  263. } else {
  264. // directive
  265. currentProp.rawExp = currentAttrValue
  266. currentProp.exp = createSimpleExpression(
  267. currentAttrValue,
  268. false,
  269. getLoc(currentAttrStartIndex, currentAttrEndIndex)
  270. )
  271. if (currentProp.name === 'for') {
  272. currentProp.forParseResult = parseForExpression(currentProp.exp)
  273. }
  274. }
  275. }
  276. currentProp.loc.end = tokenizer.getPos(end)
  277. if (
  278. currentProp.type !== NodeTypes.DIRECTIVE ||
  279. currentProp.name !== 'pre'
  280. ) {
  281. currentElement.props.push(currentProp)
  282. }
  283. }
  284. currentAttrValue = ''
  285. currentAttrStartIndex = currentAttrEndIndex = -1
  286. },
  287. oncomment(start, end) {
  288. if (currentOptions.comments) {
  289. addNode({
  290. type: NodeTypes.COMMENT,
  291. content: getSlice(start, end),
  292. loc: getLoc(start - 4, end + 3)
  293. })
  294. }
  295. },
  296. onend() {
  297. const end = currentInput.length - 1
  298. for (let index = 0; index < stack.length; index++) {
  299. onCloseTag(stack[index], end)
  300. }
  301. },
  302. oncdata(start, end) {
  303. // TODO throw error
  304. }
  305. })
  306. // This regex doesn't cover the case if key or index aliases have destructuring,
  307. // but those do not make sense in the first place, so this works in practice.
  308. const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
  309. const stripParensRE = /^\(|\)$/g
  310. function parseForExpression(
  311. input: SimpleExpressionNode
  312. ): ForParseResult | undefined {
  313. const loc = input.loc
  314. const exp = input.content
  315. const inMatch = exp.match(forAliasRE)
  316. if (!inMatch) return
  317. const [, LHS, RHS] = inMatch
  318. const createAliasExpression = (content: string, offset: number) => {
  319. const start = loc.start.offset + offset
  320. const end = start + content.length
  321. return createSimpleExpression(content, false, getLoc(start, end))
  322. }
  323. const result: ForParseResult = {
  324. source: createAliasExpression(RHS.trim(), exp.indexOf(RHS, LHS.length)),
  325. value: undefined,
  326. key: undefined,
  327. index: undefined,
  328. finalized: false
  329. }
  330. let valueContent = LHS.trim().replace(stripParensRE, '').trim()
  331. const trimmedOffset = LHS.indexOf(valueContent)
  332. const iteratorMatch = valueContent.match(forIteratorRE)
  333. if (iteratorMatch) {
  334. valueContent = valueContent.replace(forIteratorRE, '').trim()
  335. const keyContent = iteratorMatch[1].trim()
  336. let keyOffset: number | undefined
  337. if (keyContent) {
  338. keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
  339. result.key = createAliasExpression(keyContent, keyOffset)
  340. }
  341. if (iteratorMatch[2]) {
  342. const indexContent = iteratorMatch[2].trim()
  343. if (indexContent) {
  344. result.index = createAliasExpression(
  345. indexContent,
  346. exp.indexOf(
  347. indexContent,
  348. result.key
  349. ? keyOffset! + keyContent.length
  350. : trimmedOffset + valueContent.length
  351. )
  352. )
  353. }
  354. }
  355. }
  356. if (valueContent) {
  357. result.value = createAliasExpression(valueContent, trimmedOffset)
  358. }
  359. return result
  360. }
  361. function getSlice(start: number, end: number) {
  362. return currentInput.slice(start, end)
  363. }
  364. function endOpenTag(end: number) {
  365. addNode(currentElement!)
  366. const name = currentElement!.tag
  367. if (currentOptions.isPreTag(name)) {
  368. inPre++
  369. }
  370. if (currentOptions.isVoidTag(name)) {
  371. onCloseTag(currentElement!, end)
  372. } else {
  373. stack.unshift(currentElement!)
  374. }
  375. currentElement = null
  376. }
  377. function closeCurrentTag(end: number) {
  378. const name = currentElement!.tag
  379. endOpenTag(end)
  380. if (stack[0].tag === name) {
  381. onCloseTag(stack.shift()!, end)
  382. }
  383. }
  384. function onText(content: string, start: number, end: number) {
  385. const parent = getParent()
  386. const lastNode = parent.children[parent.children.length - 1]
  387. if (lastNode?.type === NodeTypes.TEXT) {
  388. // merge
  389. lastNode.content += content
  390. lastNode.loc.end = tokenizer.getPos(end)
  391. } else {
  392. parent.children.push({
  393. type: NodeTypes.TEXT,
  394. content,
  395. loc: getLoc(start, end)
  396. })
  397. }
  398. }
  399. function onCloseTag(el: ElementNode, end: number) {
  400. // attach end position
  401. let offset = 0
  402. while (currentInput.charCodeAt(end + offset) !== CharCodes.Gt) {
  403. offset++
  404. }
  405. el.loc.end = tokenizer.getPos(end + offset + 1)
  406. // refine element type
  407. const tag = el.tag
  408. if (!inVPre) {
  409. if (tag === 'slot') {
  410. el.tagType = ElementTypes.SLOT
  411. } else if (isFragmentTemplate(el)) {
  412. el.tagType = ElementTypes.TEMPLATE
  413. } else if (isComponent(el)) {
  414. el.tagType = ElementTypes.COMPONENT
  415. }
  416. }
  417. // whitepsace management
  418. if (!tokenizer.inRCDATA) {
  419. el.children = condenseWhitespace(el.children, el.tag)
  420. }
  421. if (currentOptions.isPreTag(tag)) {
  422. inPre--
  423. }
  424. if (currentVPreBoundary === el) {
  425. inVPre = false
  426. currentVPreBoundary = null
  427. }
  428. }
  429. const specialTemplateDir = new Set(['if', 'else', 'else-if', 'for', 'slot'])
  430. function isFragmentTemplate({ tag, props }: ElementNode): boolean {
  431. if (tag === 'template') {
  432. for (let i = 0; i < props.length; i++) {
  433. if (
  434. props[i].type === NodeTypes.DIRECTIVE &&
  435. specialTemplateDir.has((props[i] as DirectiveNode).name)
  436. ) {
  437. return true
  438. }
  439. }
  440. }
  441. return false
  442. }
  443. function isComponent({ tag, props }: ElementNode): boolean {
  444. if (currentOptions.isCustomElement(tag)) {
  445. return false
  446. }
  447. if (
  448. tag === 'component' ||
  449. isUpperCase(tag.charCodeAt(0)) ||
  450. isCoreComponent(tag) ||
  451. currentOptions.isBuiltInComponent?.(tag) ||
  452. (currentOptions.isNativeTag && !currentOptions.isNativeTag(tag))
  453. ) {
  454. return true
  455. }
  456. // at this point the tag should be a native tag, but check for potential "is"
  457. // casting
  458. for (let i = 0; i < props.length; i++) {
  459. const p = props[i]
  460. if (p.type === NodeTypes.ATTRIBUTE) {
  461. if (p.name === 'is' && p.value) {
  462. if (p.value.content.startsWith('vue:')) {
  463. return true
  464. }
  465. // TODO else if (
  466. // __COMPAT__ &&
  467. // checkCompatEnabled(
  468. // CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
  469. // context,
  470. // p.loc
  471. // )
  472. // ) {
  473. // return true
  474. // }
  475. }
  476. }
  477. // TODO else if (
  478. // __COMPAT__ &&
  479. // // :is on plain element - only treat as component in compat mode
  480. // p.name === 'bind' &&
  481. // isStaticArgOf(p.arg, 'is') &&
  482. // checkCompatEnabled(
  483. // CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
  484. // context,
  485. // p.loc
  486. // )
  487. // ) {
  488. // return true
  489. // }
  490. }
  491. return false
  492. }
  493. function isUpperCase(c: number) {
  494. return c > 64 && c < 91
  495. }
  496. const windowsNewlineRE = /\r\n/g
  497. function condenseWhitespace(
  498. nodes: TemplateChildNode[],
  499. tag?: string
  500. ): TemplateChildNode[] {
  501. const shouldCondense = currentOptions.whitespace !== 'preserve'
  502. let removedWhitespace = false
  503. for (let i = 0; i < nodes.length; i++) {
  504. const node = nodes[i]
  505. if (node.type === NodeTypes.TEXT) {
  506. if (!inPre) {
  507. if (isAllWhitespace(node.content)) {
  508. const prev = nodes[i - 1]?.type
  509. const next = nodes[i + 1]?.type
  510. // Remove if:
  511. // - the whitespace is the first or last node, or:
  512. // - (condense mode) the whitespace is between two comments, or:
  513. // - (condense mode) the whitespace is between comment and element, or:
  514. // - (condense mode) the whitespace is between two elements AND contains newline
  515. if (
  516. !prev ||
  517. !next ||
  518. (shouldCondense &&
  519. ((prev === NodeTypes.COMMENT &&
  520. (next === NodeTypes.COMMENT || next === NodeTypes.ELEMENT)) ||
  521. (prev === NodeTypes.ELEMENT &&
  522. (next === NodeTypes.COMMENT ||
  523. (next === NodeTypes.ELEMENT &&
  524. hasNewlineChar(node.content))))))
  525. ) {
  526. removedWhitespace = true
  527. nodes[i] = null as any
  528. } else {
  529. // Otherwise, the whitespace is condensed into a single space
  530. node.content = ' '
  531. }
  532. } else if (shouldCondense) {
  533. // in condense mode, consecutive whitespaces in text are condensed
  534. // down to a single space.
  535. node.content = condense(node.content)
  536. }
  537. } else {
  538. // #6410 normalize windows newlines in <pre>:
  539. // in SSR, browsers normalize server-rendered \r\n into a single \n
  540. // in the DOM
  541. node.content = node.content.replace(windowsNewlineRE, '\n')
  542. }
  543. }
  544. }
  545. if (inPre && tag && currentOptions.isPreTag(tag)) {
  546. // remove leading newline per html spec
  547. // https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
  548. const first = nodes[0]
  549. if (first && first.type === NodeTypes.TEXT) {
  550. first.content = first.content.replace(/^\r?\n/, '')
  551. }
  552. }
  553. return removedWhitespace ? nodes.filter(Boolean) : nodes
  554. }
  555. function isAllWhitespace(str: string) {
  556. for (let i = 0; i < str.length; i++) {
  557. if (!isWhitespace(str.charCodeAt(i))) {
  558. return false
  559. }
  560. }
  561. return true
  562. }
  563. function hasNewlineChar(str: string) {
  564. for (let i = 0; i < str.length; i++) {
  565. const c = str.charCodeAt(i)
  566. if (c === CharCodes.NewLine || c === CharCodes.CarriageReturn) {
  567. return true
  568. }
  569. }
  570. return false
  571. }
  572. function condense(str: string) {
  573. let ret = ''
  574. let prevCharIsWhitespace = false
  575. for (let i = 0; i < str.length; i++) {
  576. if (isWhitespace(str.charCodeAt(i))) {
  577. if (!prevCharIsWhitespace) {
  578. ret += ' '
  579. prevCharIsWhitespace = true
  580. }
  581. } else {
  582. ret += str[i]
  583. prevCharIsWhitespace = false
  584. }
  585. }
  586. return ret
  587. }
  588. function addNode(node: TemplateChildNode) {
  589. getParent().children.push(node)
  590. }
  591. function getParent() {
  592. return stack[0] || currentRoot
  593. }
  594. function getLoc(start: number, end?: number): SourceLocation {
  595. return {
  596. start: tokenizer.getPos(start),
  597. // @ts-expect-error allow late attachment
  598. end: end && tokenizer.getPos(end)
  599. }
  600. }
  601. function dirToAttr(dir: DirectiveNode): AttributeNode {
  602. const attr: AttributeNode = {
  603. type: NodeTypes.ATTRIBUTE,
  604. name: dir.rawName!,
  605. nameLoc: getLoc(
  606. dir.loc.start.offset,
  607. dir.loc.start.offset + dir.rawName!.length
  608. ),
  609. value: undefined,
  610. loc: dir.loc
  611. }
  612. if (dir.exp) {
  613. // account for quotes
  614. const loc = dir.exp.loc
  615. if (loc.end.offset < dir.loc.end.offset) {
  616. loc.start.offset--
  617. loc.start.column--
  618. loc.end.offset++
  619. loc.end.column++
  620. }
  621. attr.value = {
  622. type: NodeTypes.TEXT,
  623. content: (dir.exp as SimpleExpressionNode).content,
  624. loc
  625. }
  626. }
  627. return attr
  628. }
  629. function reset() {
  630. tokenizer.reset()
  631. currentElement = null
  632. currentProp = null
  633. currentAttrs.clear()
  634. currentAttrValue = ''
  635. currentAttrStartIndex = -1
  636. currentAttrEndIndex = -1
  637. stack.length = 0
  638. }
  639. export function baseParse(input: string, options?: ParserOptions): RootNode {
  640. reset()
  641. currentInput = input
  642. currentOptions = extend({}, defaultParserOptions, options)
  643. tokenizer.mode =
  644. currentOptions.parseMode === 'html'
  645. ? ParseMode.HTML
  646. : currentOptions.parseMode === 'sfc'
  647. ? ParseMode.SFC
  648. : ParseMode.BASE
  649. const delimiters = options?.delimiters
  650. if (delimiters) {
  651. tokenizer.delimiterOpen = toCharCodes(delimiters[0])
  652. tokenizer.delimiterClose = toCharCodes(delimiters[1])
  653. }
  654. const root = (currentRoot = createRoot([], input))
  655. tokenizer.parse(currentInput)
  656. root.loc = getLoc(0, input.length)
  657. root.children = condenseWhitespace(root.children)
  658. currentRoot = null
  659. return root
  660. }