packages/compiler-sfc/test/compileScript.spec.ts TYPESCRIPT 1,636 lines View on github.com → Search inside
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.