1import { BindingTypes } from '../src/types'2import { compile, assertCode } from './util'34describe('SFC compile <script setup>', () => {5 test('should expose top level declarations', () => {6 const { content, bindings } = compile(`7 <script setup>8 import { x } from './x'9 let a = 110 const b = 211 function c() {}12 class d {}13 </script>1415 <script>16 import { xx } from './x'17 let aa = 118 const bb = 219 function cc() {}20 class dd {}21 </script>22 `)23 expect(content).toMatch('return { aa, bb, cc, dd, a, b, c, d, xx, x }')24 expect(bindings).toStrictEqual({25 x: BindingTypes.SETUP_MAYBE_REF,26 a: BindingTypes.SETUP_LET,27 b: BindingTypes.SETUP_CONST,28 c: BindingTypes.SETUP_CONST,29 d: BindingTypes.SETUP_CONST,30 xx: BindingTypes.SETUP_MAYBE_REF,31 aa: BindingTypes.SETUP_LET,32 bb: BindingTypes.SETUP_CONST,33 cc: BindingTypes.SETUP_CONST,34 dd: BindingTypes.SETUP_CONST35 })36 assertCode(content)37 })3839 test('binding analysis for destructure', () => {40 const { content, bindings } = compile(`41 <script setup>42 const { foo, b: bar, ['x' + 'y']: baz, x: { y, zz: { z }}} = {}43 </script>44 `)45 expect(content).toMatch('return { foo, bar, baz, y, z }')46 expect(bindings).toStrictEqual({47 foo: BindingTypes.SETUP_MAYBE_REF,48 bar: BindingTypes.SETUP_MAYBE_REF,49 baz: BindingTypes.SETUP_MAYBE_REF,50 y: BindingTypes.SETUP_MAYBE_REF,51 z: BindingTypes.SETUP_MAYBE_REF52 })53 assertCode(content)54 })5556 test('defineProps()', () => {57 const { content, bindings } = compile(`58<script setup>59const props = defineProps({60 foo: String61})62const bar = 163</script>64 `)65 // should generate working code66 assertCode(content)67 // should analyze bindings68 expect(bindings).toStrictEqual({69 foo: BindingTypes.PROPS,70 bar: BindingTypes.SETUP_CONST,71 props: BindingTypes.SETUP_REACTIVE_CONST72 })7374 // should remove defineOptions import and call75 expect(content).not.toMatch('defineProps')76 // should generate correct setup signature77 expect(content).toMatch(`setup(__props) {`)78 // should assign user identifier to it79 expect(content).toMatch(`const props = __props`)80 // should include context options in default export81 expect(content).toMatch(`export default {82 props: {83 foo: String84},`)85 })8687 test('defineProps w/ external definition', () => {88 const { content } = compile(`89 <script setup>90 import { propsModel } from './props'91 const props = defineProps(propsModel)92 </script>93 `)94 assertCode(content)95 expect(content).toMatch(`export default {96 props: propsModel,`)97 })9899 // #4764100 test('defineProps w/ leading code', () => {101 const { content } = compile(`102 <script setup>import { x } from './x'103 const props = defineProps({})104 </script>105 `)106 // props declaration should be inside setup, not moved along with the import107 expect(content).not.toMatch(`const props = __props\nimport`)108 assertCode(content)109 })110111 test('defineEmits()', () => {112 const { content, bindings } = compile(`113<script setup>114const myEmit = defineEmits(['foo', 'bar'])115</script>116 `)117 assertCode(content)118 expect(bindings).toStrictEqual({119 myEmit: BindingTypes.SETUP_CONST120 })121 // should remove defineOptions import and call122 expect(content).not.toMatch('defineEmits')123 // should generate correct setup signature124 expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)125 // should include context options in default export126 expect(content).toMatch(`export default {127 emits: ['foo', 'bar'],`)128 })129130 test('defineProps/defineEmits in multi-variable declaration', () => {131 const { content } = compile(`132 <script setup>133 const props = defineProps(['item']),134 a = 1,135 emit = defineEmits(['a']);136 </script>137 `)138 assertCode(content)139 expect(content).toMatch(`const a = 1;`) // test correct removal140 expect(content).toMatch(`props: ['item'],`)141 expect(content).toMatch(`emits: ['a'],`)142 })143144 // vuejs/core #6757145 test('defineProps/defineEmits in multi-variable declaration fix #6757 ', () => {146 const { content } = compile(`147 <script setup>148 const a = 1,149 props = defineProps(['item']),150 emit = defineEmits(['a']);151 </script>152 `)153 assertCode(content)154 expect(content).toMatch(`const a = 1;`) // test correct removal155 expect(content).toMatch(`props: ['item'],`)156 expect(content).toMatch(`emits: ['a'],`)157 })158159 test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {160 const { content } = compile(`161 <script setup>162 const props = defineProps(['item']),163 emit = defineEmits(['a']);164 </script>165 `)166 assertCode(content)167 expect(content).toMatch(`props: ['item'],`)168 expect(content).toMatch(`emits: ['a'],`)169 })170171 test('defineExpose()', () => {172 const { content } = compile(`173<script setup>174defineExpose({ foo: 123 })175</script>176 `)177 assertCode(content)178 // should remove defineOptions import and call179 expect(content).not.toMatch('defineExpose')180 // should generate correct setup signature181 expect(content).toMatch(`setup(__props, { expose }) {`)182 // should replace callee183 expect(content).toMatch(/\bexpose\(\{ foo: 123 \}\)/)184 })185186 test('<script> after <script setup> the script content not end with `\\n`', () => {187 const { content } = compile(`188 <script setup>189 import { x } from './x'190 </script>191 <script>const n = 1</script>192 `)193 assertCode(content)194 })195196 describe('<script> and <script setup> co-usage', () => {197 test('script first', () => {198 const { content } = compile(`199 <script>200 export const n = 1201202 export default {}203 </script>204 <script setup>205 import { x } from './x'206 x()207 </script>208 `)209 assertCode(content)210 })211212 test('script setup first', () => {213 const { content } = compile(`214 <script setup>215 import { x } from './x'216 x()217 </script>218 <script>219 export const n = 1220 export default {}221 </script>222 `)223 assertCode(content)224 })225226 test('script setup first, named default export', () => {227 const { content } = compile(`228 <script setup>229 import { x } from './x'230 x()231 </script>232 <script>233 export const n = 1234 const def = {}235 export { def as default }236 </script>237 `)238 assertCode(content)239 })240241 // #4395242 test('script setup first, lang="ts", script block content export default', () => {243 const { content } = compile(`244 <script setup lang="ts">245 import { x } from './x'246 x()247 </script>248 <script lang="ts">249 export default {250 name: "test"251 }252 </script>253 `)254 // ensure __default__ is declared before used255 expect(content).toMatch(/const __default__[\S\s]*\.\.\.__default__/m)256 assertCode(content)257 })258259 describe('spaces in ExportDefaultDeclaration node', () => {260 // #4371261 test('with many spaces and newline', () => {262 // #4371263 const { content } = compile(`264 <script>265 export const n = 1266 export default267 {268 some:'option'269 }270 </script>271 <script setup>272 import { x } from './x'273 x()274 </script>275 `)276 assertCode(content)277 })278279 test('with minimal spaces', () => {280 const { content } = compile(`281 <script>282 export const n = 1283 export default{284 some:'option'285 }286 </script>287 <script setup>288 import { x } from './x'289 x()290 </script>291 `)292 assertCode(content)293 })294 })295 })296297 describe('imports', () => {298 test('should hoist and expose imports', () => {299 assertCode(300 compile(`<script setup>301 import { ref } from 'vue'302 import 'foo/css'303 </script>`).content304 )305 })306307 test('should extract comment for import or type declarations', () => {308 assertCode(309 compile(`310 <script setup>311 import a from 'a' // comment312 import b from 'b'313 </script>314 `).content315 )316 })317318 // #2740319 test('should allow defineProps/Emit at the start of imports', () => {320 assertCode(321 compile(`<script setup>322 import { ref } from 'vue'323 defineProps(['foo'])324 defineEmits(['bar'])325 const r = ref(0)326 </script>`).content327 )328 })329330 test('import dedupe between <script> and <script setup>', () => {331 const { content } = compile(`332 <script>333 import { x } from './x'334 </script>335 <script setup>336 import { x } from './x'337 x()338 </script>339 `)340 assertCode(content)341 expect(content.indexOf(`import { x }`)).toEqual(342 content.lastIndexOf(`import { x }`)343 )344 })345 })346347 // in dev mode, declared bindings are returned as an object from setup()348 // when using TS, users may import types which should not be returned as349 // values, so we need to check import usage in the template to determine350 // what to be returned.351 describe('dev mode import usage check', () => {352 test('components', () => {353 const { content } = compile(`354 <script setup lang="ts">355 import { FooBar, FooBaz, FooQux, foo } from './x'356 const fooBar: FooBar = 1357 </script>358 <template>359 <FooBaz></FooBaz>360 <foo-qux/>361 <foo/>362 FooBar363 </template>364 `)365 // FooBar: should not be matched by plain text or incorrect case366 // FooBaz: used as PascalCase component367 // FooQux: used as kebab-case component368 // foo: lowercase component369 expect(content).toMatch(`return { fooBar, FooBaz, FooQux, foo }`)370 assertCode(content)371 })372373 test('directive', () => {374 const { content } = compile(`375 <script setup lang="ts">376 import { vMyDir } from './x'377 </script>378 <template>379 <div v-my-dir></div>380 </template>381 `)382 expect(content).toMatch(`return { vMyDir }`)383 assertCode(content)384 })385386 // https://github.com/vuejs/core/issues/4599387 test('attribute expressions', () => {388 const { content } = compile(`389 <script setup lang="ts">390 import { bar, baz } from './x'391 const cond = true392 </script>393 <template>394 <div :class="[cond ? '' : bar(), 'default']" :style="baz"></div>395 </template>396 `)397 expect(content).toMatch(`return { cond, bar, baz }`)398 assertCode(content)399 })400401 test('imported ref as template ref', () => {402 const { content } = compile(`403 <script setup lang="ts">404 import { aref } from './x'405 </script>406 <template>407 <div ref="aref"></div>408 </template>409 `)410 expect(content).toMatch(`return { aref }`)411 assertCode(content)412 })413414 test('vue interpolations', () => {415 const { content } = compile(`416 <script setup lang="ts">417 import { x, y, z, x$y } from './x'418 </script>419 <template>420 <div :id="z + 'y'">{{ x }} {{ yy }} {{ x$y }}</div>421 </template>422 `)423 // x: used in interpolation424 // y: should not be matched by {{ yy }} or 'y' in binding exps425 // x$y: #4274 should escape special chars when creating Regex426 expect(content).toMatch(`return { x, z, x$y }`)427 assertCode(content)428 })429430 // #4340 interpolations in template strings431 test('js template string interpolations', () => {432 const { content } = compile(`433 <script setup lang="ts">434 import { VAR, VAR2, VAR3 } from './x'435 </script>436 <template>437 {{ \`\${VAR}VAR2\${VAR3}\` }}438 </template>439 `)440 // VAR2 should not be matched441 expect(content).toMatch(`return { VAR, VAR3 }`)442 assertCode(content)443 })444445 // edge case: last tag in template446 test('last tag', () => {447 const { content } = compile(`448 <script setup lang="ts">449 import { FooBaz, Last } from './x'450 </script>451 <template>452 <FooBaz></FooBaz>453 <Last/>454 </template>455 `)456 expect(content).toMatch(`return { FooBaz, Last }`)457 assertCode(content)458 })459460 test('TS annotations', () => {461 const { content } = compile(`462 <script setup lang="ts">463 import { Foo, Baz, Qux, Fred } from './x'464 const a = 1465 function b() {}466 </script>467 <template>468 {{ a as Foo }}469 {{ Baz }}470 <Comp v-slot="{ data }: Qux">{{ data }}</Comp>471 <div v-for="{ z = x as Qux } in list as Fred"/>472 </template>473 `)474475 expect(content).toMatch(`return { a, b, Baz }`)476 assertCode(content)477 })478 })479480 // describe('inlineTemplate mode', () => {481 // test('should work', () => {482 // const { content } = compile(483 // `484 // <script setup>485 // import { ref } from 'vue'486 // const count = ref(0)487 // </script>488 // <template>489 // <div>{{ count }}</div>490 // <div>static</div>491 // </template>492 // `,493 // { inlineTemplate: true }494 // )495 // // check snapshot and make sure helper imports and496 // // hoists are placed correctly.497 // assertCode(content)498 // // in inline mode, no need to call expose() since nothing is exposed499 // // anyway!500 // expect(content).not.toMatch(`expose()`)501 // })502503 // test('with defineExpose()', () => {504 // const { content } = compile(505 // `506 // <script setup>507 // const count = ref(0)508 // defineExpose({ count })509 // </script>510 // `,511 // { inlineTemplate: true }512 // )513 // assertCode(content)514 // expect(content).toMatch(`setup(__props, { expose })`)515 // expect(content).toMatch(`expose({ count })`)516 // })517518 // test('referencing scope components and directives', () => {519 // const { content } = compile(520 // `521 // <script setup>522 // import ChildComp from './Child.vue'523 // import SomeOtherComp from './Other.vue'524 // import vMyDir from './my-dir'525 // </script>526 // <template>527 // <div v-my-dir></div>528 // <ChildComp/>529 // <some-other-comp/>530 // </template>531 // `,532 // { inlineTemplate: true }533 // )534 // expect(content).toMatch('[_unref(vMyDir)]')535 // expect(content).toMatch('_createVNode(ChildComp)')536 // // kebab-case component support537 // expect(content).toMatch('_createVNode(SomeOtherComp)')538 // assertCode(content)539 // })540541 // test('avoid unref() when necessary', () => {542 // // function, const, component import543 // const { content } = compile(544 // `<script setup>545 // import { ref } from 'vue'546 // import Foo, { bar } from './Foo.vue'547 // import other from './util'548 // import * as tree from './tree'549 // const count = ref(0)550 // const constant = {}551 // const maybe = foo()552 // let lett = 1553 // function fn() {}554 // </script>555 // <template>556 // <Foo>{{ bar }}</Foo>557 // <div @click="fn">{{ count }} {{ constant }} {{ maybe }} {{ lett }} {{ other }}</div>558 // {{ tree.foo() }}559 // </template>560 // `,561 // { inlineTemplate: true }562 // )563 // // no need to unref vue component import564 // expect(content).toMatch(`createVNode(Foo,`)565 // // #2699 should unref named imports from .vue566 // expect(content).toMatch(`unref(bar)`)567 // // should unref other imports568 // expect(content).toMatch(`unref(other)`)569 // // no need to unref constant literals570 // expect(content).not.toMatch(`unref(constant)`)571 // // should directly use .value for known refs572 // expect(content).toMatch(`count.value`)573 // // should unref() on const bindings that may be refs574 // expect(content).toMatch(`unref(maybe)`)575 // // should unref() on let bindings576 // expect(content).toMatch(`unref(lett)`)577 // // no need to unref namespace import (this also preserves tree-shaking)578 // expect(content).toMatch(`tree.foo()`)579 // // no need to unref function declarations580 // expect(content).toMatch(`{ onClick: fn }`)581 // // no need to mark constant fns in patch flag582 // expect(content).not.toMatch(`PROPS`)583 // assertCode(content)584 // })585586 // test('v-model codegen', () => {587 // const { content } = compile(588 // `<script setup>589 // import { ref } from 'vue'590 // const count = ref(0)591 // const maybe = foo()592 // let lett = 1593 // </script>594 // <template>595 // <input v-model="count">596 // <input v-model="maybe">597 // <input v-model="lett">598 // </template>599 // `,600 // { inlineTemplate: true }601 // )602 // // known const ref: set value603 // expect(content).toMatch(`(count).value = $event`)604 // // const but maybe ref: assign if ref, otherwise do nothing605 // expect(content).toMatch(`_isRef(maybe) ? (maybe).value = $event : null`)606 // // let: handle both cases607 // expect(content).toMatch(608 // `_isRef(lett) ? (lett).value = $event : lett = $event`609 // )610 // assertCode(content)611 // })612613 // test('template assignment expression codegen', () => {614 // const { content } = compile(615 // `<script setup>616 // import { ref } from 'vue'617 // const count = ref(0)618 // const maybe = foo()619 // let lett = 1620 // let v = ref(1)621 // </script>622 // <template>623 // <div @click="count = 1"/>624 // <div @click="maybe = count"/>625 // <div @click="lett = count"/>626 // <div @click="v += 1"/>627 // <div @click="v -= 1"/>628 // <div @click="() => {629 // let a = '' + lett630 // v = a631 // }"/>632 // <div @click="() => {633 // // nested scopes634 // (()=>{635 // let x = a636 // (()=>{637 // let z = x638 // let z2 = z639 // })640 // let lz = z641 // })642 // v = a643 // }"/>644 // </template>645 // `,646 // { inlineTemplate: true }647 // )648 // // known const ref: set value649 // expect(content).toMatch(`count.value = 1`)650 // // const but maybe ref: only assign after check651 // expect(content).toMatch(`maybe.value = count.value`)652 // // let: handle both cases653 // expect(content).toMatch(654 // `_isRef(lett) ? lett.value = count.value : lett = count.value`655 // )656 // expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)657 // expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)658 // expect(content).toMatch(`_isRef(v) ? v.value = a : v = a`)659 // expect(content).toMatch(`_isRef(v) ? v.value = _ctx.a : v = _ctx.a`)660 // assertCode(content)661 // })662663 // test('template update expression codegen', () => {664 // const { content } = compile(665 // `<script setup>666 // import { ref } from 'vue'667 // const count = ref(0)668 // const maybe = foo()669 // let lett = 1670 // </script>671 // <template>672 // <div @click="count++"/>673 // <div @click="--count"/>674 // <div @click="maybe++"/>675 // <div @click="--maybe"/>676 // <div @click="lett++"/>677 // <div @click="--lett"/>678 // </template>679 // `,680 // { inlineTemplate: true }681 // )682 // // known const ref: set value683 // expect(content).toMatch(`count.value++`)684 // expect(content).toMatch(`--count.value`)685 // // const but maybe ref (non-ref case ignored)686 // expect(content).toMatch(`maybe.value++`)687 // expect(content).toMatch(`--maybe.value`)688 // // let: handle both cases689 // expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`)690 // expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`)691 // assertCode(content)692 // })693694 // test('template destructure assignment codegen', () => {695 // const { content } = compile(696 // `<script setup>697 // import { ref } from 'vue'698 // const val = {}699 // const count = ref(0)700 // const maybe = foo()701 // let lett = 1702 // </script>703 // <template>704 // <div @click="({ count } = val)"/>705 // <div @click="[maybe] = val"/>706 // <div @click="({ lett } = val)"/>707 // </template>708 // `,709 // { inlineTemplate: true }710 // )711 // // known const ref: set value712 // expect(content).toMatch(`({ count: count.value } = val)`)713 // // const but maybe ref (non-ref case ignored)714 // expect(content).toMatch(`[maybe.value] = val`)715 // // let: assumes non-ref716 // expect(content).toMatch(`{ lett: lett } = val`)717 // assertCode(content)718 // })719720 // test('ssr codegen', () => {721 // const { content } = compile(722 // `723 // <script setup>724 // import { ref } from 'vue'725 // const count = ref(0)726 // </script>727 // <template>728 // <div>{{ count }}</div>729 // <div>static</div>730 // </template>731 // <style>732 // div { color: v-bind(count) }733 // </style>734 // `,735 // {736 // inlineTemplate: true,737 // templateOptions: {738 // ssr: true739 // }740 // }741 // )742 // expect(content).toMatch(`\n __ssrInlineRender: true,\n`)743 // expect(content).toMatch(`return (_ctx, _push`)744 // expect(content).toMatch(`ssrInterpolate`)745 // expect(content).not.toMatch(`useCssVars`)746 // expect(content).toMatch(`"--${mockId}-count": (count.value)`)747 // assertCode(content)748 // })749 // })750751 describe('with TypeScript', () => {752 test('hoist type declarations', () => {753 const { content } = compile(`754 <script setup lang="ts">755 export interface Foo {}756 type Bar = {}757 </script>`)758 assertCode(content)759 })760761 test('defineProps/Emit w/ runtime options', () => {762 const { content } = compile(`763<script setup lang="ts">764const props = defineProps({ foo: String })765const emit = defineEmits(['a', 'b'])766</script>767 `)768 assertCode(content)769 expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({770 props: { foo: String },771 emits: ['a', 'b'],772 setup(__props, { emit }) {`)773 })774775 test('defineProps w/ type', () => {776 const { content, bindings } = compile(`777 <script setup lang="ts">778 interface Test {}779780 type Alias = number[]781782 defineProps<{783 string: string784 number: number785 boolean: boolean786 object: object787 objectLiteral: { a: number }788 fn: (n: number) => void789 functionRef: Function790 objectRef: Object791 dateTime: Date792 array: string[]793 arrayRef: Array<any>794 tuple: [number, number]795 set: Set<string>796 literal: 'foo'797 optional?: any798 recordRef: Record<string, null>799 interface: Test800 alias: Alias801 method(): void802 symbol: symbol803804 union: string | number805 literalUnion: 'foo' | 'bar'806 literalUnionNumber: 1 | 2 | 3 | 4 | 5807 literalUnionMixed: 'foo' | 1 | boolean808 intersection: Test & {}809 foo: ((item: any) => boolean) | null810 }>()811 </script>`)812 assertCode(content)813 expect(content).toMatch(`string: { type: String, required: true }`)814 expect(content).toMatch(`number: { type: Number, required: true }`)815 expect(content).toMatch(`boolean: { type: Boolean, required: true }`)816 expect(content).toMatch(`object: { type: Object, required: true }`)817 expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)818 expect(content).toMatch(`fn: { type: Function, required: true }`)819 expect(content).toMatch(`functionRef: { type: Function, required: true }`)820 expect(content).toMatch(`objectRef: { type: Object, required: true }`)821 expect(content).toMatch(`dateTime: { type: Date, required: true }`)822 expect(content).toMatch(`array: { type: Array, required: true }`)823 expect(content).toMatch(`arrayRef: { type: Array, required: true }`)824 expect(content).toMatch(`tuple: { type: Array, required: true }`)825 expect(content).toMatch(`set: { type: Set, required: true }`)826 expect(content).toMatch(`literal: { type: String, required: true }`)827 expect(content).toMatch(`optional: { type: null, required: false }`)828 expect(content).toMatch(`recordRef: { type: Object, required: true }`)829 expect(content).toMatch(`interface: { type: Object, required: true }`)830 expect(content).toMatch(`alias: { type: Array, required: true }`)831 expect(content).toMatch(`method: { type: Function, required: true }`)832 expect(content).toMatch(`symbol: { type: Symbol, required: true }`)833 expect(content).toMatch(834 `union: { type: [String, Number], required: true }`835 )836 expect(content).toMatch(`literalUnion: { type: String, required: true }`)837 expect(content).toMatch(838 `literalUnionNumber: { type: Number, required: true }`839 )840 expect(content).toMatch(841 `literalUnionMixed: { type: [String, Number, Boolean], required: true }`842 )843 expect(content).toMatch(`intersection: { type: Object, required: true }`)844 expect(content).toMatch(`foo: { type: [Function, null], required: true }`)845 expect(bindings).toStrictEqual({846 string: BindingTypes.PROPS,847 number: BindingTypes.PROPS,848 boolean: BindingTypes.PROPS,849 object: BindingTypes.PROPS,850 objectLiteral: BindingTypes.PROPS,851 fn: BindingTypes.PROPS,852 functionRef: BindingTypes.PROPS,853 objectRef: BindingTypes.PROPS,854 dateTime: BindingTypes.PROPS,855 array: BindingTypes.PROPS,856 arrayRef: BindingTypes.PROPS,857 tuple: BindingTypes.PROPS,858 set: BindingTypes.PROPS,859 literal: BindingTypes.PROPS,860 optional: BindingTypes.PROPS,861 recordRef: BindingTypes.PROPS,862 interface: BindingTypes.PROPS,863 alias: BindingTypes.PROPS,864 method: BindingTypes.PROPS,865 symbol: BindingTypes.PROPS,866 union: BindingTypes.PROPS,867 literalUnion: BindingTypes.PROPS,868 literalUnionNumber: BindingTypes.PROPS,869 literalUnionMixed: BindingTypes.PROPS,870 intersection: BindingTypes.PROPS,871 foo: BindingTypes.PROPS872 })873 })874875 test('defineProps w/ interface', () => {876 const { content, bindings } = compile(`877 <script setup lang="ts">878 interface Props { x?: number }879 defineProps<Props>()880 </script>881 `)882 assertCode(content)883 expect(content).toMatch(`x: { type: Number, required: false }`)884 expect(bindings).toStrictEqual({885 x: BindingTypes.PROPS886 })887 })888889 test('defineProps w/ exported interface', () => {890 const { content, bindings } = compile(`891 <script setup lang="ts">892 export interface Props { x?: number }893 defineProps<Props>()894 </script>895 `)896 assertCode(content)897 expect(content).toMatch(`x: { type: Number, required: false }`)898 expect(bindings).toStrictEqual({899 x: BindingTypes.PROPS900 })901 })902903 test('defineProps w/ exported interface in normal script', () => {904 const { content, bindings } = compile(`905 <script lang="ts">906 export interface Props { x?: number }907 </script>908 <script setup lang="ts">909 defineProps<Props>()910 </script>911 `)912 assertCode(content)913 expect(content).toMatch(`x: { type: Number, required: false }`)914 expect(bindings).toStrictEqual({915 x: BindingTypes.PROPS916 })917 })918919 test('defineProps w/ type alias', () => {920 const { content, bindings } = compile(`921 <script setup lang="ts">922 type Props = { x?: number }923 defineProps<Props>()924 </script>925 `)926 assertCode(content)927 expect(content).toMatch(`x: { type: Number, required: false }`)928 expect(bindings).toStrictEqual({929 x: BindingTypes.PROPS930 })931 })932933 test('defineProps w/ exported type alias', () => {934 const { content, bindings } = compile(`935 <script setup lang="ts">936 export type Props = { x?: number }937 defineProps<Props>()938 </script>939 `)940 assertCode(content)941 expect(content).toMatch(`x: { type: Number, required: false }`)942 expect(bindings).toStrictEqual({943 x: BindingTypes.PROPS944 })945 })946947 test('withDefaults (static)', () => {948 const { content, bindings } = compile(`949 <script setup lang="ts">950 const props = withDefaults(defineProps<{951 foo?: string952 bar?: number;953 baz: boolean;954 qux?(): number955 }>(), {956 foo: 'hi',957 qux() { return 1 }958 })959 </script>960 `)961 assertCode(content)962 expect(content).toMatch(963 `foo: { type: String, required: false, default: 'hi' }`964 )965 expect(content).toMatch(`bar: { type: Number, required: false }`)966 expect(content).toMatch(`baz: { type: Boolean, required: true }`)967 expect(content).toMatch(968 `qux: { type: Function, required: false, default() { return 1 } }`969 )970 expect(content).toMatch(971 `{ foo: string, bar?: number, baz: boolean, qux(): number }`972 )973 expect(content).toMatch(`const props = __props`)974 expect(bindings).toStrictEqual({975 foo: BindingTypes.PROPS,976 bar: BindingTypes.PROPS,977 baz: BindingTypes.PROPS,978 qux: BindingTypes.PROPS,979 props: BindingTypes.SETUP_CONST980 })981 })982983 test('withDefaults (dynamic)', () => {984 const { content } = compile(`985 <script setup lang="ts">986 import { defaults } from './foo'987 const props = withDefaults(defineProps<{988 foo?: string989 bar?: number990 baz: boolean991 }>(), { ...defaults })992 </script>993 `)994 assertCode(content)995 expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)996 expect(content).toMatch(997 `998 _mergeDefaults({999 foo: { type: String, required: false },1000 bar: { type: Number, required: false },1001 baz: { type: Boolean, required: true }1002 }, { ...defaults })`.trim()1003 )1004 })10051006 test('defineEmits w/ type', () => {1007 const { content } = compile(`1008 <script setup lang="ts">1009 const emit = defineEmits<(e: 'foo' | 'bar') => void>()1010 </script>1011 `)1012 assertCode(content)1013 expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)1014 expect(content).toMatch(`emits: ["foo", "bar"]`)1015 })10161017 test('defineEmits w/ type (union)', () => {1018 const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`1019 expect(() =>1020 compile(`1021 <script setup lang="ts">1022 const emit = defineEmits<${type}>()1023 </script>1024 `)1025 ).toThrow()1026 })10271028 test('defineEmits w/ type (type literal w/ call signatures)', () => {1029 const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`1030 const { content } = compile(`1031 <script setup lang="ts">1032 const emit = defineEmits<${type}>()1033 </script>1034 `)1035 assertCode(content)1036 expect(content).toMatch(`emit: (${type}),`)1037 expect(content).toMatch(`emits: ["foo", "bar", "baz"]`)1038 })10391040 test('defineEmits w/ type (interface)', () => {1041 const { content } = compile(`1042 <script setup lang="ts">1043 interface Emits { (e: 'foo' | 'bar'): void }1044 const emit = defineEmits<Emits>()1045 </script>1046 `)1047 assertCode(content)1048 expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)1049 expect(content).toMatch(`emits: ["foo", "bar"]`)1050 })10511052 test('defineEmits w/ type (exported interface)', () => {1053 const { content } = compile(`1054 <script setup lang="ts">1055 export interface Emits { (e: 'foo' | 'bar'): void }1056 const emit = defineEmits<Emits>()1057 </script>1058 `)1059 assertCode(content)1060 expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)1061 expect(content).toMatch(`emits: ["foo", "bar"]`)1062 })10631064 test('defineEmits w/ type (type alias)', () => {1065 const { content } = compile(`1066 <script setup lang="ts">1067 type Emits = { (e: 'foo' | 'bar'): void }1068 const emit = defineEmits<Emits>()1069 </script>1070 `)1071 assertCode(content)1072 expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)1073 expect(content).toMatch(`emits: ["foo", "bar"]`)1074 })10751076 test('defineEmits w/ type (exported type alias)', () => {1077 const { content } = compile(`1078 <script setup lang="ts">1079 export type Emits = { (e: 'foo' | 'bar'): void }1080 const emit = defineEmits<Emits>()1081 </script>1082 `)1083 assertCode(content)1084 expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)1085 expect(content).toMatch(`emits: ["foo", "bar"]`)1086 })10871088 test('defineEmits w/ type (referenced function type)', () => {1089 const { content } = compile(`1090 <script setup lang="ts">1091 type Emits = (e: 'foo' | 'bar') => void1092 const emit = defineEmits<Emits>()1093 </script>1094 `)1095 assertCode(content)1096 expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)1097 expect(content).toMatch(`emits: ["foo", "bar"]`)1098 })10991100 test('defineEmits w/ type (referenced exported function type)', () => {1101 const { content } = compile(`1102 <script setup lang="ts">1103 export type Emits = (e: 'foo' | 'bar') => void1104 const emit = defineEmits<Emits>()1105 </script>1106 `)1107 assertCode(content)1108 expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)1109 expect(content).toMatch(`emits: ["foo", "bar"]`)1110 })11111112 // https://github.com/vuejs/core/issues/53931113 test('defineEmits w/ type (interface ts type)', () => {1114 const { content } = compile(`1115 <script setup lang="ts">1116 interface Emits { (e: 'foo'): void }1117 const emit: Emits = defineEmits(['foo'])1118 </script>1119 `)1120 assertCode(content)1121 expect(content).toMatch(`setup(__props, { emit }) {`)1122 expect(content).toMatch(`emits: ['foo']`)1123 })11241125 test('runtime Enum', () => {1126 const { content, bindings } = compile(1127 `<script setup lang="ts">1128 enum Foo { A = 123 }1129 </script>`1130 )1131 assertCode(content)1132 expect(bindings).toStrictEqual({1133 Foo: BindingTypes.SETUP_CONST1134 })1135 })11361137 test('runtime Enum in normal script', () => {1138 const { content, bindings } = compile(1139 `<script lang="ts">1140 export enum D { D = "D" }1141 const enum C { C = "C" }1142 enum B { B = "B" }1143 </script>1144 <script setup lang="ts">1145 enum Foo { A = 123 }1146 </script>`1147 )1148 assertCode(content)1149 expect(bindings).toStrictEqual({1150 D: BindingTypes.SETUP_CONST,1151 C: BindingTypes.SETUP_CONST,1152 B: BindingTypes.SETUP_CONST,1153 Foo: BindingTypes.SETUP_CONST1154 })1155 })11561157 test('const Enum', () => {1158 const { content, bindings } = compile(1159 `<script setup lang="ts">1160 const enum Foo { A = 123 }1161 </script>`1162 )1163 assertCode(content)1164 expect(bindings).toStrictEqual({1165 Foo: BindingTypes.SETUP_CONST1166 })1167 })11681169 test('import type', () => {1170 const { content } = compile(1171 `<script setup lang="ts">1172 import type { Foo } from './main.ts'1173 import { type Bar, Baz } from './main.ts'1174 </script>`1175 )1176 expect(content).toMatch(`return { Baz }`)1177 assertCode(content)1178 })1179 })11801181 describe('errors', () => {1182 test('<script> and <script setup> must have same lang', () => {1183 expect(() =>1184 compile(`<script>foo()</script><script setup lang="ts">bar()</script>`)1185 ).toThrow(`<script> and <script setup> must have the same language type`)1186 })11871188 const moduleErrorMsg = `cannot contain ES module exports`11891190 test('non-type named exports', () => {1191 expect(() =>1192 compile(`<script setup>1193 export const a = 11194 </script>`)1195 ).toThrow(moduleErrorMsg)11961197 expect(() =>1198 compile(`<script setup>1199 export * from './foo'1200 </script>`)1201 ).toThrow(moduleErrorMsg)12021203 expect(() =>1204 compile(`<script setup>1205 const bar = 11206 export { bar as default }1207 </script>`)1208 ).toThrow(moduleErrorMsg)1209 })12101211 test('defineProps/Emit() w/ both type and non-type args', () => {1212 expect(() => {1213 compile(`<script setup lang="ts">1214 defineProps<{}>({})1215 </script>`)1216 }).toThrow(`cannot accept both type and non-type arguments`)12171218 expect(() => {1219 compile(`<script setup lang="ts">1220 defineEmits<{}>({})1221 </script>`)1222 }).toThrow(`cannot accept both type and non-type arguments`)1223 })12241225 test('defineProps/Emit() referencing local var', () => {1226 expect(() =>1227 compile(`<script setup>1228 const bar = 11229 defineProps({1230 foo: {1231 default: () => bar1232 }1233 })1234 </script>`)1235 ).toThrow(`cannot reference locally declared variables`)12361237 expect(() =>1238 compile(`<script setup>1239 const bar = 'hello'1240 defineEmits([bar])1241 </script>`)1242 ).toThrow(`cannot reference locally declared variables`)12431244 // #46441245 expect(() =>1246 compile(`1247 <script>const bar = 1</script>1248 <script setup>1249 defineProps({1250 foo: {1251 default: () => bar1252 }1253 })1254 </script>`)1255 ).not.toThrow(`cannot reference locally declared variables`)1256 })12571258 test('should allow defineProps/Emit() referencing scope var', () => {1259 assertCode(1260 compile(`<script setup>1261 const bar = 11262 defineProps({1263 foo: {1264 default: bar => bar + 11265 }1266 })1267 defineEmits({1268 foo: bar => bar > 11269 })1270 </script>`).content1271 )1272 })12731274 test('should allow defineProps/Emit() referencing imported binding', () => {1275 assertCode(1276 compile(`<script setup>1277 import { bar } from './bar'1278 defineProps({1279 foo: {1280 default: () => bar1281 }1282 })1283 defineEmits({1284 foo: () => bar > 11285 })1286 </script>`).content1287 )1288 })1289 })1290})12911292describe('SFC analyze <script> bindings', () => {1293 it('can parse decorators syntax in typescript block', () => {1294 const { scriptAst } = compile(`1295 <script lang="ts">1296 import { Options, Vue } from 'vue-class-component';1297 @Options({1298 components: {1299 HelloWorld,1300 },1301 props: ['foo', 'bar']1302 })1303 export default class Home extends Vue {}1304 </script>1305 `)13061307 expect(scriptAst).toBeDefined()1308 })13091310 it('recognizes props array declaration', () => {1311 const { bindings } = compile(`1312 <script>1313 export default {1314 props: ['foo', 'bar']1315 }1316 </script>1317 `)1318 expect(bindings).toStrictEqual({1319 foo: BindingTypes.PROPS,1320 bar: BindingTypes.PROPS1321 })1322 expect(bindings!.__isScriptSetup).toBe(false)1323 })13241325 it('recognizes props object declaration', () => {1326 const { bindings } = compile(`1327 <script>1328 export default {1329 props: {1330 foo: String,1331 bar: {1332 type: String,1333 },1334 baz: null,1335 qux: [String, Number]1336 }1337 }1338 </script>1339 `)1340 expect(bindings).toStrictEqual({1341 foo: BindingTypes.PROPS,1342 bar: BindingTypes.PROPS,1343 baz: BindingTypes.PROPS,1344 qux: BindingTypes.PROPS1345 })1346 expect(bindings!.__isScriptSetup).toBe(false)1347 })13481349 it('recognizes setup return', () => {1350 const { bindings } = compile(`1351 <script>1352 const bar = 21353 export default {1354 setup() {1355 return {1356 foo: 1,1357 bar1358 }1359 }1360 }1361 </script>1362 `)1363 expect(bindings).toStrictEqual({1364 foo: BindingTypes.SETUP_MAYBE_REF,1365 bar: BindingTypes.SETUP_MAYBE_REF1366 })1367 expect(bindings!.__isScriptSetup).toBe(false)1368 })13691370 it('recognizes exported vars', () => {1371 const { bindings } = compile(`1372 <script>1373 export const foo = 21374 </script>1375 <script setup>1376 console.log(foo)1377 </script>1378 `)1379 expect(bindings).toStrictEqual({1380 foo: BindingTypes.SETUP_CONST1381 })1382 })13831384 it('recognizes async setup return', () => {1385 const { bindings } = compile(`1386 <script>1387 const bar = 21388 export default {1389 async setup() {1390 return {1391 foo: 1,1392 bar1393 }1394 }1395 }1396 </script>1397 `)1398 expect(bindings).toStrictEqual({1399 foo: BindingTypes.SETUP_MAYBE_REF,1400 bar: BindingTypes.SETUP_MAYBE_REF1401 })1402 expect(bindings!.__isScriptSetup).toBe(false)1403 })14041405 it('recognizes data return', () => {1406 const { bindings } = compile(`1407 <script>1408 const bar = 21409 export default {1410 data() {1411 return {1412 foo: null,1413 bar1414 }1415 }1416 }1417 </script>1418 `)1419 expect(bindings).toStrictEqual({1420 foo: BindingTypes.DATA,1421 bar: BindingTypes.DATA1422 })1423 })14241425 it('recognizes methods', () => {1426 const { bindings } = compile(`1427 <script>1428 export default {1429 methods: {1430 foo() {}1431 }1432 }1433 </script>1434 `)1435 expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })1436 })14371438 it('recognizes computeds', () => {1439 const { bindings } = compile(`1440 <script>1441 export default {1442 computed: {1443 foo() {},1444 bar: {1445 get() {},1446 set() {},1447 }1448 }1449 }1450 </script>1451 `)1452 expect(bindings).toStrictEqual({1453 foo: BindingTypes.OPTIONS,1454 bar: BindingTypes.OPTIONS1455 })1456 })14571458 it('recognizes injections array declaration', () => {1459 const { bindings } = compile(`1460 <script>1461 export default {1462 inject: ['foo', 'bar']1463 }1464 </script>1465 `)1466 expect(bindings).toStrictEqual({1467 foo: BindingTypes.OPTIONS,1468 bar: BindingTypes.OPTIONS1469 })1470 })14711472 it('recognizes injections object declaration', () => {1473 const { bindings } = compile(`1474 <script>1475 export default {1476 inject: {1477 foo: {},1478 bar: {},1479 }1480 }1481 </script>1482 `)1483 expect(bindings).toStrictEqual({1484 foo: BindingTypes.OPTIONS,1485 bar: BindingTypes.OPTIONS1486 })1487 })14881489 it('works for mixed bindings', () => {1490 const { bindings } = compile(`1491 <script>1492 export default {1493 inject: ['foo'],1494 props: {1495 bar: String,1496 },1497 setup() {1498 return {1499 baz: null,1500 }1501 },1502 data() {1503 return {1504 qux: null1505 }1506 },1507 methods: {1508 quux() {}1509 },1510 computed: {1511 quuz() {}1512 }1513 }1514 </script>1515 `)1516 expect(bindings).toStrictEqual({1517 foo: BindingTypes.OPTIONS,1518 bar: BindingTypes.PROPS,1519 baz: BindingTypes.SETUP_MAYBE_REF,1520 qux: BindingTypes.DATA,1521 quux: BindingTypes.OPTIONS,1522 quuz: BindingTypes.OPTIONS1523 })1524 })15251526 it('works for script setup', () => {1527 const { bindings } = compile(`1528 <script setup>1529 import { ref as r } from 'vue'1530 defineProps({1531 foo: String1532 })15331534 const a = r(1)1535 let b = 21536 const c = 31537 const { d } = someFoo()1538 let { e } = someBar()1539 </script>1540 `)15411542 expect(bindings).toStrictEqual({1543 r: BindingTypes.SETUP_CONST,1544 a: BindingTypes.SETUP_REF,1545 b: BindingTypes.SETUP_LET,1546 c: BindingTypes.SETUP_CONST,1547 d: BindingTypes.SETUP_MAYBE_REF,1548 e: BindingTypes.SETUP_LET,1549 foo: BindingTypes.PROPS1550 })1551 })15521553 describe('auto name inference', () => {1554 test('basic', () => {1555 const { content } = compile(1556 `<script setup>const a = 1</script>1557 <template>{{ a }}</template>`,1558 undefined,1559 {1560 filename: 'FooBar.vue'1561 }1562 )1563 expect(content).toMatch(`export default {1564 __name: 'FooBar'`)1565 assertCode(content)1566 })15671568 test('do not overwrite manual name (object)', () => {1569 const { content } = compile(1570 `<script>1571 export default {1572 name: 'Baz'1573 }1574 </script>1575 <script setup>const a = 1</script>1576 <template>{{ a }}</template>`,1577 undefined,1578 {1579 filename: 'FooBar.vue'1580 }1581 )1582 expect(content).not.toMatch(`name: 'FooBar'`)1583 expect(content).toMatch(`name: 'Baz'`)1584 assertCode(content)1585 })15861587 test('do not overwrite manual name (call)', () => {1588 const { content } = compile(1589 `<script>1590 import { defineComponent } from 'vue'1591 export default defineComponent({1592 name: 'Baz'1593 })1594 </script>1595 <script setup>const a = 1</script>1596 <template>{{ a }}</template>`,1597 undefined,1598 {1599 filename: 'FooBar.vue'1600 }1601 )1602 expect(content).not.toMatch(`name: 'FooBar'`)1603 expect(content).toMatch(`name: 'Baz'`)1604 assertCode(content)1605 })16061607 // #125911608 test('should not error when performing ts expression check for v-on inline statement', () => {1609 compile(`1610 <script setup lang="ts">1611 import { foo } from './foo'1612 </script>1613 <template>1614 <div @click="$emit('update:a');"></div>1615 </template>1616 `)1617 })16181619 // #128411620 test('should not error when performing ts expression check for v-slot destructured default value', () => {1621 compile(`1622 <script setup lang="ts">1623 import FooComp from './Foo.vue'1624 </script>1625 <template>1626 <FooComp>1627 <template #bar="{ bar = { baz: '' } }">1628 {{ bar.baz }}1629 </template>1630 </FooComp>1631 </template>1632 `)1633 })1634 })1635})
Findings
✓ No findings reported for this file.