Skip to content

Commit

Permalink
feat: types for <script setup> macros
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 16, 2022
1 parent 3c2707b commit 7173ad4
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/core/util/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ export function resolveAsset(
// fallback to prototype chain
const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
if (__DEV__ && warnMissing && !res) {
warn('Failed to resolve ' + type.slice(0, -1) + ': ' + id, options)
warn('Failed to resolve ' + type.slice(0, -1) + ': ' + id)
}
return res
}
12 changes: 7 additions & 5 deletions src/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export type ComponentOptions = {

// data
data: object | Function | void
props?: { [key: string]: PropOptions }
props?:
| string[]
| Record<string, Function | Array<Function> | null | PropOptions>
propsData?: object
computed?: {
[key: string]:
Expand Down Expand Up @@ -105,8 +107,8 @@ export type ComponentOptions = {
}

export type PropOptions = {
type: Function | Array<Function> | null
default: any
required: boolean | null
validator: Function | null
type?: Function | Array<Function> | null
default?: any
required?: boolean | null
validator?: Function | null
}
41 changes: 40 additions & 1 deletion src/v3/apiSetup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { Component } from 'types/component'
import { PropOptions } from 'types/options'
import { def, invokeWithErrorHandling, isReserved, warn } from '../core/util'
import VNode from '../core/vdom/vnode'
import { bind, emptyObject, isFunction, isObject } from '../shared/util'
import {
bind,
emptyObject,
isArray,
isFunction,
isObject
} from '../shared/util'
import { currentInstance, setCurrentInstance } from './currentInstance'
import { isRef } from './reactivity/ref'

Expand Down Expand Up @@ -193,3 +200,35 @@ function getContext(): SetupContext {
const vm = currentInstance!
return vm._setupContext || (vm._setupContext = createSetupContext(vm))
}

/**
* Runtime helper for merging default declarations. Imported by compiled code
* only.
* @internal
*/
export function mergeDefaults(
raw: string[] | Record<string, PropOptions>,
defaults: Record<string, any>
): Record<string, PropOptions> {
const props = isArray(raw)
? raw.reduce(
(normalized, p) => ((normalized[p] = {}), normalized),
{} as Record<string, PropOptions>
)
: raw
for (const key in defaults) {
const opt = props[key]
if (opt) {
if (isArray(opt) || isFunction(opt)) {
props[key] = { type: opt, default: defaults[key] }
} else {
opt.default = defaults[key]
}
} else if (opt === null) {
props[key] = { default: defaults[key] }
} else if (__DEV__) {
warn(`props default key "${key}" has no corresponding declaration.`)
}
}
return props
}
6 changes: 2 additions & 4 deletions src/v3/apiWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,8 @@ function doWatch(

const warnInvalidSource = (s: unknown) => {
warn(
`Invalid watch source: `,
s,
`A watch source can only be a getter/effect function, a ref, ` +
`a reactive object, or an array of these types.`
`Invalid watch source: ${s}. A watch source can only be a getter/effect ` +
`function, a ref, a reactive object, or an array of these types.`
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/v3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export { provide, inject, InjectionKey } from './apiInject'

export { h } from './h'
export { getCurrentInstance } from './currentInstance'
export { useSlots, useAttrs } from './apiSetup'
export { useSlots, useAttrs, mergeDefaults } from './apiSetup'
export { nextTick } from 'core/util/next-tick'
export { set, del } from 'core/observer'

Expand Down
2 changes: 2 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export {

export * from './v3-manual-apis'
export * from './v3-generated'
// <script setup> helpers
export * from './v3-setup-helpers'

export { Data } from './common'
export { SetupContext } from './v3-setup-context'
Expand Down
134 changes: 134 additions & 0 deletions types/test/setup-helpers-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { useAttrs, useSlots, SetupContext } from '../index'
import { describe, expectType } from './utils'

describe('defineProps w/ type declaration', () => {
// type declaration
const props = defineProps<{
foo: string
}>()
// explicitly declared type should be refined
expectType<string>(props.foo)
// @ts-expect-error
props.bar
})

describe('defineProps w/ type declaration + withDefaults', () => {
const res = withDefaults(
defineProps<{
number?: number
arr?: string[]
obj?: { x: number }
fn?: (e: string) => void
x?: string
genStr?: string
}>(),
{
number: 123,
arr: () => [],
obj: () => ({ x: 123 }),
fn: () => {},
genStr: () => ''
}
)

res.number + 1
res.arr.push('hi')
res.obj.x
res.fn('hi')
// @ts-expect-error
res.x.slice()
res.genStr.slice()
})

describe('defineProps w/ union type declaration + withDefaults', () => {
withDefaults(
defineProps<{
union1?: number | number[] | { x: number }
union2?: number | number[] | { x: number }
union3?: number | number[] | { x: number }
union4?: number | number[] | { x: number }
}>(),
{
union1: 123,
union2: () => [123],
union3: () => ({ x: 123 }),
union4: () => 123
}
)
})

describe('defineProps w/ runtime declaration', () => {
// runtime declaration
const props = defineProps({
foo: String,
bar: {
type: Number,
default: 1
},
baz: {
type: Array,
required: true
}
})
expectType<{
foo?: string
bar: number
baz: unknown[]
}>(props)

props.foo && props.foo + 'bar'
props.bar + 1
// @ts-expect-error should be readonly
props.bar++
props.baz.push(1)

const props2 = defineProps(['foo', 'bar'])
props2.foo + props2.bar
// @ts-expect-error
props2.baz
})

describe('defineEmits w/ type declaration', () => {
const emit = defineEmits<(e: 'change') => void>()
emit('change')
// @ts-expect-error
emit()
// @ts-expect-error
emit('bar')

type Emits = { (e: 'foo' | 'bar'): void; (e: 'baz', id: number): void }
const emit2 = defineEmits<Emits>()

emit2('foo')
emit2('bar')
emit2('baz', 123)
// @ts-expect-error
emit2('baz')
})

describe('defineEmits w/ runtime declaration', () => {
const emit = defineEmits({
foo: () => {},
bar: null
})
emit('foo')
emit('bar', 123)
// @ts-expect-error
emit('baz')

const emit2 = defineEmits(['foo', 'bar'])
emit2('foo')
emit2('bar', 123)
// @ts-expect-error
emit2('baz')
})

describe('useAttrs', () => {
const attrs = useAttrs()
expectType<Record<string, unknown>>(attrs)
})

describe('useSlots', () => {
const slots = useSlots()
expectType<SetupContext['slots']>(slots)
})
Loading

0 comments on commit 7173ad4

Please sign in to comment.