compileScript.spec.ts 32 KB

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