compileScript.spec.ts 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450
  1. import { BindingTypes } from '@vue/compiler-core'
  2. import { compileSFCScript as compile, assertCode } from './utils'
  3. describe('SFC compile <script setup>', () => {
  4. test('should expose top level declarations', () => {
  5. const { content, bindings } = compile(`
  6. <script setup>
  7. import { x } from './x'
  8. let a = 1
  9. const b = 2
  10. function c() {}
  11. class d {}
  12. </script>
  13. <script>
  14. import { xx } from './x'
  15. let aa = 1
  16. const bb = 2
  17. function cc() {}
  18. class dd {}
  19. </script>
  20. `)
  21. expect(content).toMatch('return { aa, bb, cc, dd, a, b, c, d, xx, x }')
  22. expect(bindings).toStrictEqual({
  23. x: BindingTypes.SETUP_MAYBE_REF,
  24. a: BindingTypes.SETUP_LET,
  25. b: BindingTypes.SETUP_CONST,
  26. c: BindingTypes.SETUP_CONST,
  27. d: BindingTypes.SETUP_CONST,
  28. xx: BindingTypes.SETUP_MAYBE_REF,
  29. aa: BindingTypes.SETUP_LET,
  30. bb: BindingTypes.SETUP_CONST,
  31. cc: BindingTypes.SETUP_CONST,
  32. dd: BindingTypes.SETUP_CONST
  33. })
  34. assertCode(content)
  35. })
  36. test('defineProps()', () => {
  37. const { content, bindings } = compile(`
  38. <script setup>
  39. const props = defineProps({
  40. foo: String
  41. })
  42. const bar = 1
  43. </script>
  44. `)
  45. // should generate working code
  46. assertCode(content)
  47. // should anayze bindings
  48. expect(bindings).toStrictEqual({
  49. foo: BindingTypes.PROPS,
  50. bar: BindingTypes.SETUP_CONST,
  51. props: BindingTypes.SETUP_CONST
  52. })
  53. // should remove defineOptions import and call
  54. expect(content).not.toMatch('defineProps')
  55. // should generate correct setup signature
  56. expect(content).toMatch(`setup(__props, { expose }) {`)
  57. // should assign user identifier to it
  58. expect(content).toMatch(`const props = __props`)
  59. // should include context options in default export
  60. expect(content).toMatch(`export default {
  61. props: {
  62. foo: String
  63. },`)
  64. })
  65. test('defineProps w/ external definition', () => {
  66. const { content } = compile(`
  67. <script setup>
  68. import { propsModel } from './props'
  69. const props = defineProps(propsModel)
  70. </script>
  71. `)
  72. assertCode(content)
  73. expect(content).toMatch(`export default {
  74. props: propsModel,`)
  75. })
  76. test('defineEmits()', () => {
  77. const { content, bindings } = compile(`
  78. <script setup>
  79. const myEmit = defineEmits(['foo', 'bar'])
  80. </script>
  81. `)
  82. assertCode(content)
  83. expect(bindings).toStrictEqual({
  84. myEmit: BindingTypes.SETUP_CONST
  85. })
  86. // should remove defineOptions import and call
  87. expect(content).not.toMatch('defineEmits')
  88. // should generate correct setup signature
  89. expect(content).toMatch(`setup(__props, { expose, emit: myEmit }) {`)
  90. // should include context options in default export
  91. expect(content).toMatch(`export default {
  92. emits: ['foo', 'bar'],`)
  93. })
  94. test('defineProps/defineEmits in multi-variable declaration', () => {
  95. const { content } = compile(`
  96. <script setup>
  97. const props = defineProps(['item']),
  98. a = 1,
  99. emit = defineEmits(['a']);
  100. </script>
  101. `)
  102. assertCode(content)
  103. expect(content).toMatch(`const a = 1;`) // test correct removal
  104. expect(content).toMatch(`props: ['item'],`)
  105. expect(content).toMatch(`emits: ['a'],`)
  106. })
  107. test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
  108. const { content } = compile(`
  109. <script setup>
  110. const props = defineProps(['item']),
  111. emit = defineEmits(['a']);
  112. </script>
  113. `)
  114. assertCode(content)
  115. expect(content).toMatch(`props: ['item'],`)
  116. expect(content).toMatch(`emits: ['a'],`)
  117. })
  118. test('defineExpose()', () => {
  119. const { content } = compile(`
  120. <script setup>
  121. defineExpose({ foo: 123 })
  122. </script>
  123. `)
  124. assertCode(content)
  125. // should remove defineOptions import and call
  126. expect(content).not.toMatch('defineExpose')
  127. // should generate correct setup signature
  128. expect(content).toMatch(`setup(__props, { expose }) {`)
  129. // should replace callee
  130. expect(content).toMatch(/\bexpose\(\{ foo: 123 \}\)/)
  131. })
  132. describe('<script> and <script setup> co-usage', () => {
  133. describe('spaces in ExportDefaultDeclaration node', () => {
  134. // #4371
  135. test('with many spaces and newline', () => {
  136. // #4371
  137. const { content } = compile(`
  138. <script>
  139. export const n = 1
  140. export default
  141. {
  142. some:'option'
  143. }
  144. </script>
  145. <script setup>
  146. import { x } from './x'
  147. x()
  148. </script>
  149. `)
  150. assertCode(content)
  151. })
  152. test('with minimal spaces', () => {
  153. const { content } = compile(`
  154. <script>
  155. export const n = 1
  156. export default{
  157. some:'option'
  158. }
  159. </script>
  160. <script setup>
  161. import { x } from './x'
  162. x()
  163. </script>
  164. `)
  165. assertCode(content)
  166. })
  167. })
  168. test('script first', () => {
  169. const { content } = compile(`
  170. <script>
  171. export const n = 1
  172. </script>
  173. <script setup>
  174. import { x } from './x'
  175. x()
  176. </script>
  177. `)
  178. assertCode(content)
  179. })
  180. test('script setup first', () => {
  181. const { content } = compile(`
  182. <script setup>
  183. import { x } from './x'
  184. x()
  185. </script>
  186. <script>
  187. export const n = 1
  188. </script>
  189. `)
  190. assertCode(content)
  191. })
  192. // #4395
  193. test('script setup first, lang="ts", script block content export default', () => {
  194. const { content } = compile(`
  195. <script setup lang="ts">
  196. import { x } from './x'
  197. x()
  198. </script>
  199. <script lang="ts">
  200. export default {
  201. name: "test"
  202. }
  203. </script>
  204. `)
  205. // ensure __default__ is declared before used
  206. expect(content).toMatch(/const __default__[\S\s]*\.\.\.__default__/m)
  207. assertCode(content)
  208. })
  209. })
  210. describe('imports', () => {
  211. test('should hoist and expose imports', () => {
  212. assertCode(
  213. compile(`<script setup>
  214. import { ref } from 'vue'
  215. import 'foo/css'
  216. </script>`).content
  217. )
  218. })
  219. test('should extract comment for import or type declarations', () => {
  220. assertCode(
  221. compile(`
  222. <script setup>
  223. import a from 'a' // comment
  224. import b from 'b'
  225. </script>
  226. `).content
  227. )
  228. })
  229. // #2740
  230. test('should allow defineProps/Emit at the start of imports', () => {
  231. assertCode(
  232. compile(`<script setup>
  233. import { ref } from 'vue'
  234. defineProps(['foo'])
  235. defineEmits(['bar'])
  236. const r = ref(0)
  237. </script>`).content
  238. )
  239. })
  240. test('dedupe between user & helper', () => {
  241. const { content } = compile(
  242. `
  243. <script setup>
  244. import { ref } from 'vue'
  245. let foo = $ref(1)
  246. </script>
  247. `,
  248. { refSugar: true }
  249. )
  250. assertCode(content)
  251. expect(content).toMatch(`import { ref } from 'vue'`)
  252. })
  253. test('import dedupe between <script> and <script setup>', () => {
  254. const { content } = compile(`
  255. <script>
  256. import { x } from './x'
  257. </script>
  258. <script setup>
  259. import { x } from './x'
  260. x()
  261. </script>
  262. `)
  263. assertCode(content)
  264. expect(content.indexOf(`import { x }`)).toEqual(
  265. content.lastIndexOf(`import { x }`)
  266. )
  267. })
  268. })
  269. // in dev mode, declared bindings are returned as an object from setup()
  270. // when using TS, users may import types which should not be returned as
  271. // values, so we need to check import usage in the template to determine
  272. // what to be returned.
  273. describe('dev mode import usage check', () => {
  274. test('components', () => {
  275. const { content } = compile(`
  276. <script setup lang="ts">
  277. import { FooBar, FooBaz, FooQux, foo } from './x'
  278. const fooBar: FooBar = 1
  279. </script>
  280. <template>
  281. <FooBaz></FooBaz>
  282. <foo-qux/>
  283. <foo/>
  284. FooBar
  285. </template>
  286. `)
  287. // FooBar: should not be matched by plain text or incorrect case
  288. // FooBaz: used as PascalCase component
  289. // FooQux: used as kebab-case component
  290. // foo: lowercase component
  291. expect(content).toMatch(`return { fooBar, FooBaz, FooQux, foo }`)
  292. assertCode(content)
  293. })
  294. test('directive', () => {
  295. const { content } = compile(`
  296. <script setup lang="ts">
  297. import { vMyDir } from './x'
  298. </script>
  299. <template>
  300. <div v-my-dir></div>
  301. </template>
  302. `)
  303. expect(content).toMatch(`return { vMyDir }`)
  304. assertCode(content)
  305. })
  306. test('vue interpolations', () => {
  307. const { content } = compile(`
  308. <script setup lang="ts">
  309. import { x, y, z, x$y } from './x'
  310. </script>
  311. <template>
  312. <div :id="z + 'y'">{{ x }} {{ yy }} {{ x$y }}</div>
  313. </template>
  314. `)
  315. // x: used in interpolation
  316. // y: should not be matched by {{ yy }} or 'y' in binding exps
  317. // x$y: #4274 should escape special chars when creating Regex
  318. expect(content).toMatch(`return { x, z, x$y }`)
  319. assertCode(content)
  320. })
  321. // #4340 interpolations in tempalte strings
  322. test('js template string interpolations', () => {
  323. const { content } = compile(`
  324. <script setup lang="ts">
  325. import { VAR, VAR2, VAR3 } from './x'
  326. </script>
  327. <template>
  328. {{ \`\${VAR}VAR2\${VAR3}\` }}
  329. </template>
  330. `)
  331. // VAR2 should not be matched
  332. expect(content).toMatch(`return { VAR, VAR3 }`)
  333. assertCode(content)
  334. })
  335. // edge case: last tag in template
  336. test('last tag', () => {
  337. const { content } = compile(`
  338. <script setup lang="ts">
  339. import { FooBaz, Last } from './x'
  340. </script>
  341. <template>
  342. <FooBaz></FooBaz>
  343. <Last/>
  344. </template>
  345. `)
  346. expect(content).toMatch(`return { FooBaz, Last }`)
  347. assertCode(content)
  348. })
  349. })
  350. describe('inlineTemplate mode', () => {
  351. test('should work', () => {
  352. const { content } = compile(
  353. `
  354. <script setup>
  355. import { ref } from 'vue'
  356. const count = ref(0)
  357. </script>
  358. <template>
  359. <div>{{ count }}</div>
  360. <div>static</div>
  361. </template>
  362. `,
  363. { inlineTemplate: true }
  364. )
  365. // check snapshot and make sure helper imports and
  366. // hoists are placed correctly.
  367. assertCode(content)
  368. // in inline mode, no need to call expose() since nothing is exposed
  369. // anyway!
  370. expect(content).not.toMatch(`expose()`)
  371. })
  372. test('with defineExpose()', () => {
  373. const { content } = compile(
  374. `
  375. <script setup>
  376. const count = ref(0)
  377. defineExpose({ count })
  378. </script>
  379. `,
  380. { inlineTemplate: true }
  381. )
  382. assertCode(content)
  383. expect(content).toMatch(`setup(__props, { expose })`)
  384. expect(content).toMatch(`expose({ count })`)
  385. })
  386. test('referencing scope components and directives', () => {
  387. const { content } = compile(
  388. `
  389. <script setup>
  390. import ChildComp from './Child.vue'
  391. import SomeOtherComp from './Other.vue'
  392. import vMyDir from './my-dir'
  393. </script>
  394. <template>
  395. <div v-my-dir></div>
  396. <ChildComp/>
  397. <some-other-comp/>
  398. </template>
  399. `,
  400. { inlineTemplate: true }
  401. )
  402. expect(content).toMatch('[_unref(vMyDir)]')
  403. expect(content).toMatch('_createVNode(ChildComp)')
  404. // kebab-case component support
  405. expect(content).toMatch('_createVNode(SomeOtherComp)')
  406. assertCode(content)
  407. })
  408. test('avoid unref() when necessary', () => {
  409. // function, const, component import
  410. const { content } = compile(
  411. `<script setup>
  412. import { ref } from 'vue'
  413. import Foo, { bar } from './Foo.vue'
  414. import other from './util'
  415. const count = ref(0)
  416. const constant = {}
  417. const maybe = foo()
  418. let lett = 1
  419. function fn() {}
  420. </script>
  421. <template>
  422. <Foo>{{ bar }}</Foo>
  423. <div @click="fn">{{ count }} {{ constant }} {{ maybe }} {{ lett }} {{ other }}</div>
  424. </template>
  425. `,
  426. { inlineTemplate: true }
  427. )
  428. // no need to unref vue component import
  429. expect(content).toMatch(`createVNode(Foo,`)
  430. // #2699 should unref named imports from .vue
  431. expect(content).toMatch(`unref(bar)`)
  432. // should unref other imports
  433. expect(content).toMatch(`unref(other)`)
  434. // no need to unref constant literals
  435. expect(content).not.toMatch(`unref(constant)`)
  436. // should directly use .value for known refs
  437. expect(content).toMatch(`count.value`)
  438. // should unref() on const bindings that may be refs
  439. expect(content).toMatch(`unref(maybe)`)
  440. // should unref() on let bindings
  441. expect(content).toMatch(`unref(lett)`)
  442. // no need to unref function declarations
  443. expect(content).toMatch(`{ onClick: fn }`)
  444. // no need to mark constant fns in patch flag
  445. expect(content).not.toMatch(`PROPS`)
  446. assertCode(content)
  447. })
  448. test('v-model codegen', () => {
  449. const { content } = compile(
  450. `<script setup>
  451. import { ref } from 'vue'
  452. const count = ref(0)
  453. const maybe = foo()
  454. let lett = 1
  455. </script>
  456. <template>
  457. <input v-model="count">
  458. <input v-model="maybe">
  459. <input v-model="lett">
  460. </template>
  461. `,
  462. { inlineTemplate: true }
  463. )
  464. // known const ref: set value
  465. expect(content).toMatch(`count.value = $event`)
  466. // const but maybe ref: also assign .value directly since non-ref
  467. // won't work
  468. expect(content).toMatch(`maybe.value = $event`)
  469. // let: handle both cases
  470. expect(content).toMatch(
  471. `_isRef(lett) ? lett.value = $event : lett = $event`
  472. )
  473. assertCode(content)
  474. })
  475. test('template assignment expression codegen', () => {
  476. const { content } = compile(
  477. `<script setup>
  478. import { ref } from 'vue'
  479. const count = ref(0)
  480. const maybe = foo()
  481. let lett = 1
  482. let v = ref(1)
  483. </script>
  484. <template>
  485. <div @click="count = 1"/>
  486. <div @click="maybe = count"/>
  487. <div @click="lett = count"/>
  488. <div @click="v += 1"/>
  489. <div @click="v -= 1"/>
  490. <div @click="() => {
  491. let a = '' + lett
  492. v = a
  493. }"/>
  494. <div @click="() => {
  495. // nested scopes
  496. (()=>{
  497. let x = a
  498. (()=>{
  499. let z = x
  500. let z2 = z
  501. })
  502. let lz = z
  503. })
  504. v = a
  505. }"/>
  506. </template>
  507. `,
  508. { inlineTemplate: true }
  509. )
  510. // known const ref: set value
  511. expect(content).toMatch(`count.value = 1`)
  512. // const but maybe ref: only assign after check
  513. expect(content).toMatch(`maybe.value = count.value`)
  514. // let: handle both cases
  515. expect(content).toMatch(
  516. `_isRef(lett) ? lett.value = count.value : lett = count.value`
  517. )
  518. expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)
  519. expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)
  520. expect(content).toMatch(`_isRef(v) ? v.value = a : v = a`)
  521. expect(content).toMatch(`_isRef(v) ? v.value = _ctx.a : v = _ctx.a`)
  522. assertCode(content)
  523. })
  524. test('template update expression codegen', () => {
  525. const { content } = compile(
  526. `<script setup>
  527. import { ref } from 'vue'
  528. const count = ref(0)
  529. const maybe = foo()
  530. let lett = 1
  531. </script>
  532. <template>
  533. <div @click="count++"/>
  534. <div @click="--count"/>
  535. <div @click="maybe++"/>
  536. <div @click="--maybe"/>
  537. <div @click="lett++"/>
  538. <div @click="--lett"/>
  539. </template>
  540. `,
  541. { inlineTemplate: true }
  542. )
  543. // known const ref: set value
  544. expect(content).toMatch(`count.value++`)
  545. expect(content).toMatch(`--count.value`)
  546. // const but maybe ref (non-ref case ignored)
  547. expect(content).toMatch(`maybe.value++`)
  548. expect(content).toMatch(`--maybe.value`)
  549. // let: handle both cases
  550. expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`)
  551. expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`)
  552. assertCode(content)
  553. })
  554. test('template destructure assignment codegen', () => {
  555. const { content } = compile(
  556. `<script setup>
  557. import { ref } from 'vue'
  558. const val = {}
  559. const count = ref(0)
  560. const maybe = foo()
  561. let lett = 1
  562. </script>
  563. <template>
  564. <div @click="({ count } = val)"/>
  565. <div @click="[maybe] = val"/>
  566. <div @click="({ lett } = val)"/>
  567. </template>
  568. `,
  569. { inlineTemplate: true }
  570. )
  571. // known const ref: set value
  572. expect(content).toMatch(`({ count: count.value } = val)`)
  573. // const but maybe ref (non-ref case ignored)
  574. expect(content).toMatch(`[maybe.value] = val`)
  575. // let: assumes non-ref
  576. expect(content).toMatch(`{ lett: lett } = val`)
  577. assertCode(content)
  578. })
  579. test('ssr codegen', () => {
  580. const { content } = compile(
  581. `
  582. <script setup>
  583. import { ref } from 'vue'
  584. const count = ref(0)
  585. </script>
  586. <template>
  587. <div>{{ count }}</div>
  588. <div>static</div>
  589. </template>
  590. <style>
  591. div { color: v-bind(count) }
  592. </style>
  593. `,
  594. {
  595. inlineTemplate: true,
  596. templateOptions: {
  597. ssr: true
  598. }
  599. }
  600. )
  601. expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
  602. expect(content).toMatch(`return (_ctx, _push`)
  603. expect(content).toMatch(`ssrInterpolate`)
  604. assertCode(content)
  605. })
  606. })
  607. describe('with TypeScript', () => {
  608. test('hoist type declarations', () => {
  609. const { content } = compile(`
  610. <script setup lang="ts">
  611. export interface Foo {}
  612. type Bar = {}
  613. </script>`)
  614. assertCode(content)
  615. })
  616. test('defineProps/Emit w/ runtime options', () => {
  617. const { content } = compile(`
  618. <script setup lang="ts">
  619. const props = defineProps({ foo: String })
  620. const emit = defineEmits(['a', 'b'])
  621. </script>
  622. `)
  623. assertCode(content)
  624. expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
  625. props: { foo: String },
  626. emits: ['a', 'b'],
  627. setup(__props, { expose, emit }) {`)
  628. })
  629. test('defineProps w/ type', () => {
  630. const { content, bindings } = compile(`
  631. <script setup lang="ts">
  632. interface Test {}
  633. type Alias = number[]
  634. defineProps<{
  635. string: string
  636. number: number
  637. boolean: boolean
  638. object: object
  639. objectLiteral: { a: number }
  640. fn: (n: number) => void
  641. functionRef: Function
  642. objectRef: Object
  643. dateTime: Date
  644. array: string[]
  645. arrayRef: Array<any>
  646. tuple: [number, number]
  647. set: Set<string>
  648. literal: 'foo'
  649. optional?: any
  650. recordRef: Record<string, null>
  651. interface: Test
  652. alias: Alias
  653. method(): void
  654. union: string | number
  655. literalUnion: 'foo' | 'bar'
  656. literalUnionNumber: 1 | 2 | 3 | 4 | 5
  657. literalUnionMixed: 'foo' | 1 | boolean
  658. intersection: Test & {}
  659. foo: ((item: any) => boolean) | null
  660. }>()
  661. </script>`)
  662. assertCode(content)
  663. expect(content).toMatch(`string: { type: String, required: true }`)
  664. expect(content).toMatch(`number: { type: Number, required: true }`)
  665. expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
  666. expect(content).toMatch(`object: { type: Object, required: true }`)
  667. expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
  668. expect(content).toMatch(`fn: { type: Function, required: true }`)
  669. expect(content).toMatch(`functionRef: { type: Function, required: true }`)
  670. expect(content).toMatch(`objectRef: { type: Object, required: true }`)
  671. expect(content).toMatch(`dateTime: { type: Date, required: true }`)
  672. expect(content).toMatch(`array: { type: Array, required: true }`)
  673. expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
  674. expect(content).toMatch(`tuple: { type: Array, required: true }`)
  675. expect(content).toMatch(`set: { type: Set, required: true }`)
  676. expect(content).toMatch(`literal: { type: String, required: true }`)
  677. expect(content).toMatch(`optional: { type: null, required: false }`)
  678. expect(content).toMatch(`recordRef: { type: Object, required: true }`)
  679. expect(content).toMatch(`interface: { type: Object, required: true }`)
  680. expect(content).toMatch(`alias: { type: Array, required: true }`)
  681. expect(content).toMatch(`method: { type: Function, required: true }`)
  682. expect(content).toMatch(
  683. `union: { type: [String, Number], required: true }`
  684. )
  685. expect(content).toMatch(`literalUnion: { type: String, required: true }`)
  686. expect(content).toMatch(
  687. `literalUnionNumber: { type: Number, required: true }`
  688. )
  689. expect(content).toMatch(
  690. `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
  691. )
  692. expect(content).toMatch(`intersection: { type: Object, required: true }`)
  693. expect(content).toMatch(`foo: { type: [Function, null], required: true }`)
  694. expect(bindings).toStrictEqual({
  695. string: BindingTypes.PROPS,
  696. number: BindingTypes.PROPS,
  697. boolean: BindingTypes.PROPS,
  698. object: BindingTypes.PROPS,
  699. objectLiteral: BindingTypes.PROPS,
  700. fn: BindingTypes.PROPS,
  701. functionRef: BindingTypes.PROPS,
  702. objectRef: BindingTypes.PROPS,
  703. dateTime: BindingTypes.PROPS,
  704. array: BindingTypes.PROPS,
  705. arrayRef: BindingTypes.PROPS,
  706. tuple: BindingTypes.PROPS,
  707. set: BindingTypes.PROPS,
  708. literal: BindingTypes.PROPS,
  709. optional: BindingTypes.PROPS,
  710. recordRef: BindingTypes.PROPS,
  711. interface: BindingTypes.PROPS,
  712. alias: BindingTypes.PROPS,
  713. method: BindingTypes.PROPS,
  714. union: BindingTypes.PROPS,
  715. literalUnion: BindingTypes.PROPS,
  716. literalUnionNumber: BindingTypes.PROPS,
  717. literalUnionMixed: BindingTypes.PROPS,
  718. intersection: BindingTypes.PROPS,
  719. foo: BindingTypes.PROPS
  720. })
  721. })
  722. test('defineProps w/ interface', () => {
  723. const { content, bindings } = compile(`
  724. <script setup lang="ts">
  725. interface Props { x?: number }
  726. defineProps<Props>()
  727. </script>
  728. `)
  729. assertCode(content)
  730. expect(content).toMatch(`x: { type: Number, required: false }`)
  731. expect(bindings).toStrictEqual({
  732. x: BindingTypes.PROPS
  733. })
  734. })
  735. test('defineProps w/ exported interface', () => {
  736. const { content, bindings } = compile(`
  737. <script setup lang="ts">
  738. export interface Props { x?: number }
  739. defineProps<Props>()
  740. </script>
  741. `)
  742. assertCode(content)
  743. expect(content).toMatch(`x: { type: Number, required: false }`)
  744. expect(bindings).toStrictEqual({
  745. x: BindingTypes.PROPS
  746. })
  747. })
  748. test('defineProps w/ exported interface in normal script', () => {
  749. const { content, bindings } = compile(`
  750. <script lang="ts">
  751. export interface Props { x?: number }
  752. </script>
  753. <script setup lang="ts">
  754. defineProps<Props>()
  755. </script>
  756. `)
  757. assertCode(content)
  758. expect(content).toMatch(`x: { type: Number, required: false }`)
  759. expect(bindings).toStrictEqual({
  760. x: BindingTypes.PROPS
  761. })
  762. })
  763. test('defineProps w/ type alias', () => {
  764. const { content, bindings } = compile(`
  765. <script setup lang="ts">
  766. type Props = { x?: number }
  767. defineProps<Props>()
  768. </script>
  769. `)
  770. assertCode(content)
  771. expect(content).toMatch(`x: { type: Number, required: false }`)
  772. expect(bindings).toStrictEqual({
  773. x: BindingTypes.PROPS
  774. })
  775. })
  776. test('defineProps w/ exported type alias', () => {
  777. const { content, bindings } = compile(`
  778. <script setup lang="ts">
  779. export type Props = { x?: number }
  780. defineProps<Props>()
  781. </script>
  782. `)
  783. assertCode(content)
  784. expect(content).toMatch(`x: { type: Number, required: false }`)
  785. expect(bindings).toStrictEqual({
  786. x: BindingTypes.PROPS
  787. })
  788. })
  789. test('withDefaults (static)', () => {
  790. const { content, bindings } = compile(`
  791. <script setup lang="ts">
  792. const props = withDefaults(defineProps<{
  793. foo?: string
  794. bar?: number;
  795. baz: boolean;
  796. qux?(): number
  797. }>(), {
  798. foo: 'hi',
  799. qux() { return 1 }
  800. })
  801. </script>
  802. `)
  803. assertCode(content)
  804. expect(content).toMatch(
  805. `foo: { type: String, required: false, default: 'hi' }`
  806. )
  807. expect(content).toMatch(`bar: { type: Number, required: false }`)
  808. expect(content).toMatch(`baz: { type: Boolean, required: true }`)
  809. expect(content).toMatch(
  810. `qux: { type: Function, required: false, default() { return 1 } }`
  811. )
  812. expect(content).toMatch(
  813. `{ foo: string, bar?: number, baz: boolean, qux(): number }`
  814. )
  815. expect(content).toMatch(`const props = __props`)
  816. expect(bindings).toStrictEqual({
  817. foo: BindingTypes.PROPS,
  818. bar: BindingTypes.PROPS,
  819. baz: BindingTypes.PROPS,
  820. qux: BindingTypes.PROPS,
  821. props: BindingTypes.SETUP_CONST
  822. })
  823. })
  824. test('withDefaults (dynamic)', () => {
  825. const { content } = compile(`
  826. <script setup lang="ts">
  827. import { defaults } from './foo'
  828. const props = withDefaults(defineProps<{
  829. foo?: string
  830. bar?: number
  831. baz: boolean
  832. }>(), { ...defaults })
  833. </script>
  834. `)
  835. assertCode(content)
  836. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  837. expect(content).toMatch(
  838. `
  839. _mergeDefaults({
  840. foo: { type: String, required: false },
  841. bar: { type: Number, required: false },
  842. baz: { type: Boolean, required: true }
  843. }, { ...defaults })`.trim()
  844. )
  845. })
  846. test('defineEmits w/ type', () => {
  847. const { content } = compile(`
  848. <script setup lang="ts">
  849. const emit = defineEmits<(e: 'foo' | 'bar') => void>()
  850. </script>
  851. `)
  852. assertCode(content)
  853. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  854. expect(content).toMatch(`emits: ["foo", "bar"]`)
  855. })
  856. test('defineEmits w/ type (union)', () => {
  857. const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
  858. expect(() =>
  859. compile(`
  860. <script setup lang="ts">
  861. const emit = defineEmits<${type}>()
  862. </script>
  863. `)
  864. ).toThrow()
  865. })
  866. test('defineEmits w/ type (type literal w/ call signatures)', () => {
  867. const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
  868. const { content } = compile(`
  869. <script setup lang="ts">
  870. const emit = defineEmits<${type}>()
  871. </script>
  872. `)
  873. assertCode(content)
  874. expect(content).toMatch(`emit: (${type}),`)
  875. expect(content).toMatch(`emits: ["foo", "bar", "baz"]`)
  876. })
  877. test('defineEmits w/ type (interface)', () => {
  878. const { content } = compile(`
  879. <script setup lang="ts">
  880. interface Emits { (e: 'foo' | 'bar'): void }
  881. const emit = defineEmits<Emits>()
  882. </script>
  883. `)
  884. assertCode(content)
  885. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  886. expect(content).toMatch(`emits: ["foo", "bar"]`)
  887. })
  888. test('defineEmits w/ type (exported interface)', () => {
  889. const { content } = compile(`
  890. <script setup lang="ts">
  891. export interface Emits { (e: 'foo' | 'bar'): void }
  892. const emit = defineEmits<Emits>()
  893. </script>
  894. `)
  895. assertCode(content)
  896. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  897. expect(content).toMatch(`emits: ["foo", "bar"]`)
  898. })
  899. test('defineEmits w/ type (type alias)', () => {
  900. const { content } = compile(`
  901. <script setup lang="ts">
  902. type Emits = { (e: 'foo' | 'bar'): void }
  903. const emit = defineEmits<Emits>()
  904. </script>
  905. `)
  906. assertCode(content)
  907. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  908. expect(content).toMatch(`emits: ["foo", "bar"]`)
  909. })
  910. test('defineEmits w/ type (exported type alias)', () => {
  911. const { content } = compile(`
  912. <script setup lang="ts">
  913. export type Emits = { (e: 'foo' | 'bar'): void }
  914. const emit = defineEmits<Emits>()
  915. </script>
  916. `)
  917. assertCode(content)
  918. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  919. expect(content).toMatch(`emits: ["foo", "bar"]`)
  920. })
  921. test('defineEmits w/ type (referenced function type)', () => {
  922. const { content } = compile(`
  923. <script setup lang="ts">
  924. type Emits = (e: 'foo' | 'bar') => void
  925. const emit = defineEmits<Emits>()
  926. </script>
  927. `)
  928. assertCode(content)
  929. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  930. expect(content).toMatch(`emits: ["foo", "bar"]`)
  931. })
  932. test('defineEmits w/ type (referenced exported function type)', () => {
  933. const { content } = compile(`
  934. <script setup lang="ts">
  935. export type Emits = (e: 'foo' | 'bar') => void
  936. const emit = defineEmits<Emits>()
  937. </script>
  938. `)
  939. assertCode(content)
  940. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  941. expect(content).toMatch(`emits: ["foo", "bar"]`)
  942. })
  943. test('runtime Enum', () => {
  944. const { content, bindings } = compile(
  945. `<script setup lang="ts">
  946. enum Foo { A = 123 }
  947. </script>`
  948. )
  949. assertCode(content)
  950. expect(bindings).toStrictEqual({
  951. Foo: BindingTypes.SETUP_CONST
  952. })
  953. })
  954. test('const Enum', () => {
  955. const { content, bindings } = compile(
  956. `<script setup lang="ts">
  957. const enum Foo { A = 123 }
  958. </script>`
  959. )
  960. assertCode(content)
  961. expect(bindings).toStrictEqual({
  962. Foo: BindingTypes.SETUP_CONST
  963. })
  964. })
  965. })
  966. describe('async/await detection', () => {
  967. function assertAwaitDetection(
  968. code: string,
  969. expected: string | ((content: string) => boolean),
  970. shouldAsync = true
  971. ) {
  972. const { content } = compile(`<script setup>${code}</script>`, {
  973. refSugar: true
  974. })
  975. if (shouldAsync) {
  976. expect(content).toMatch(`let __temp, __restore`)
  977. }
  978. expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
  979. if (typeof expected === 'string') {
  980. expect(content).toMatch(expected)
  981. } else {
  982. expect(expected(content)).toBe(true)
  983. }
  984. }
  985. test('expression statement', () => {
  986. assertAwaitDetection(
  987. `await foo`,
  988. `;(([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore())`
  989. )
  990. })
  991. test('variable', () => {
  992. assertAwaitDetection(
  993. `const a = 1 + (await foo)`,
  994. `1 + ((([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore(),__temp))`
  995. )
  996. })
  997. test('ref', () => {
  998. assertAwaitDetection(
  999. `let a = $ref(1 + (await foo))`,
  1000. `1 + ((([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore(),__temp))`
  1001. )
  1002. })
  1003. test('nested statements', () => {
  1004. assertAwaitDetection(`if (ok) { await foo } else { await bar }`, code => {
  1005. return (
  1006. code.includes(
  1007. `;(([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore())`
  1008. ) &&
  1009. code.includes(
  1010. `;(([__temp,__restore]=_withAsyncContext(()=>(bar))),__temp=await __temp,__restore())`
  1011. )
  1012. )
  1013. })
  1014. })
  1015. test('should ignore await inside functions', () => {
  1016. // function declaration
  1017. assertAwaitDetection(
  1018. `async function foo() { await bar }`,
  1019. `await bar`,
  1020. false
  1021. )
  1022. // function expression
  1023. assertAwaitDetection(
  1024. `const foo = async () => { await bar }`,
  1025. `await bar`,
  1026. false
  1027. )
  1028. // object method
  1029. assertAwaitDetection(
  1030. `const obj = { async method() { await bar }}`,
  1031. `await bar`,
  1032. false
  1033. )
  1034. // class method
  1035. assertAwaitDetection(
  1036. `const cls = class Foo { async method() { await bar }}`,
  1037. `await bar`,
  1038. false
  1039. )
  1040. })
  1041. })
  1042. describe('errors', () => {
  1043. test('<script> and <script setup> must have same lang', () => {
  1044. expect(() =>
  1045. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`)
  1046. ).toThrow(`<script> and <script setup> must have the same language type`)
  1047. })
  1048. const moduleErrorMsg = `cannot contain ES module exports`
  1049. test('non-type named exports', () => {
  1050. expect(() =>
  1051. compile(`<script setup>
  1052. export const a = 1
  1053. </script>`)
  1054. ).toThrow(moduleErrorMsg)
  1055. expect(() =>
  1056. compile(`<script setup>
  1057. export * from './foo'
  1058. </script>`)
  1059. ).toThrow(moduleErrorMsg)
  1060. expect(() =>
  1061. compile(`<script setup>
  1062. const bar = 1
  1063. export { bar as default }
  1064. </script>`)
  1065. ).toThrow(moduleErrorMsg)
  1066. })
  1067. test('defineProps/Emit() w/ both type and non-type args', () => {
  1068. expect(() => {
  1069. compile(`<script setup lang="ts">
  1070. defineProps<{}>({})
  1071. </script>`)
  1072. }).toThrow(`cannot accept both type and non-type arguments`)
  1073. expect(() => {
  1074. compile(`<script setup lang="ts">
  1075. defineEmits<{}>({})
  1076. </script>`)
  1077. }).toThrow(`cannot accept both type and non-type arguments`)
  1078. })
  1079. test('defineProps/Emit() referencing local var', () => {
  1080. expect(() =>
  1081. compile(`<script setup>
  1082. const bar = 1
  1083. defineProps({
  1084. foo: {
  1085. default: () => bar
  1086. }
  1087. })
  1088. </script>`)
  1089. ).toThrow(`cannot reference locally declared variables`)
  1090. expect(() =>
  1091. compile(`<script setup>
  1092. const bar = 'hello'
  1093. defineEmits([bar])
  1094. </script>`)
  1095. ).toThrow(`cannot reference locally declared variables`)
  1096. })
  1097. test('should allow defineProps/Emit() referencing scope var', () => {
  1098. assertCode(
  1099. compile(`<script setup>
  1100. const bar = 1
  1101. defineProps({
  1102. foo: {
  1103. default: bar => bar + 1
  1104. }
  1105. })
  1106. defineEmits({
  1107. foo: bar => bar > 1
  1108. })
  1109. </script>`).content
  1110. )
  1111. })
  1112. test('should allow defineProps/Emit() referencing imported binding', () => {
  1113. assertCode(
  1114. compile(`<script setup>
  1115. import { bar } from './bar'
  1116. defineProps({
  1117. foo: {
  1118. default: () => bar
  1119. }
  1120. })
  1121. defineEmits({
  1122. foo: () => bar > 1
  1123. })
  1124. </script>`).content
  1125. )
  1126. })
  1127. })
  1128. })
  1129. describe('SFC analyze <script> bindings', () => {
  1130. it('can parse decorators syntax in typescript block', () => {
  1131. const { scriptAst } = compile(`
  1132. <script lang="ts">
  1133. import { Options, Vue } from 'vue-class-component';
  1134. @Options({
  1135. components: {
  1136. HelloWorld,
  1137. },
  1138. props: ['foo', 'bar']
  1139. })
  1140. export default class Home extends Vue {}
  1141. </script>
  1142. `)
  1143. expect(scriptAst).toBeDefined()
  1144. })
  1145. it('recognizes props array declaration', () => {
  1146. const { bindings } = compile(`
  1147. <script>
  1148. export default {
  1149. props: ['foo', 'bar']
  1150. }
  1151. </script>
  1152. `)
  1153. expect(bindings).toStrictEqual({
  1154. foo: BindingTypes.PROPS,
  1155. bar: BindingTypes.PROPS
  1156. })
  1157. expect(bindings!.__isScriptSetup).toBe(false)
  1158. })
  1159. it('recognizes props object declaration', () => {
  1160. const { bindings } = compile(`
  1161. <script>
  1162. export default {
  1163. props: {
  1164. foo: String,
  1165. bar: {
  1166. type: String,
  1167. },
  1168. baz: null,
  1169. qux: [String, Number]
  1170. }
  1171. }
  1172. </script>
  1173. `)
  1174. expect(bindings).toStrictEqual({
  1175. foo: BindingTypes.PROPS,
  1176. bar: BindingTypes.PROPS,
  1177. baz: BindingTypes.PROPS,
  1178. qux: BindingTypes.PROPS
  1179. })
  1180. expect(bindings!.__isScriptSetup).toBe(false)
  1181. })
  1182. it('recognizes setup return', () => {
  1183. const { bindings } = compile(`
  1184. <script>
  1185. const bar = 2
  1186. export default {
  1187. setup() {
  1188. return {
  1189. foo: 1,
  1190. bar
  1191. }
  1192. }
  1193. }
  1194. </script>
  1195. `)
  1196. expect(bindings).toStrictEqual({
  1197. foo: BindingTypes.SETUP_MAYBE_REF,
  1198. bar: BindingTypes.SETUP_MAYBE_REF
  1199. })
  1200. expect(bindings!.__isScriptSetup).toBe(false)
  1201. })
  1202. it('recognizes async setup return', () => {
  1203. const { bindings } = compile(`
  1204. <script>
  1205. const bar = 2
  1206. export default {
  1207. async setup() {
  1208. return {
  1209. foo: 1,
  1210. bar
  1211. }
  1212. }
  1213. }
  1214. </script>
  1215. `)
  1216. expect(bindings).toStrictEqual({
  1217. foo: BindingTypes.SETUP_MAYBE_REF,
  1218. bar: BindingTypes.SETUP_MAYBE_REF
  1219. })
  1220. expect(bindings!.__isScriptSetup).toBe(false)
  1221. })
  1222. it('recognizes data return', () => {
  1223. const { bindings } = compile(`
  1224. <script>
  1225. const bar = 2
  1226. export default {
  1227. data() {
  1228. return {
  1229. foo: null,
  1230. bar
  1231. }
  1232. }
  1233. }
  1234. </script>
  1235. `)
  1236. expect(bindings).toStrictEqual({
  1237. foo: BindingTypes.DATA,
  1238. bar: BindingTypes.DATA
  1239. })
  1240. })
  1241. it('recognizes methods', () => {
  1242. const { bindings } = compile(`
  1243. <script>
  1244. export default {
  1245. methods: {
  1246. foo() {}
  1247. }
  1248. }
  1249. </script>
  1250. `)
  1251. expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })
  1252. })
  1253. it('recognizes computeds', () => {
  1254. const { bindings } = compile(`
  1255. <script>
  1256. export default {
  1257. computed: {
  1258. foo() {},
  1259. bar: {
  1260. get() {},
  1261. set() {},
  1262. }
  1263. }
  1264. }
  1265. </script>
  1266. `)
  1267. expect(bindings).toStrictEqual({
  1268. foo: BindingTypes.OPTIONS,
  1269. bar: BindingTypes.OPTIONS
  1270. })
  1271. })
  1272. it('recognizes injections array declaration', () => {
  1273. const { bindings } = compile(`
  1274. <script>
  1275. export default {
  1276. inject: ['foo', 'bar']
  1277. }
  1278. </script>
  1279. `)
  1280. expect(bindings).toStrictEqual({
  1281. foo: BindingTypes.OPTIONS,
  1282. bar: BindingTypes.OPTIONS
  1283. })
  1284. })
  1285. it('recognizes injections object declaration', () => {
  1286. const { bindings } = compile(`
  1287. <script>
  1288. export default {
  1289. inject: {
  1290. foo: {},
  1291. bar: {},
  1292. }
  1293. }
  1294. </script>
  1295. `)
  1296. expect(bindings).toStrictEqual({
  1297. foo: BindingTypes.OPTIONS,
  1298. bar: BindingTypes.OPTIONS
  1299. })
  1300. })
  1301. it('works for mixed bindings', () => {
  1302. const { bindings } = compile(`
  1303. <script>
  1304. export default {
  1305. inject: ['foo'],
  1306. props: {
  1307. bar: String,
  1308. },
  1309. setup() {
  1310. return {
  1311. baz: null,
  1312. }
  1313. },
  1314. data() {
  1315. return {
  1316. qux: null
  1317. }
  1318. },
  1319. methods: {
  1320. quux() {}
  1321. },
  1322. computed: {
  1323. quuz() {}
  1324. }
  1325. }
  1326. </script>
  1327. `)
  1328. expect(bindings).toStrictEqual({
  1329. foo: BindingTypes.OPTIONS,
  1330. bar: BindingTypes.PROPS,
  1331. baz: BindingTypes.SETUP_MAYBE_REF,
  1332. qux: BindingTypes.DATA,
  1333. quux: BindingTypes.OPTIONS,
  1334. quuz: BindingTypes.OPTIONS
  1335. })
  1336. })
  1337. it('works for script setup', () => {
  1338. const { bindings } = compile(`
  1339. <script setup>
  1340. import { ref as r } from 'vue'
  1341. defineProps({
  1342. foo: String
  1343. })
  1344. const a = r(1)
  1345. let b = 2
  1346. const c = 3
  1347. const { d } = someFoo()
  1348. let { e } = someBar()
  1349. </script>
  1350. `)
  1351. expect(bindings).toStrictEqual({
  1352. r: BindingTypes.SETUP_CONST,
  1353. a: BindingTypes.SETUP_REF,
  1354. b: BindingTypes.SETUP_LET,
  1355. c: BindingTypes.SETUP_CONST,
  1356. d: BindingTypes.SETUP_MAYBE_REF,
  1357. e: BindingTypes.SETUP_LET,
  1358. foo: BindingTypes.PROPS
  1359. })
  1360. })
  1361. })