]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(sfc): make ref sugar disabled by default
authorEvan You <yyx990803@gmail.com>
Tue, 29 Jun 2021 19:22:26 +0000 (15:22 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 29 Jun 2021 19:22:26 +0000 (15:22 -0400)
packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
packages/compiler-sfc/__tests__/compileScript.spec.ts
packages/compiler-sfc/__tests__/compileScriptRefSugar.ts [new file with mode: 0644]
packages/compiler-sfc/src/compileScript.ts

index 6c3586544a8d62abda446ddadd8e3870c02fcdff..792de768a0104f87146f28d0d0d79d6af215fa0c 100644 (file)
@@ -554,212 +554,6 @@ return () => {}
 }"
 `;
 
-exports[`SFC compile <script setup> ref: syntax sugar accessing ref binding 1`] = `
-"import { ref as _ref } from 'vue'
-
-export default {
-  setup(__props, { expose }) {
-  expose()
-
-      const a = _ref(1)
-      console.log(a.value)
-      function get() {
-        return a.value + 1
-      }
-      
-return { a, get }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> ref: syntax sugar array destructure 1`] = `
-"import { ref as _ref } from 'vue'
-
-export default {
-  setup(__props, { expose }) {
-  expose()
-
-      const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()
-const a = _ref(__a);
-const b = _ref(__b);
-const c = _ref(__c);
-      console.log(n.value, a.value, b.value, c.value)
-      
-return { n, a, b, c }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> ref: syntax sugar convert ref declarations 1`] = `
-"import { ref as _ref } from 'vue'
-
-export default {
-  setup(__props, { expose }) {
-  expose()
-
-      const foo = _ref()
-      const a = _ref(1)
-      const b = _ref({
-        count: 0
-      })
-      let c = () => {}
-      let d
-      
-return { foo, a, b, c, d }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> ref: syntax sugar multi ref declarations 1`] = `
-"import { ref as _ref } from 'vue'
-
-export default {
-  setup(__props, { expose }) {
-  expose()
-
-      const a = _ref(1), b = _ref(2), c = _ref({
-        count: 0
-      })
-      
-return { a, b, c }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> ref: syntax sugar mutating ref binding 1`] = `
-"import { ref as _ref } from 'vue'
-
-export default {
-  setup(__props, { expose }) {
-  expose()
-
-      const a = _ref(1)
-      const b = _ref({ count: 0 })
-      function inc() {
-        a.value++
-        a.value = a.value + 1
-        b.value.count++
-        b.value.count = b.value.count + 1
-        ;({ a: a.value } = { a: 2 })
-        ;[a.value] = [1]
-      }
-      
-return { a, b, inc }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> ref: syntax sugar nested destructure 1`] = `
-"import { ref as _ref } from 'vue'
-
-export default {
-  setup(__props, { expose }) {
-  expose()
-
-      const [{ a: { b: __b }}] = useFoo()
-const b = _ref(__b);
-      const { c: [__d, __e] } = useBar()
-const d = _ref(__d);
-const e = _ref(__e);
-      console.log(b.value, d.value, e.value)
-      
-return { b, d, e }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> ref: syntax sugar object destructure 1`] = `
-"import { ref as _ref } from 'vue'
-
-export default {
-  setup(__props, { expose }) {
-  expose()
-
-      const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()
-const a = _ref(__a);
-const c = _ref(__c);
-const d = _ref(__d);
-const f = _ref(__f);
-const g = _ref(__g);
-      const { foo: __foo } = useSomthing(() => 1);
-const foo = _ref(__foo);
-      console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)
-      
-return { n, a, c, d, f, g, foo }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> ref: syntax sugar should not convert non ref labels 1`] = `
-"export default {
-  setup(__props, { expose }) {
-  expose()
-
-      foo: a = 1, b = 2, c = {
-        count: 0
-      }
-      
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> ref: syntax sugar should not rewrite scope variable 1`] = `
-"import { ref as _ref } from 'vue'
-
-export default {
-  setup(__props, { expose }) {
-  expose()
-
-        const a = _ref(1)
-        const b = _ref(1)
-        const d = _ref(1)
-        const e = 1
-        function test() {
-          const a = 2
-          console.log(a)
-          console.log(b.value)
-          let c = { c: 3 }
-          console.log(c)
-          let $d
-          console.log($d)
-          console.log(d.value)
-          console.log(e)
-        }
-      
-return { a, b, d, e, test }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> ref: syntax sugar using ref binding in property shorthand 1`] = `
-"import { ref as _ref } from 'vue'
-
-export default {
-  setup(__props, { expose }) {
-  expose()
-
-      const a = _ref(1)
-      const b = { a: a.value }
-      function test() {
-        const { a } = b
-      }
-      
-return { a, b, test }
-}
-
-}"
-`;
-
 exports[`SFC compile <script setup> should expose top level declarations 1`] = `
 "import { x } from './x'
       
index 54a4b698b47b6257271e505d0dadf8e850c7daf7..5c98f39ee931fc27e3c80775f27ef8b473f19133 100644 (file)
@@ -1,4 +1,4 @@
-import { BindingTypes } from '@vue/compiler-dom'
+import { BindingTypes } from '@vue/compiler-core'
 import { compileSFCScript as compile, assertCode } from './utils'
 
 describe('SFC compile <script setup>', () => {
@@ -200,12 +200,15 @@ defineExpose({ foo: 123 })
     })
 
     test('dedupe between user & helper', () => {
-      const { content } = compile(`
+      const { content } = compile(
+        `
       <script setup>
       import { ref } from 'vue'
       ref: foo = 1
       </script>
-      `)
+      `,
+        { refSugar: true }
+      )
       assertCode(content)
       expect(content).toMatch(`import { ref } from 'vue'`)
     })
@@ -829,7 +832,9 @@ const emit = defineEmits(['a', 'b'])
       expected: string | ((content: string) => boolean),
       shouldAsync = true
     ) {
-      const { content } = compile(`<script setup>${code}</script>`)
+      const { content } = compile(`<script setup>${code}</script>`, {
+        refSugar: true
+      })
       expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
       if (typeof expected === 'string') {
         expect(content).toMatch(expected)
@@ -893,238 +898,6 @@ const emit = defineEmits(['a', 'b'])
     })
   })
 
-  describe('ref: syntax sugar', () => {
-    test('convert ref declarations', () => {
-      const { content, bindings } = compile(`<script setup>
-      ref: foo
-      ref: a = 1
-      ref: b = {
-        count: 0
-      }
-      let c = () => {}
-      let d
-      </script>`)
-      expect(content).toMatch(`import { ref as _ref } from 'vue'`)
-      expect(content).not.toMatch(`ref: foo`)
-      expect(content).not.toMatch(`ref: a`)
-      expect(content).not.toMatch(`ref: b`)
-      expect(content).toMatch(`const foo = _ref()`)
-      expect(content).toMatch(`const a = _ref(1)`)
-      expect(content).toMatch(`
-      const b = _ref({
-        count: 0
-      })
-      `)
-      // normal declarations left untouched
-      expect(content).toMatch(`let c = () => {}`)
-      expect(content).toMatch(`let d`)
-      assertCode(content)
-      expect(bindings).toStrictEqual({
-        foo: BindingTypes.SETUP_REF,
-        a: BindingTypes.SETUP_REF,
-        b: BindingTypes.SETUP_REF,
-        c: BindingTypes.SETUP_LET,
-        d: BindingTypes.SETUP_LET
-      })
-    })
-
-    test('multi ref declarations', () => {
-      const { content, bindings } = compile(`<script setup>
-      ref: a = 1, b = 2, c = {
-        count: 0
-      }
-      </script>`)
-      expect(content).toMatch(`
-      const a = _ref(1), b = _ref(2), c = _ref({
-        count: 0
-      })
-      `)
-      expect(content).toMatch(`return { a, b, c }`)
-      assertCode(content)
-      expect(bindings).toStrictEqual({
-        a: BindingTypes.SETUP_REF,
-        b: BindingTypes.SETUP_REF,
-        c: BindingTypes.SETUP_REF
-      })
-    })
-
-    test('should not convert non ref labels', () => {
-      const { content } = compile(`<script setup>
-      foo: a = 1, b = 2, c = {
-        count: 0
-      }
-      </script>`)
-      expect(content).toMatch(`foo: a = 1, b = 2`)
-      assertCode(content)
-    })
-
-    test('accessing ref binding', () => {
-      const { content } = compile(`<script setup>
-      ref: a = 1
-      console.log(a)
-      function get() {
-        return a + 1
-      }
-      </script>`)
-      expect(content).toMatch(`console.log(a.value)`)
-      expect(content).toMatch(`return a.value + 1`)
-      assertCode(content)
-    })
-
-    test('cases that should not append .value', () => {
-      const { content } = compile(`<script setup>
-      ref: a = 1
-      console.log(b.a)
-      function get(a) {
-        return a + 1
-      }
-      </script>`)
-      expect(content).not.toMatch(`a.value`)
-    })
-
-    test('mutating ref binding', () => {
-      const { content } = compile(`<script setup>
-      ref: a = 1
-      ref: b = { count: 0 }
-      function inc() {
-        a++
-        a = a + 1
-        b.count++
-        b.count = b.count + 1
-        ;({ a } = { a: 2 })
-        ;[a] = [1]
-      }
-      </script>`)
-      expect(content).toMatch(`a.value++`)
-      expect(content).toMatch(`a.value = a.value + 1`)
-      expect(content).toMatch(`b.value.count++`)
-      expect(content).toMatch(`b.value.count = b.value.count + 1`)
-      expect(content).toMatch(`;({ a: a.value } = { a: 2 })`)
-      expect(content).toMatch(`;[a.value] = [1]`)
-      assertCode(content)
-    })
-
-    test('using ref binding in property shorthand', () => {
-      const { content } = compile(`<script setup>
-      ref: a = 1
-      const b = { a }
-      function test() {
-        const { a } = b
-      }
-      </script>`)
-      expect(content).toMatch(`const b = { a: a.value }`)
-      // should not convert destructure
-      expect(content).toMatch(`const { a } = b`)
-      assertCode(content)
-    })
-
-    test('should not rewrite scope variable', () => {
-      const { content } = compile(`
-      <script setup>
-        ref: a = 1
-        ref: b = 1
-        ref: d = 1
-        const e = 1
-        function test() {
-          const a = 2
-          console.log(a)
-          console.log(b)
-          let c = { c: 3 }
-          console.log(c)
-          let $d
-          console.log($d)
-          console.log(d)
-          console.log(e)
-        }
-      </script>`)
-      expect(content).toMatch('console.log(a)')
-      expect(content).toMatch('console.log(b.value)')
-      expect(content).toMatch('console.log(c)')
-      expect(content).toMatch('console.log($d)')
-      expect(content).toMatch('console.log(d.value)')
-      expect(content).toMatch('console.log(e)')
-      assertCode(content)
-    })
-
-    test('object destructure', () => {
-      const { content, bindings } = compile(`<script setup>
-      ref: n = 1, ({ a, b: c, d = 1, e: f = 2, ...g } = useFoo())
-      ref: ({ foo } = useSomthing(() => 1));
-      console.log(n, a, c, d, f, g, foo)
-      </script>`)
-      expect(content).toMatch(
-        `const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()`
-      )
-      expect(content).toMatch(`const { foo: __foo } = useSomthing(() => 1)`)
-      expect(content).toMatch(`\nconst a = _ref(__a);`)
-      expect(content).not.toMatch(`\nconst b = _ref(__b);`)
-      expect(content).toMatch(`\nconst c = _ref(__c);`)
-      expect(content).toMatch(`\nconst d = _ref(__d);`)
-      expect(content).not.toMatch(`\nconst e = _ref(__e);`)
-      expect(content).toMatch(`\nconst f = _ref(__f);`)
-      expect(content).toMatch(`\nconst g = _ref(__g);`)
-      expect(content).toMatch(`\nconst foo = _ref(__foo);`)
-      expect(content).toMatch(
-        `console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)`
-      )
-      expect(content).toMatch(`return { n, a, c, d, f, g, foo }`)
-      expect(bindings).toStrictEqual({
-        n: BindingTypes.SETUP_REF,
-        a: BindingTypes.SETUP_REF,
-        c: BindingTypes.SETUP_REF,
-        d: BindingTypes.SETUP_REF,
-        f: BindingTypes.SETUP_REF,
-        g: BindingTypes.SETUP_REF,
-        foo: BindingTypes.SETUP_REF
-      })
-      assertCode(content)
-    })
-
-    test('array destructure', () => {
-      const { content, bindings } = compile(`<script setup>
-      ref: n = 1, [a, b = 1, ...c] = useFoo()
-      console.log(n, a, b, c)
-      </script>`)
-      expect(content).toMatch(
-        `const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()`
-      )
-      expect(content).toMatch(`\nconst a = _ref(__a);`)
-      expect(content).toMatch(`\nconst b = _ref(__b);`)
-      expect(content).toMatch(`\nconst c = _ref(__c);`)
-      expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
-      expect(content).toMatch(`return { n, a, b, c }`)
-      expect(bindings).toStrictEqual({
-        n: BindingTypes.SETUP_REF,
-        a: BindingTypes.SETUP_REF,
-        b: BindingTypes.SETUP_REF,
-        c: BindingTypes.SETUP_REF
-      })
-      assertCode(content)
-    })
-
-    test('nested destructure', () => {
-      const { content, bindings } = compile(`<script setup>
-      ref: [{ a: { b }}] = useFoo()
-      ref: ({ c: [d, e] } = useBar())
-      console.log(b, d, e)
-      </script>`)
-      expect(content).toMatch(`const [{ a: { b: __b }}] = useFoo()`)
-      expect(content).toMatch(`const { c: [__d, __e] } = useBar()`)
-      expect(content).not.toMatch(`\nconst a = _ref(__a);`)
-      expect(content).not.toMatch(`\nconst c = _ref(__c);`)
-      expect(content).toMatch(`\nconst b = _ref(__b);`)
-      expect(content).toMatch(`\nconst d = _ref(__d);`)
-      expect(content).toMatch(`\nconst e = _ref(__e);`)
-      expect(content).toMatch(`return { b, d, e }`)
-      expect(bindings).toStrictEqual({
-        b: BindingTypes.SETUP_REF,
-        d: BindingTypes.SETUP_REF,
-        e: BindingTypes.SETUP_REF
-      })
-      assertCode(content)
-    })
-  })
-
   describe('errors', () => {
     test('<script> and <script setup> must have same lang', () => {
       expect(() =>
@@ -1155,14 +928,6 @@ const emit = defineEmits(['a', 'b'])
       ).toThrow(moduleErrorMsg)
     })
 
-    test('ref: non-assignment expressions', () => {
-      expect(() =>
-        compile(`<script setup>
-        ref: a = 1, foo()
-        </script>`)
-      ).toThrow(`ref: statements can only contain assignment expressions`)
-    })
-
     test('defineProps/Emit() w/ both type and non-type args', () => {
       expect(() => {
         compile(`<script setup lang="ts">
@@ -1197,26 +962,6 @@ const emit = defineEmits(['a', 'b'])
       ).toThrow(`cannot reference locally declared variables`)
     })
 
-    test('defineProps/Emit() referencing ref declarations', () => {
-      expect(() =>
-        compile(`<script setup>
-        ref: bar = 1
-        defineProps({
-          bar
-        })
-      </script>`)
-      ).toThrow(`cannot reference locally declared variables`)
-
-      expect(() =>
-        compile(`<script setup>
-        ref: bar = 1
-        defineEmits({
-          bar
-        })
-      </script>`)
-      ).toThrow(`cannot reference locally declared variables`)
-    })
-
     test('should allow defineProps/Emit() referencing scope var', () => {
       assertCode(
         compile(`<script setup>
diff --git a/packages/compiler-sfc/__tests__/compileScriptRefSugar.ts b/packages/compiler-sfc/__tests__/compileScriptRefSugar.ts
new file mode 100644 (file)
index 0000000..df75905
--- /dev/null
@@ -0,0 +1,277 @@
+import { BindingTypes } from '@vue/compiler-core'
+import { compileSFCScript as compile, assertCode } from './utils'
+
+describe('<script setup> ref sugar', () => {
+  function compileWithRefSugar(src: string) {
+    return compile(src, { refSugar: true })
+  }
+
+  test('convert ref declarations', () => {
+    const { content, bindings } = compileWithRefSugar(`<script setup>
+    ref: foo
+    ref: a = 1
+    ref: b = {
+      count: 0
+    }
+    let c = () => {}
+    let d
+    </script>`)
+    expect(content).toMatch(`import { ref as _ref } from 'vue'`)
+    expect(content).not.toMatch(`ref: foo`)
+    expect(content).not.toMatch(`ref: a`)
+    expect(content).not.toMatch(`ref: b`)
+    expect(content).toMatch(`const foo = _ref()`)
+    expect(content).toMatch(`const a = _ref(1)`)
+    expect(content).toMatch(`
+    const b = _ref({
+      count: 0
+    })
+    `)
+    // normal declarations left untouched
+    expect(content).toMatch(`let c = () => {}`)
+    expect(content).toMatch(`let d`)
+    assertCode(content)
+    expect(bindings).toStrictEqual({
+      foo: BindingTypes.SETUP_REF,
+      a: BindingTypes.SETUP_REF,
+      b: BindingTypes.SETUP_REF,
+      c: BindingTypes.SETUP_LET,
+      d: BindingTypes.SETUP_LET
+    })
+  })
+
+  test('multi ref declarations', () => {
+    const { content, bindings } = compileWithRefSugar(`<script setup>
+    ref: a = 1, b = 2, c = {
+      count: 0
+    }
+    </script>`)
+    expect(content).toMatch(`
+    const a = _ref(1), b = _ref(2), c = _ref({
+      count: 0
+    })
+    `)
+    expect(content).toMatch(`return { a, b, c }`)
+    assertCode(content)
+    expect(bindings).toStrictEqual({
+      a: BindingTypes.SETUP_REF,
+      b: BindingTypes.SETUP_REF,
+      c: BindingTypes.SETUP_REF
+    })
+  })
+
+  test('should not convert non ref labels', () => {
+    const { content } = compileWithRefSugar(`<script setup>
+    foo: a = 1, b = 2, c = {
+      count: 0
+    }
+    </script>`)
+    expect(content).toMatch(`foo: a = 1, b = 2`)
+    assertCode(content)
+  })
+
+  test('accessing ref binding', () => {
+    const { content } = compileWithRefSugar(`<script setup>
+    ref: a = 1
+    console.log(a)
+    function get() {
+      return a + 1
+    }
+    </script>`)
+    expect(content).toMatch(`console.log(a.value)`)
+    expect(content).toMatch(`return a.value + 1`)
+    assertCode(content)
+  })
+
+  test('cases that should not append .value', () => {
+    const { content } = compileWithRefSugar(`<script setup>
+    ref: a = 1
+    console.log(b.a)
+    function get(a) {
+      return a + 1
+    }
+    </script>`)
+    expect(content).not.toMatch(`a.value`)
+  })
+
+  test('mutating ref binding', () => {
+    const { content } = compileWithRefSugar(`<script setup>
+    ref: a = 1
+    ref: b = { count: 0 }
+    function inc() {
+      a++
+      a = a + 1
+      b.count++
+      b.count = b.count + 1
+      ;({ a } = { a: 2 })
+      ;[a] = [1]
+    }
+    </script>`)
+    expect(content).toMatch(`a.value++`)
+    expect(content).toMatch(`a.value = a.value + 1`)
+    expect(content).toMatch(`b.value.count++`)
+    expect(content).toMatch(`b.value.count = b.value.count + 1`)
+    expect(content).toMatch(`;({ a: a.value } = { a: 2 })`)
+    expect(content).toMatch(`;[a.value] = [1]`)
+    assertCode(content)
+  })
+
+  test('using ref binding in property shorthand', () => {
+    const { content } = compileWithRefSugar(`<script setup>
+    ref: a = 1
+    const b = { a }
+    function test() {
+      const { a } = b
+    }
+    </script>`)
+    expect(content).toMatch(`const b = { a: a.value }`)
+    // should not convert destructure
+    expect(content).toMatch(`const { a } = b`)
+    assertCode(content)
+  })
+
+  test('should not rewrite scope variable', () => {
+    const { content } = compileWithRefSugar(`
+    <script setup>
+      ref: a = 1
+      ref: b = 1
+      ref: d = 1
+      const e = 1
+      function test() {
+        const a = 2
+        console.log(a)
+        console.log(b)
+        let c = { c: 3 }
+        console.log(c)
+        let $d
+        console.log($d)
+        console.log(d)
+        console.log(e)
+      }
+    </script>`)
+    expect(content).toMatch('console.log(a)')
+    expect(content).toMatch('console.log(b.value)')
+    expect(content).toMatch('console.log(c)')
+    expect(content).toMatch('console.log($d)')
+    expect(content).toMatch('console.log(d.value)')
+    expect(content).toMatch('console.log(e)')
+    assertCode(content)
+  })
+
+  test('object destructure', () => {
+    const { content, bindings } = compileWithRefSugar(`<script setup>
+    ref: n = 1, ({ a, b: c, d = 1, e: f = 2, ...g } = useFoo())
+    ref: ({ foo } = useSomthing(() => 1));
+    console.log(n, a, c, d, f, g, foo)
+    </script>`)
+    expect(content).toMatch(
+      `const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()`
+    )
+    expect(content).toMatch(`const { foo: __foo } = useSomthing(() => 1)`)
+    expect(content).toMatch(`\nconst a = _ref(__a);`)
+    expect(content).not.toMatch(`\nconst b = _ref(__b);`)
+    expect(content).toMatch(`\nconst c = _ref(__c);`)
+    expect(content).toMatch(`\nconst d = _ref(__d);`)
+    expect(content).not.toMatch(`\nconst e = _ref(__e);`)
+    expect(content).toMatch(`\nconst f = _ref(__f);`)
+    expect(content).toMatch(`\nconst g = _ref(__g);`)
+    expect(content).toMatch(`\nconst foo = _ref(__foo);`)
+    expect(content).toMatch(
+      `console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)`
+    )
+    expect(content).toMatch(`return { n, a, c, d, f, g, foo }`)
+    expect(bindings).toStrictEqual({
+      n: BindingTypes.SETUP_REF,
+      a: BindingTypes.SETUP_REF,
+      c: BindingTypes.SETUP_REF,
+      d: BindingTypes.SETUP_REF,
+      f: BindingTypes.SETUP_REF,
+      g: BindingTypes.SETUP_REF,
+      foo: BindingTypes.SETUP_REF
+    })
+    assertCode(content)
+  })
+
+  test('array destructure', () => {
+    const { content, bindings } = compileWithRefSugar(`<script setup>
+    ref: n = 1, [a, b = 1, ...c] = useFoo()
+    console.log(n, a, b, c)
+    </script>`)
+    expect(content).toMatch(
+      `const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()`
+    )
+    expect(content).toMatch(`\nconst a = _ref(__a);`)
+    expect(content).toMatch(`\nconst b = _ref(__b);`)
+    expect(content).toMatch(`\nconst c = _ref(__c);`)
+    expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
+    expect(content).toMatch(`return { n, a, b, c }`)
+    expect(bindings).toStrictEqual({
+      n: BindingTypes.SETUP_REF,
+      a: BindingTypes.SETUP_REF,
+      b: BindingTypes.SETUP_REF,
+      c: BindingTypes.SETUP_REF
+    })
+    assertCode(content)
+  })
+
+  test('nested destructure', () => {
+    const { content, bindings } = compileWithRefSugar(`<script setup>
+    ref: [{ a: { b }}] = useFoo()
+    ref: ({ c: [d, e] } = useBar())
+    console.log(b, d, e)
+    </script>`)
+    expect(content).toMatch(`const [{ a: { b: __b }}] = useFoo()`)
+    expect(content).toMatch(`const { c: [__d, __e] } = useBar()`)
+    expect(content).not.toMatch(`\nconst a = _ref(__a);`)
+    expect(content).not.toMatch(`\nconst c = _ref(__c);`)
+    expect(content).toMatch(`\nconst b = _ref(__b);`)
+    expect(content).toMatch(`\nconst d = _ref(__d);`)
+    expect(content).toMatch(`\nconst e = _ref(__e);`)
+    expect(content).toMatch(`return { b, d, e }`)
+    expect(bindings).toStrictEqual({
+      b: BindingTypes.SETUP_REF,
+      d: BindingTypes.SETUP_REF,
+      e: BindingTypes.SETUP_REF
+    })
+    assertCode(content)
+  })
+
+  describe('errors', () => {
+    test('ref: non-assignment expressions', () => {
+      expect(() =>
+        compile(
+          `<script setup>
+        ref: a = 1, foo()
+        </script>`,
+          { refSugar: true }
+        )
+      ).toThrow(`ref: statements can only contain assignment expressions`)
+    })
+
+    test('defineProps/Emit() referencing ref declarations', () => {
+      expect(() =>
+        compile(
+          `<script setup>
+        ref: bar = 1
+        defineProps({
+          bar
+        })
+      </script>`,
+          { refSugar: true }
+        )
+      ).toThrow(`cannot reference locally declared variables`)
+
+      expect(() =>
+        compile(
+          `<script setup>
+        ref: bar = 1
+        defineEmits({
+          bar
+        })
+      </script>`,
+          { refSugar: true }
+        )
+      ).toThrow(`cannot reference locally declared variables`)
+    })
+  })
+})
index 9347e996b50cd116f34f1c41766d87be84451735..d88736106d233bbd1281d028114ff391ddb2957d 100644 (file)
@@ -189,7 +189,7 @@ export function compileScript(
   const setupBindings: Record<string, BindingTypes> = Object.create(null)
   const refBindings: Record<string, BindingTypes> = Object.create(null)
   const refIdentifiers: Set<Identifier> = new Set()
-  const enableRefSugar = options.refSugar !== false
+  const enableRefSugar = !!options.refSugar
   let defaultExport: Node | undefined
   let hasDefinePropsCall = false
   let hasDefineEmitCall = false
@@ -732,10 +732,11 @@ export function compileScript(
         )
         processRefExpression(node.body.expression, node)
       } else {
-        // TODO if we end up shipping ref: sugar as an opt-in feature,
-        // need to proxy the option in vite, vue-loader and rollup-plugin-vue.
         error(
-          `ref: sugar needs to be explicitly enabled via vite or vue-loader options.`,
+          `ref: sugar now needs to be explicitly enabled via @vitejs/plugin-vue ` +
+            `or vue-loader options:\n` +
+            `- @vitejs/plugin-vue: via \`script.refSugar\`\n` +
+            `- vue-loader: via \`refSugar\` (requires 16.3.0+)`,
           node
         )
       }