parser.ts 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079
  1. import {
  2. type AttributeNode,
  3. ConstantTypes,
  4. type DirectiveNode,
  5. type ElementNode,
  6. ElementTypes,
  7. type ForParseResult,
  8. Namespaces,
  9. NodeTypes,
  10. type RootNode,
  11. type SimpleExpressionNode,
  12. type SourceLocation,
  13. type TemplateChildNode,
  14. createRoot,
  15. createSimpleExpression,
  16. } from './ast'
  17. import type { ParserOptions } from './options'
  18. import Tokenizer, {
  19. CharCodes,
  20. ParseMode,
  21. QuoteType,
  22. Sequences,
  23. State,
  24. isWhitespace,
  25. toCharCodes,
  26. } from './tokenizer'
  27. import {
  28. type CompilerCompatOptions,
  29. CompilerDeprecationTypes,
  30. checkCompatEnabled,
  31. isCompatEnabled,
  32. warnDeprecation,
  33. } from './compat/compatConfig'
  34. import { NO, extend } from '@vue/shared'
  35. import {
  36. ErrorCodes,
  37. createCompilerError,
  38. defaultOnError,
  39. defaultOnWarn,
  40. } from './errors'
  41. import {
  42. forAliasRE,
  43. isAllWhitespace,
  44. isCoreComponent,
  45. isSimpleIdentifier,
  46. isStaticArgOf,
  47. isVPre,
  48. } from './utils'
  49. import { decodeHTML } from 'entities/decode'
  50. import {
  51. type ParserOptions as BabelOptions,
  52. parse,
  53. parseExpression,
  54. } from '@babel/parser'
  55. type OptionalOptions =
  56. | 'decodeEntities'
  57. | 'whitespace'
  58. | 'isNativeTag'
  59. | 'isBuiltInComponent'
  60. | 'expressionPlugins'
  61. | keyof CompilerCompatOptions
  62. export type MergedParserOptions = Omit<
  63. Required<ParserOptions>,
  64. OptionalOptions
  65. > &
  66. Pick<ParserOptions, OptionalOptions>
  67. export const defaultParserOptions: MergedParserOptions = {
  68. parseMode: 'base',
  69. ns: Namespaces.HTML,
  70. delimiters: [`{{`, `}}`],
  71. getNamespace: () => Namespaces.HTML,
  72. isVoidTag: NO,
  73. isPreTag: NO,
  74. isIgnoreNewlineTag: NO,
  75. isCustomElement: NO,
  76. onError: defaultOnError,
  77. onWarn: defaultOnWarn,
  78. comments: __DEV__,
  79. prefixIdentifiers: false,
  80. }
  81. let currentOptions: MergedParserOptions = defaultParserOptions
  82. let currentRoot: RootNode | null = null
  83. // parser state
  84. let currentInput = ''
  85. let currentOpenTag: ElementNode | null = null
  86. let currentProp: AttributeNode | DirectiveNode | null = null
  87. let currentAttrValue = ''
  88. let currentAttrStartIndex = -1
  89. let currentAttrEndIndex = -1
  90. let inPre = 0
  91. let inVPre = false
  92. let currentVPreBoundary: ElementNode | null = null
  93. const stack: ElementNode[] = []
  94. const tokenizer = new Tokenizer(stack, {
  95. onerr: emitError,
  96. ontext(start, end) {
  97. onText(getSlice(start, end), start, end)
  98. },
  99. ontextentity(char, start, end) {
  100. onText(char, start, end)
  101. },
  102. oninterpolation(start, end) {
  103. if (inVPre) {
  104. return onText(getSlice(start, end), start, end)
  105. }
  106. let innerStart = start + tokenizer.delimiterOpen.length
  107. let innerEnd = end - tokenizer.delimiterClose.length
  108. while (isWhitespace(currentInput.charCodeAt(innerStart))) {
  109. innerStart++
  110. }
  111. while (isWhitespace(currentInput.charCodeAt(innerEnd - 1))) {
  112. innerEnd--
  113. }
  114. let exp = getSlice(innerStart, innerEnd)
  115. // decode entities for backwards compat
  116. if (exp.includes('&')) {
  117. if (__BROWSER__) {
  118. exp = currentOptions.decodeEntities!(exp, false)
  119. } else {
  120. exp = decodeHTML(exp)
  121. }
  122. }
  123. addNode({
  124. type: NodeTypes.INTERPOLATION,
  125. content: createExp(exp, false, getLoc(innerStart, innerEnd)),
  126. loc: getLoc(start, end),
  127. })
  128. },
  129. onopentagname(start, end) {
  130. const name = getSlice(start, end)
  131. currentOpenTag = {
  132. type: NodeTypes.ELEMENT,
  133. tag: name,
  134. ns: currentOptions.getNamespace(name, stack[0], currentOptions.ns),
  135. tagType: ElementTypes.ELEMENT, // will be refined on tag close
  136. props: [],
  137. children: [],
  138. loc: getLoc(start - 1, end),
  139. codegenNode: undefined,
  140. }
  141. },
  142. onopentagend(end) {
  143. endOpenTag(end)
  144. },
  145. onclosetag(start, end) {
  146. const name = getSlice(start, end)
  147. if (!currentOptions.isVoidTag(name)) {
  148. let found = false
  149. for (let i = 0; i < stack.length; i++) {
  150. const e = stack[i]
  151. if (e.tag.toLowerCase() === name.toLowerCase()) {
  152. found = true
  153. if (i > 0) {
  154. emitError(ErrorCodes.X_MISSING_END_TAG, stack[0].loc.start.offset)
  155. }
  156. for (let j = 0; j <= i; j++) {
  157. const el = stack.shift()!
  158. onCloseTag(el, end, j < i)
  159. }
  160. break
  161. }
  162. }
  163. if (!found) {
  164. emitError(ErrorCodes.X_INVALID_END_TAG, backTrack(start, CharCodes.Lt))
  165. }
  166. }
  167. },
  168. onselfclosingtag(end) {
  169. const name = currentOpenTag!.tag
  170. currentOpenTag!.isSelfClosing = true
  171. endOpenTag(end)
  172. if (stack[0] && stack[0].tag === name) {
  173. onCloseTag(stack.shift()!, end)
  174. }
  175. },
  176. onattribname(start, end) {
  177. // plain attribute
  178. currentProp = {
  179. type: NodeTypes.ATTRIBUTE,
  180. name: getSlice(start, end),
  181. nameLoc: getLoc(start, end),
  182. value: undefined,
  183. loc: getLoc(start),
  184. }
  185. },
  186. ondirname(start, end) {
  187. const raw = getSlice(start, end)
  188. const name =
  189. raw === '.' || raw === ':'
  190. ? 'bind'
  191. : raw === '@'
  192. ? 'on'
  193. : raw === '#'
  194. ? 'slot'
  195. : raw.slice(2)
  196. if (!inVPre && name === '') {
  197. emitError(ErrorCodes.X_MISSING_DIRECTIVE_NAME, start)
  198. }
  199. if (inVPre || name === '') {
  200. currentProp = {
  201. type: NodeTypes.ATTRIBUTE,
  202. name: raw,
  203. nameLoc: getLoc(start, end),
  204. value: undefined,
  205. loc: getLoc(start),
  206. }
  207. } else {
  208. currentProp = {
  209. type: NodeTypes.DIRECTIVE,
  210. name,
  211. rawName: raw,
  212. exp: undefined,
  213. arg: undefined,
  214. modifiers: raw === '.' ? [createSimpleExpression('prop')] : [],
  215. loc: getLoc(start),
  216. }
  217. if (name === 'pre') {
  218. inVPre = tokenizer.inVPre = true
  219. currentVPreBoundary = currentOpenTag
  220. // convert dirs before this one to attributes
  221. const props = currentOpenTag!.props
  222. for (let i = 0; i < props.length; i++) {
  223. if (props[i].type === NodeTypes.DIRECTIVE) {
  224. props[i] = dirToAttr(props[i] as DirectiveNode)
  225. }
  226. }
  227. }
  228. }
  229. },
  230. ondirarg(start, end) {
  231. if (start === end) return
  232. const arg = getSlice(start, end)
  233. if (inVPre && !isVPre(currentProp!)) {
  234. ;(currentProp as AttributeNode).name += arg
  235. setLocEnd((currentProp as AttributeNode).nameLoc, end)
  236. } else {
  237. const isStatic = arg[0] !== `[`
  238. ;(currentProp as DirectiveNode).arg = createExp(
  239. isStatic ? arg : arg.slice(1, -1),
  240. isStatic,
  241. getLoc(start, end),
  242. isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT,
  243. )
  244. }
  245. },
  246. ondirmodifier(start, end) {
  247. const mod = getSlice(start, end)
  248. if (inVPre && !isVPre(currentProp!)) {
  249. ;(currentProp as AttributeNode).name += '.' + mod
  250. setLocEnd((currentProp as AttributeNode).nameLoc, end)
  251. } else if ((currentProp as DirectiveNode).name === 'slot') {
  252. // slot has no modifiers, special case for edge cases like
  253. // https://github.com/vuejs/language-tools/issues/2710
  254. const arg = (currentProp as DirectiveNode).arg
  255. if (arg) {
  256. ;(arg as SimpleExpressionNode).content += '.' + mod
  257. setLocEnd(arg.loc, end)
  258. }
  259. } else {
  260. const exp = createSimpleExpression(mod, true, getLoc(start, end))
  261. ;(currentProp as DirectiveNode).modifiers.push(exp)
  262. }
  263. },
  264. onattribdata(start, end) {
  265. currentAttrValue += getSlice(start, end)
  266. if (currentAttrStartIndex < 0) currentAttrStartIndex = start
  267. currentAttrEndIndex = end
  268. },
  269. onattribentity(char, start, end) {
  270. currentAttrValue += char
  271. if (currentAttrStartIndex < 0) currentAttrStartIndex = start
  272. currentAttrEndIndex = end
  273. },
  274. onattribnameend(end) {
  275. const start = currentProp!.loc.start.offset
  276. const name = getSlice(start, end)
  277. if (currentProp!.type === NodeTypes.DIRECTIVE) {
  278. currentProp!.rawName = name
  279. }
  280. // check duplicate attrs
  281. if (
  282. currentOpenTag!.props.some(
  283. p => (p.type === NodeTypes.DIRECTIVE ? p.rawName : p.name) === name,
  284. )
  285. ) {
  286. emitError(ErrorCodes.DUPLICATE_ATTRIBUTE, start)
  287. }
  288. },
  289. onattribend(quote, end) {
  290. if (currentOpenTag && currentProp) {
  291. // finalize end pos
  292. setLocEnd(currentProp.loc, end)
  293. if (quote !== QuoteType.NoValue) {
  294. if (__BROWSER__ && currentAttrValue.includes('&')) {
  295. currentAttrValue = currentOptions.decodeEntities!(
  296. currentAttrValue,
  297. true,
  298. )
  299. }
  300. if (currentProp.type === NodeTypes.ATTRIBUTE) {
  301. // assign value
  302. // condense whitespaces in class
  303. if (currentProp!.name === 'class') {
  304. currentAttrValue = condense(currentAttrValue).trim()
  305. }
  306. if (quote === QuoteType.Unquoted && !currentAttrValue) {
  307. emitError(ErrorCodes.MISSING_ATTRIBUTE_VALUE, end)
  308. }
  309. currentProp!.value = {
  310. type: NodeTypes.TEXT,
  311. content: currentAttrValue,
  312. loc:
  313. quote === QuoteType.Unquoted
  314. ? getLoc(currentAttrStartIndex, currentAttrEndIndex)
  315. : getLoc(currentAttrStartIndex - 1, currentAttrEndIndex + 1),
  316. }
  317. if (
  318. tokenizer.inSFCRoot &&
  319. currentOpenTag.tag === 'template' &&
  320. currentProp.name === 'lang' &&
  321. currentAttrValue &&
  322. currentAttrValue !== 'html'
  323. ) {
  324. // SFC root template with preprocessor lang, force tokenizer to
  325. // RCDATA mode
  326. tokenizer.enterRCDATA(toCharCodes(`</template`), 0)
  327. }
  328. } else {
  329. // directive
  330. let expParseMode = ExpParseMode.Normal
  331. if (!__BROWSER__) {
  332. if (currentProp.name === 'for') {
  333. expParseMode = ExpParseMode.Skip
  334. } else if (currentProp.name === 'slot') {
  335. expParseMode = ExpParseMode.Params
  336. } else if (
  337. currentProp.name === 'on' &&
  338. currentAttrValue.includes(';')
  339. ) {
  340. expParseMode = ExpParseMode.Statements
  341. }
  342. }
  343. currentProp.exp = createExp(
  344. currentAttrValue,
  345. false,
  346. getLoc(currentAttrStartIndex, currentAttrEndIndex),
  347. ConstantTypes.NOT_CONSTANT,
  348. expParseMode,
  349. )
  350. if (currentProp.name === 'for') {
  351. currentProp.forParseResult = parseForExpression(currentProp.exp)
  352. }
  353. // 2.x compat v-bind:foo.sync -> v-model:foo
  354. let syncIndex = -1
  355. if (
  356. __COMPAT__ &&
  357. currentProp.name === 'bind' &&
  358. (syncIndex = currentProp.modifiers.findIndex(
  359. mod => mod.content === 'sync',
  360. )) > -1 &&
  361. checkCompatEnabled(
  362. CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
  363. currentOptions,
  364. currentProp.loc,
  365. currentProp.arg!.loc.source,
  366. )
  367. ) {
  368. currentProp.name = 'model'
  369. currentProp.modifiers.splice(syncIndex, 1)
  370. }
  371. }
  372. }
  373. if (
  374. currentProp.type !== NodeTypes.DIRECTIVE ||
  375. currentProp.name !== 'pre'
  376. ) {
  377. currentOpenTag.props.push(currentProp)
  378. }
  379. }
  380. currentAttrValue = ''
  381. currentAttrStartIndex = currentAttrEndIndex = -1
  382. },
  383. oncomment(start, end) {
  384. if (currentOptions.comments) {
  385. addNode({
  386. type: NodeTypes.COMMENT,
  387. content: getSlice(start, end),
  388. loc: getLoc(start - 4, end + 3),
  389. })
  390. }
  391. },
  392. onend() {
  393. const end = currentInput.length
  394. // EOF ERRORS
  395. if ((__DEV__ || !__BROWSER__) && tokenizer.state !== State.Text) {
  396. switch (tokenizer.state) {
  397. case State.BeforeTagName:
  398. case State.BeforeClosingTagName:
  399. emitError(ErrorCodes.EOF_BEFORE_TAG_NAME, end)
  400. break
  401. case State.Interpolation:
  402. case State.InterpolationClose:
  403. emitError(
  404. ErrorCodes.X_MISSING_INTERPOLATION_END,
  405. tokenizer.sectionStart,
  406. )
  407. break
  408. case State.InCommentLike:
  409. if (tokenizer.currentSequence === Sequences.CdataEnd) {
  410. emitError(ErrorCodes.EOF_IN_CDATA, end)
  411. } else {
  412. emitError(ErrorCodes.EOF_IN_COMMENT, end)
  413. }
  414. break
  415. case State.InTagName:
  416. case State.InSelfClosingTag:
  417. case State.InClosingTagName:
  418. case State.BeforeAttrName:
  419. case State.InAttrName:
  420. case State.InDirName:
  421. case State.InDirArg:
  422. case State.InDirDynamicArg:
  423. case State.InDirModifier:
  424. case State.AfterAttrName:
  425. case State.BeforeAttrValue:
  426. case State.InAttrValueDq: // "
  427. case State.InAttrValueSq: // '
  428. case State.InAttrValueNq:
  429. emitError(ErrorCodes.EOF_IN_TAG, end)
  430. break
  431. default:
  432. // console.log(tokenizer.state)
  433. break
  434. }
  435. }
  436. for (let index = 0; index < stack.length; index++) {
  437. onCloseTag(stack[index], end - 1)
  438. emitError(ErrorCodes.X_MISSING_END_TAG, stack[index].loc.start.offset)
  439. }
  440. },
  441. oncdata(start, end) {
  442. if (stack[0].ns !== Namespaces.HTML) {
  443. onText(getSlice(start, end), start, end)
  444. } else {
  445. emitError(ErrorCodes.CDATA_IN_HTML_CONTENT, start - 9)
  446. }
  447. },
  448. onprocessinginstruction(start) {
  449. // ignore as we do not have runtime handling for this, only check error
  450. if ((stack[0] ? stack[0].ns : currentOptions.ns) === Namespaces.HTML) {
  451. emitError(
  452. ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
  453. start - 1,
  454. )
  455. }
  456. },
  457. })
  458. // This regex doesn't cover the case if key or index aliases have destructuring,
  459. // but those do not make sense in the first place, so this works in practice.
  460. const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
  461. const stripParensRE = /^\(|\)$/g
  462. function parseForExpression(
  463. input: SimpleExpressionNode,
  464. ): ForParseResult | undefined {
  465. const loc = input.loc
  466. const exp = input.content
  467. const inMatch = exp.match(forAliasRE)
  468. if (!inMatch) return
  469. const [, LHS, RHS] = inMatch
  470. const createAliasExpression = (
  471. content: string,
  472. offset: number,
  473. asParam = false,
  474. ) => {
  475. const start = loc.start.offset + offset
  476. const end = start + content.length
  477. return createExp(
  478. content,
  479. false,
  480. getLoc(start, end),
  481. ConstantTypes.NOT_CONSTANT,
  482. asParam ? ExpParseMode.Params : ExpParseMode.Normal,
  483. )
  484. }
  485. const result: ForParseResult = {
  486. source: createAliasExpression(RHS.trim(), exp.indexOf(RHS, LHS.length)),
  487. value: undefined,
  488. key: undefined,
  489. index: undefined,
  490. finalized: false,
  491. }
  492. let valueContent = LHS.trim().replace(stripParensRE, '').trim()
  493. const trimmedOffset = LHS.indexOf(valueContent)
  494. const iteratorMatch = valueContent.match(forIteratorRE)
  495. if (iteratorMatch) {
  496. valueContent = valueContent.replace(forIteratorRE, '').trim()
  497. const keyContent = iteratorMatch[1].trim()
  498. let keyOffset: number | undefined
  499. if (keyContent) {
  500. keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
  501. result.key = createAliasExpression(keyContent, keyOffset, true)
  502. }
  503. if (iteratorMatch[2]) {
  504. const indexContent = iteratorMatch[2].trim()
  505. if (indexContent) {
  506. result.index = createAliasExpression(
  507. indexContent,
  508. exp.indexOf(
  509. indexContent,
  510. result.key
  511. ? keyOffset! + keyContent.length
  512. : trimmedOffset + valueContent.length,
  513. ),
  514. true,
  515. )
  516. }
  517. }
  518. }
  519. if (valueContent) {
  520. result.value = createAliasExpression(valueContent, trimmedOffset, true)
  521. }
  522. return result
  523. }
  524. function getSlice(start: number, end: number) {
  525. return currentInput.slice(start, end)
  526. }
  527. function endOpenTag(end: number) {
  528. if (tokenizer.inSFCRoot) {
  529. // in SFC mode, generate locations for root-level tags' inner content.
  530. currentOpenTag!.innerLoc = getLoc(end + 1, end + 1)
  531. }
  532. addNode(currentOpenTag!)
  533. const { tag, ns } = currentOpenTag!
  534. if (ns === Namespaces.HTML && currentOptions.isPreTag(tag)) {
  535. inPre++
  536. }
  537. if (currentOptions.isVoidTag(tag)) {
  538. onCloseTag(currentOpenTag!, end)
  539. } else {
  540. stack.unshift(currentOpenTag!)
  541. if (ns === Namespaces.SVG || ns === Namespaces.MATH_ML) {
  542. tokenizer.inXML = true
  543. }
  544. }
  545. currentOpenTag = null
  546. }
  547. function onText(content: string, start: number, end: number) {
  548. if (__BROWSER__) {
  549. const tag = stack[0] && stack[0].tag
  550. if (tag !== 'script' && tag !== 'style' && content.includes('&')) {
  551. content = currentOptions.decodeEntities!(content, false)
  552. }
  553. }
  554. const parent = stack[0] || currentRoot
  555. const lastNode = parent.children[parent.children.length - 1]
  556. if (lastNode && lastNode.type === NodeTypes.TEXT) {
  557. // merge
  558. lastNode.content += content
  559. setLocEnd(lastNode.loc, end)
  560. } else {
  561. parent.children.push({
  562. type: NodeTypes.TEXT,
  563. content,
  564. loc: getLoc(start, end),
  565. })
  566. }
  567. }
  568. function onCloseTag(el: ElementNode, end: number, isImplied = false) {
  569. // attach end position
  570. if (isImplied) {
  571. // implied close, end should be backtracked to close
  572. setLocEnd(el.loc, backTrack(end, CharCodes.Lt))
  573. } else {
  574. setLocEnd(el.loc, lookAhead(end, CharCodes.Gt) + 1)
  575. }
  576. if (tokenizer.inSFCRoot) {
  577. // SFC root tag, resolve inner end
  578. if (el.children.length) {
  579. el.innerLoc!.end = extend({}, el.children[el.children.length - 1].loc.end)
  580. } else {
  581. el.innerLoc!.end = extend({}, el.innerLoc!.start)
  582. }
  583. el.innerLoc!.source = getSlice(
  584. el.innerLoc!.start.offset,
  585. el.innerLoc!.end.offset,
  586. )
  587. }
  588. // refine element type
  589. const { tag, ns, children } = el
  590. if (!inVPre) {
  591. if (tag === 'slot') {
  592. el.tagType = ElementTypes.SLOT
  593. } else if (isFragmentTemplate(el)) {
  594. el.tagType = ElementTypes.TEMPLATE
  595. } else if (isComponent(el)) {
  596. el.tagType = ElementTypes.COMPONENT
  597. }
  598. }
  599. // whitespace management
  600. if (!tokenizer.inRCDATA) {
  601. el.children = condenseWhitespace(children)
  602. }
  603. if (ns === Namespaces.HTML && currentOptions.isIgnoreNewlineTag(tag)) {
  604. // remove leading newline for <textarea> and <pre> per html spec
  605. // https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody
  606. const first = children[0]
  607. if (first && first.type === NodeTypes.TEXT) {
  608. first.content = first.content.replace(/^\r?\n/, '')
  609. }
  610. }
  611. if (ns === Namespaces.HTML && currentOptions.isPreTag(tag)) {
  612. inPre--
  613. }
  614. if (currentVPreBoundary === el) {
  615. inVPre = tokenizer.inVPre = false
  616. currentVPreBoundary = null
  617. }
  618. if (
  619. tokenizer.inXML &&
  620. (stack[0] ? stack[0].ns : currentOptions.ns) === Namespaces.HTML
  621. ) {
  622. tokenizer.inXML = false
  623. }
  624. // 2.x compat / deprecation checks
  625. if (__COMPAT__) {
  626. const props = el.props
  627. if (
  628. __DEV__ &&
  629. isCompatEnabled(
  630. CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
  631. currentOptions,
  632. )
  633. ) {
  634. let hasIf = false
  635. let hasFor = false
  636. for (let i = 0; i < props.length; i++) {
  637. const p = props[i]
  638. if (p.type === NodeTypes.DIRECTIVE) {
  639. if (p.name === 'if') {
  640. hasIf = true
  641. } else if (p.name === 'for') {
  642. hasFor = true
  643. }
  644. }
  645. if (hasIf && hasFor) {
  646. warnDeprecation(
  647. CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
  648. currentOptions,
  649. el.loc,
  650. )
  651. break
  652. }
  653. }
  654. }
  655. if (
  656. !tokenizer.inSFCRoot &&
  657. isCompatEnabled(
  658. CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
  659. currentOptions,
  660. ) &&
  661. el.tag === 'template' &&
  662. !isFragmentTemplate(el)
  663. ) {
  664. __DEV__ &&
  665. warnDeprecation(
  666. CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
  667. currentOptions,
  668. el.loc,
  669. )
  670. // unwrap
  671. const parent = stack[0] || currentRoot
  672. const index = parent.children.indexOf(el)
  673. parent.children.splice(index, 1, ...el.children)
  674. }
  675. const inlineTemplateProp = props.find(
  676. p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template',
  677. ) as AttributeNode
  678. if (
  679. inlineTemplateProp &&
  680. checkCompatEnabled(
  681. CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE,
  682. currentOptions,
  683. inlineTemplateProp.loc,
  684. ) &&
  685. el.children.length
  686. ) {
  687. inlineTemplateProp.value = {
  688. type: NodeTypes.TEXT,
  689. content: getSlice(
  690. el.children[0].loc.start.offset,
  691. el.children[el.children.length - 1].loc.end.offset,
  692. ),
  693. loc: inlineTemplateProp.loc,
  694. }
  695. }
  696. }
  697. }
  698. function lookAhead(index: number, c: number) {
  699. let i = index
  700. while (currentInput.charCodeAt(i) !== c && i < currentInput.length - 1) i++
  701. return i
  702. }
  703. function backTrack(index: number, c: number) {
  704. let i = index
  705. while (currentInput.charCodeAt(i) !== c && i >= 0) i--
  706. return i
  707. }
  708. const specialTemplateDir = new Set(['if', 'else', 'else-if', 'for', 'slot'])
  709. function isFragmentTemplate({ tag, props }: ElementNode): boolean {
  710. if (tag === 'template') {
  711. for (let i = 0; i < props.length; i++) {
  712. if (
  713. props[i].type === NodeTypes.DIRECTIVE &&
  714. specialTemplateDir.has((props[i] as DirectiveNode).name)
  715. ) {
  716. return true
  717. }
  718. }
  719. }
  720. return false
  721. }
  722. function isComponent({ tag, props }: ElementNode): boolean {
  723. if (currentOptions.isCustomElement(tag)) {
  724. return false
  725. }
  726. if (
  727. tag === 'component' ||
  728. isUpperCase(tag.charCodeAt(0)) ||
  729. isCoreComponent(tag) ||
  730. (currentOptions.isBuiltInComponent &&
  731. currentOptions.isBuiltInComponent(tag)) ||
  732. (currentOptions.isNativeTag && !currentOptions.isNativeTag(tag))
  733. ) {
  734. return true
  735. }
  736. // at this point the tag should be a native tag, but check for potential "is"
  737. // casting
  738. for (let i = 0; i < props.length; i++) {
  739. const p = props[i]
  740. if (p.type === NodeTypes.ATTRIBUTE) {
  741. if (p.name === 'is' && p.value) {
  742. if (p.value.content.startsWith('vue:')) {
  743. return true
  744. } else if (
  745. __COMPAT__ &&
  746. checkCompatEnabled(
  747. CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
  748. currentOptions,
  749. p.loc,
  750. )
  751. ) {
  752. return true
  753. }
  754. }
  755. } else if (
  756. __COMPAT__ &&
  757. // :is on plain element - only treat as component in compat mode
  758. p.name === 'bind' &&
  759. isStaticArgOf(p.arg, 'is') &&
  760. checkCompatEnabled(
  761. CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
  762. currentOptions,
  763. p.loc,
  764. )
  765. ) {
  766. return true
  767. }
  768. }
  769. return false
  770. }
  771. function isUpperCase(c: number) {
  772. return c > 64 && c < 91
  773. }
  774. const windowsNewlineRE = /\r\n/g
  775. function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] {
  776. const shouldCondense = currentOptions.whitespace !== 'preserve'
  777. let removedWhitespace = false
  778. for (let i = 0; i < nodes.length; i++) {
  779. const node = nodes[i]
  780. if (node.type === NodeTypes.TEXT) {
  781. if (!inPre) {
  782. if (isAllWhitespace(node.content)) {
  783. const prev = nodes[i - 1] && nodes[i - 1].type
  784. const next = nodes[i + 1] && nodes[i + 1].type
  785. // Remove if:
  786. // - the whitespace is the first or last node, or:
  787. // - (condense mode) the whitespace is between two comments, or:
  788. // - (condense mode) the whitespace is between comment and element, or:
  789. // - (condense mode) the whitespace is between two elements AND contains newline
  790. if (
  791. !prev ||
  792. !next ||
  793. (shouldCondense &&
  794. ((prev === NodeTypes.COMMENT &&
  795. (next === NodeTypes.COMMENT || next === NodeTypes.ELEMENT)) ||
  796. (prev === NodeTypes.ELEMENT &&
  797. (next === NodeTypes.COMMENT ||
  798. (next === NodeTypes.ELEMENT &&
  799. hasNewlineChar(node.content))))))
  800. ) {
  801. removedWhitespace = true
  802. nodes[i] = null as any
  803. } else {
  804. // Otherwise, the whitespace is condensed into a single space
  805. node.content = ' '
  806. }
  807. } else if (shouldCondense) {
  808. // in condense mode, consecutive whitespaces in text are condensed
  809. // down to a single space.
  810. node.content = condense(node.content)
  811. }
  812. } else {
  813. // #6410 normalize windows newlines in <pre>:
  814. // in SSR, browsers normalize server-rendered \r\n into a single \n
  815. // in the DOM
  816. node.content = node.content.replace(windowsNewlineRE, '\n')
  817. }
  818. }
  819. }
  820. return removedWhitespace ? nodes.filter(Boolean) : nodes
  821. }
  822. function hasNewlineChar(str: string) {
  823. for (let i = 0; i < str.length; i++) {
  824. const c = str.charCodeAt(i)
  825. if (c === CharCodes.NewLine || c === CharCodes.CarriageReturn) {
  826. return true
  827. }
  828. }
  829. return false
  830. }
  831. function condense(str: string) {
  832. let ret = ''
  833. let prevCharIsWhitespace = false
  834. for (let i = 0; i < str.length; i++) {
  835. if (isWhitespace(str.charCodeAt(i))) {
  836. if (!prevCharIsWhitespace) {
  837. ret += ' '
  838. prevCharIsWhitespace = true
  839. }
  840. } else {
  841. ret += str[i]
  842. prevCharIsWhitespace = false
  843. }
  844. }
  845. return ret
  846. }
  847. function addNode(node: TemplateChildNode) {
  848. ;(stack[0] || currentRoot).children.push(node)
  849. }
  850. function getLoc(start: number, end?: number): SourceLocation {
  851. return {
  852. start: tokenizer.getPos(start),
  853. // @ts-expect-error allow late attachment
  854. end: end == null ? end : tokenizer.getPos(end),
  855. // @ts-expect-error allow late attachment
  856. source: end == null ? end : getSlice(start, end),
  857. }
  858. }
  859. export function cloneLoc(loc: SourceLocation): SourceLocation {
  860. return getLoc(loc.start.offset, loc.end.offset)
  861. }
  862. function setLocEnd(loc: SourceLocation, end: number) {
  863. loc.end = tokenizer.getPos(end)
  864. loc.source = getSlice(loc.start.offset, end)
  865. }
  866. function dirToAttr(dir: DirectiveNode): AttributeNode {
  867. const attr: AttributeNode = {
  868. type: NodeTypes.ATTRIBUTE,
  869. name: dir.rawName!,
  870. nameLoc: getLoc(
  871. dir.loc.start.offset,
  872. dir.loc.start.offset + dir.rawName!.length,
  873. ),
  874. value: undefined,
  875. loc: dir.loc,
  876. }
  877. if (dir.exp) {
  878. // account for quotes
  879. const loc = dir.exp.loc
  880. if (loc.end.offset < dir.loc.end.offset) {
  881. loc.start.offset--
  882. loc.start.column--
  883. loc.end.offset++
  884. loc.end.column++
  885. }
  886. attr.value = {
  887. type: NodeTypes.TEXT,
  888. content: (dir.exp as SimpleExpressionNode).content,
  889. loc,
  890. }
  891. }
  892. return attr
  893. }
  894. enum ExpParseMode {
  895. Normal,
  896. Params,
  897. Statements,
  898. Skip,
  899. }
  900. function createExp(
  901. content: SimpleExpressionNode['content'],
  902. isStatic: SimpleExpressionNode['isStatic'] = false,
  903. loc: SourceLocation,
  904. constType: ConstantTypes = ConstantTypes.NOT_CONSTANT,
  905. parseMode = ExpParseMode.Normal,
  906. ) {
  907. const exp = createSimpleExpression(content, isStatic, loc, constType)
  908. if (
  909. !__BROWSER__ &&
  910. !isStatic &&
  911. currentOptions.prefixIdentifiers &&
  912. parseMode !== ExpParseMode.Skip &&
  913. content.trim()
  914. ) {
  915. if (isSimpleIdentifier(content)) {
  916. exp.ast = null // fast path
  917. return exp
  918. }
  919. try {
  920. const plugins = currentOptions.expressionPlugins
  921. const options: BabelOptions = {
  922. plugins: plugins ? [...plugins, 'typescript'] : ['typescript'],
  923. }
  924. if (parseMode === ExpParseMode.Statements) {
  925. // v-on with multi-inline-statements, pad 1 char
  926. exp.ast = parse(` ${content} `, options).program
  927. } else if (parseMode === ExpParseMode.Params) {
  928. exp.ast = parseExpression(`(${content})=>{}`, options)
  929. } else {
  930. // normal exp, wrap with parens
  931. exp.ast = parseExpression(`(${content})`, options)
  932. }
  933. } catch (e: any) {
  934. exp.ast = false // indicate an error
  935. emitError(ErrorCodes.X_INVALID_EXPRESSION, loc.start.offset, e.message)
  936. }
  937. }
  938. return exp
  939. }
  940. function emitError(code: ErrorCodes, index: number, message?: string) {
  941. currentOptions.onError(
  942. createCompilerError(code, getLoc(index, index), undefined, message),
  943. )
  944. }
  945. function reset() {
  946. tokenizer.reset()
  947. currentOpenTag = null
  948. currentProp = null
  949. currentAttrValue = ''
  950. currentAttrStartIndex = -1
  951. currentAttrEndIndex = -1
  952. stack.length = 0
  953. }
  954. export function baseParse(input: string, options?: ParserOptions): RootNode {
  955. reset()
  956. currentInput = input
  957. currentOptions = extend({}, defaultParserOptions)
  958. if (options) {
  959. let key: keyof ParserOptions
  960. for (key in options) {
  961. if (options[key] != null) {
  962. // @ts-expect-error
  963. currentOptions[key] = options[key]
  964. }
  965. }
  966. }
  967. if (__DEV__) {
  968. if (!__BROWSER__ && currentOptions.decodeEntities) {
  969. console.warn(
  970. `[@vue/compiler-core] decodeEntities option is passed but will be ` +
  971. `ignored in non-browser builds.`,
  972. )
  973. } else if (__BROWSER__ && !__TEST__ && !currentOptions.decodeEntities) {
  974. throw new Error(
  975. `[@vue/compiler-core] decodeEntities option is required in browser builds.`,
  976. )
  977. }
  978. }
  979. tokenizer.mode =
  980. currentOptions.parseMode === 'html'
  981. ? ParseMode.HTML
  982. : currentOptions.parseMode === 'sfc'
  983. ? ParseMode.SFC
  984. : ParseMode.BASE
  985. tokenizer.inXML =
  986. currentOptions.ns === Namespaces.SVG ||
  987. currentOptions.ns === Namespaces.MATH_ML
  988. const delimiters = options && options.delimiters
  989. if (delimiters) {
  990. tokenizer.delimiterOpen = toCharCodes(delimiters[0])
  991. tokenizer.delimiterClose = toCharCodes(delimiters[1])
  992. }
  993. const root = (currentRoot = createRoot([], input))
  994. tokenizer.parse(currentInput)
  995. root.loc = getLoc(0, input.length)
  996. root.children = condenseWhitespace(root.children)
  997. currentRoot = null
  998. return root
  999. }