compileScript.spec.ts 28 KB

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