parse.spec.ts 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755
  1. import { ParserOptions } from '../src/options'
  2. import { baseParse, TextModes } from '../src/parse'
  3. import { ErrorCodes } from '../src/errors'
  4. import {
  5. CommentNode,
  6. ElementNode,
  7. ElementTypes,
  8. Namespaces,
  9. NodeTypes,
  10. Position,
  11. TextNode,
  12. AttributeNode,
  13. InterpolationNode
  14. } from '../src/ast'
  15. describe('compiler: parse', () => {
  16. describe('Text', () => {
  17. test('simple text', () => {
  18. const ast = baseParse('some text')
  19. const text = ast.children[0] as TextNode
  20. expect(text).toStrictEqual({
  21. type: NodeTypes.TEXT,
  22. content: 'some text',
  23. loc: {
  24. start: { offset: 0, line: 1, column: 1 },
  25. end: { offset: 9, line: 1, column: 10 },
  26. source: 'some text'
  27. }
  28. })
  29. })
  30. test('simple text with invalid end tag', () => {
  31. const ast = baseParse('some text</div>', {
  32. onError: () => {}
  33. })
  34. const text = ast.children[0] as TextNode
  35. expect(text).toStrictEqual({
  36. type: NodeTypes.TEXT,
  37. content: 'some text',
  38. loc: {
  39. start: { offset: 0, line: 1, column: 1 },
  40. end: { offset: 9, line: 1, column: 10 },
  41. source: 'some text'
  42. }
  43. })
  44. })
  45. test('text with interpolation', () => {
  46. const ast = baseParse('some {{ foo + bar }} text')
  47. const text1 = ast.children[0] as TextNode
  48. const text2 = ast.children[2] as TextNode
  49. expect(text1).toStrictEqual({
  50. type: NodeTypes.TEXT,
  51. content: 'some ',
  52. loc: {
  53. start: { offset: 0, line: 1, column: 1 },
  54. end: { offset: 5, line: 1, column: 6 },
  55. source: 'some '
  56. }
  57. })
  58. expect(text2).toStrictEqual({
  59. type: NodeTypes.TEXT,
  60. content: ' text',
  61. loc: {
  62. start: { offset: 20, line: 1, column: 21 },
  63. end: { offset: 25, line: 1, column: 26 },
  64. source: ' text'
  65. }
  66. })
  67. })
  68. test('text with interpolation which has `<`', () => {
  69. const ast = baseParse('some {{ a<b && c>d }} text')
  70. const text1 = ast.children[0] as TextNode
  71. const text2 = ast.children[2] as TextNode
  72. expect(text1).toStrictEqual({
  73. type: NodeTypes.TEXT,
  74. content: 'some ',
  75. loc: {
  76. start: { offset: 0, line: 1, column: 1 },
  77. end: { offset: 5, line: 1, column: 6 },
  78. source: 'some '
  79. }
  80. })
  81. expect(text2).toStrictEqual({
  82. type: NodeTypes.TEXT,
  83. content: ' text',
  84. loc: {
  85. start: { offset: 21, line: 1, column: 22 },
  86. end: { offset: 26, line: 1, column: 27 },
  87. source: ' text'
  88. }
  89. })
  90. })
  91. test('text with mix of tags and interpolations', () => {
  92. const ast = baseParse('some <span>{{ foo < bar + foo }} text</span>')
  93. const text1 = ast.children[0] as TextNode
  94. const text2 = (ast.children[1] as ElementNode).children![1] as TextNode
  95. expect(text1).toStrictEqual({
  96. type: NodeTypes.TEXT,
  97. content: 'some ',
  98. loc: {
  99. start: { offset: 0, line: 1, column: 1 },
  100. end: { offset: 5, line: 1, column: 6 },
  101. source: 'some '
  102. }
  103. })
  104. expect(text2).toStrictEqual({
  105. type: NodeTypes.TEXT,
  106. content: ' text',
  107. loc: {
  108. start: { offset: 32, line: 1, column: 33 },
  109. end: { offset: 37, line: 1, column: 38 },
  110. source: ' text'
  111. }
  112. })
  113. })
  114. test('lonly "<" don\'t separate nodes', () => {
  115. const ast = baseParse('a < b', {
  116. onError: err => {
  117. if (err.code !== ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME) {
  118. throw err
  119. }
  120. }
  121. })
  122. const text = ast.children[0] as TextNode
  123. expect(text).toStrictEqual({
  124. type: NodeTypes.TEXT,
  125. content: 'a < b',
  126. loc: {
  127. start: { offset: 0, line: 1, column: 1 },
  128. end: { offset: 5, line: 1, column: 6 },
  129. source: 'a < b'
  130. }
  131. })
  132. })
  133. test('lonly "{{" don\'t separate nodes', () => {
  134. const ast = baseParse('a {{ b', {
  135. onError: error => {
  136. if (error.code !== ErrorCodes.X_MISSING_INTERPOLATION_END) {
  137. throw error
  138. }
  139. }
  140. })
  141. const text = ast.children[0] as TextNode
  142. expect(text).toStrictEqual({
  143. type: NodeTypes.TEXT,
  144. content: 'a {{ b',
  145. loc: {
  146. start: { offset: 0, line: 1, column: 1 },
  147. end: { offset: 6, line: 1, column: 7 },
  148. source: 'a {{ b'
  149. }
  150. })
  151. })
  152. test('HTML entities compatibility in text (https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state).', () => {
  153. const spy = jest.fn()
  154. const ast = baseParse('&ampersand;', {
  155. namedCharacterReferences: { amp: '&' },
  156. onError: spy
  157. })
  158. const text = ast.children[0] as TextNode
  159. expect(text).toStrictEqual({
  160. type: NodeTypes.TEXT,
  161. content: '&ersand;',
  162. loc: {
  163. start: { offset: 0, line: 1, column: 1 },
  164. end: { offset: 11, line: 1, column: 12 },
  165. source: '&ampersand;'
  166. }
  167. })
  168. expect(spy.mock.calls).toMatchObject([
  169. [
  170. {
  171. code: ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE,
  172. loc: {
  173. start: { offset: 4, line: 1, column: 5 }
  174. }
  175. }
  176. ]
  177. ])
  178. })
  179. test('HTML entities compatibility in attribute (https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state).', () => {
  180. const spy = jest.fn()
  181. const ast = baseParse(
  182. '<div a="&ampersand;" b="&amp;ersand;" c="&amp!"></div>',
  183. {
  184. namedCharacterReferences: { amp: '&', 'amp;': '&' },
  185. onError: spy
  186. }
  187. )
  188. const element = ast.children[0] as ElementNode
  189. const text1 = (element.props[0] as AttributeNode).value
  190. const text2 = (element.props[1] as AttributeNode).value
  191. const text3 = (element.props[2] as AttributeNode).value
  192. expect(text1).toStrictEqual({
  193. type: NodeTypes.TEXT,
  194. content: '&ampersand;',
  195. loc: {
  196. start: { offset: 7, line: 1, column: 8 },
  197. end: { offset: 20, line: 1, column: 21 },
  198. source: '"&ampersand;"'
  199. }
  200. })
  201. expect(text2).toStrictEqual({
  202. type: NodeTypes.TEXT,
  203. content: '&ersand;',
  204. loc: {
  205. start: { offset: 23, line: 1, column: 24 },
  206. end: { offset: 37, line: 1, column: 38 },
  207. source: '"&amp;ersand;"'
  208. }
  209. })
  210. expect(text3).toStrictEqual({
  211. type: NodeTypes.TEXT,
  212. content: '&!',
  213. loc: {
  214. start: { offset: 40, line: 1, column: 41 },
  215. end: { offset: 47, line: 1, column: 48 },
  216. source: '"&amp!"'
  217. }
  218. })
  219. expect(spy.mock.calls).toMatchObject([
  220. [
  221. {
  222. code: ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE,
  223. loc: {
  224. start: { offset: 45, line: 1, column: 46 }
  225. }
  226. }
  227. ]
  228. ])
  229. })
  230. test('Some control character reference should be replaced.', () => {
  231. const spy = jest.fn()
  232. const ast = baseParse('&#x86;', { onError: spy })
  233. const text = ast.children[0] as TextNode
  234. expect(text).toStrictEqual({
  235. type: NodeTypes.TEXT,
  236. content: '†',
  237. loc: {
  238. start: { offset: 0, line: 1, column: 1 },
  239. end: { offset: 6, line: 1, column: 7 },
  240. source: '&#x86;'
  241. }
  242. })
  243. expect(spy.mock.calls).toMatchObject([
  244. [
  245. {
  246. code: ErrorCodes.CONTROL_CHARACTER_REFERENCE,
  247. loc: {
  248. start: { offset: 0, line: 1, column: 1 }
  249. }
  250. }
  251. ]
  252. ])
  253. })
  254. })
  255. describe('Interpolation', () => {
  256. test('simple interpolation', () => {
  257. const ast = baseParse('{{message}}')
  258. const interpolation = ast.children[0] as InterpolationNode
  259. expect(interpolation).toStrictEqual({
  260. type: NodeTypes.INTERPOLATION,
  261. content: {
  262. type: NodeTypes.SIMPLE_EXPRESSION,
  263. content: `message`,
  264. isStatic: false,
  265. isConstant: false,
  266. loc: {
  267. start: { offset: 2, line: 1, column: 3 },
  268. end: { offset: 9, line: 1, column: 10 },
  269. source: `message`
  270. }
  271. },
  272. loc: {
  273. start: { offset: 0, line: 1, column: 1 },
  274. end: { offset: 11, line: 1, column: 12 },
  275. source: '{{message}}'
  276. }
  277. })
  278. })
  279. test('it can have tag-like notation', () => {
  280. const ast = baseParse('{{ a<b }}')
  281. const interpolation = ast.children[0] as InterpolationNode
  282. expect(interpolation).toStrictEqual({
  283. type: NodeTypes.INTERPOLATION,
  284. content: {
  285. type: NodeTypes.SIMPLE_EXPRESSION,
  286. content: `a<b`,
  287. isStatic: false,
  288. isConstant: false,
  289. loc: {
  290. start: { offset: 3, line: 1, column: 4 },
  291. end: { offset: 6, line: 1, column: 7 },
  292. source: 'a<b'
  293. }
  294. },
  295. loc: {
  296. start: { offset: 0, line: 1, column: 1 },
  297. end: { offset: 9, line: 1, column: 10 },
  298. source: '{{ a<b }}'
  299. }
  300. })
  301. })
  302. test('it can have tag-like notation (2)', () => {
  303. const ast = baseParse('{{ a<b }}{{ c>d }}')
  304. const interpolation1 = ast.children[0] as InterpolationNode
  305. const interpolation2 = ast.children[1] as InterpolationNode
  306. expect(interpolation1).toStrictEqual({
  307. type: NodeTypes.INTERPOLATION,
  308. content: {
  309. type: NodeTypes.SIMPLE_EXPRESSION,
  310. content: `a<b`,
  311. isStatic: false,
  312. isConstant: false,
  313. loc: {
  314. start: { offset: 3, line: 1, column: 4 },
  315. end: { offset: 6, line: 1, column: 7 },
  316. source: 'a<b'
  317. }
  318. },
  319. loc: {
  320. start: { offset: 0, line: 1, column: 1 },
  321. end: { offset: 9, line: 1, column: 10 },
  322. source: '{{ a<b }}'
  323. }
  324. })
  325. expect(interpolation2).toStrictEqual({
  326. type: NodeTypes.INTERPOLATION,
  327. content: {
  328. type: NodeTypes.SIMPLE_EXPRESSION,
  329. isStatic: false,
  330. isConstant: false,
  331. content: 'c>d',
  332. loc: {
  333. start: { offset: 12, line: 1, column: 13 },
  334. end: { offset: 15, line: 1, column: 16 },
  335. source: 'c>d'
  336. }
  337. },
  338. loc: {
  339. start: { offset: 9, line: 1, column: 10 },
  340. end: { offset: 18, line: 1, column: 19 },
  341. source: '{{ c>d }}'
  342. }
  343. })
  344. })
  345. test('it can have tag-like notation (3)', () => {
  346. const ast = baseParse('<div>{{ "</div>" }}</div>')
  347. const element = ast.children[0] as ElementNode
  348. const interpolation = element.children[0] as InterpolationNode
  349. expect(interpolation).toStrictEqual({
  350. type: NodeTypes.INTERPOLATION,
  351. content: {
  352. type: NodeTypes.SIMPLE_EXPRESSION,
  353. isStatic: false,
  354. // The `isConstant` is the default value and will be determined in `transformExpression`.
  355. isConstant: false,
  356. content: '"</div>"',
  357. loc: {
  358. start: { offset: 8, line: 1, column: 9 },
  359. end: { offset: 16, line: 1, column: 17 },
  360. source: '"</div>"'
  361. }
  362. },
  363. loc: {
  364. start: { offset: 5, line: 1, column: 6 },
  365. end: { offset: 19, line: 1, column: 20 },
  366. source: '{{ "</div>" }}'
  367. }
  368. })
  369. })
  370. test('custom delimiters', () => {
  371. const ast = baseParse('<p>{msg}</p>', {
  372. delimiters: ['{', '}']
  373. })
  374. const element = ast.children[0] as ElementNode
  375. const interpolation = element.children[0] as InterpolationNode
  376. expect(interpolation).toStrictEqual({
  377. type: NodeTypes.INTERPOLATION,
  378. content: {
  379. type: NodeTypes.SIMPLE_EXPRESSION,
  380. content: `msg`,
  381. isStatic: false,
  382. isConstant: false,
  383. loc: {
  384. start: { offset: 4, line: 1, column: 5 },
  385. end: { offset: 7, line: 1, column: 8 },
  386. source: 'msg'
  387. }
  388. },
  389. loc: {
  390. start: { offset: 3, line: 1, column: 4 },
  391. end: { offset: 8, line: 1, column: 9 },
  392. source: '{msg}'
  393. }
  394. })
  395. })
  396. })
  397. describe('Comment', () => {
  398. test('empty comment', () => {
  399. const ast = baseParse('<!---->')
  400. const comment = ast.children[0] as CommentNode
  401. expect(comment).toStrictEqual({
  402. type: NodeTypes.COMMENT,
  403. content: '',
  404. loc: {
  405. start: { offset: 0, line: 1, column: 1 },
  406. end: { offset: 7, line: 1, column: 8 },
  407. source: '<!---->'
  408. }
  409. })
  410. })
  411. test('simple comment', () => {
  412. const ast = baseParse('<!--abc-->')
  413. const comment = ast.children[0] as CommentNode
  414. expect(comment).toStrictEqual({
  415. type: NodeTypes.COMMENT,
  416. content: 'abc',
  417. loc: {
  418. start: { offset: 0, line: 1, column: 1 },
  419. end: { offset: 10, line: 1, column: 11 },
  420. source: '<!--abc-->'
  421. }
  422. })
  423. })
  424. test('two comments', () => {
  425. const ast = baseParse('<!--abc--><!--def-->')
  426. const comment1 = ast.children[0] as CommentNode
  427. const comment2 = ast.children[1] as CommentNode
  428. expect(comment1).toStrictEqual({
  429. type: NodeTypes.COMMENT,
  430. content: 'abc',
  431. loc: {
  432. start: { offset: 0, line: 1, column: 1 },
  433. end: { offset: 10, line: 1, column: 11 },
  434. source: '<!--abc-->'
  435. }
  436. })
  437. expect(comment2).toStrictEqual({
  438. type: NodeTypes.COMMENT,
  439. content: 'def',
  440. loc: {
  441. start: { offset: 10, line: 1, column: 11 },
  442. end: { offset: 20, line: 1, column: 21 },
  443. source: '<!--def-->'
  444. }
  445. })
  446. })
  447. })
  448. describe('Element', () => {
  449. test('simple div', () => {
  450. const ast = baseParse('<div>hello</div>')
  451. const element = ast.children[0] as ElementNode
  452. expect(element).toStrictEqual({
  453. type: NodeTypes.ELEMENT,
  454. ns: Namespaces.HTML,
  455. tag: 'div',
  456. tagType: ElementTypes.ELEMENT,
  457. codegenNode: undefined,
  458. props: [],
  459. isSelfClosing: false,
  460. children: [
  461. {
  462. type: NodeTypes.TEXT,
  463. content: 'hello',
  464. loc: {
  465. start: { offset: 5, line: 1, column: 6 },
  466. end: { offset: 10, line: 1, column: 11 },
  467. source: 'hello'
  468. }
  469. }
  470. ],
  471. loc: {
  472. start: { offset: 0, line: 1, column: 1 },
  473. end: { offset: 16, line: 1, column: 17 },
  474. source: '<div>hello</div>'
  475. }
  476. })
  477. })
  478. test('empty', () => {
  479. const ast = baseParse('<div></div>')
  480. const element = ast.children[0] as ElementNode
  481. expect(element).toStrictEqual({
  482. type: NodeTypes.ELEMENT,
  483. ns: Namespaces.HTML,
  484. tag: 'div',
  485. tagType: ElementTypes.ELEMENT,
  486. codegenNode: undefined,
  487. props: [],
  488. isSelfClosing: false,
  489. children: [],
  490. loc: {
  491. start: { offset: 0, line: 1, column: 1 },
  492. end: { offset: 11, line: 1, column: 12 },
  493. source: '<div></div>'
  494. }
  495. })
  496. })
  497. test('self closing', () => {
  498. const ast = baseParse('<div/>after')
  499. const element = ast.children[0] as ElementNode
  500. expect(element).toStrictEqual({
  501. type: NodeTypes.ELEMENT,
  502. ns: Namespaces.HTML,
  503. tag: 'div',
  504. tagType: ElementTypes.ELEMENT,
  505. codegenNode: undefined,
  506. props: [],
  507. isSelfClosing: true,
  508. children: [],
  509. loc: {
  510. start: { offset: 0, line: 1, column: 1 },
  511. end: { offset: 6, line: 1, column: 7 },
  512. source: '<div/>'
  513. }
  514. })
  515. })
  516. test('void element', () => {
  517. const ast = baseParse('<img>after', {
  518. isVoidTag: tag => tag === 'img'
  519. })
  520. const element = ast.children[0] as ElementNode
  521. expect(element).toStrictEqual({
  522. type: NodeTypes.ELEMENT,
  523. ns: Namespaces.HTML,
  524. tag: 'img',
  525. tagType: ElementTypes.ELEMENT,
  526. codegenNode: undefined,
  527. props: [],
  528. isSelfClosing: false,
  529. children: [],
  530. loc: {
  531. start: { offset: 0, line: 1, column: 1 },
  532. end: { offset: 5, line: 1, column: 6 },
  533. source: '<img>'
  534. }
  535. })
  536. })
  537. test('native element with `isNativeTag`', () => {
  538. const ast = baseParse('<div></div><comp></comp><Comp></Comp>', {
  539. isNativeTag: tag => tag === 'div'
  540. })
  541. expect(ast.children[0]).toMatchObject({
  542. type: NodeTypes.ELEMENT,
  543. tag: 'div',
  544. tagType: ElementTypes.ELEMENT
  545. })
  546. expect(ast.children[1]).toMatchObject({
  547. type: NodeTypes.ELEMENT,
  548. tag: 'comp',
  549. tagType: ElementTypes.COMPONENT
  550. })
  551. expect(ast.children[2]).toMatchObject({
  552. type: NodeTypes.ELEMENT,
  553. tag: 'Comp',
  554. tagType: ElementTypes.COMPONENT
  555. })
  556. })
  557. test('native element without `isNativeTag`', () => {
  558. const ast = baseParse('<div></div><comp></comp><Comp></Comp>')
  559. expect(ast.children[0]).toMatchObject({
  560. type: NodeTypes.ELEMENT,
  561. tag: 'div',
  562. tagType: ElementTypes.ELEMENT
  563. })
  564. expect(ast.children[1]).toMatchObject({
  565. type: NodeTypes.ELEMENT,
  566. tag: 'comp',
  567. tagType: ElementTypes.ELEMENT
  568. })
  569. expect(ast.children[2]).toMatchObject({
  570. type: NodeTypes.ELEMENT,
  571. tag: 'Comp',
  572. tagType: ElementTypes.COMPONENT
  573. })
  574. })
  575. test('custom element', () => {
  576. const ast = baseParse('<div></div><comp></comp>', {
  577. isNativeTag: tag => tag === 'div',
  578. isCustomElement: tag => tag === 'comp'
  579. })
  580. expect(ast.children[0]).toMatchObject({
  581. type: NodeTypes.ELEMENT,
  582. tag: 'div',
  583. tagType: ElementTypes.ELEMENT
  584. })
  585. expect(ast.children[1]).toMatchObject({
  586. type: NodeTypes.ELEMENT,
  587. tag: 'comp',
  588. tagType: ElementTypes.ELEMENT
  589. })
  590. })
  591. test('attribute with no value', () => {
  592. const ast = baseParse('<div id></div>')
  593. const element = ast.children[0] as ElementNode
  594. expect(element).toStrictEqual({
  595. type: NodeTypes.ELEMENT,
  596. ns: Namespaces.HTML,
  597. tag: 'div',
  598. tagType: ElementTypes.ELEMENT,
  599. codegenNode: undefined,
  600. props: [
  601. {
  602. type: NodeTypes.ATTRIBUTE,
  603. name: 'id',
  604. value: undefined,
  605. loc: {
  606. start: { offset: 5, line: 1, column: 6 },
  607. end: { offset: 7, line: 1, column: 8 },
  608. source: 'id'
  609. }
  610. }
  611. ],
  612. isSelfClosing: false,
  613. children: [],
  614. loc: {
  615. start: { offset: 0, line: 1, column: 1 },
  616. end: { offset: 14, line: 1, column: 15 },
  617. source: '<div id></div>'
  618. }
  619. })
  620. })
  621. test('attribute with empty value, double quote', () => {
  622. const ast = baseParse('<div id=""></div>')
  623. const element = ast.children[0] as ElementNode
  624. expect(element).toStrictEqual({
  625. type: NodeTypes.ELEMENT,
  626. ns: Namespaces.HTML,
  627. tag: 'div',
  628. tagType: ElementTypes.ELEMENT,
  629. codegenNode: undefined,
  630. props: [
  631. {
  632. type: NodeTypes.ATTRIBUTE,
  633. name: 'id',
  634. value: {
  635. type: NodeTypes.TEXT,
  636. content: '',
  637. loc: {
  638. start: { offset: 8, line: 1, column: 9 },
  639. end: { offset: 10, line: 1, column: 11 },
  640. source: '""'
  641. }
  642. },
  643. loc: {
  644. start: { offset: 5, line: 1, column: 6 },
  645. end: { offset: 10, line: 1, column: 11 },
  646. source: 'id=""'
  647. }
  648. }
  649. ],
  650. isSelfClosing: false,
  651. children: [],
  652. loc: {
  653. start: { offset: 0, line: 1, column: 1 },
  654. end: { offset: 17, line: 1, column: 18 },
  655. source: '<div id=""></div>'
  656. }
  657. })
  658. })
  659. test('attribute with empty value, single quote', () => {
  660. const ast = baseParse("<div id=''></div>")
  661. const element = ast.children[0] as ElementNode
  662. expect(element).toStrictEqual({
  663. type: NodeTypes.ELEMENT,
  664. ns: Namespaces.HTML,
  665. tag: 'div',
  666. tagType: ElementTypes.ELEMENT,
  667. codegenNode: undefined,
  668. props: [
  669. {
  670. type: NodeTypes.ATTRIBUTE,
  671. name: 'id',
  672. value: {
  673. type: NodeTypes.TEXT,
  674. content: '',
  675. loc: {
  676. start: { offset: 8, line: 1, column: 9 },
  677. end: { offset: 10, line: 1, column: 11 },
  678. source: "''"
  679. }
  680. },
  681. loc: {
  682. start: { offset: 5, line: 1, column: 6 },
  683. end: { offset: 10, line: 1, column: 11 },
  684. source: "id=''"
  685. }
  686. }
  687. ],
  688. isSelfClosing: false,
  689. children: [],
  690. loc: {
  691. start: { offset: 0, line: 1, column: 1 },
  692. end: { offset: 17, line: 1, column: 18 },
  693. source: "<div id=''></div>"
  694. }
  695. })
  696. })
  697. test('attribute with value, double quote', () => {
  698. const ast = baseParse('<div id=">\'"></div>')
  699. const element = ast.children[0] as ElementNode
  700. expect(element).toStrictEqual({
  701. type: NodeTypes.ELEMENT,
  702. ns: Namespaces.HTML,
  703. tag: 'div',
  704. tagType: ElementTypes.ELEMENT,
  705. codegenNode: undefined,
  706. props: [
  707. {
  708. type: NodeTypes.ATTRIBUTE,
  709. name: 'id',
  710. value: {
  711. type: NodeTypes.TEXT,
  712. content: ">'",
  713. loc: {
  714. start: { offset: 8, line: 1, column: 9 },
  715. end: { offset: 12, line: 1, column: 13 },
  716. source: '">\'"'
  717. }
  718. },
  719. loc: {
  720. start: { offset: 5, line: 1, column: 6 },
  721. end: { offset: 12, line: 1, column: 13 },
  722. source: 'id=">\'"'
  723. }
  724. }
  725. ],
  726. isSelfClosing: false,
  727. children: [],
  728. loc: {
  729. start: { offset: 0, line: 1, column: 1 },
  730. end: { offset: 19, line: 1, column: 20 },
  731. source: '<div id=">\'"></div>'
  732. }
  733. })
  734. })
  735. test('attribute with value, single quote', () => {
  736. const ast = baseParse("<div id='>\"'></div>")
  737. const element = ast.children[0] as ElementNode
  738. expect(element).toStrictEqual({
  739. type: NodeTypes.ELEMENT,
  740. ns: Namespaces.HTML,
  741. tag: 'div',
  742. tagType: ElementTypes.ELEMENT,
  743. codegenNode: undefined,
  744. props: [
  745. {
  746. type: NodeTypes.ATTRIBUTE,
  747. name: 'id',
  748. value: {
  749. type: NodeTypes.TEXT,
  750. content: '>"',
  751. loc: {
  752. start: { offset: 8, line: 1, column: 9 },
  753. end: { offset: 12, line: 1, column: 13 },
  754. source: "'>\"'"
  755. }
  756. },
  757. loc: {
  758. start: { offset: 5, line: 1, column: 6 },
  759. end: { offset: 12, line: 1, column: 13 },
  760. source: "id='>\"'"
  761. }
  762. }
  763. ],
  764. isSelfClosing: false,
  765. children: [],
  766. loc: {
  767. start: { offset: 0, line: 1, column: 1 },
  768. end: { offset: 19, line: 1, column: 20 },
  769. source: "<div id='>\"'></div>"
  770. }
  771. })
  772. })
  773. test('attribute with value, unquoted', () => {
  774. const ast = baseParse('<div id=a/></div>')
  775. const element = ast.children[0] as ElementNode
  776. expect(element).toStrictEqual({
  777. type: NodeTypes.ELEMENT,
  778. ns: Namespaces.HTML,
  779. tag: 'div',
  780. tagType: ElementTypes.ELEMENT,
  781. codegenNode: undefined,
  782. props: [
  783. {
  784. type: NodeTypes.ATTRIBUTE,
  785. name: 'id',
  786. value: {
  787. type: NodeTypes.TEXT,
  788. content: 'a/',
  789. loc: {
  790. start: { offset: 8, line: 1, column: 9 },
  791. end: { offset: 10, line: 1, column: 11 },
  792. source: 'a/'
  793. }
  794. },
  795. loc: {
  796. start: { offset: 5, line: 1, column: 6 },
  797. end: { offset: 10, line: 1, column: 11 },
  798. source: 'id=a/'
  799. }
  800. }
  801. ],
  802. isSelfClosing: false,
  803. children: [],
  804. loc: {
  805. start: { offset: 0, line: 1, column: 1 },
  806. end: { offset: 17, line: 1, column: 18 },
  807. source: '<div id=a/></div>'
  808. }
  809. })
  810. })
  811. test('multiple attributes', () => {
  812. const ast = baseParse('<div id=a class="c" inert style=\'\'></div>')
  813. const element = ast.children[0] as ElementNode
  814. expect(element).toStrictEqual({
  815. type: NodeTypes.ELEMENT,
  816. ns: Namespaces.HTML,
  817. tag: 'div',
  818. tagType: ElementTypes.ELEMENT,
  819. codegenNode: undefined,
  820. props: [
  821. {
  822. type: NodeTypes.ATTRIBUTE,
  823. name: 'id',
  824. value: {
  825. type: NodeTypes.TEXT,
  826. content: 'a',
  827. loc: {
  828. start: { offset: 8, line: 1, column: 9 },
  829. end: { offset: 9, line: 1, column: 10 },
  830. source: 'a'
  831. }
  832. },
  833. loc: {
  834. start: { offset: 5, line: 1, column: 6 },
  835. end: { offset: 9, line: 1, column: 10 },
  836. source: 'id=a'
  837. }
  838. },
  839. {
  840. type: NodeTypes.ATTRIBUTE,
  841. name: 'class',
  842. value: {
  843. type: NodeTypes.TEXT,
  844. content: 'c',
  845. loc: {
  846. start: { offset: 16, line: 1, column: 17 },
  847. end: { offset: 19, line: 1, column: 20 },
  848. source: '"c"'
  849. }
  850. },
  851. loc: {
  852. start: { offset: 10, line: 1, column: 11 },
  853. end: { offset: 19, line: 1, column: 20 },
  854. source: 'class="c"'
  855. }
  856. },
  857. {
  858. type: NodeTypes.ATTRIBUTE,
  859. name: 'inert',
  860. value: undefined,
  861. loc: {
  862. start: { offset: 20, line: 1, column: 21 },
  863. end: { offset: 25, line: 1, column: 26 },
  864. source: 'inert'
  865. }
  866. },
  867. {
  868. type: NodeTypes.ATTRIBUTE,
  869. name: 'style',
  870. value: {
  871. type: NodeTypes.TEXT,
  872. content: '',
  873. loc: {
  874. start: { offset: 32, line: 1, column: 33 },
  875. end: { offset: 34, line: 1, column: 35 },
  876. source: "''"
  877. }
  878. },
  879. loc: {
  880. start: { offset: 26, line: 1, column: 27 },
  881. end: { offset: 34, line: 1, column: 35 },
  882. source: "style=''"
  883. }
  884. }
  885. ],
  886. isSelfClosing: false,
  887. children: [],
  888. loc: {
  889. start: { offset: 0, line: 1, column: 1 },
  890. end: { offset: 41, line: 1, column: 42 },
  891. source: '<div id=a class="c" inert style=\'\'></div>'
  892. }
  893. })
  894. })
  895. test('directive with no value', () => {
  896. const ast = baseParse('<div v-if/>')
  897. const directive = (ast.children[0] as ElementNode).props[0]
  898. expect(directive).toStrictEqual({
  899. type: NodeTypes.DIRECTIVE,
  900. name: 'if',
  901. arg: undefined,
  902. modifiers: [],
  903. exp: undefined,
  904. loc: {
  905. start: { offset: 5, line: 1, column: 6 },
  906. end: { offset: 9, line: 1, column: 10 },
  907. source: 'v-if'
  908. }
  909. })
  910. })
  911. test('directive with value', () => {
  912. const ast = baseParse('<div v-if="a"/>')
  913. const directive = (ast.children[0] as ElementNode).props[0]
  914. expect(directive).toStrictEqual({
  915. type: NodeTypes.DIRECTIVE,
  916. name: 'if',
  917. arg: undefined,
  918. modifiers: [],
  919. exp: {
  920. type: NodeTypes.SIMPLE_EXPRESSION,
  921. content: 'a',
  922. isStatic: false,
  923. isConstant: false,
  924. loc: {
  925. start: { offset: 11, line: 1, column: 12 },
  926. end: { offset: 12, line: 1, column: 13 },
  927. source: 'a'
  928. }
  929. },
  930. loc: {
  931. start: { offset: 5, line: 1, column: 6 },
  932. end: { offset: 13, line: 1, column: 14 },
  933. source: 'v-if="a"'
  934. }
  935. })
  936. })
  937. test('directive with argument', () => {
  938. const ast = baseParse('<div v-on:click/>')
  939. const directive = (ast.children[0] as ElementNode).props[0]
  940. expect(directive).toStrictEqual({
  941. type: NodeTypes.DIRECTIVE,
  942. name: 'on',
  943. arg: {
  944. type: NodeTypes.SIMPLE_EXPRESSION,
  945. content: 'click',
  946. isStatic: true,
  947. isConstant: true,
  948. loc: {
  949. source: 'click',
  950. start: {
  951. column: 11,
  952. line: 1,
  953. offset: 10
  954. },
  955. end: {
  956. column: 16,
  957. line: 1,
  958. offset: 15
  959. }
  960. }
  961. },
  962. modifiers: [],
  963. exp: undefined,
  964. loc: {
  965. start: { offset: 5, line: 1, column: 6 },
  966. end: { offset: 15, line: 1, column: 16 },
  967. source: 'v-on:click'
  968. }
  969. })
  970. })
  971. test('directive with a modifier', () => {
  972. const ast = baseParse('<div v-on.enter/>')
  973. const directive = (ast.children[0] as ElementNode).props[0]
  974. expect(directive).toStrictEqual({
  975. type: NodeTypes.DIRECTIVE,
  976. name: 'on',
  977. arg: undefined,
  978. modifiers: ['enter'],
  979. exp: undefined,
  980. loc: {
  981. start: { offset: 5, line: 1, column: 6 },
  982. end: { offset: 15, line: 1, column: 16 },
  983. source: 'v-on.enter'
  984. }
  985. })
  986. })
  987. test('directive with two modifiers', () => {
  988. const ast = baseParse('<div v-on.enter.exact/>')
  989. const directive = (ast.children[0] as ElementNode).props[0]
  990. expect(directive).toStrictEqual({
  991. type: NodeTypes.DIRECTIVE,
  992. name: 'on',
  993. arg: undefined,
  994. modifiers: ['enter', 'exact'],
  995. exp: undefined,
  996. loc: {
  997. start: { offset: 5, line: 1, column: 6 },
  998. end: { offset: 21, line: 1, column: 22 },
  999. source: 'v-on.enter.exact'
  1000. }
  1001. })
  1002. })
  1003. test('directive with argument and modifiers', () => {
  1004. const ast = baseParse('<div v-on:click.enter.exact/>')
  1005. const directive = (ast.children[0] as ElementNode).props[0]
  1006. expect(directive).toStrictEqual({
  1007. type: NodeTypes.DIRECTIVE,
  1008. name: 'on',
  1009. arg: {
  1010. type: NodeTypes.SIMPLE_EXPRESSION,
  1011. content: 'click',
  1012. isStatic: true,
  1013. isConstant: true,
  1014. loc: {
  1015. source: 'click',
  1016. start: {
  1017. column: 11,
  1018. line: 1,
  1019. offset: 10
  1020. },
  1021. end: {
  1022. column: 16,
  1023. line: 1,
  1024. offset: 15
  1025. }
  1026. }
  1027. },
  1028. modifiers: ['enter', 'exact'],
  1029. exp: undefined,
  1030. loc: {
  1031. start: { offset: 5, line: 1, column: 6 },
  1032. end: { offset: 27, line: 1, column: 28 },
  1033. source: 'v-on:click.enter.exact'
  1034. }
  1035. })
  1036. })
  1037. test('v-bind shorthand', () => {
  1038. const ast = baseParse('<div :a=b />')
  1039. const directive = (ast.children[0] as ElementNode).props[0]
  1040. expect(directive).toStrictEqual({
  1041. type: NodeTypes.DIRECTIVE,
  1042. name: 'bind',
  1043. arg: {
  1044. type: NodeTypes.SIMPLE_EXPRESSION,
  1045. content: 'a',
  1046. isStatic: true,
  1047. isConstant: true,
  1048. loc: {
  1049. source: 'a',
  1050. start: {
  1051. column: 7,
  1052. line: 1,
  1053. offset: 6
  1054. },
  1055. end: {
  1056. column: 8,
  1057. line: 1,
  1058. offset: 7
  1059. }
  1060. }
  1061. },
  1062. modifiers: [],
  1063. exp: {
  1064. type: NodeTypes.SIMPLE_EXPRESSION,
  1065. content: 'b',
  1066. isStatic: false,
  1067. isConstant: false,
  1068. loc: {
  1069. start: { offset: 8, line: 1, column: 9 },
  1070. end: { offset: 9, line: 1, column: 10 },
  1071. source: 'b'
  1072. }
  1073. },
  1074. loc: {
  1075. start: { offset: 5, line: 1, column: 6 },
  1076. end: { offset: 9, line: 1, column: 10 },
  1077. source: ':a=b'
  1078. }
  1079. })
  1080. })
  1081. test('v-bind shorthand with modifier', () => {
  1082. const ast = baseParse('<div :a.sync=b />')
  1083. const directive = (ast.children[0] as ElementNode).props[0]
  1084. expect(directive).toStrictEqual({
  1085. type: NodeTypes.DIRECTIVE,
  1086. name: 'bind',
  1087. arg: {
  1088. type: NodeTypes.SIMPLE_EXPRESSION,
  1089. content: 'a',
  1090. isStatic: true,
  1091. isConstant: true,
  1092. loc: {
  1093. source: 'a',
  1094. start: {
  1095. column: 7,
  1096. line: 1,
  1097. offset: 6
  1098. },
  1099. end: {
  1100. column: 8,
  1101. line: 1,
  1102. offset: 7
  1103. }
  1104. }
  1105. },
  1106. modifiers: ['sync'],
  1107. exp: {
  1108. type: NodeTypes.SIMPLE_EXPRESSION,
  1109. content: 'b',
  1110. isStatic: false,
  1111. isConstant: false,
  1112. loc: {
  1113. start: { offset: 13, line: 1, column: 14 },
  1114. end: { offset: 14, line: 1, column: 15 },
  1115. source: 'b'
  1116. }
  1117. },
  1118. loc: {
  1119. start: { offset: 5, line: 1, column: 6 },
  1120. end: { offset: 14, line: 1, column: 15 },
  1121. source: ':a.sync=b'
  1122. }
  1123. })
  1124. })
  1125. test('v-on shorthand', () => {
  1126. const ast = baseParse('<div @a=b />')
  1127. const directive = (ast.children[0] as ElementNode).props[0]
  1128. expect(directive).toStrictEqual({
  1129. type: NodeTypes.DIRECTIVE,
  1130. name: 'on',
  1131. arg: {
  1132. type: NodeTypes.SIMPLE_EXPRESSION,
  1133. content: 'a',
  1134. isStatic: true,
  1135. isConstant: true,
  1136. loc: {
  1137. source: 'a',
  1138. start: {
  1139. column: 7,
  1140. line: 1,
  1141. offset: 6
  1142. },
  1143. end: {
  1144. column: 8,
  1145. line: 1,
  1146. offset: 7
  1147. }
  1148. }
  1149. },
  1150. modifiers: [],
  1151. exp: {
  1152. type: NodeTypes.SIMPLE_EXPRESSION,
  1153. content: 'b',
  1154. isStatic: false,
  1155. isConstant: false,
  1156. loc: {
  1157. start: { offset: 8, line: 1, column: 9 },
  1158. end: { offset: 9, line: 1, column: 10 },
  1159. source: 'b'
  1160. }
  1161. },
  1162. loc: {
  1163. start: { offset: 5, line: 1, column: 6 },
  1164. end: { offset: 9, line: 1, column: 10 },
  1165. source: '@a=b'
  1166. }
  1167. })
  1168. })
  1169. test('v-on shorthand with modifier', () => {
  1170. const ast = baseParse('<div @a.enter=b />')
  1171. const directive = (ast.children[0] as ElementNode).props[0]
  1172. expect(directive).toStrictEqual({
  1173. type: NodeTypes.DIRECTIVE,
  1174. name: 'on',
  1175. arg: {
  1176. type: NodeTypes.SIMPLE_EXPRESSION,
  1177. content: 'a',
  1178. isStatic: true,
  1179. isConstant: true,
  1180. loc: {
  1181. source: 'a',
  1182. start: {
  1183. column: 7,
  1184. line: 1,
  1185. offset: 6
  1186. },
  1187. end: {
  1188. column: 8,
  1189. line: 1,
  1190. offset: 7
  1191. }
  1192. }
  1193. },
  1194. modifiers: ['enter'],
  1195. exp: {
  1196. type: NodeTypes.SIMPLE_EXPRESSION,
  1197. content: 'b',
  1198. isStatic: false,
  1199. isConstant: false,
  1200. loc: {
  1201. start: { offset: 14, line: 1, column: 15 },
  1202. end: { offset: 15, line: 1, column: 16 },
  1203. source: 'b'
  1204. }
  1205. },
  1206. loc: {
  1207. start: { offset: 5, line: 1, column: 6 },
  1208. end: { offset: 15, line: 1, column: 16 },
  1209. source: '@a.enter=b'
  1210. }
  1211. })
  1212. })
  1213. test('v-slot shorthand', () => {
  1214. const ast = baseParse('<Comp #a="{ b }" />')
  1215. const directive = (ast.children[0] as ElementNode).props[0]
  1216. expect(directive).toStrictEqual({
  1217. type: NodeTypes.DIRECTIVE,
  1218. name: 'slot',
  1219. arg: {
  1220. type: NodeTypes.SIMPLE_EXPRESSION,
  1221. content: 'a',
  1222. isStatic: true,
  1223. isConstant: true,
  1224. loc: {
  1225. source: 'a',
  1226. start: {
  1227. column: 8,
  1228. line: 1,
  1229. offset: 7
  1230. },
  1231. end: {
  1232. column: 9,
  1233. line: 1,
  1234. offset: 8
  1235. }
  1236. }
  1237. },
  1238. modifiers: [],
  1239. exp: {
  1240. type: NodeTypes.SIMPLE_EXPRESSION,
  1241. content: '{ b }',
  1242. isStatic: false,
  1243. // The `isConstant` is the default value and will be determined in transformExpression
  1244. isConstant: false,
  1245. loc: {
  1246. start: { offset: 10, line: 1, column: 11 },
  1247. end: { offset: 15, line: 1, column: 16 },
  1248. source: '{ b }'
  1249. }
  1250. },
  1251. loc: {
  1252. start: { offset: 6, line: 1, column: 7 },
  1253. end: { offset: 16, line: 1, column: 17 },
  1254. source: '#a="{ b }"'
  1255. }
  1256. })
  1257. })
  1258. test('v-pre', () => {
  1259. const ast = baseParse(
  1260. `<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
  1261. `<div :id="foo"><Comp/>{{ bar }}</div>`
  1262. )
  1263. const divWithPre = ast.children[0] as ElementNode
  1264. expect(divWithPre.props).toMatchObject([
  1265. {
  1266. type: NodeTypes.ATTRIBUTE,
  1267. name: `:id`,
  1268. value: {
  1269. type: NodeTypes.TEXT,
  1270. content: `foo`
  1271. },
  1272. loc: {
  1273. source: `:id="foo"`,
  1274. start: {
  1275. line: 1,
  1276. column: 12
  1277. },
  1278. end: {
  1279. line: 1,
  1280. column: 21
  1281. }
  1282. }
  1283. }
  1284. ])
  1285. expect(divWithPre.children[0]).toMatchObject({
  1286. type: NodeTypes.ELEMENT,
  1287. tagType: ElementTypes.ELEMENT,
  1288. tag: `Comp`
  1289. })
  1290. expect(divWithPre.children[1]).toMatchObject({
  1291. type: NodeTypes.TEXT,
  1292. content: `{{ bar }}`
  1293. })
  1294. // should not affect siblings after it
  1295. const divWithoutPre = ast.children[1] as ElementNode
  1296. expect(divWithoutPre.props).toMatchObject([
  1297. {
  1298. type: NodeTypes.DIRECTIVE,
  1299. name: `bind`,
  1300. arg: {
  1301. type: NodeTypes.SIMPLE_EXPRESSION,
  1302. isStatic: true,
  1303. content: `id`
  1304. },
  1305. exp: {
  1306. type: NodeTypes.SIMPLE_EXPRESSION,
  1307. isStatic: false,
  1308. content: `foo`
  1309. },
  1310. loc: {
  1311. source: `:id="foo"`,
  1312. start: {
  1313. line: 2,
  1314. column: 6
  1315. },
  1316. end: {
  1317. line: 2,
  1318. column: 15
  1319. }
  1320. }
  1321. }
  1322. ])
  1323. expect(divWithoutPre.children[0]).toMatchObject({
  1324. type: NodeTypes.ELEMENT,
  1325. tagType: ElementTypes.COMPONENT,
  1326. tag: `Comp`
  1327. })
  1328. expect(divWithoutPre.children[1]).toMatchObject({
  1329. type: NodeTypes.INTERPOLATION,
  1330. content: {
  1331. type: NodeTypes.SIMPLE_EXPRESSION,
  1332. content: `bar`,
  1333. isStatic: false
  1334. }
  1335. })
  1336. })
  1337. test('end tags are case-insensitive.', () => {
  1338. const ast = baseParse('<div>hello</DIV>after')
  1339. const element = ast.children[0] as ElementNode
  1340. const text = element.children[0] as TextNode
  1341. expect(text).toStrictEqual({
  1342. type: NodeTypes.TEXT,
  1343. content: 'hello',
  1344. loc: {
  1345. start: { offset: 5, line: 1, column: 6 },
  1346. end: { offset: 10, line: 1, column: 11 },
  1347. source: 'hello'
  1348. }
  1349. })
  1350. })
  1351. })
  1352. test('self closing single tag', () => {
  1353. const ast = baseParse('<div :class="{ some: condition }" />')
  1354. expect(ast.children).toHaveLength(1)
  1355. expect(ast.children[0]).toMatchObject({ tag: 'div' })
  1356. })
  1357. test('self closing multiple tag', () => {
  1358. const ast = baseParse(
  1359. `<div :class="{ some: condition }" />\n` +
  1360. `<p v-bind:style="{ color: 'red' }"/>`
  1361. )
  1362. expect(ast).toMatchSnapshot()
  1363. expect(ast.children).toHaveLength(2)
  1364. expect(ast.children[0]).toMatchObject({ tag: 'div' })
  1365. expect(ast.children[1]).toMatchObject({ tag: 'p' })
  1366. })
  1367. test('valid html', () => {
  1368. const ast = baseParse(
  1369. `<div :class="{ some: condition }">\n` +
  1370. ` <p v-bind:style="{ color: 'red' }"/>\n` +
  1371. ` <!-- a comment with <html> inside it -->\n` +
  1372. `</div>`
  1373. )
  1374. expect(ast).toMatchSnapshot()
  1375. expect(ast.children).toHaveLength(1)
  1376. const el = ast.children[0] as any
  1377. expect(el).toMatchObject({
  1378. tag: 'div'
  1379. })
  1380. expect(el.children).toHaveLength(2)
  1381. expect(el.children[0]).toMatchObject({
  1382. tag: 'p'
  1383. })
  1384. expect(el.children[1]).toMatchObject({
  1385. type: NodeTypes.COMMENT
  1386. })
  1387. })
  1388. test('invalid html', () => {
  1389. expect(() => {
  1390. baseParse(`<div>\n<span>\n</div>\n</span>`)
  1391. }).toThrow('Element is missing end tag.')
  1392. const spy = jest.fn()
  1393. const ast = baseParse(`<div>\n<span>\n</div>\n</span>`, {
  1394. onError: spy
  1395. })
  1396. expect(spy.mock.calls).toMatchObject([
  1397. [
  1398. {
  1399. code: ErrorCodes.X_MISSING_END_TAG,
  1400. loc: {
  1401. start: {
  1402. offset: 6,
  1403. line: 2,
  1404. column: 1
  1405. }
  1406. }
  1407. }
  1408. ],
  1409. [
  1410. {
  1411. code: ErrorCodes.X_INVALID_END_TAG,
  1412. loc: {
  1413. start: {
  1414. offset: 20,
  1415. line: 4,
  1416. column: 1
  1417. }
  1418. }
  1419. }
  1420. ]
  1421. ])
  1422. expect(ast).toMatchSnapshot()
  1423. })
  1424. test('parse with correct location info', () => {
  1425. const [foo, bar, but, baz] = baseParse(
  1426. `
  1427. foo
  1428. is {{ bar }} but {{ baz }}`.trim()
  1429. ).children
  1430. let offset = 0
  1431. expect(foo.loc.start).toEqual({ line: 1, column: 1, offset })
  1432. offset += foo.loc.source.length
  1433. expect(foo.loc.end).toEqual({ line: 2, column: 5, offset })
  1434. expect(bar.loc.start).toEqual({ line: 2, column: 5, offset })
  1435. const barInner = (bar as InterpolationNode).content
  1436. offset += 3
  1437. expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset })
  1438. offset += barInner.loc.source.length
  1439. expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset })
  1440. offset += 3
  1441. expect(bar.loc.end).toEqual({ line: 2, column: 14, offset })
  1442. expect(but.loc.start).toEqual({ line: 2, column: 14, offset })
  1443. offset += but.loc.source.length
  1444. expect(but.loc.end).toEqual({ line: 2, column: 19, offset })
  1445. expect(baz.loc.start).toEqual({ line: 2, column: 19, offset })
  1446. const bazInner = (baz as InterpolationNode).content
  1447. offset += 3
  1448. expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset })
  1449. offset += bazInner.loc.source.length
  1450. expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset })
  1451. offset += 3
  1452. expect(baz.loc.end).toEqual({ line: 2, column: 28, offset })
  1453. })
  1454. describe('namedCharacterReferences option', () => {
  1455. test('use the given map', () => {
  1456. const ast: any = baseParse('&amp;&cups;', {
  1457. namedCharacterReferences: {
  1458. 'cups;': '\u222A\uFE00' // UNION with serifs
  1459. },
  1460. onError: () => {} // Ignore errors
  1461. })
  1462. expect(ast.children.length).toBe(1)
  1463. expect(ast.children[0].type).toBe(NodeTypes.TEXT)
  1464. expect(ast.children[0].content).toBe('&amp;\u222A\uFE00')
  1465. })
  1466. })
  1467. describe('whitespace management', () => {
  1468. it('should remove whitespaces at start/end inside an element', () => {
  1469. const ast = baseParse(`<div> <span/> </div>`)
  1470. expect((ast.children[0] as ElementNode).children.length).toBe(1)
  1471. })
  1472. it('should remove whitespaces w/ newline between elements', () => {
  1473. const ast = baseParse(`<div/> \n <div/> \n <div/>`)
  1474. expect(ast.children.length).toBe(3)
  1475. expect(ast.children.every(c => c.type === NodeTypes.ELEMENT)).toBe(true)
  1476. })
  1477. it('should remove whitespaces adjacent to comments', () => {
  1478. const ast = baseParse(`<div/> \n <!--foo--> <div/>`)
  1479. expect(ast.children.length).toBe(3)
  1480. expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
  1481. expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
  1482. expect(ast.children[2].type).toBe(NodeTypes.ELEMENT)
  1483. })
  1484. it('should remove whitespaces w/ newline between comments and elements', () => {
  1485. const ast = baseParse(`<div/> \n <!--foo--> \n <div/>`)
  1486. expect(ast.children.length).toBe(3)
  1487. expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
  1488. expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
  1489. expect(ast.children[2].type).toBe(NodeTypes.ELEMENT)
  1490. })
  1491. it('should NOT remove whitespaces w/ newline between interpolations', () => {
  1492. const ast = baseParse(`{{ foo }} \n {{ bar }}`)
  1493. expect(ast.children.length).toBe(3)
  1494. expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
  1495. expect(ast.children[1]).toMatchObject({
  1496. type: NodeTypes.TEXT,
  1497. content: ' '
  1498. })
  1499. expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION)
  1500. })
  1501. it('should NOT remove whitespaces w/o newline between elements', () => {
  1502. const ast = baseParse(`<div/> <div/> <div/>`)
  1503. expect(ast.children.length).toBe(5)
  1504. expect(ast.children.map(c => c.type)).toMatchObject([
  1505. NodeTypes.ELEMENT,
  1506. NodeTypes.TEXT,
  1507. NodeTypes.ELEMENT,
  1508. NodeTypes.TEXT,
  1509. NodeTypes.ELEMENT
  1510. ])
  1511. })
  1512. it('should condense consecutive whitespaces in text', () => {
  1513. const ast = baseParse(` foo \n bar baz `)
  1514. expect((ast.children[0] as TextNode).content).toBe(` foo bar baz `)
  1515. })
  1516. })
  1517. describe('Errors', () => {
  1518. const patterns: {
  1519. [key: string]: Array<{
  1520. code: string
  1521. errors: Array<{ type: ErrorCodes; loc: Position }>
  1522. options?: Partial<ParserOptions>
  1523. }>
  1524. } = {
  1525. ABRUPT_CLOSING_OF_EMPTY_COMMENT: [
  1526. {
  1527. code: '<template><!--></template>',
  1528. errors: [
  1529. {
  1530. type: ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT,
  1531. loc: { offset: 10, line: 1, column: 11 }
  1532. }
  1533. ]
  1534. },
  1535. {
  1536. code: '<template><!---></template>',
  1537. errors: [
  1538. {
  1539. type: ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT,
  1540. loc: { offset: 10, line: 1, column: 11 }
  1541. }
  1542. ]
  1543. },
  1544. {
  1545. code: '<template><!----></template>',
  1546. errors: []
  1547. }
  1548. ],
  1549. ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE: [
  1550. {
  1551. code: '<template>&#a;</template>',
  1552. errors: [
  1553. {
  1554. type: ErrorCodes.ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE,
  1555. loc: { offset: 10, line: 1, column: 11 }
  1556. }
  1557. ]
  1558. },
  1559. {
  1560. code: '<template>&#xg;</template>',
  1561. errors: [
  1562. {
  1563. type: ErrorCodes.ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE,
  1564. loc: { offset: 10, line: 1, column: 11 }
  1565. }
  1566. ]
  1567. },
  1568. {
  1569. code: '<template>&#99;</template>',
  1570. errors: []
  1571. },
  1572. {
  1573. code: '<template>&#xff;</template>',
  1574. errors: []
  1575. },
  1576. {
  1577. code: '<template attr="&#a;"></template>',
  1578. errors: [
  1579. {
  1580. type: ErrorCodes.ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE,
  1581. loc: { offset: 16, line: 1, column: 17 }
  1582. }
  1583. ]
  1584. },
  1585. {
  1586. code: '<template attr="&#xg;"></template>',
  1587. errors: [
  1588. {
  1589. type: ErrorCodes.ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE,
  1590. loc: { offset: 16, line: 1, column: 17 }
  1591. }
  1592. ]
  1593. },
  1594. {
  1595. code: '<template attr="&#99;"></template>',
  1596. errors: []
  1597. },
  1598. {
  1599. code: '<template attr="&#xff;"></template>',
  1600. errors: []
  1601. }
  1602. ],
  1603. CDATA_IN_HTML_CONTENT: [
  1604. {
  1605. code: '<template><![CDATA[cdata]]></template>',
  1606. errors: [
  1607. {
  1608. type: ErrorCodes.CDATA_IN_HTML_CONTENT,
  1609. loc: { offset: 10, line: 1, column: 11 }
  1610. }
  1611. ]
  1612. },
  1613. {
  1614. code: '<template><svg><![CDATA[cdata]]></svg></template>',
  1615. errors: []
  1616. }
  1617. ],
  1618. CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE: [
  1619. {
  1620. code: '<template>&#1234567;</template>',
  1621. errors: [
  1622. {
  1623. type: ErrorCodes.CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE,
  1624. loc: { offset: 10, line: 1, column: 11 }
  1625. }
  1626. ]
  1627. }
  1628. ],
  1629. CONTROL_CHARACTER_REFERENCE: [
  1630. {
  1631. code: '<template>&#0003;</template>',
  1632. errors: [
  1633. {
  1634. type: ErrorCodes.CONTROL_CHARACTER_REFERENCE,
  1635. loc: { offset: 10, line: 1, column: 11 }
  1636. }
  1637. ]
  1638. },
  1639. {
  1640. code: '<template>&#x7F;</template>',
  1641. errors: [
  1642. {
  1643. type: ErrorCodes.CONTROL_CHARACTER_REFERENCE,
  1644. loc: { offset: 10, line: 1, column: 11 }
  1645. }
  1646. ]
  1647. }
  1648. ],
  1649. DUPLICATE_ATTRIBUTE: [
  1650. {
  1651. code: '<template><div id="" id=""></div></template>',
  1652. errors: [
  1653. {
  1654. type: ErrorCodes.DUPLICATE_ATTRIBUTE,
  1655. loc: { offset: 21, line: 1, column: 22 }
  1656. }
  1657. ]
  1658. }
  1659. ],
  1660. END_TAG_WITH_ATTRIBUTES: [
  1661. {
  1662. code: '<template><div></div id=""></template>',
  1663. errors: [
  1664. {
  1665. type: ErrorCodes.END_TAG_WITH_ATTRIBUTES,
  1666. loc: { offset: 21, line: 1, column: 22 }
  1667. }
  1668. ]
  1669. }
  1670. ],
  1671. END_TAG_WITH_TRAILING_SOLIDUS: [
  1672. {
  1673. code: '<template><div></div/></template>',
  1674. errors: [
  1675. {
  1676. type: ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS,
  1677. loc: { offset: 20, line: 1, column: 21 }
  1678. }
  1679. ]
  1680. }
  1681. ],
  1682. EOF_BEFORE_TAG_NAME: [
  1683. {
  1684. code: '<template><',
  1685. errors: [
  1686. {
  1687. type: ErrorCodes.EOF_BEFORE_TAG_NAME,
  1688. loc: { offset: 11, line: 1, column: 12 }
  1689. },
  1690. {
  1691. type: ErrorCodes.X_MISSING_END_TAG,
  1692. loc: { offset: 0, line: 1, column: 1 }
  1693. }
  1694. ]
  1695. },
  1696. {
  1697. code: '<template></',
  1698. errors: [
  1699. {
  1700. type: ErrorCodes.EOF_BEFORE_TAG_NAME,
  1701. loc: { offset: 12, line: 1, column: 13 }
  1702. },
  1703. {
  1704. type: ErrorCodes.X_MISSING_END_TAG,
  1705. loc: { offset: 0, line: 1, column: 1 }
  1706. }
  1707. ]
  1708. }
  1709. ],
  1710. EOF_IN_CDATA: [
  1711. {
  1712. code: '<template><svg><![CDATA[cdata',
  1713. errors: [
  1714. {
  1715. type: ErrorCodes.EOF_IN_CDATA,
  1716. loc: { offset: 29, line: 1, column: 30 }
  1717. },
  1718. {
  1719. type: ErrorCodes.X_MISSING_END_TAG,
  1720. loc: { offset: 10, line: 1, column: 11 }
  1721. },
  1722. {
  1723. type: ErrorCodes.X_MISSING_END_TAG,
  1724. loc: { offset: 0, line: 1, column: 1 }
  1725. }
  1726. ]
  1727. },
  1728. {
  1729. code: '<template><svg><![CDATA[',
  1730. errors: [
  1731. {
  1732. type: ErrorCodes.EOF_IN_CDATA,
  1733. loc: { offset: 24, line: 1, column: 25 }
  1734. },
  1735. {
  1736. type: ErrorCodes.X_MISSING_END_TAG,
  1737. loc: { offset: 10, line: 1, column: 11 }
  1738. },
  1739. {
  1740. type: ErrorCodes.X_MISSING_END_TAG,
  1741. loc: { offset: 0, line: 1, column: 1 }
  1742. }
  1743. ]
  1744. }
  1745. ],
  1746. EOF_IN_COMMENT: [
  1747. {
  1748. code: '<template><!--comment',
  1749. errors: [
  1750. {
  1751. type: ErrorCodes.EOF_IN_COMMENT,
  1752. loc: { offset: 21, line: 1, column: 22 }
  1753. },
  1754. {
  1755. type: ErrorCodes.X_MISSING_END_TAG,
  1756. loc: { offset: 0, line: 1, column: 1 }
  1757. }
  1758. ]
  1759. },
  1760. {
  1761. code: '<template><!--',
  1762. errors: [
  1763. {
  1764. type: ErrorCodes.EOF_IN_COMMENT,
  1765. loc: { offset: 14, line: 1, column: 15 }
  1766. },
  1767. {
  1768. type: ErrorCodes.X_MISSING_END_TAG,
  1769. loc: { offset: 0, line: 1, column: 1 }
  1770. }
  1771. ]
  1772. },
  1773. // Bogus comments don't throw eof-in-comment error.
  1774. // https://html.spec.whatwg.org/multipage/parsing.html#bogus-comment-state
  1775. {
  1776. code: '<template><!',
  1777. errors: [
  1778. {
  1779. type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
  1780. loc: { offset: 10, line: 1, column: 11 }
  1781. },
  1782. {
  1783. type: ErrorCodes.X_MISSING_END_TAG,
  1784. loc: { offset: 0, line: 1, column: 1 }
  1785. }
  1786. ]
  1787. },
  1788. {
  1789. code: '<template><!-',
  1790. errors: [
  1791. {
  1792. type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
  1793. loc: { offset: 10, line: 1, column: 11 }
  1794. },
  1795. {
  1796. type: ErrorCodes.X_MISSING_END_TAG,
  1797. loc: { offset: 0, line: 1, column: 1 }
  1798. }
  1799. ]
  1800. },
  1801. {
  1802. code: '<template><!abc',
  1803. errors: [
  1804. {
  1805. type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
  1806. loc: { offset: 10, line: 1, column: 11 }
  1807. },
  1808. {
  1809. type: ErrorCodes.X_MISSING_END_TAG,
  1810. loc: { offset: 0, line: 1, column: 1 }
  1811. }
  1812. ]
  1813. }
  1814. ],
  1815. EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT: [
  1816. {
  1817. code: "<script><!--console.log('hello')",
  1818. errors: [
  1819. {
  1820. type: ErrorCodes.X_MISSING_END_TAG,
  1821. loc: { offset: 0, line: 1, column: 1 }
  1822. },
  1823. {
  1824. type: ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT,
  1825. loc: { offset: 32, line: 1, column: 33 }
  1826. }
  1827. ]
  1828. },
  1829. {
  1830. code: "<script>console.log('hello')",
  1831. errors: [
  1832. {
  1833. type: ErrorCodes.X_MISSING_END_TAG,
  1834. loc: { offset: 0, line: 1, column: 1 }
  1835. }
  1836. ]
  1837. }
  1838. ],
  1839. EOF_IN_TAG: [
  1840. {
  1841. code: '<template><div',
  1842. errors: [
  1843. {
  1844. type: ErrorCodes.EOF_IN_TAG,
  1845. loc: { offset: 14, line: 1, column: 15 }
  1846. },
  1847. {
  1848. type: ErrorCodes.X_MISSING_END_TAG,
  1849. loc: { offset: 10, line: 1, column: 11 }
  1850. },
  1851. {
  1852. type: ErrorCodes.X_MISSING_END_TAG,
  1853. loc: { offset: 0, line: 1, column: 1 }
  1854. }
  1855. ]
  1856. },
  1857. {
  1858. code: '<template><div ',
  1859. errors: [
  1860. {
  1861. type: ErrorCodes.EOF_IN_TAG,
  1862. loc: { offset: 15, line: 1, column: 16 }
  1863. },
  1864. {
  1865. type: ErrorCodes.X_MISSING_END_TAG,
  1866. loc: { offset: 10, line: 1, column: 11 }
  1867. },
  1868. {
  1869. type: ErrorCodes.X_MISSING_END_TAG,
  1870. loc: { offset: 0, line: 1, column: 1 }
  1871. }
  1872. ]
  1873. },
  1874. {
  1875. code: '<template><div id',
  1876. errors: [
  1877. {
  1878. type: ErrorCodes.EOF_IN_TAG,
  1879. loc: { offset: 17, line: 1, column: 18 }
  1880. },
  1881. {
  1882. type: ErrorCodes.X_MISSING_END_TAG,
  1883. loc: { offset: 10, line: 1, column: 11 }
  1884. },
  1885. {
  1886. type: ErrorCodes.X_MISSING_END_TAG,
  1887. loc: { offset: 0, line: 1, column: 1 }
  1888. }
  1889. ]
  1890. },
  1891. {
  1892. code: '<template><div id ',
  1893. errors: [
  1894. {
  1895. type: ErrorCodes.EOF_IN_TAG,
  1896. loc: { offset: 18, line: 1, column: 19 }
  1897. },
  1898. {
  1899. type: ErrorCodes.X_MISSING_END_TAG,
  1900. loc: { offset: 10, line: 1, column: 11 }
  1901. },
  1902. {
  1903. type: ErrorCodes.X_MISSING_END_TAG,
  1904. loc: { offset: 0, line: 1, column: 1 }
  1905. }
  1906. ]
  1907. },
  1908. {
  1909. code: '<template><div id =',
  1910. errors: [
  1911. {
  1912. type: ErrorCodes.MISSING_ATTRIBUTE_VALUE,
  1913. loc: { offset: 19, line: 1, column: 20 }
  1914. },
  1915. {
  1916. type: ErrorCodes.EOF_IN_TAG,
  1917. loc: { offset: 19, line: 1, column: 20 }
  1918. },
  1919. {
  1920. type: ErrorCodes.X_MISSING_END_TAG,
  1921. loc: { offset: 10, line: 1, column: 11 }
  1922. },
  1923. {
  1924. type: ErrorCodes.X_MISSING_END_TAG,
  1925. loc: { offset: 0, line: 1, column: 1 }
  1926. }
  1927. ]
  1928. },
  1929. {
  1930. code: "<template><div id='abc",
  1931. errors: [
  1932. {
  1933. type: ErrorCodes.EOF_IN_TAG,
  1934. loc: { offset: 22, line: 1, column: 23 }
  1935. },
  1936. {
  1937. type: ErrorCodes.X_MISSING_END_TAG,
  1938. loc: { offset: 10, line: 1, column: 11 }
  1939. },
  1940. {
  1941. type: ErrorCodes.X_MISSING_END_TAG,
  1942. loc: { offset: 0, line: 1, column: 1 }
  1943. }
  1944. ]
  1945. },
  1946. {
  1947. code: '<template><div id="abc',
  1948. errors: [
  1949. {
  1950. type: ErrorCodes.EOF_IN_TAG,
  1951. loc: { offset: 22, line: 1, column: 23 }
  1952. },
  1953. {
  1954. type: ErrorCodes.X_MISSING_END_TAG,
  1955. loc: { offset: 10, line: 1, column: 11 }
  1956. },
  1957. {
  1958. type: ErrorCodes.X_MISSING_END_TAG,
  1959. loc: { offset: 0, line: 1, column: 1 }
  1960. }
  1961. ]
  1962. },
  1963. {
  1964. code: "<template><div id='abc'",
  1965. errors: [
  1966. {
  1967. type: ErrorCodes.EOF_IN_TAG,
  1968. loc: { offset: 23, line: 1, column: 24 }
  1969. },
  1970. {
  1971. type: ErrorCodes.X_MISSING_END_TAG,
  1972. loc: { offset: 10, line: 1, column: 11 }
  1973. },
  1974. {
  1975. type: ErrorCodes.X_MISSING_END_TAG,
  1976. loc: { offset: 0, line: 1, column: 1 }
  1977. }
  1978. ]
  1979. },
  1980. {
  1981. code: '<template><div id="abc"',
  1982. errors: [
  1983. {
  1984. type: ErrorCodes.EOF_IN_TAG,
  1985. loc: { offset: 23, line: 1, column: 24 }
  1986. },
  1987. {
  1988. type: ErrorCodes.X_MISSING_END_TAG,
  1989. loc: { offset: 10, line: 1, column: 11 }
  1990. },
  1991. {
  1992. type: ErrorCodes.X_MISSING_END_TAG,
  1993. loc: { offset: 0, line: 1, column: 1 }
  1994. }
  1995. ]
  1996. },
  1997. {
  1998. code: '<template><div id=abc',
  1999. errors: [
  2000. {
  2001. type: ErrorCodes.EOF_IN_TAG,
  2002. loc: { offset: 21, line: 1, column: 22 }
  2003. },
  2004. {
  2005. type: ErrorCodes.X_MISSING_END_TAG,
  2006. loc: { offset: 10, line: 1, column: 11 }
  2007. },
  2008. {
  2009. type: ErrorCodes.X_MISSING_END_TAG,
  2010. loc: { offset: 0, line: 1, column: 1 }
  2011. }
  2012. ]
  2013. },
  2014. {
  2015. code: "<template><div id='abc'/",
  2016. errors: [
  2017. {
  2018. type: ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG,
  2019. loc: { offset: 23, line: 1, column: 24 }
  2020. },
  2021. {
  2022. type: ErrorCodes.EOF_IN_TAG,
  2023. loc: { offset: 24, line: 1, column: 25 }
  2024. },
  2025. {
  2026. type: ErrorCodes.X_MISSING_END_TAG,
  2027. loc: { offset: 10, line: 1, column: 11 }
  2028. },
  2029. {
  2030. type: ErrorCodes.X_MISSING_END_TAG,
  2031. loc: { offset: 0, line: 1, column: 1 }
  2032. }
  2033. ]
  2034. },
  2035. {
  2036. code: '<template><div id="abc"/',
  2037. errors: [
  2038. {
  2039. type: ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG,
  2040. loc: { offset: 23, line: 1, column: 24 }
  2041. },
  2042. {
  2043. type: ErrorCodes.EOF_IN_TAG,
  2044. loc: { offset: 24, line: 1, column: 25 }
  2045. },
  2046. {
  2047. type: ErrorCodes.X_MISSING_END_TAG,
  2048. loc: { offset: 10, line: 1, column: 11 }
  2049. },
  2050. {
  2051. type: ErrorCodes.X_MISSING_END_TAG,
  2052. loc: { offset: 0, line: 1, column: 1 }
  2053. }
  2054. ]
  2055. },
  2056. {
  2057. code: '<template><div id=abc /',
  2058. errors: [
  2059. {
  2060. type: ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG,
  2061. loc: { offset: 22, line: 1, column: 23 }
  2062. },
  2063. {
  2064. type: ErrorCodes.EOF_IN_TAG,
  2065. loc: { offset: 23, line: 1, column: 24 }
  2066. },
  2067. {
  2068. type: ErrorCodes.X_MISSING_END_TAG,
  2069. loc: { offset: 10, line: 1, column: 11 }
  2070. },
  2071. {
  2072. type: ErrorCodes.X_MISSING_END_TAG,
  2073. loc: { offset: 0, line: 1, column: 1 }
  2074. }
  2075. ]
  2076. }
  2077. ],
  2078. INCORRECTLY_CLOSED_COMMENT: [
  2079. {
  2080. code: '<template><!--comment--!></template>',
  2081. errors: [
  2082. {
  2083. type: ErrorCodes.INCORRECTLY_CLOSED_COMMENT,
  2084. loc: { offset: 10, line: 1, column: 11 }
  2085. }
  2086. ]
  2087. }
  2088. ],
  2089. INCORRECTLY_OPENED_COMMENT: [
  2090. {
  2091. code: '<template><!></template>',
  2092. errors: [
  2093. {
  2094. type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
  2095. loc: { offset: 10, line: 1, column: 11 }
  2096. }
  2097. ]
  2098. },
  2099. {
  2100. code: '<template><!-></template>',
  2101. errors: [
  2102. {
  2103. type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
  2104. loc: { offset: 10, line: 1, column: 11 }
  2105. }
  2106. ]
  2107. },
  2108. {
  2109. code: '<template><!ELEMENT br EMPTY></template>',
  2110. errors: [
  2111. {
  2112. type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
  2113. loc: { offset: 10, line: 1, column: 11 }
  2114. }
  2115. ]
  2116. },
  2117. // Just ignore doctype.
  2118. {
  2119. code: '<!DOCTYPE html>',
  2120. errors: []
  2121. }
  2122. ],
  2123. INVALID_FIRST_CHARACTER_OF_TAG_NAME: [
  2124. {
  2125. code: '<template>a < b</template>',
  2126. errors: [
  2127. {
  2128. type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
  2129. loc: { offset: 13, line: 1, column: 14 }
  2130. }
  2131. ]
  2132. },
  2133. {
  2134. code: '<template><�></template>',
  2135. errors: [
  2136. {
  2137. type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
  2138. loc: { offset: 11, line: 1, column: 12 }
  2139. }
  2140. ]
  2141. },
  2142. {
  2143. code: '<template>a </ b</template>',
  2144. errors: [
  2145. {
  2146. type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
  2147. loc: { offset: 14, line: 1, column: 15 }
  2148. },
  2149. {
  2150. type: ErrorCodes.X_MISSING_END_TAG,
  2151. loc: { offset: 0, line: 1, column: 1 }
  2152. }
  2153. ]
  2154. },
  2155. {
  2156. code: '<template></�></template>',
  2157. errors: [
  2158. {
  2159. type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
  2160. loc: { offset: 12, line: 1, column: 13 }
  2161. }
  2162. ]
  2163. },
  2164. // Don't throw invalid-first-character-of-tag-name in interpolation
  2165. {
  2166. code: '<template>{{a < b}}</template>',
  2167. errors: []
  2168. }
  2169. ],
  2170. MISSING_ATTRIBUTE_VALUE: [
  2171. {
  2172. code: '<template><div id=></div></template>',
  2173. errors: [
  2174. {
  2175. type: ErrorCodes.MISSING_ATTRIBUTE_VALUE,
  2176. loc: { offset: 18, line: 1, column: 19 }
  2177. }
  2178. ]
  2179. },
  2180. {
  2181. code: '<template><div id= ></div></template>',
  2182. errors: [
  2183. {
  2184. type: ErrorCodes.MISSING_ATTRIBUTE_VALUE,
  2185. loc: { offset: 19, line: 1, column: 20 }
  2186. }
  2187. ]
  2188. },
  2189. {
  2190. code: '<template><div id= /></div></template>',
  2191. errors: []
  2192. }
  2193. ],
  2194. MISSING_END_TAG_NAME: [
  2195. {
  2196. code: '<template></></template>',
  2197. errors: [
  2198. {
  2199. type: ErrorCodes.MISSING_END_TAG_NAME,
  2200. loc: { offset: 12, line: 1, column: 13 }
  2201. }
  2202. ]
  2203. }
  2204. ],
  2205. MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE: [
  2206. {
  2207. code: '<template>&amp</template>',
  2208. options: { namedCharacterReferences: { amp: '&' } },
  2209. errors: [
  2210. {
  2211. type: ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE,
  2212. loc: { offset: 14, line: 1, column: 15 }
  2213. }
  2214. ]
  2215. },
  2216. {
  2217. code: '<template>&#40</template>',
  2218. errors: [
  2219. {
  2220. type: ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE,
  2221. loc: { offset: 14, line: 1, column: 15 }
  2222. }
  2223. ]
  2224. },
  2225. {
  2226. code: '<template>&#x40</template>',
  2227. errors: [
  2228. {
  2229. type: ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE,
  2230. loc: { offset: 15, line: 1, column: 16 }
  2231. }
  2232. ]
  2233. }
  2234. ],
  2235. MISSING_WHITESPACE_BETWEEN_ATTRIBUTES: [
  2236. {
  2237. code: '<template><div id="foo"class="bar"></div></template>',
  2238. errors: [
  2239. {
  2240. type: ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES,
  2241. loc: { offset: 23, line: 1, column: 24 }
  2242. }
  2243. ]
  2244. },
  2245. // CR doesn't appear in tokenization phase, but all CR are removed in preprocessing.
  2246. // https://html.spec.whatwg.org/multipage/parsing.html#preprocessing-the-input-stream
  2247. {
  2248. code: '<template><div id="foo"\r\nclass="bar"></div></template>',
  2249. errors: []
  2250. }
  2251. ],
  2252. NESTED_COMMENT: [
  2253. {
  2254. code: '<template><!--a<!--b--></template>',
  2255. errors: [
  2256. {
  2257. type: ErrorCodes.NESTED_COMMENT,
  2258. loc: { offset: 15, line: 1, column: 16 }
  2259. }
  2260. ]
  2261. },
  2262. {
  2263. code: '<template><!--a<!--b<!--c--></template>',
  2264. errors: [
  2265. {
  2266. type: ErrorCodes.NESTED_COMMENT,
  2267. loc: { offset: 15, line: 1, column: 16 }
  2268. },
  2269. {
  2270. type: ErrorCodes.NESTED_COMMENT,
  2271. loc: { offset: 20, line: 1, column: 21 }
  2272. }
  2273. ]
  2274. },
  2275. {
  2276. code: '<template><!--a<!--></template>',
  2277. errors: []
  2278. },
  2279. {
  2280. code: '<template><!--a<!--',
  2281. errors: [
  2282. {
  2283. type: ErrorCodes.EOF_IN_COMMENT,
  2284. loc: { offset: 19, line: 1, column: 20 }
  2285. },
  2286. {
  2287. type: ErrorCodes.X_MISSING_END_TAG,
  2288. loc: { offset: 0, line: 1, column: 1 }
  2289. }
  2290. ]
  2291. }
  2292. ],
  2293. NONCHARACTER_CHARACTER_REFERENCE: [
  2294. {
  2295. code: '<template>&#xFFFE;</template>',
  2296. errors: [
  2297. {
  2298. type: ErrorCodes.NONCHARACTER_CHARACTER_REFERENCE,
  2299. loc: { offset: 10, line: 1, column: 11 }
  2300. }
  2301. ]
  2302. },
  2303. {
  2304. code: '<template>&#x1FFFF;</template>',
  2305. errors: [
  2306. {
  2307. type: ErrorCodes.NONCHARACTER_CHARACTER_REFERENCE,
  2308. loc: { offset: 10, line: 1, column: 11 }
  2309. }
  2310. ]
  2311. }
  2312. ],
  2313. NULL_CHARACTER_REFERENCE: [
  2314. {
  2315. code: '<template>&#0000;</template>',
  2316. errors: [
  2317. {
  2318. type: ErrorCodes.NULL_CHARACTER_REFERENCE,
  2319. loc: { offset: 10, line: 1, column: 11 }
  2320. }
  2321. ]
  2322. }
  2323. ],
  2324. SURROGATE_CHARACTER_REFERENCE: [
  2325. {
  2326. code: '<template>&#xD800;</template>',
  2327. errors: [
  2328. {
  2329. type: ErrorCodes.SURROGATE_CHARACTER_REFERENCE,
  2330. loc: { offset: 10, line: 1, column: 11 }
  2331. }
  2332. ]
  2333. }
  2334. ],
  2335. UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME: [
  2336. {
  2337. code: "<template><div a\"bc=''></div></template>",
  2338. errors: [
  2339. {
  2340. type: ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
  2341. loc: { offset: 16, line: 1, column: 17 }
  2342. }
  2343. ]
  2344. },
  2345. {
  2346. code: "<template><div a'bc=''></div></template>",
  2347. errors: [
  2348. {
  2349. type: ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
  2350. loc: { offset: 16, line: 1, column: 17 }
  2351. }
  2352. ]
  2353. },
  2354. {
  2355. code: "<template><div a<bc=''></div></template>",
  2356. errors: [
  2357. {
  2358. type: ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
  2359. loc: { offset: 16, line: 1, column: 17 }
  2360. }
  2361. ]
  2362. }
  2363. ],
  2364. UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE: [
  2365. {
  2366. code: '<template><div foo=bar"></div></template>',
  2367. errors: [
  2368. {
  2369. type: ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
  2370. loc: { offset: 22, line: 1, column: 23 }
  2371. }
  2372. ]
  2373. },
  2374. {
  2375. code: "<template><div foo=bar'></div></template>",
  2376. errors: [
  2377. {
  2378. type: ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
  2379. loc: { offset: 22, line: 1, column: 23 }
  2380. }
  2381. ]
  2382. },
  2383. {
  2384. code: '<template><div foo=bar<div></div></template>',
  2385. errors: [
  2386. {
  2387. type: ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
  2388. loc: { offset: 22, line: 1, column: 23 }
  2389. }
  2390. ]
  2391. },
  2392. {
  2393. code: '<template><div foo=bar=baz></div></template>',
  2394. errors: [
  2395. {
  2396. type: ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
  2397. loc: { offset: 22, line: 1, column: 23 }
  2398. }
  2399. ]
  2400. },
  2401. {
  2402. code: '<template><div foo=bar`></div></template>',
  2403. errors: [
  2404. {
  2405. type: ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
  2406. loc: { offset: 22, line: 1, column: 23 }
  2407. }
  2408. ]
  2409. }
  2410. ],
  2411. UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME: [
  2412. {
  2413. code: '<template><div =foo=bar></div></template>',
  2414. errors: [
  2415. {
  2416. type: ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME,
  2417. loc: { offset: 15, line: 1, column: 16 }
  2418. }
  2419. ]
  2420. },
  2421. {
  2422. code: '<template><div =></div></template>',
  2423. errors: [
  2424. {
  2425. type: ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME,
  2426. loc: { offset: 15, line: 1, column: 16 }
  2427. }
  2428. ]
  2429. }
  2430. ],
  2431. UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME: [
  2432. {
  2433. code: '<template><?xml?></template>',
  2434. errors: [
  2435. {
  2436. type: ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
  2437. loc: { offset: 11, line: 1, column: 12 }
  2438. }
  2439. ]
  2440. }
  2441. ],
  2442. UNEXPECTED_SOLIDUS_IN_TAG: [
  2443. {
  2444. code: '<template><div a/b></div></template>',
  2445. errors: [
  2446. {
  2447. type: ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG,
  2448. loc: { offset: 16, line: 1, column: 17 }
  2449. }
  2450. ]
  2451. }
  2452. ],
  2453. UNKNOWN_NAMED_CHARACTER_REFERENCE: [
  2454. {
  2455. code: '<template>&unknown;</template>',
  2456. errors: [
  2457. {
  2458. type: ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE,
  2459. loc: { offset: 10, line: 1, column: 11 }
  2460. }
  2461. ]
  2462. }
  2463. ],
  2464. X_INVALID_END_TAG: [
  2465. {
  2466. code: '<template></div></template>',
  2467. errors: [
  2468. {
  2469. type: ErrorCodes.X_INVALID_END_TAG,
  2470. loc: { offset: 10, line: 1, column: 11 }
  2471. }
  2472. ]
  2473. },
  2474. {
  2475. code: '<template></div></div></template>',
  2476. errors: [
  2477. {
  2478. type: ErrorCodes.X_INVALID_END_TAG,
  2479. loc: { offset: 10, line: 1, column: 11 }
  2480. },
  2481. {
  2482. type: ErrorCodes.X_INVALID_END_TAG,
  2483. loc: { offset: 16, line: 1, column: 17 }
  2484. }
  2485. ]
  2486. },
  2487. {
  2488. code: "<template>{{'</div>'}}</template>",
  2489. errors: []
  2490. },
  2491. {
  2492. code: '<textarea></div></textarea>',
  2493. errors: []
  2494. },
  2495. {
  2496. code: '<svg><![CDATA[</div>]]></svg>',
  2497. errors: []
  2498. },
  2499. {
  2500. code: '<svg><!--</div>--></svg>',
  2501. errors: []
  2502. }
  2503. ],
  2504. X_MISSING_END_TAG: [
  2505. {
  2506. code: '<template><div></template>',
  2507. errors: [
  2508. {
  2509. type: ErrorCodes.X_MISSING_END_TAG,
  2510. loc: { offset: 10, line: 1, column: 11 }
  2511. }
  2512. ]
  2513. },
  2514. {
  2515. code: '<template><div>',
  2516. errors: [
  2517. {
  2518. type: ErrorCodes.X_MISSING_END_TAG,
  2519. loc: { offset: 10, line: 1, column: 11 }
  2520. },
  2521. {
  2522. type: ErrorCodes.X_MISSING_END_TAG,
  2523. loc: { offset: 0, line: 1, column: 1 }
  2524. }
  2525. ]
  2526. }
  2527. ],
  2528. X_MISSING_INTERPOLATION_END: [
  2529. {
  2530. code: '{{ foo',
  2531. errors: [
  2532. {
  2533. type: ErrorCodes.X_MISSING_INTERPOLATION_END,
  2534. loc: { offset: 0, line: 1, column: 1 }
  2535. }
  2536. ]
  2537. },
  2538. {
  2539. code: '{{',
  2540. errors: [
  2541. {
  2542. type: ErrorCodes.X_MISSING_INTERPOLATION_END,
  2543. loc: { offset: 0, line: 1, column: 1 }
  2544. }
  2545. ]
  2546. },
  2547. {
  2548. code: '{{}}',
  2549. errors: []
  2550. }
  2551. ],
  2552. X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END: [
  2553. {
  2554. code: `<div v-foo:[sef fsef] />`,
  2555. errors: [
  2556. {
  2557. type: ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END,
  2558. loc: { offset: 15, line: 1, column: 16 }
  2559. }
  2560. ]
  2561. }
  2562. ]
  2563. }
  2564. for (const key of Object.keys(patterns) as (keyof (typeof patterns))[]) {
  2565. describe(key, () => {
  2566. for (const { code, errors, options } of patterns[key]) {
  2567. test(
  2568. code.replace(
  2569. /[\r\n]/g,
  2570. c => `\\x0${c.codePointAt(0)!.toString(16)};`
  2571. ),
  2572. () => {
  2573. const spy = jest.fn()
  2574. const ast = baseParse(code, {
  2575. getNamespace: (tag, parent) => {
  2576. const ns = parent ? parent.ns : Namespaces.HTML
  2577. if (ns === Namespaces.HTML) {
  2578. if (tag === 'svg') {
  2579. return (Namespaces.HTML + 1) as any
  2580. }
  2581. }
  2582. return ns
  2583. },
  2584. getTextMode: tag => {
  2585. if (tag === 'textarea') {
  2586. return TextModes.RCDATA
  2587. }
  2588. if (tag === 'script') {
  2589. return TextModes.RAWTEXT
  2590. }
  2591. return TextModes.DATA
  2592. },
  2593. ...options,
  2594. onError: spy
  2595. })
  2596. expect(
  2597. spy.mock.calls.map(([err]) => ({
  2598. type: err.code,
  2599. loc: err.loc.start
  2600. }))
  2601. ).toMatchObject(errors)
  2602. expect(ast).toMatchSnapshot()
  2603. }
  2604. )
  2605. }
  2606. })
  2607. }
  2608. })
  2609. })