compileScript.spec.ts 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144
  1. import { BindingTypes } from '@vue/compiler-dom'
  2. import { compileSFCScript as compile, assertCode } from './utils'
  3. describe('SFC compile <script setup>', () => {
  4. test('should expose top level declarations', () => {
  5. const { content } = 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. `)
  14. assertCode(content)
  15. expect(content).toMatch('return { a, b, c, d, x }')
  16. })
  17. test('defineProps()', () => {
  18. const { content, bindings } = compile(`
  19. <script setup>
  20. import { defineProps } from 'vue'
  21. const props = defineProps({
  22. foo: String
  23. })
  24. const bar = 1
  25. </script>
  26. `)
  27. // should generate working code
  28. assertCode(content)
  29. // should anayze bindings
  30. expect(bindings).toStrictEqual({
  31. foo: BindingTypes.PROPS,
  32. bar: BindingTypes.SETUP_CONST,
  33. props: BindingTypes.SETUP_CONST
  34. })
  35. // should remove defineOptions import and call
  36. expect(content).not.toMatch('defineProps')
  37. // should generate correct setup signature
  38. expect(content).toMatch(`setup(__props) {`)
  39. // should assign user identifier to it
  40. expect(content).toMatch(`const props = __props`)
  41. // should include context options in default export
  42. expect(content).toMatch(`export default {
  43. expose: [],
  44. props: {
  45. foo: String
  46. },`)
  47. })
  48. test('defineEmit()', () => {
  49. const { content, bindings } = compile(`
  50. <script setup>
  51. import { defineEmit } from 'vue'
  52. const myEmit = defineEmit(['foo', 'bar'])
  53. </script>
  54. `)
  55. assertCode(content)
  56. expect(bindings).toStrictEqual({
  57. myEmit: BindingTypes.SETUP_CONST
  58. })
  59. // should remove defineOptions import and call
  60. expect(content).not.toMatch('defineEmit')
  61. // should generate correct setup signature
  62. expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)
  63. // should include context options in default export
  64. expect(content).toMatch(`export default {
  65. expose: [],
  66. emits: ['foo', 'bar'],`)
  67. })
  68. test('<template inherit-attrs="false">', () => {
  69. const { content } = compile(`
  70. <script>
  71. export default {}
  72. </script>
  73. <template inherit-attrs="false">
  74. {{ a }}
  75. </template>
  76. `)
  77. assertCode(content)
  78. const { content: content2 } = compile(`
  79. <script setup>
  80. const a = 1
  81. </script>
  82. <template inherit-attrs="false">
  83. {{ a }}
  84. </template>
  85. `)
  86. assertCode(content2)
  87. })
  88. describe('<script> and <script setup> co-usage', () => {
  89. test('script first', () => {
  90. const { content } = compile(`
  91. <script>
  92. export const n = 1
  93. </script>
  94. <script setup>
  95. import { x } from './x'
  96. x()
  97. </script>
  98. `)
  99. assertCode(content)
  100. })
  101. test('script setup first', () => {
  102. const { content } = compile(`
  103. <script setup>
  104. import { x } from './x'
  105. x()
  106. </script>
  107. <script>
  108. export const n = 1
  109. </script>
  110. `)
  111. assertCode(content)
  112. })
  113. })
  114. describe('imports', () => {
  115. test('should hoist and expose imports', () => {
  116. assertCode(
  117. compile(`<script setup>import { ref } from 'vue'</script>`).content
  118. )
  119. })
  120. test('should extract comment for import or type declarations', () => {
  121. assertCode(
  122. compile(`
  123. <script setup>
  124. import a from 'a' // comment
  125. import b from 'b'
  126. </script>
  127. `).content
  128. )
  129. })
  130. test('dedupe between user & helper', () => {
  131. const { content } = compile(`
  132. <script setup>
  133. import { ref } from 'vue'
  134. ref: foo = 1
  135. </script>
  136. `)
  137. assertCode(content)
  138. expect(content).toMatch(`import { ref } from 'vue'`)
  139. })
  140. test('import dedupe between <script> and <script setup>', () => {
  141. const { content } = compile(`
  142. <script>
  143. import { x } from './x'
  144. </script>
  145. <script setup>
  146. import { x } from './x'
  147. x()
  148. </script>
  149. `)
  150. assertCode(content)
  151. expect(content.indexOf(`import { x }`)).toEqual(
  152. content.lastIndexOf(`import { x }`)
  153. )
  154. })
  155. })
  156. describe('inlineTemplate mode', () => {
  157. test('should work', () => {
  158. const { content } = compile(
  159. `
  160. <script setup>
  161. import { ref } from 'vue'
  162. const count = ref(0)
  163. </script>
  164. <template>
  165. <div>{{ count }}</div>
  166. <div>static</div>
  167. </template>
  168. `,
  169. { inlineTemplate: true }
  170. )
  171. // check snapshot and make sure helper imports and
  172. // hoists are placed correctly.
  173. assertCode(content)
  174. })
  175. test('referencing scope components and directives', () => {
  176. const { content } = compile(
  177. `
  178. <script setup>
  179. import ChildComp from './Child.vue'
  180. import SomeOtherComp from './Other.vue'
  181. import vMyDir from './my-dir'
  182. </script>
  183. <template>
  184. <div v-my-dir></div>
  185. <ChildComp/>
  186. <some-other-comp/>
  187. </template>
  188. `,
  189. { inlineTemplate: true }
  190. )
  191. expect(content).toMatch('[_unref(vMyDir)]')
  192. expect(content).toMatch('_createVNode(ChildComp)')
  193. // kebab-case component support
  194. expect(content).toMatch('_createVNode(SomeOtherComp)')
  195. assertCode(content)
  196. })
  197. test('avoid unref() when necessary', () => {
  198. // function, const, component import
  199. const { content } = compile(
  200. `<script setup>
  201. import { ref } from 'vue'
  202. import Foo from './Foo.vue'
  203. import other from './util'
  204. const count = ref(0)
  205. const constant = {}
  206. const maybe = foo()
  207. let lett = 1
  208. function fn() {}
  209. </script>
  210. <template>
  211. <Foo/>
  212. <div @click="fn">{{ count }} {{ constant }} {{ maybe }} {{ lett }} {{ other }}</div>
  213. </template>
  214. `,
  215. { inlineTemplate: true }
  216. )
  217. // no need to unref vue component import
  218. expect(content).toMatch(`createVNode(Foo)`)
  219. // should unref other imports
  220. expect(content).toMatch(`unref(other)`)
  221. // no need to unref constant literals
  222. expect(content).not.toMatch(`unref(constant)`)
  223. // should directly use .value for known refs
  224. expect(content).toMatch(`count.value`)
  225. // should unref() on const bindings that may be refs
  226. expect(content).toMatch(`unref(maybe)`)
  227. // should unref() on let bindings
  228. expect(content).toMatch(`unref(lett)`)
  229. // no need to unref function declarations
  230. expect(content).toMatch(`{ onClick: fn }`)
  231. // no need to mark constant fns in patch flag
  232. expect(content).not.toMatch(`PROPS`)
  233. assertCode(content)
  234. })
  235. test('v-model codegen', () => {
  236. const { content } = compile(
  237. `<script setup>
  238. import { ref } from 'vue'
  239. const count = ref(0)
  240. const maybe = foo()
  241. let lett = 1
  242. </script>
  243. <template>
  244. <input v-model="count">
  245. <input v-model="maybe">
  246. <input v-model="lett">
  247. </template>
  248. `,
  249. { inlineTemplate: true }
  250. )
  251. // known const ref: set value
  252. expect(content).toMatch(`count.value = $event`)
  253. // const but maybe ref: also assign .value directly since non-ref
  254. // won't work
  255. expect(content).toMatch(`maybe.value = $event`)
  256. // let: handle both cases
  257. expect(content).toMatch(
  258. `_isRef(lett) ? lett.value = $event : lett = $event`
  259. )
  260. assertCode(content)
  261. })
  262. test('template assignment expression codegen', () => {
  263. const { content } = compile(
  264. `<script setup>
  265. import { ref } from 'vue'
  266. const count = ref(0)
  267. const maybe = foo()
  268. let lett = 1
  269. </script>
  270. <template>
  271. <div @click="count = 1"/>
  272. <div @click="maybe = count"/>
  273. <div @click="lett = count"/>
  274. </template>
  275. `,
  276. { inlineTemplate: true }
  277. )
  278. // known const ref: set value
  279. expect(content).toMatch(`count.value = 1`)
  280. // const but maybe ref: only assign after check
  281. expect(content).toMatch(`maybe.value = count.value`)
  282. // let: handle both cases
  283. expect(content).toMatch(
  284. `_isRef(lett) ? lett.value = count.value : lett = count.value`
  285. )
  286. assertCode(content)
  287. })
  288. test('template update expression codegen', () => {
  289. const { content } = compile(
  290. `<script setup>
  291. import { ref } from 'vue'
  292. const count = ref(0)
  293. const maybe = foo()
  294. let lett = 1
  295. </script>
  296. <template>
  297. <div @click="count++"/>
  298. <div @click="--count"/>
  299. <div @click="maybe++"/>
  300. <div @click="--maybe"/>
  301. <div @click="lett++"/>
  302. <div @click="--lett"/>
  303. </template>
  304. `,
  305. { inlineTemplate: true }
  306. )
  307. // known const ref: set value
  308. expect(content).toMatch(`count.value++`)
  309. expect(content).toMatch(`--count.value`)
  310. // const but maybe ref (non-ref case ignored)
  311. expect(content).toMatch(`maybe.value++`)
  312. expect(content).toMatch(`--maybe.value`)
  313. // let: handle both cases
  314. expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`)
  315. expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`)
  316. assertCode(content)
  317. })
  318. test('template destructure assignment codegen', () => {
  319. const { content } = compile(
  320. `<script setup>
  321. import { ref } from 'vue'
  322. const val = {}
  323. const count = ref(0)
  324. const maybe = foo()
  325. let lett = 1
  326. </script>
  327. <template>
  328. <div @click="({ count } = val)"/>
  329. <div @click="[maybe] = val"/>
  330. <div @click="({ lett } = val)"/>
  331. </template>
  332. `,
  333. { inlineTemplate: true }
  334. )
  335. // known const ref: set value
  336. expect(content).toMatch(`({ count: count.value } = val)`)
  337. // const but maybe ref (non-ref case ignored)
  338. expect(content).toMatch(`[maybe.value] = val`)
  339. // let: assumes non-ref
  340. expect(content).toMatch(`{ lett: lett } = val`)
  341. assertCode(content)
  342. })
  343. test('ssr codegen', () => {
  344. const { content } = compile(
  345. `
  346. <script setup>
  347. import { ref } from 'vue'
  348. const count = ref(0)
  349. </script>
  350. <template>
  351. <div>{{ count }}</div>
  352. <div>static</div>
  353. </template>
  354. <style>
  355. div { color: v-bind(count) }
  356. </style>
  357. `,
  358. {
  359. inlineTemplate: true,
  360. templateOptions: {
  361. ssr: true
  362. }
  363. }
  364. )
  365. expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
  366. expect(content).toMatch(`return (_ctx, _push`)
  367. expect(content).toMatch(`ssrInterpolate`)
  368. assertCode(content)
  369. })
  370. })
  371. describe('with TypeScript', () => {
  372. test('hoist type declarations', () => {
  373. const { content } = compile(`
  374. <script setup lang="ts">
  375. export interface Foo {}
  376. type Bar = {}
  377. </script>`)
  378. assertCode(content)
  379. })
  380. test('defineProps/Emit w/ runtime options', () => {
  381. const { content } = compile(`
  382. <script setup lang="ts">
  383. import { defineProps, defineEmit } from 'vue'
  384. const props = defineProps({ foo: String })
  385. const emit = defineEmit(['a', 'b'])
  386. </script>
  387. `)
  388. assertCode(content)
  389. expect(content).toMatch(`export default _defineComponent({
  390. expose: [],
  391. props: { foo: String },
  392. emits: ['a', 'b'],
  393. setup(__props, { emit }) {`)
  394. })
  395. test('defineProps w/ type', () => {
  396. const { content, bindings } = compile(`
  397. <script setup lang="ts">
  398. import { defineProps } from 'vue'
  399. interface Test {}
  400. type Alias = number[]
  401. defineProps<{
  402. string: string
  403. number: number
  404. boolean: boolean
  405. object: object
  406. objectLiteral: { a: number }
  407. fn: (n: number) => void
  408. functionRef: Function
  409. objectRef: Object
  410. array: string[]
  411. arrayRef: Array<any>
  412. tuple: [number, number]
  413. set: Set<string>
  414. literal: 'foo'
  415. optional?: any
  416. recordRef: Record<string, null>
  417. interface: Test
  418. alias: Alias
  419. union: string | number
  420. literalUnion: 'foo' | 'bar'
  421. literalUnionMixed: 'foo' | 1 | boolean
  422. intersection: Test & {}
  423. }>()
  424. </script>`)
  425. assertCode(content)
  426. expect(content).toMatch(`string: { type: String, required: true }`)
  427. expect(content).toMatch(`number: { type: Number, required: true }`)
  428. expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
  429. expect(content).toMatch(`object: { type: Object, required: true }`)
  430. expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
  431. expect(content).toMatch(`fn: { type: Function, required: true }`)
  432. expect(content).toMatch(`functionRef: { type: Function, required: true }`)
  433. expect(content).toMatch(`objectRef: { type: Object, required: true }`)
  434. expect(content).toMatch(`array: { type: Array, required: true }`)
  435. expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
  436. expect(content).toMatch(`tuple: { type: Array, required: true }`)
  437. expect(content).toMatch(`set: { type: Set, required: true }`)
  438. expect(content).toMatch(`literal: { type: String, required: true }`)
  439. expect(content).toMatch(`optional: { type: null, required: false }`)
  440. expect(content).toMatch(`recordRef: { type: Object, required: true }`)
  441. expect(content).toMatch(`interface: { type: Object, required: true }`)
  442. expect(content).toMatch(`alias: { type: Array, required: true }`)
  443. expect(content).toMatch(
  444. `union: { type: [String, Number], required: true }`
  445. )
  446. expect(content).toMatch(
  447. `literalUnion: { type: [String, String], required: true }`
  448. )
  449. expect(content).toMatch(
  450. `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
  451. )
  452. expect(content).toMatch(`intersection: { type: Object, required: true }`)
  453. expect(bindings).toStrictEqual({
  454. string: BindingTypes.PROPS,
  455. number: BindingTypes.PROPS,
  456. boolean: BindingTypes.PROPS,
  457. object: BindingTypes.PROPS,
  458. objectLiteral: BindingTypes.PROPS,
  459. fn: BindingTypes.PROPS,
  460. functionRef: BindingTypes.PROPS,
  461. objectRef: BindingTypes.PROPS,
  462. array: BindingTypes.PROPS,
  463. arrayRef: BindingTypes.PROPS,
  464. tuple: BindingTypes.PROPS,
  465. set: BindingTypes.PROPS,
  466. literal: BindingTypes.PROPS,
  467. optional: BindingTypes.PROPS,
  468. recordRef: BindingTypes.PROPS,
  469. interface: BindingTypes.PROPS,
  470. alias: BindingTypes.PROPS,
  471. union: BindingTypes.PROPS,
  472. literalUnion: BindingTypes.PROPS,
  473. literalUnionMixed: BindingTypes.PROPS,
  474. intersection: BindingTypes.PROPS
  475. })
  476. })
  477. test('defineEmit w/ type', () => {
  478. const { content } = compile(`
  479. <script setup lang="ts">
  480. import { defineEmit } from 'vue'
  481. const emit = defineEmit<(e: 'foo' | 'bar') => void>()
  482. </script>
  483. `)
  484. assertCode(content)
  485. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  486. expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
  487. })
  488. test('defineEmit w/ type (union)', () => {
  489. const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
  490. const { content } = compile(`
  491. <script setup lang="ts">
  492. import { defineEmit } from 'vue'
  493. const emit = defineEmit<${type}>()
  494. </script>
  495. `)
  496. assertCode(content)
  497. expect(content).toMatch(`emit: (${type}),`)
  498. expect(content).toMatch(
  499. `emits: ["foo", "bar", "baz"] as unknown as undefined`
  500. )
  501. })
  502. })
  503. describe('async/await detection', () => {
  504. function assertAwaitDetection(code: string, shouldAsync = true) {
  505. const { content } = compile(`<script setup>${code}</script>`)
  506. expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
  507. }
  508. test('expression statement', () => {
  509. assertAwaitDetection(`await foo`)
  510. })
  511. test('variable', () => {
  512. assertAwaitDetection(`const a = 1 + (await foo)`)
  513. })
  514. test('ref', () => {
  515. assertAwaitDetection(`ref: a = 1 + (await foo)`)
  516. })
  517. test('nested statements', () => {
  518. assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
  519. })
  520. test('should ignore await inside functions', () => {
  521. // function declaration
  522. assertAwaitDetection(`async function foo() { await bar }`, false)
  523. // function expression
  524. assertAwaitDetection(`const foo = async () => { await bar }`, false)
  525. // object method
  526. assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
  527. // class method
  528. assertAwaitDetection(
  529. `const cls = class Foo { async method() { await bar }}`,
  530. false
  531. )
  532. })
  533. })
  534. describe('ref: syntax sugar', () => {
  535. test('convert ref declarations', () => {
  536. const { content, bindings } = compile(`<script setup>
  537. ref: foo
  538. ref: a = 1
  539. ref: b = {
  540. count: 0
  541. }
  542. let c = () => {}
  543. let d
  544. </script>`)
  545. expect(content).toMatch(`import { ref as _ref } from 'vue'`)
  546. expect(content).not.toMatch(`ref: foo`)
  547. expect(content).not.toMatch(`ref: a`)
  548. expect(content).not.toMatch(`ref: b`)
  549. expect(content).toMatch(`const foo = _ref()`)
  550. expect(content).toMatch(`const a = _ref(1)`)
  551. expect(content).toMatch(`
  552. const b = _ref({
  553. count: 0
  554. })
  555. `)
  556. // normal declarations left untouched
  557. expect(content).toMatch(`let c = () => {}`)
  558. expect(content).toMatch(`let d`)
  559. assertCode(content)
  560. expect(bindings).toStrictEqual({
  561. foo: BindingTypes.SETUP_REF,
  562. a: BindingTypes.SETUP_REF,
  563. b: BindingTypes.SETUP_REF,
  564. c: BindingTypes.SETUP_LET,
  565. d: BindingTypes.SETUP_LET
  566. })
  567. })
  568. test('multi ref declarations', () => {
  569. const { content, bindings } = compile(`<script setup>
  570. ref: a = 1, b = 2, c = {
  571. count: 0
  572. }
  573. </script>`)
  574. expect(content).toMatch(`
  575. const a = _ref(1), b = _ref(2), c = _ref({
  576. count: 0
  577. })
  578. `)
  579. expect(content).toMatch(`return { a, b, c }`)
  580. assertCode(content)
  581. expect(bindings).toStrictEqual({
  582. a: BindingTypes.SETUP_REF,
  583. b: BindingTypes.SETUP_REF,
  584. c: BindingTypes.SETUP_REF
  585. })
  586. })
  587. test('should not convert non ref labels', () => {
  588. const { content } = compile(`<script setup>
  589. foo: a = 1, b = 2, c = {
  590. count: 0
  591. }
  592. </script>`)
  593. expect(content).toMatch(`foo: a = 1, b = 2`)
  594. assertCode(content)
  595. })
  596. test('accessing ref binding', () => {
  597. const { content } = compile(`<script setup>
  598. ref: a = 1
  599. console.log(a)
  600. function get() {
  601. return a + 1
  602. }
  603. </script>`)
  604. expect(content).toMatch(`console.log(a.value)`)
  605. expect(content).toMatch(`return a.value + 1`)
  606. assertCode(content)
  607. })
  608. test('cases that should not append .value', () => {
  609. const { content } = compile(`<script setup>
  610. ref: a = 1
  611. console.log(b.a)
  612. function get(a) {
  613. return a + 1
  614. }
  615. </script>`)
  616. expect(content).not.toMatch(`a.value`)
  617. })
  618. test('mutating ref binding', () => {
  619. const { content } = compile(`<script setup>
  620. ref: a = 1
  621. ref: b = { count: 0 }
  622. function inc() {
  623. a++
  624. a = a + 1
  625. b.count++
  626. b.count = b.count + 1
  627. ;({ a } = { a: 2 })
  628. ;[a] = [1]
  629. }
  630. </script>`)
  631. expect(content).toMatch(`a.value++`)
  632. expect(content).toMatch(`a.value = a.value + 1`)
  633. expect(content).toMatch(`b.value.count++`)
  634. expect(content).toMatch(`b.value.count = b.value.count + 1`)
  635. expect(content).toMatch(`;({ a: a.value } = { a: 2 })`)
  636. expect(content).toMatch(`;[a.value] = [1]`)
  637. assertCode(content)
  638. })
  639. test('using ref binding in property shorthand', () => {
  640. const { content } = compile(`<script setup>
  641. ref: a = 1
  642. const b = { a }
  643. function test() {
  644. const { a } = b
  645. }
  646. </script>`)
  647. expect(content).toMatch(`const b = { a: a.value }`)
  648. // should not convert destructure
  649. expect(content).toMatch(`const { a } = b`)
  650. assertCode(content)
  651. })
  652. test('object destructure', () => {
  653. const { content, bindings } = compile(`<script setup>
  654. ref: n = 1, ({ a, b: c, d = 1, e: f = 2, ...g } = useFoo())
  655. console.log(n, a, c, d, f, g)
  656. </script>`)
  657. expect(content).toMatch(
  658. `const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()`
  659. )
  660. expect(content).toMatch(`\nconst a = _ref(__a);`)
  661. expect(content).not.toMatch(`\nconst b = _ref(__b);`)
  662. expect(content).toMatch(`\nconst c = _ref(__c);`)
  663. expect(content).toMatch(`\nconst d = _ref(__d);`)
  664. expect(content).not.toMatch(`\nconst e = _ref(__e);`)
  665. expect(content).toMatch(`\nconst f = _ref(__f);`)
  666. expect(content).toMatch(`\nconst g = _ref(__g);`)
  667. expect(content).toMatch(
  668. `console.log(n.value, a.value, c.value, d.value, f.value, g.value)`
  669. )
  670. expect(content).toMatch(`return { n, a, c, d, f, g }`)
  671. expect(bindings).toStrictEqual({
  672. n: BindingTypes.SETUP_REF,
  673. a: BindingTypes.SETUP_REF,
  674. c: BindingTypes.SETUP_REF,
  675. d: BindingTypes.SETUP_REF,
  676. f: BindingTypes.SETUP_REF,
  677. g: BindingTypes.SETUP_REF
  678. })
  679. assertCode(content)
  680. })
  681. test('array destructure', () => {
  682. const { content, bindings } = compile(`<script setup>
  683. ref: n = 1, [a, b = 1, ...c] = useFoo()
  684. console.log(n, a, b, c)
  685. </script>`)
  686. expect(content).toMatch(
  687. `const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()`
  688. )
  689. expect(content).toMatch(`\nconst a = _ref(__a);`)
  690. expect(content).toMatch(`\nconst b = _ref(__b);`)
  691. expect(content).toMatch(`\nconst c = _ref(__c);`)
  692. expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
  693. expect(content).toMatch(`return { n, a, b, c }`)
  694. expect(bindings).toStrictEqual({
  695. n: BindingTypes.SETUP_REF,
  696. a: BindingTypes.SETUP_REF,
  697. b: BindingTypes.SETUP_REF,
  698. c: BindingTypes.SETUP_REF
  699. })
  700. assertCode(content)
  701. })
  702. test('nested destructure', () => {
  703. const { content, bindings } = compile(`<script setup>
  704. ref: [{ a: { b }}] = useFoo()
  705. ref: ({ c: [d, e] } = useBar())
  706. console.log(b, d, e)
  707. </script>`)
  708. expect(content).toMatch(`const [{ a: { b: __b }}] = useFoo()`)
  709. expect(content).toMatch(`const { c: [__d, __e] } = useBar()`)
  710. expect(content).not.toMatch(`\nconst a = _ref(__a);`)
  711. expect(content).not.toMatch(`\nconst c = _ref(__c);`)
  712. expect(content).toMatch(`\nconst b = _ref(__b);`)
  713. expect(content).toMatch(`\nconst d = _ref(__d);`)
  714. expect(content).toMatch(`\nconst e = _ref(__e);`)
  715. expect(content).toMatch(`return { b, d, e }`)
  716. expect(bindings).toStrictEqual({
  717. b: BindingTypes.SETUP_REF,
  718. d: BindingTypes.SETUP_REF,
  719. e: BindingTypes.SETUP_REF
  720. })
  721. assertCode(content)
  722. })
  723. })
  724. describe('errors', () => {
  725. test('<script> and <script setup> must have same lang', () => {
  726. expect(() =>
  727. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`)
  728. ).toThrow(`<script> and <script setup> must have the same language type`)
  729. })
  730. const moduleErrorMsg = `cannot contain ES module exports`
  731. test('non-type named exports', () => {
  732. expect(() =>
  733. compile(`<script setup>
  734. export const a = 1
  735. </script>`)
  736. ).toThrow(moduleErrorMsg)
  737. expect(() =>
  738. compile(`<script setup>
  739. export * from './foo'
  740. </script>`)
  741. ).toThrow(moduleErrorMsg)
  742. expect(() =>
  743. compile(`<script setup>
  744. const bar = 1
  745. export { bar as default }
  746. </script>`)
  747. ).toThrow(moduleErrorMsg)
  748. })
  749. test('ref: non-assignment expressions', () => {
  750. expect(() =>
  751. compile(`<script setup>
  752. ref: a = 1, foo()
  753. </script>`)
  754. ).toThrow(`ref: statements can only contain assignment expressions`)
  755. })
  756. test('defineProps/Emit() w/ both type and non-type args', () => {
  757. expect(() => {
  758. compile(`<script setup lang="ts">
  759. import { defineProps } from 'vue'
  760. defineProps<{}>({})
  761. </script>`)
  762. }).toThrow(`cannot accept both type and non-type arguments`)
  763. expect(() => {
  764. compile(`<script setup lang="ts">
  765. import { defineEmit } from 'vue'
  766. defineEmit<{}>({})
  767. </script>`)
  768. }).toThrow(`cannot accept both type and non-type arguments`)
  769. })
  770. test('defineProps/Emit() referencing local var', () => {
  771. expect(() =>
  772. compile(`<script setup>
  773. import { defineProps } from 'vue'
  774. const bar = 1
  775. defineProps({
  776. foo: {
  777. default: () => bar
  778. }
  779. })
  780. </script>`)
  781. ).toThrow(`cannot reference locally declared variables`)
  782. expect(() =>
  783. compile(`<script setup>
  784. import { defineEmit } from 'vue'
  785. const bar = 'hello'
  786. defineEmit([bar])
  787. </script>`)
  788. ).toThrow(`cannot reference locally declared variables`)
  789. })
  790. test('defineProps/Emit() referencing ref declarations', () => {
  791. expect(() =>
  792. compile(`<script setup>
  793. import { defineProps } from 'vue'
  794. ref: bar = 1
  795. defineProps({
  796. bar
  797. })
  798. </script>`)
  799. ).toThrow(`cannot reference locally declared variables`)
  800. expect(() =>
  801. compile(`<script setup>
  802. import { defineEmit } from 'vue'
  803. ref: bar = 1
  804. defineEmit({
  805. bar
  806. })
  807. </script>`)
  808. ).toThrow(`cannot reference locally declared variables`)
  809. })
  810. test('should allow defineProps/Emit() referencing scope var', () => {
  811. assertCode(
  812. compile(`<script setup>
  813. import { defineProps, defineEmit } from 'vue'
  814. const bar = 1
  815. defineProps({
  816. foo: {
  817. default: bar => bar + 1
  818. }
  819. })
  820. defineEmit({
  821. foo: bar => bar > 1
  822. })
  823. </script>`).content
  824. )
  825. })
  826. test('should allow defineProps/Emit() referencing imported binding', () => {
  827. assertCode(
  828. compile(`<script setup>
  829. import { defineProps, defineEmit } from 'vue'
  830. import { bar } from './bar'
  831. defineProps({
  832. foo: {
  833. default: () => bar
  834. }
  835. })
  836. defineEmit({
  837. foo: () => bar > 1
  838. })
  839. </script>`).content
  840. )
  841. })
  842. })
  843. })
  844. describe('SFC analyze <script> bindings', () => {
  845. it('can parse decorators syntax in typescript block', () => {
  846. const { scriptAst } = compile(`
  847. <script lang="ts">
  848. import { Options, Vue } from 'vue-class-component';
  849. @Options({
  850. components: {
  851. HelloWorld,
  852. },
  853. props: ['foo', 'bar']
  854. })
  855. export default class Home extends Vue {}
  856. </script>
  857. `)
  858. expect(scriptAst).toBeDefined()
  859. })
  860. it('recognizes props array declaration', () => {
  861. const { bindings } = compile(`
  862. <script>
  863. export default {
  864. props: ['foo', 'bar']
  865. }
  866. </script>
  867. `)
  868. expect(bindings).toStrictEqual({
  869. foo: BindingTypes.PROPS,
  870. bar: BindingTypes.PROPS
  871. })
  872. })
  873. it('recognizes props object declaration', () => {
  874. const { bindings } = compile(`
  875. <script>
  876. export default {
  877. props: {
  878. foo: String,
  879. bar: {
  880. type: String,
  881. },
  882. baz: null,
  883. qux: [String, Number]
  884. }
  885. }
  886. </script>
  887. `)
  888. expect(bindings).toStrictEqual({
  889. foo: BindingTypes.PROPS,
  890. bar: BindingTypes.PROPS,
  891. baz: BindingTypes.PROPS,
  892. qux: BindingTypes.PROPS
  893. })
  894. })
  895. it('recognizes setup return', () => {
  896. const { bindings } = compile(`
  897. <script>
  898. const bar = 2
  899. export default {
  900. setup() {
  901. return {
  902. foo: 1,
  903. bar
  904. }
  905. }
  906. }
  907. </script>
  908. `)
  909. expect(bindings).toStrictEqual({
  910. foo: BindingTypes.SETUP_MAYBE_REF,
  911. bar: BindingTypes.SETUP_MAYBE_REF
  912. })
  913. })
  914. it('recognizes async setup return', () => {
  915. const { bindings } = compile(`
  916. <script>
  917. const bar = 2
  918. export default {
  919. async setup() {
  920. return {
  921. foo: 1,
  922. bar
  923. }
  924. }
  925. }
  926. </script>
  927. `)
  928. expect(bindings).toStrictEqual({
  929. foo: BindingTypes.SETUP_MAYBE_REF,
  930. bar: BindingTypes.SETUP_MAYBE_REF
  931. })
  932. })
  933. it('recognizes data return', () => {
  934. const { bindings } = compile(`
  935. <script>
  936. const bar = 2
  937. export default {
  938. data() {
  939. return {
  940. foo: null,
  941. bar
  942. }
  943. }
  944. }
  945. </script>
  946. `)
  947. expect(bindings).toStrictEqual({
  948. foo: BindingTypes.DATA,
  949. bar: BindingTypes.DATA
  950. })
  951. })
  952. it('recognizes methods', () => {
  953. const { bindings } = compile(`
  954. <script>
  955. export default {
  956. methods: {
  957. foo() {}
  958. }
  959. }
  960. </script>
  961. `)
  962. expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })
  963. })
  964. it('recognizes computeds', () => {
  965. const { bindings } = compile(`
  966. <script>
  967. export default {
  968. computed: {
  969. foo() {},
  970. bar: {
  971. get() {},
  972. set() {},
  973. }
  974. }
  975. }
  976. </script>
  977. `)
  978. expect(bindings).toStrictEqual({
  979. foo: BindingTypes.OPTIONS,
  980. bar: BindingTypes.OPTIONS
  981. })
  982. })
  983. it('recognizes injections array declaration', () => {
  984. const { bindings } = compile(`
  985. <script>
  986. export default {
  987. inject: ['foo', 'bar']
  988. }
  989. </script>
  990. `)
  991. expect(bindings).toStrictEqual({
  992. foo: BindingTypes.OPTIONS,
  993. bar: BindingTypes.OPTIONS
  994. })
  995. })
  996. it('recognizes injections object declaration', () => {
  997. const { bindings } = compile(`
  998. <script>
  999. export default {
  1000. inject: {
  1001. foo: {},
  1002. bar: {},
  1003. }
  1004. }
  1005. </script>
  1006. `)
  1007. expect(bindings).toStrictEqual({
  1008. foo: BindingTypes.OPTIONS,
  1009. bar: BindingTypes.OPTIONS
  1010. })
  1011. })
  1012. it('works for mixed bindings', () => {
  1013. const { bindings } = compile(`
  1014. <script>
  1015. export default {
  1016. inject: ['foo'],
  1017. props: {
  1018. bar: String,
  1019. },
  1020. setup() {
  1021. return {
  1022. baz: null,
  1023. }
  1024. },
  1025. data() {
  1026. return {
  1027. qux: null
  1028. }
  1029. },
  1030. methods: {
  1031. quux() {}
  1032. },
  1033. computed: {
  1034. quuz() {}
  1035. }
  1036. }
  1037. </script>
  1038. `)
  1039. expect(bindings).toStrictEqual({
  1040. foo: BindingTypes.OPTIONS,
  1041. bar: BindingTypes.PROPS,
  1042. baz: BindingTypes.SETUP_MAYBE_REF,
  1043. qux: BindingTypes.DATA,
  1044. quux: BindingTypes.OPTIONS,
  1045. quuz: BindingTypes.OPTIONS
  1046. })
  1047. })
  1048. it('works for script setup', () => {
  1049. const { bindings } = compile(`
  1050. <script setup>
  1051. import { defineProps, ref as r } from 'vue'
  1052. defineProps({
  1053. foo: String
  1054. })
  1055. const a = r(1)
  1056. let b = 2
  1057. const c = 3
  1058. const { d } = someFoo()
  1059. let { e } = someBar()
  1060. </script>
  1061. `)
  1062. expect(bindings).toStrictEqual({
  1063. r: BindingTypes.SETUP_CONST,
  1064. a: BindingTypes.SETUP_REF,
  1065. b: BindingTypes.SETUP_LET,
  1066. c: BindingTypes.SETUP_CONST,
  1067. d: BindingTypes.SETUP_MAYBE_REF,
  1068. e: BindingTypes.SETUP_LET,
  1069. foo: BindingTypes.PROPS
  1070. })
  1071. })
  1072. })