wip: tests for defineContext()
This commit is contained in:
parent
128621d6a0
commit
0ca9137188
@ -272,8 +272,10 @@ export function resolveComponentType(
|
|||||||
}
|
}
|
||||||
const tagFromConst = checkType(BindingTypes.CONST)
|
const tagFromConst = checkType(BindingTypes.CONST)
|
||||||
if (tagFromConst) {
|
if (tagFromConst) {
|
||||||
// constant setup bindings (e.g. imports) can be used as-is
|
return context.inline
|
||||||
return tagFromConst
|
? // in inline mode, const setup bindings (e.g. imports) can be used as-is
|
||||||
|
tagFromConst
|
||||||
|
: `$setup[${JSON.stringify(tagFromConst)}]`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,19 +102,18 @@ export function processExpression(
|
|||||||
const { inline, bindingMetadata } = context
|
const { inline, bindingMetadata } = context
|
||||||
|
|
||||||
// const bindings exposed from setup - we know they never change
|
// const bindings exposed from setup - we know they never change
|
||||||
if (inline && bindingMetadata[node.content] === BindingTypes.CONST) {
|
if (bindingMetadata[node.content] === BindingTypes.CONST) {
|
||||||
node.isRuntimeConstant = true
|
node.isRuntimeConstant = true
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefix = (raw: string) => {
|
const prefix = (raw: string) => {
|
||||||
const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw]
|
const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw]
|
||||||
if (type === BindingTypes.CONST) {
|
|
||||||
return raw
|
|
||||||
}
|
|
||||||
if (inline) {
|
if (inline) {
|
||||||
// setup inline mode
|
// setup inline mode
|
||||||
if (type === BindingTypes.SETUP) {
|
if (type === BindingTypes.CONST) {
|
||||||
|
return raw
|
||||||
|
} else if (type === BindingTypes.SETUP) {
|
||||||
return `${context.helperString(UNREF)}(${raw})`
|
return `${context.helperString(UNREF)}(${raw})`
|
||||||
} else if (type === BindingTypes.PROPS) {
|
} else if (type === BindingTypes.PROPS) {
|
||||||
// use __props which is generated by compileScript so in ts mode
|
// use __props which is generated by compileScript so in ts mode
|
||||||
@ -122,8 +121,16 @@ export function processExpression(
|
|||||||
return `__props.${raw}`
|
return `__props.${raw}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fallback to normal
|
|
||||||
return `${type ? `$${type}` : `_ctx`}.${raw}`
|
if (type === BindingTypes.CONST) {
|
||||||
|
// setup const binding in non-inline mode
|
||||||
|
return `$setup.${raw}`
|
||||||
|
} else if (type) {
|
||||||
|
return `$${type}.${raw}`
|
||||||
|
} else {
|
||||||
|
// fallback to ctx
|
||||||
|
return `_ctx.${raw}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fast path if expression is a simple identifier.
|
// fast path if expression is a simple identifier.
|
||||||
|
@ -1,106 +1,5 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`SFC compile <script setup> <script setup lang="ts"> extract emits 1`] = `
|
|
||||||
"import { Slots, defineComponent } from 'vue'
|
|
||||||
declare function __emit__(e: 'foo' | 'bar'): void
|
|
||||||
declare function __emit__(e: 'baz', id: number): void
|
|
||||||
|
|
||||||
export function setup(_: {}, { emit: myEmit }: {
|
|
||||||
emit: typeof __emit__,
|
|
||||||
slots: Slots,
|
|
||||||
attrs: Record<string, any>
|
|
||||||
}) {
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
emits: [\\"foo\\", \\"bar\\", \\"baz\\"] as unknown as undefined,
|
|
||||||
setup
|
|
||||||
})"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> <script setup lang="ts"> extract props 1`] = `
|
|
||||||
"import { Slots, defineComponent } from 'vue'
|
|
||||||
interface Test {}
|
|
||||||
|
|
||||||
type Alias = number[]
|
|
||||||
|
|
||||||
|
|
||||||
export function setup(myProps: {
|
|
||||||
string: string
|
|
||||||
number: number
|
|
||||||
boolean: boolean
|
|
||||||
object: object
|
|
||||||
objectLiteral: { a: number }
|
|
||||||
fn: (n: number) => void
|
|
||||||
functionRef: Function
|
|
||||||
objectRef: Object
|
|
||||||
array: string[]
|
|
||||||
arrayRef: Array<any>
|
|
||||||
tuple: [number, number]
|
|
||||||
set: Set<string>
|
|
||||||
literal: 'foo'
|
|
||||||
optional?: any
|
|
||||||
recordRef: Record<string, null>
|
|
||||||
interface: Test
|
|
||||||
alias: Alias
|
|
||||||
|
|
||||||
union: string | number
|
|
||||||
literalUnion: 'foo' | 'bar'
|
|
||||||
literalUnionMixed: 'foo' | 1 | boolean
|
|
||||||
intersection: Test & {}
|
|
||||||
}) {
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
string: { type: String, required: true },
|
|
||||||
number: { type: Number, required: true },
|
|
||||||
boolean: { type: Boolean, required: true },
|
|
||||||
object: { type: Object, required: true },
|
|
||||||
objectLiteral: { type: Object, required: true },
|
|
||||||
fn: { type: Function, required: true },
|
|
||||||
functionRef: { type: Function, required: true },
|
|
||||||
objectRef: { type: Object, required: true },
|
|
||||||
array: { type: Array, required: true },
|
|
||||||
arrayRef: { type: Array, required: true },
|
|
||||||
tuple: { type: Array, required: true },
|
|
||||||
set: { type: Set, required: true },
|
|
||||||
literal: { type: String, required: true },
|
|
||||||
optional: { type: null, required: false },
|
|
||||||
recordRef: { type: Object, required: true },
|
|
||||||
interface: { type: Object, required: true },
|
|
||||||
alias: { type: Array, required: true },
|
|
||||||
union: { type: [String, Number], required: true },
|
|
||||||
literalUnion: { type: [String, String], required: true },
|
|
||||||
literalUnionMixed: { type: [String, Number, Boolean], required: true },
|
|
||||||
intersection: { type: Object, required: true }
|
|
||||||
} as unknown as undefined,
|
|
||||||
setup
|
|
||||||
})"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> <script setup lang="ts"> hoist type declarations 1`] = `
|
|
||||||
"import { Slots, defineComponent } from 'vue'
|
|
||||||
export interface Foo {}
|
|
||||||
type Bar = {}
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup
|
|
||||||
})"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> CSS vars injection <script> w/ default export 1`] = `
|
exports[`SFC compile <script setup> CSS vars injection <script> w/ default export 1`] = `
|
||||||
"const __default__ = { setup() {} }
|
"const __default__ = { setup() {} }
|
||||||
import { useCssVars as __useCssVars__ } from 'vue'
|
import { useCssVars as __useCssVars__ } from 'vue'
|
||||||
@ -147,116 +46,177 @@ export default __default__"
|
|||||||
exports[`SFC compile <script setup> CSS vars injection w/ <script setup> 1`] = `
|
exports[`SFC compile <script setup> CSS vars injection w/ <script setup> 1`] = `
|
||||||
"import { useCssVars } from 'vue'
|
"import { useCssVars } from 'vue'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
const color = 'red'
|
const color = 'red'
|
||||||
__useCssVars__(_ctx => ({ color }))
|
__useCssVars__(_ctx => ({ color }))
|
||||||
return { color }
|
return { color }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> errors should allow export default referencing imported binding 1`] = `
|
exports[`SFC compile <script setup> defineContext() 1`] = `
|
||||||
"import { bar } from './bar'
|
"export default {
|
||||||
|
props: {
|
||||||
export function setup() {
|
foo: String
|
||||||
|
},
|
||||||
|
emit: ['a', 'b'],
|
||||||
|
setup(__props, { props, emit }) {
|
||||||
|
|
||||||
|
|
||||||
return { bar }
|
|
||||||
|
const bar = 1
|
||||||
|
|
||||||
|
return { props, emit, bar }
|
||||||
}
|
}
|
||||||
|
|
||||||
const __default__ = {
|
}"
|
||||||
props: {
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> errors should allow defineContext() referencing imported binding 1`] = `
|
||||||
|
"import { bar } from './bar'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
foo: {
|
foo: {
|
||||||
default: () => bar
|
default: () => bar
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
setup() {
|
||||||
__default__.setup = setup
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> errors should allow export default referencing scope var 1`] = `
|
|
||||||
"export function setup() {
|
|
||||||
|
|
||||||
const bar = 1
|
|
||||||
|
|
||||||
return { bar }
|
return { bar }
|
||||||
}
|
}
|
||||||
|
|
||||||
const __default__ = {
|
}"
|
||||||
props: {
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> errors should allow defineContext() referencing scope var 1`] = `
|
||||||
|
"export default {
|
||||||
|
props: {
|
||||||
foo: {
|
foo: {
|
||||||
default: bar => bar + 1
|
default: bar => bar + 1
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
setup() {
|
||||||
__default__.setup = setup
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> explicit setup signature 1`] = `
|
const bar = 1
|
||||||
"export function setup(props, { emit }) {
|
|
||||||
emit('foo')
|
|
||||||
return { }
|
return { bar }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> imports dedupe between user & helper 1`] = `
|
exports[`SFC compile <script setup> imports dedupe between user & helper 1`] = `
|
||||||
"import { ref } from 'vue'
|
"import { ref } from 'vue'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
const foo = ref(1)
|
const foo = ref(1)
|
||||||
|
|
||||||
return { ref, foo }
|
return { foo, ref }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> imports import dedupe between <script> and <script setup> 1`] = `
|
exports[`SFC compile <script setup> imports import dedupe between <script> and <script setup> 1`] = `
|
||||||
"import { x } from './x'
|
"import { x } from './x'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
x()
|
x()
|
||||||
|
|
||||||
return { x }
|
return { x }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> imports should extract comment for import or type declarations 1`] = `
|
exports[`SFC compile <script setup> imports should extract comment for import or type declarations 1`] = `
|
||||||
"import a from 'a' // comment
|
"import a from 'a' // comment
|
||||||
import b from 'b'
|
import b from 'b'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
|
||||||
return { a, b }
|
return { a, b }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> imports should hoist and expose imports 1`] = `
|
exports[`SFC compile <script setup> imports should hoist and expose imports 1`] = `
|
||||||
"import { ref } from 'vue'
|
"import { ref } from 'vue'
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
return { ref }
|
return { ref }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> inlineTemplate mode avoid unref() when necessary 1`] = `
|
||||||
|
"import { createVNode as _createVNode, unref as _unref, toDisplayString as _toDisplayString, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import Foo from './Foo.vue'
|
||||||
|
import other from './util'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
const constant = {}
|
||||||
|
function fn() {}
|
||||||
|
|
||||||
|
return (_ctx, _cache, $props, $setup, $data, $options) => {
|
||||||
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
|
_createVNode(Foo),
|
||||||
|
_createVNode(\\"div\\", { onClick: fn }, _toDisplayString(_unref(count)) + \\" \\" + _toDisplayString(constant) + \\" \\" + _toDisplayString(_unref(other)), 1 /* TEXT */)
|
||||||
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> inlineTemplate mode should work 1`] = `
|
||||||
|
"import { unref as _unref, toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
|
|
||||||
|
const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"static\\", -1 /* HOISTED */)
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
return (_ctx, _cache, $props, $setup, $data, $options) => {
|
||||||
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
|
_createVNode(\\"div\\", null, _toDisplayString(_unref(count)), 1 /* TEXT */),
|
||||||
|
_hoisted_1
|
||||||
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> ref: syntax sugar accessing ref binding 1`] = `
|
exports[`SFC compile <script setup> ref: syntax sugar accessing ref binding 1`] = `
|
||||||
"import { ref } from 'vue'
|
"import { ref } from 'vue'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
const a = ref(1)
|
const a = ref(1)
|
||||||
console.log(a.value)
|
console.log(a.value)
|
||||||
@ -267,13 +227,14 @@ export function setup() {
|
|||||||
return { a, get }
|
return { a, get }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> ref: syntax sugar array destructure 1`] = `
|
exports[`SFC compile <script setup> ref: syntax sugar array destructure 1`] = `
|
||||||
"import { ref } from 'vue'
|
"import { ref } from 'vue'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
const n = ref(1), [__a, __b = 1, ...__c] = useFoo()
|
const n = ref(1), [__a, __b = 1, ...__c] = useFoo()
|
||||||
const a = ref(__a);
|
const a = ref(__a);
|
||||||
@ -284,13 +245,14 @@ const c = ref(__c);
|
|||||||
return { n, a, b, c }
|
return { n, a, b, c }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> ref: syntax sugar convert ref declarations 1`] = `
|
exports[`SFC compile <script setup> ref: syntax sugar convert ref declarations 1`] = `
|
||||||
"import { ref } from 'vue'
|
"import { ref } from 'vue'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
const foo = ref()
|
const foo = ref()
|
||||||
const a = ref(1)
|
const a = ref(1)
|
||||||
@ -303,13 +265,14 @@ export function setup() {
|
|||||||
return { foo, a, b, c, d }
|
return { foo, a, b, c, d }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> ref: syntax sugar multi ref declarations 1`] = `
|
exports[`SFC compile <script setup> ref: syntax sugar multi ref declarations 1`] = `
|
||||||
"import { ref } from 'vue'
|
"import { ref } from 'vue'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
const a = ref(1), b = ref(2), c = ref({
|
const a = ref(1), b = ref(2), c = ref({
|
||||||
count: 0
|
count: 0
|
||||||
@ -318,13 +281,14 @@ export function setup() {
|
|||||||
return { a, b, c }
|
return { a, b, c }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> ref: syntax sugar mutating ref binding 1`] = `
|
exports[`SFC compile <script setup> ref: syntax sugar mutating ref binding 1`] = `
|
||||||
"import { ref } from 'vue'
|
"import { ref } from 'vue'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
const a = ref(1)
|
const a = ref(1)
|
||||||
const b = ref({ count: 0 })
|
const b = ref({ count: 0 })
|
||||||
@ -338,13 +302,14 @@ export function setup() {
|
|||||||
return { a, b, inc }
|
return { a, b, inc }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> ref: syntax sugar nested destructure 1`] = `
|
exports[`SFC compile <script setup> ref: syntax sugar nested destructure 1`] = `
|
||||||
"import { ref } from 'vue'
|
"import { ref } from 'vue'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
const [{ a: { b: __b }}] = useFoo()
|
const [{ a: { b: __b }}] = useFoo()
|
||||||
const b = ref(__b);
|
const b = ref(__b);
|
||||||
@ -356,13 +321,14 @@ const e = ref(__e);
|
|||||||
return { b, d, e }
|
return { b, d, e }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> ref: syntax sugar object destructure 1`] = `
|
exports[`SFC compile <script setup> ref: syntax sugar object destructure 1`] = `
|
||||||
"import { ref } from 'vue'
|
"import { ref } from 'vue'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
const n = ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()
|
const n = ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()
|
||||||
const a = ref(__a);
|
const a = ref(__a);
|
||||||
@ -375,11 +341,12 @@ const g = ref(__g);
|
|||||||
return { n, a, c, d, f, g }
|
return { n, a, c, d, f, g }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> ref: syntax sugar should not convert non ref labels 1`] = `
|
exports[`SFC compile <script setup> ref: syntax sugar should not convert non ref labels 1`] = `
|
||||||
"export function setup() {
|
"export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
foo: a = 1, b = 2, c = {
|
foo: a = 1, b = 2, c = {
|
||||||
count: 0
|
count: 0
|
||||||
@ -388,13 +355,14 @@ exports[`SFC compile <script setup> ref: syntax sugar should not convert non ref
|
|||||||
return { }
|
return { }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> ref: syntax sugar using ref binding in property shorthand 1`] = `
|
exports[`SFC compile <script setup> ref: syntax sugar using ref binding in property shorthand 1`] = `
|
||||||
"import { ref } from 'vue'
|
"import { ref } from 'vue'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
const a = ref(1)
|
const a = ref(1)
|
||||||
const b = { a: a.value }
|
const b = { a: a.value }
|
||||||
@ -405,21 +373,138 @@ export function setup() {
|
|||||||
return { a, b, test }
|
return { a, b, test }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> should expose top level declarations 1`] = `
|
exports[`SFC compile <script setup> should expose top level declarations 1`] = `
|
||||||
"import { x } from './x'
|
"import { x } from './x'
|
||||||
|
|
||||||
export function setup() {
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
let a = 1
|
let a = 1
|
||||||
const b = 2
|
const b = 2
|
||||||
function c() {}
|
function c() {}
|
||||||
class d {}
|
class d {}
|
||||||
|
|
||||||
return { x, a, b, c, d }
|
return { a, b, c, d, x }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setup }"
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> with TypeScript defineContext w/ runtime options 1`] = `
|
||||||
|
"import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: { foo: String },
|
||||||
|
emits: ['a', 'b'],
|
||||||
|
setup(__props, { props, emit }) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { props, emit }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> with TypeScript defineContext w/ type / extract emits (union) 1`] = `
|
||||||
|
"import { Slots, defineComponent } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: [\\"foo\\", \\"bar\\", \\"baz\\"] as unknown as undefined,
|
||||||
|
setup(__props, { emit }: {
|
||||||
|
props: {},
|
||||||
|
emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void),
|
||||||
|
slots: Slots,
|
||||||
|
attrs: Record<string, any>
|
||||||
|
}) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { emit }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> with TypeScript defineContext w/ type / extract emits 1`] = `
|
||||||
|
"import { Slots, defineComponent } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: [\\"foo\\", \\"bar\\"] as unknown as undefined,
|
||||||
|
setup(__props, { emit }: {
|
||||||
|
props: {},
|
||||||
|
emit: (e: 'foo' | 'bar') => void,
|
||||||
|
slots: Slots,
|
||||||
|
attrs: Record<string, any>
|
||||||
|
}) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { emit }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> with TypeScript defineContext w/ type / extract props 1`] = `
|
||||||
|
"import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
interface Test {}
|
||||||
|
|
||||||
|
type Alias = number[]
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
string: { type: String, required: true },
|
||||||
|
number: { type: Number, required: true },
|
||||||
|
boolean: { type: Boolean, required: true },
|
||||||
|
object: { type: Object, required: true },
|
||||||
|
objectLiteral: { type: Object, required: true },
|
||||||
|
fn: { type: Function, required: true },
|
||||||
|
functionRef: { type: Function, required: true },
|
||||||
|
objectRef: { type: Object, required: true },
|
||||||
|
array: { type: Array, required: true },
|
||||||
|
arrayRef: { type: Array, required: true },
|
||||||
|
tuple: { type: Array, required: true },
|
||||||
|
set: { type: Set, required: true },
|
||||||
|
literal: { type: String, required: true },
|
||||||
|
optional: { type: null, required: false },
|
||||||
|
recordRef: { type: Object, required: true },
|
||||||
|
interface: { type: Object, required: true },
|
||||||
|
alias: { type: Array, required: true },
|
||||||
|
union: { type: [String, Number], required: true },
|
||||||
|
literalUnion: { type: [String, String], required: true },
|
||||||
|
literalUnionMixed: { type: [String, Number, Boolean], required: true },
|
||||||
|
intersection: { type: Object, required: true }
|
||||||
|
} as unknown as undefined,
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> with TypeScript hoist type declarations 1`] = `
|
||||||
|
"import { defineComponent } from 'vue'
|
||||||
|
export interface Foo {}
|
||||||
|
type Bar = {}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
|
||||||
|
return { }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
`;
|
`;
|
||||||
|
@ -22,12 +22,6 @@ function assertCode(code: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('SFC compile <script setup>', () => {
|
describe('SFC compile <script setup>', () => {
|
||||||
test('explicit setup signature', () => {
|
|
||||||
assertCode(
|
|
||||||
compile(`<script setup="props, { emit }">emit('foo')</script>`).content
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should expose top level declarations', () => {
|
test('should expose top level declarations', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -39,7 +33,43 @@ describe('SFC compile <script setup>', () => {
|
|||||||
</script>
|
</script>
|
||||||
`)
|
`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(content).toMatch('return { x, a, b, c, d }')
|
expect(content).toMatch('return { a, b, c, d, x }')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('defineContext()', () => {
|
||||||
|
const { content, bindings } = compile(`
|
||||||
|
<script setup>
|
||||||
|
import { defineContext } from 'vue'
|
||||||
|
const { props, emit } = defineContext({
|
||||||
|
props: {
|
||||||
|
foo: String
|
||||||
|
},
|
||||||
|
emit: ['a', 'b']
|
||||||
|
})
|
||||||
|
|
||||||
|
const bar = 1
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
// should generate working code
|
||||||
|
assertCode(content)
|
||||||
|
// should anayze bindings
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
foo: 'props',
|
||||||
|
bar: 'const',
|
||||||
|
props: 'const',
|
||||||
|
emit: 'const'
|
||||||
|
})
|
||||||
|
|
||||||
|
// should remove defineContext import and call
|
||||||
|
expect(content).not.toMatch('defineContext')
|
||||||
|
// should generate correct setup signature
|
||||||
|
expect(content).toMatch(`setup(__props, { props, emit }) {`)
|
||||||
|
// should include context options in default export
|
||||||
|
expect(content).toMatch(`export default {
|
||||||
|
props: {
|
||||||
|
foo: String
|
||||||
|
},
|
||||||
|
emit: ['a', 'b'],`)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('imports', () => {
|
describe('imports', () => {
|
||||||
@ -51,18 +81,22 @@ describe('SFC compile <script setup>', () => {
|
|||||||
|
|
||||||
test('should extract comment for import or type declarations', () => {
|
test('should extract comment for import or type declarations', () => {
|
||||||
assertCode(
|
assertCode(
|
||||||
compile(`<script setup>
|
compile(`
|
||||||
import a from 'a' // comment
|
<script setup>
|
||||||
import b from 'b'
|
import a from 'a' // comment
|
||||||
</script>`).content
|
import b from 'b'
|
||||||
|
</script>
|
||||||
|
`).content
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('dedupe between user & helper', () => {
|
test('dedupe between user & helper', () => {
|
||||||
const { content } = compile(`<script setup>
|
const { content } = compile(`
|
||||||
import { ref } from 'vue'
|
<script setup>
|
||||||
ref: foo = 1
|
import { ref } from 'vue'
|
||||||
</script>`)
|
ref: foo = 1
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(content).toMatch(`import { ref } from 'vue'`)
|
expect(content).toMatch(`import { ref } from 'vue'`)
|
||||||
})
|
})
|
||||||
@ -84,7 +118,62 @@ describe('SFC compile <script setup>', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('<script setup lang="ts">', () => {
|
describe('inlineTemplate mode', () => {
|
||||||
|
test('should work', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
const count = ref(0)
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>{{ count }}</div>
|
||||||
|
<div>static</div>
|
||||||
|
</template>
|
||||||
|
`,
|
||||||
|
{ inlineTemplate: true }
|
||||||
|
)
|
||||||
|
// check snapshot and make sure helper imports and
|
||||||
|
// hoists are placed correctly.
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('avoid unref() when necessary', () => {
|
||||||
|
// function, const, component import
|
||||||
|
const { content } = compile(
|
||||||
|
`
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineContext } from 'vue'
|
||||||
|
import Foo from './Foo.vue'
|
||||||
|
import other from './util'
|
||||||
|
const count = ref(0)
|
||||||
|
const constant = {}
|
||||||
|
function fn() {}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Foo/>
|
||||||
|
<div @click="fn">{{ count }} {{ constant }} {{ other }}</div>
|
||||||
|
</template>
|
||||||
|
`,
|
||||||
|
{ inlineTemplate: true }
|
||||||
|
)
|
||||||
|
assertCode(content)
|
||||||
|
// no need to unref vue component import
|
||||||
|
expect(content).toMatch(`createVNode(Foo)`)
|
||||||
|
// should unref other imports
|
||||||
|
expect(content).toMatch(`unref(other)`)
|
||||||
|
// no need to unref constant literals
|
||||||
|
expect(content).not.toMatch(`unref(constant)`)
|
||||||
|
// should unref const w/ call init (e.g. ref())
|
||||||
|
expect(content).toMatch(`unref(count)`)
|
||||||
|
// no need to unref function declarations
|
||||||
|
expect(content).toMatch(`{ onClick: fn }`)
|
||||||
|
// no need to mark constant fns in patch flag
|
||||||
|
expect(content).not.toMatch(`PROPS`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with TypeScript', () => {
|
||||||
test('hoist type declarations', () => {
|
test('hoist type declarations', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -94,37 +183,57 @@ describe('SFC compile <script setup>', () => {
|
|||||||
assertCode(content)
|
assertCode(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('extract props', () => {
|
test('defineContext w/ runtime options', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup="myProps" lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { defineContext } from 'vue'
|
||||||
|
const { props, emit } = defineContext({
|
||||||
|
props: { foo: String },
|
||||||
|
emits: ['a', 'b']
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`export default defineComponent({
|
||||||
|
props: { foo: String },
|
||||||
|
emits: ['a', 'b'],
|
||||||
|
setup(__props, { props, emit }) {`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('defineContext w/ type / extract props', () => {
|
||||||
|
const { content, bindings } = compile(`
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineContext } from 'vue'
|
||||||
interface Test {}
|
interface Test {}
|
||||||
|
|
||||||
type Alias = number[]
|
type Alias = number[]
|
||||||
|
|
||||||
declare const myProps: {
|
defineContext<{
|
||||||
string: string
|
props: {
|
||||||
number: number
|
string: string
|
||||||
boolean: boolean
|
number: number
|
||||||
object: object
|
boolean: boolean
|
||||||
objectLiteral: { a: number }
|
object: object
|
||||||
fn: (n: number) => void
|
objectLiteral: { a: number }
|
||||||
functionRef: Function
|
fn: (n: number) => void
|
||||||
objectRef: Object
|
functionRef: Function
|
||||||
array: string[]
|
objectRef: Object
|
||||||
arrayRef: Array<any>
|
array: string[]
|
||||||
tuple: [number, number]
|
arrayRef: Array<any>
|
||||||
set: Set<string>
|
tuple: [number, number]
|
||||||
literal: 'foo'
|
set: Set<string>
|
||||||
optional?: any
|
literal: 'foo'
|
||||||
recordRef: Record<string, null>
|
optional?: any
|
||||||
interface: Test
|
recordRef: Record<string, null>
|
||||||
alias: Alias
|
interface: Test
|
||||||
|
alias: Alias
|
||||||
|
|
||||||
union: string | number
|
union: string | number
|
||||||
literalUnion: 'foo' | 'bar'
|
literalUnion: 'foo' | 'bar'
|
||||||
literalUnionMixed: 'foo' | 1 | boolean
|
literalUnionMixed: 'foo' | 1 | boolean
|
||||||
intersection: Test & {}
|
intersection: Test & {}
|
||||||
}
|
}
|
||||||
|
}>()
|
||||||
</script>`)
|
</script>`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(content).toMatch(`string: { type: String, required: true }`)
|
expect(content).toMatch(`string: { type: String, required: true }`)
|
||||||
@ -154,21 +263,57 @@ describe('SFC compile <script setup>', () => {
|
|||||||
`literalUnionMixed: { type: [String, Number, Boolean], required: true }`
|
`literalUnionMixed: { type: [String, Number, Boolean], required: true }`
|
||||||
)
|
)
|
||||||
expect(content).toMatch(`intersection: { type: Object, required: true }`)
|
expect(content).toMatch(`intersection: { type: Object, required: true }`)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
string: 'props',
|
||||||
|
number: 'props',
|
||||||
|
boolean: 'props',
|
||||||
|
object: 'props',
|
||||||
|
objectLiteral: 'props',
|
||||||
|
fn: 'props',
|
||||||
|
functionRef: 'props',
|
||||||
|
objectRef: 'props',
|
||||||
|
array: 'props',
|
||||||
|
arrayRef: 'props',
|
||||||
|
tuple: 'props',
|
||||||
|
set: 'props',
|
||||||
|
literal: 'props',
|
||||||
|
optional: 'props',
|
||||||
|
recordRef: 'props',
|
||||||
|
interface: 'props',
|
||||||
|
alias: 'props',
|
||||||
|
union: 'props',
|
||||||
|
literalUnion: 'props',
|
||||||
|
literalUnionMixed: 'props',
|
||||||
|
intersection: 'props'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('extract emits', () => {
|
test('defineContext w/ type / extract emits', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup="_, { emit: myEmit }" lang="ts">
|
<script setup lang="ts">
|
||||||
declare function myEmit(e: 'foo' | 'bar'): void
|
import { defineContext } from 'vue'
|
||||||
declare function myEmit(e: 'baz', id: number): void
|
const { emit } = defineContext<{
|
||||||
|
emit: (e: 'foo' | 'bar') => void
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`props: {},\n emit: (e: 'foo' | 'bar') => void,`)
|
||||||
|
expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('defineContext w/ type / extract emits (union)', () => {
|
||||||
|
const { content } = compile(`
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineContext } from 'vue'
|
||||||
|
const { emit } = defineContext<{
|
||||||
|
emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
`)
|
`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(content).toMatch(
|
expect(content).toMatch(
|
||||||
`declare function __emit__(e: 'foo' | 'bar'): void`
|
`props: {},\n emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void),`
|
||||||
)
|
|
||||||
expect(content).toMatch(
|
|
||||||
`declare function __emit__(e: 'baz', id: number): void`
|
|
||||||
)
|
)
|
||||||
expect(content).toMatch(
|
expect(content).toMatch(
|
||||||
`emits: ["foo", "bar", "baz"] as unknown as undefined`
|
`emits: ["foo", "bar", "baz"] as unknown as undefined`
|
||||||
@ -220,9 +365,7 @@ describe('SFC compile <script setup>', () => {
|
|||||||
describe('async/await detection', () => {
|
describe('async/await detection', () => {
|
||||||
function assertAwaitDetection(code: string, shouldAsync = true) {
|
function assertAwaitDetection(code: string, shouldAsync = true) {
|
||||||
const { content } = compile(`<script setup>${code}</script>`)
|
const { content } = compile(`<script setup>${code}</script>`)
|
||||||
expect(content).toMatch(
|
expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup()`)
|
||||||
`export ${shouldAsync ? `async ` : ``}function setup`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test('expression statement', () => {
|
test('expression statement', () => {
|
||||||
@ -459,25 +602,27 @@ describe('SFC compile <script setup>', () => {
|
|||||||
).toThrow(`<script> and <script setup> must have the same language type`)
|
).toThrow(`<script> and <script setup> must have the same language type`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const moduleErrorMsg = `cannot contain ES module exports`
|
||||||
|
|
||||||
test('non-type named exports', () => {
|
test('non-type named exports', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
export const a = 1
|
export const a = 1
|
||||||
</script>`)
|
</script>`)
|
||||||
).toThrow(`cannot contain non-type named or * exports`)
|
).toThrow(moduleErrorMsg)
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
export * from './foo'
|
export * from './foo'
|
||||||
</script>`)
|
</script>`)
|
||||||
).toThrow(`cannot contain non-type named or * exports`)
|
).toThrow(moduleErrorMsg)
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
const bar = 1
|
const bar = 1
|
||||||
export { bar as default }
|
export { bar as default }
|
||||||
</script>`)
|
</script>`)
|
||||||
).toThrow(`cannot contain non-type named or * exports`)
|
).toThrow(moduleErrorMsg)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('ref: non-assignment expressions', () => {
|
test('ref: non-assignment expressions', () => {
|
||||||
@ -488,97 +633,74 @@ describe('SFC compile <script setup>', () => {
|
|||||||
).toThrow(`ref: statements can only contain assignment expressions`)
|
).toThrow(`ref: statements can only contain assignment expressions`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('export default referencing local var', () => {
|
test('defineContext() w/ both type and non-type args', () => {
|
||||||
|
expect(() => {
|
||||||
|
compile(`<script setup lang="ts">
|
||||||
|
import { defineContext } from 'vue'
|
||||||
|
defineContext<{}>({})
|
||||||
|
</script>`)
|
||||||
|
}).toThrow(`cannot accept both type and non-type arguments`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('defineContext() referencing local var', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
const bar = 1
|
import { defineContext } from 'vue'
|
||||||
export default {
|
const bar = 1
|
||||||
props: {
|
defineContext({
|
||||||
foo: {
|
props: {
|
||||||
default: () => bar
|
foo: {
|
||||||
}
|
default: () => bar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
</script>`)
|
</script>`)
|
||||||
).toThrow(`cannot reference locally declared variables`)
|
).toThrow(`cannot reference locally declared variables`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('export default referencing ref declarations', () => {
|
test('defineContext() referencing ref declarations', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
|
import { defineContext } from 'vue'
|
||||||
ref: bar = 1
|
ref: bar = 1
|
||||||
export default {
|
defineContext({
|
||||||
props: bar
|
props: { bar }
|
||||||
}
|
})
|
||||||
</script>`)
|
</script>`)
|
||||||
).toThrow(`cannot reference locally declared variables`)
|
).toThrow(`cannot reference locally declared variables`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should allow export default referencing scope var', () => {
|
test('should allow defineContext() referencing scope var', () => {
|
||||||
assertCode(
|
assertCode(
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
|
import { defineContext } from 'vue'
|
||||||
const bar = 1
|
const bar = 1
|
||||||
export default {
|
defineContext({
|
||||||
props: {
|
props: {
|
||||||
foo: {
|
foo: {
|
||||||
default: bar => bar + 1
|
default: bar => bar + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
</script>`).content
|
</script>`).content
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should allow export default referencing imported binding', () => {
|
test('should allow defineContext() referencing imported binding', () => {
|
||||||
assertCode(
|
assertCode(
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
|
import { defineContext } from 'vue'
|
||||||
import { bar } from './bar'
|
import { bar } from './bar'
|
||||||
export default {
|
defineContext({
|
||||||
props: {
|
props: {
|
||||||
foo: {
|
foo: {
|
||||||
default: () => bar
|
default: () => bar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
</script>`).content
|
</script>`).content
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('error on duplicated default export', () => {
|
|
||||||
expect(() =>
|
|
||||||
compile(`
|
|
||||||
<script>
|
|
||||||
export default {}
|
|
||||||
</script>
|
|
||||||
<script setup>
|
|
||||||
export default {}
|
|
||||||
</script>
|
|
||||||
`)
|
|
||||||
).toThrow(`Default export is already declared`)
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
compile(`
|
|
||||||
<script>
|
|
||||||
export { x as default } from './y'
|
|
||||||
</script>
|
|
||||||
<script setup>
|
|
||||||
export default {}
|
|
||||||
</script>
|
|
||||||
`)
|
|
||||||
).toThrow(`Default export is already declared`)
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
compile(`
|
|
||||||
<script>
|
|
||||||
const x = {}
|
|
||||||
export { x as default }
|
|
||||||
</script>
|
|
||||||
<script setup>
|
|
||||||
export default {}
|
|
||||||
</script>
|
|
||||||
`)
|
|
||||||
).toThrow(`Default export is already declared`)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -779,11 +901,12 @@ describe('SFC analyze <script> bindings', () => {
|
|||||||
it('works for script setup', () => {
|
it('works for script setup', () => {
|
||||||
const { bindings } = compile(`
|
const { bindings } = compile(`
|
||||||
<script setup>
|
<script setup>
|
||||||
export default {
|
import { defineContext } from 'vue'
|
||||||
props: {
|
defineContext({
|
||||||
foo: String,
|
props: {
|
||||||
},
|
foo: String,
|
||||||
}
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
`)
|
`)
|
||||||
expect(bindings).toStrictEqual({
|
expect(bindings).toStrictEqual({
|
||||||
|
@ -15,12 +15,12 @@ import {
|
|||||||
TSType,
|
TSType,
|
||||||
TSTypeLiteral,
|
TSTypeLiteral,
|
||||||
TSFunctionType,
|
TSFunctionType,
|
||||||
TSDeclareFunction,
|
|
||||||
ObjectProperty,
|
ObjectProperty,
|
||||||
ArrayExpression,
|
ArrayExpression,
|
||||||
Statement,
|
Statement,
|
||||||
Expression,
|
Expression,
|
||||||
LabeledStatement
|
LabeledStatement,
|
||||||
|
TSUnionType
|
||||||
} from '@babel/types'
|
} from '@babel/types'
|
||||||
import { walk } from 'estree-walker'
|
import { walk } from 'estree-walker'
|
||||||
import { RawSourceMap } from 'source-map'
|
import { RawSourceMap } from 'source-map'
|
||||||
@ -143,6 +143,7 @@ export function compileScript(
|
|||||||
const refIdentifiers: Set<Identifier> = new Set()
|
const refIdentifiers: Set<Identifier> = new Set()
|
||||||
const enableRefSugar = options.refSugar !== false
|
const enableRefSugar = options.refSugar !== false
|
||||||
let defaultExport: Node | undefined
|
let defaultExport: Node | undefined
|
||||||
|
let hasContextCall = false
|
||||||
let setupContextExp: string | undefined
|
let setupContextExp: string | undefined
|
||||||
let setupContextArg: ObjectExpression | undefined
|
let setupContextArg: ObjectExpression | undefined
|
||||||
let setupContextType: TSTypeLiteral | undefined
|
let setupContextType: TSTypeLiteral | undefined
|
||||||
@ -182,6 +183,48 @@ export function compileScript(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processContextCall(node: Node): boolean {
|
||||||
|
if (
|
||||||
|
node.type === 'CallExpression' &&
|
||||||
|
node.callee.type === 'Identifier' &&
|
||||||
|
node.callee.name === CTX_FN_NAME
|
||||||
|
) {
|
||||||
|
if (hasContextCall) {
|
||||||
|
error('duplicate defineContext() call', node)
|
||||||
|
}
|
||||||
|
hasContextCall = true
|
||||||
|
const optsArg = node.arguments[0]
|
||||||
|
if (optsArg) {
|
||||||
|
if (optsArg.type === 'ObjectExpression') {
|
||||||
|
setupContextArg = optsArg
|
||||||
|
} else {
|
||||||
|
error(`${CTX_FN_NAME}() argument must be an object literal.`, optsArg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// context call has type parameters - infer runtime types from it
|
||||||
|
if (node.typeParameters) {
|
||||||
|
if (setupContextArg) {
|
||||||
|
error(
|
||||||
|
`${CTX_FN_NAME}() cannot accept both type and non-type arguments ` +
|
||||||
|
`at the same time. Use one or the other.`,
|
||||||
|
node
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const typeArg = node.typeParameters.params[0]
|
||||||
|
if (typeArg.type === 'TSTypeLiteral') {
|
||||||
|
setupContextType = typeArg
|
||||||
|
} else {
|
||||||
|
error(
|
||||||
|
`type argument passed to ${CTX_FN_NAME}() must be a literal type.`,
|
||||||
|
typeArg
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
function processRefExpression(exp: Expression, statement: LabeledStatement) {
|
function processRefExpression(exp: Expression, statement: LabeledStatement) {
|
||||||
if (exp.type === 'AssignmentExpression') {
|
if (exp.type === 'AssignmentExpression') {
|
||||||
helperImports.add('ref')
|
helperImports.add('ref')
|
||||||
@ -500,51 +543,24 @@ export function compileScript(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
node.type === 'ExpressionStatement' &&
|
||||||
|
processContextCall(node.expression)
|
||||||
|
) {
|
||||||
|
s.remove(node.start! + startOffset, node.end! + startOffset)
|
||||||
|
}
|
||||||
|
|
||||||
if (node.type === 'VariableDeclaration' && !node.declare) {
|
if (node.type === 'VariableDeclaration' && !node.declare) {
|
||||||
for (const decl of node.declarations) {
|
for (const decl of node.declarations) {
|
||||||
if (
|
if (decl.init && processContextCall(decl.init)) {
|
||||||
decl.init &&
|
|
||||||
decl.init.type === 'CallExpression' &&
|
|
||||||
decl.init.callee.type === 'Identifier' &&
|
|
||||||
decl.init.callee.name === CTX_FN_NAME
|
|
||||||
) {
|
|
||||||
if (node.declarations.length === 1) {
|
|
||||||
s.remove(node.start! + startOffset, node.end! + startOffset)
|
|
||||||
} else {
|
|
||||||
s.remove(decl.start! + startOffset, decl.end! + startOffset)
|
|
||||||
}
|
|
||||||
setupContextExp = scriptSetup.content.slice(
|
setupContextExp = scriptSetup.content.slice(
|
||||||
decl.id.start!,
|
decl.id.start!,
|
||||||
decl.id.end!
|
decl.id.end!
|
||||||
)
|
)
|
||||||
const optsArg = decl.init.arguments[0]
|
if (node.declarations.length === 1) {
|
||||||
if (optsArg.type === 'ObjectExpression') {
|
s.remove(node.start! + startOffset, node.end! + startOffset)
|
||||||
setupContextArg = optsArg
|
|
||||||
} else {
|
} else {
|
||||||
error(
|
s.remove(decl.start! + startOffset, decl.end! + startOffset)
|
||||||
`${CTX_FN_NAME}() argument must be an object literal.`,
|
|
||||||
optsArg
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// useSetupContext() has type parameters - infer runtime types from it
|
|
||||||
if (decl.init.typeParameters) {
|
|
||||||
if (setupContextArg) {
|
|
||||||
error(
|
|
||||||
`${CTX_FN_NAME}() cannot accept both type and non-type arguments ` +
|
|
||||||
`at the same time. Use one or the other.`,
|
|
||||||
decl.init
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const typeArg = decl.init.typeParameters.params[0]
|
|
||||||
if (typeArg.type === 'TSTypeLiteral') {
|
|
||||||
setupContextType = typeArg
|
|
||||||
} else {
|
|
||||||
error(
|
|
||||||
`type argument passed to ${CTX_FN_NAME}() must be a literal type.`,
|
|
||||||
typeArg
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -641,7 +657,8 @@ export function compileScript(
|
|||||||
typeNode.start!,
|
typeNode.start!,
|
||||||
typeNode.end!
|
typeNode.end!
|
||||||
)
|
)
|
||||||
if (m.key.name === 'props') {
|
const key = m.key.name
|
||||||
|
if (key === 'props') {
|
||||||
propsType = typeString
|
propsType = typeString
|
||||||
if (typeNode.type === 'TSTypeLiteral') {
|
if (typeNode.type === 'TSTypeLiteral') {
|
||||||
extractRuntimeProps(typeNode, typeDeclaredProps, declaredTypes)
|
extractRuntimeProps(typeNode, typeDeclaredProps, declaredTypes)
|
||||||
@ -649,18 +666,23 @@ export function compileScript(
|
|||||||
// TODO be able to trace references
|
// TODO be able to trace references
|
||||||
error(`props type must be an object literal type`, typeNode)
|
error(`props type must be an object literal type`, typeNode)
|
||||||
}
|
}
|
||||||
} else if (m.key.name === 'emit') {
|
} else if (key === 'emit') {
|
||||||
emitType = typeString
|
emitType = typeString
|
||||||
if (typeNode.type === 'TSFunctionType') {
|
if (
|
||||||
|
typeNode.type === 'TSFunctionType' ||
|
||||||
|
typeNode.type === 'TSUnionType'
|
||||||
|
) {
|
||||||
extractRuntimeEmits(typeNode, typeDeclaredEmits)
|
extractRuntimeEmits(typeNode, typeDeclaredEmits)
|
||||||
} else {
|
} else {
|
||||||
// TODO be able to trace references
|
// TODO be able to trace references
|
||||||
error(`emit type must be a function type`, typeNode)
|
error(`emit type must be a function type`, typeNode)
|
||||||
}
|
}
|
||||||
} else if (m.key.name === 'attrs') {
|
} else if (key === 'attrs') {
|
||||||
attrsType = typeString
|
attrsType = typeString
|
||||||
} else if (m.key.name === 'slots') {
|
} else if (key === 'slots') {
|
||||||
slotsType = typeString
|
slotsType = typeString
|
||||||
|
} else {
|
||||||
|
error(`invalid setup context property: "${key}"`, m.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -747,19 +769,13 @@ export function compileScript(
|
|||||||
if (setupContextArg) {
|
if (setupContextArg) {
|
||||||
Object.assign(bindingMetadata, analyzeBindingsFromOptions(setupContextArg))
|
Object.assign(bindingMetadata, analyzeBindingsFromOptions(setupContextArg))
|
||||||
}
|
}
|
||||||
if (options.inlineTemplate) {
|
for (const [key, { source }] of Object.entries(userImports)) {
|
||||||
for (const [key, { source }] of Object.entries(userImports)) {
|
bindingMetadata[key] = source.endsWith('.vue')
|
||||||
bindingMetadata[key] = source.endsWith('.vue')
|
? BindingTypes.CONST
|
||||||
? BindingTypes.CONST
|
: BindingTypes.SETUP
|
||||||
: BindingTypes.SETUP
|
}
|
||||||
}
|
for (const key in setupBindings) {
|
||||||
for (const key in setupBindings) {
|
bindingMetadata[key] = setupBindings[key]
|
||||||
bindingMetadata[key] = setupBindings[key]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const key in allBindings) {
|
|
||||||
bindingMetadata[key] = BindingTypes.SETUP
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 11. generate return statement
|
// 11. generate return statement
|
||||||
@ -1135,11 +1151,20 @@ function toRuntimeTypeString(types: string[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractRuntimeEmits(
|
function extractRuntimeEmits(
|
||||||
node: TSFunctionType | TSDeclareFunction,
|
node: TSFunctionType | TSUnionType,
|
||||||
emits: Set<string>
|
emits: Set<string>
|
||||||
) {
|
) {
|
||||||
const eventName =
|
if (node.type === 'TSUnionType') {
|
||||||
node.type === 'TSDeclareFunction' ? node.params[0] : node.parameters[0]
|
for (let t of node.types) {
|
||||||
|
if (t.type === 'TSParenthesizedType') t = t.typeAnnotation
|
||||||
|
if (t.type === 'TSFunctionType') {
|
||||||
|
extractRuntimeEmits(t, emits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventName = node.parameters[0]
|
||||||
if (
|
if (
|
||||||
eventName.type === 'Identifier' &&
|
eventName.type === 'Identifier' &&
|
||||||
eventName.typeAnnotation &&
|
eventName.typeAnnotation &&
|
||||||
|
Loading…
Reference in New Issue
Block a user