Skip to content

Commit

Permalink
feat(types): deny unknown attributes on component by default (#1614)
Browse files Browse the repository at this point in the history
close #1519
  • Loading branch information
HcySunYang committed Jul 17, 2020
1 parent 77659fa commit 5d8a64d
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 58 deletions.
15 changes: 10 additions & 5 deletions packages/runtime-core/src/apiDefineComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import {
import { ExtractPropTypes, ComponentPropsOptions } from './componentProps'
import { EmitsOptions } from './componentEmits'
import { isFunction } from '@vue/shared'
import { VNodeProps } from './vnode'
import {
VNodeProps,
AllowedComponentProps,
ComponentCustomProps
} from './vnode'

// defineComponent is a utility that is primarily used for type inference
// when declaring components. Type inference is provided in the component
Expand All @@ -40,7 +44,7 @@ export function defineComponent<Props, RawBindings = object>(
{},
{},
// public props
VNodeProps & Props
VNodeProps & Props & AllowedComponentProps & ComponentCustomProps
>
> &
FunctionalComponent<Props>
Expand Down Expand Up @@ -80,7 +84,7 @@ export function defineComponent<
Mixin,
Extends,
E,
VNodeProps & Props
VNodeProps & Props & AllowedComponentProps & ComponentCustomProps
>
> &
ComponentOptionsWithoutProps<
Expand Down Expand Up @@ -131,7 +135,8 @@ export function defineComponent<
M,
Mixin,
Extends,
E
E,
AllowedComponentProps & ComponentCustomProps
>
> &
ComponentOptionsWithArrayProps<
Expand Down Expand Up @@ -182,7 +187,7 @@ export function defineComponent<
Mixin,
Extends,
E,
VNodeProps
VNodeProps & AllowedComponentProps & ComponentCustomProps
>
> &
ComponentOptionsWithObjectProps<
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/components/Suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ function patchSuspense(
}

export interface SuspenseBoundary {
vnode: VNode
vnode: VNode<RendererNode, RendererElement, SuspenseProps>
parent: SuspenseBoundary | null
parentComponent: ComponentInternalInstance | null
isSVG: boolean
Expand Down
17 changes: 8 additions & 9 deletions packages/runtime-core/src/components/Teleport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode'
import { isString, ShapeFlags } from '@vue/shared'
import { warn } from '../warning'

export type TeleportVNode = VNode<RendererNode, RendererElement, TeleportProps>

export interface TeleportProps {
to: string | RendererElement
disabled?: boolean
Expand Down Expand Up @@ -55,8 +57,8 @@ const resolveTarget = <T = RendererElement>(
export const TeleportImpl = {
__isTeleport: true,
process(
n1: VNode | null,
n2: VNode,
n1: TeleportVNode | null,
n2: TeleportVNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
Expand Down Expand Up @@ -85,10 +87,7 @@ export const TeleportImpl = {
insert(placeholder, container, anchor)
insert(mainAnchor, container, anchor)

const target = (n2.target = resolveTarget(
n2.props as TeleportProps,
querySelector
))
const target = (n2.target = resolveTarget(n2.props, querySelector))
const targetAnchor = (n2.targetAnchor = createText(''))
if (target) {
insert(targetAnchor, target)
Expand Down Expand Up @@ -165,7 +164,7 @@ export const TeleportImpl = {
// target changed
if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
const nextTarget = (n2.target = resolveTarget(
n2.props as TeleportProps,
n2.props,
querySelector
))
if (nextTarget) {
Expand Down Expand Up @@ -267,7 +266,7 @@ interface TeleportTargetElement extends Element {

function hydrateTeleport(
node: Node,
vnode: VNode,
vnode: TeleportVNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
optimized: boolean,
Expand All @@ -284,7 +283,7 @@ function hydrateTeleport(
) => Node | null
): Node | null {
const target = (vnode.target = resolveTarget<Element>(
vnode.props as TeleportProps,
vnode.props,
querySelector
))
if (target) {
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/h.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type RawProps = VNodeProps & {
__v_isVNode?: never
// used to differ from Array children
[Symbol.iterator]?: never
}
} & { [key: string]: any }

type RawChildren =
| string
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-core/src/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
SuspenseBoundary,
queueEffectWithSuspense
} from './components/Suspense'
import { TeleportImpl } from './components/Teleport'
import { TeleportImpl, TeleportVNode } from './components/Teleport'

export type RootHydrateFunction = (
vnode: VNode<Node, Element>,
Expand Down Expand Up @@ -202,7 +202,7 @@ export function createHydrationFunctions(
} else {
nextNode = (vnode.type as typeof TeleportImpl).hydrate(
node,
vnode,
vnode as TeleportVNode,
parentComponent,
parentSuspense,
optimized,
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export { h } from './h'
// Advanced render function utilities
export { createVNode, cloneVNode, mergeProps, isVNode } from './vnode'
// VNode types
export { Fragment, Text, Comment, Static } from './vnode'
export { Fragment, Text, Comment, Static, ComponentCustomProps } from './vnode'
// Built-in components
export { Teleport, TeleportProps } from './components/Teleport'
export { Suspense, SuspenseProps } from './components/Suspense'
Expand Down
6 changes: 3 additions & 3 deletions packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import {
queueEffectWithSuspense,
SuspenseImpl
} from './components/Suspense'
import { TeleportImpl } from './components/Teleport'
import { TeleportImpl, TeleportVNode } from './components/Teleport'
import { isKeepAlive, KeepAliveContext } from './components/KeepAlive'
import { registerHMR, unregisterHMR, isHmrUpdating } from './hmr'
import {
Expand Down Expand Up @@ -477,8 +477,8 @@ function baseCreateRenderer(
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
;(type as typeof TeleportImpl).process(
n1,
n2,
n1 as TeleportVNode,
n2 as TeleportVNode,
container,
anchor,
parentComponent,
Expand Down
20 changes: 15 additions & 5 deletions packages/runtime-core/src/vnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,14 @@ export type VNodeHook =
| VNodeMountHook[]
| VNodeUpdateHook[]

export interface VNodeProps {
[key: string]: any
export interface ComponentCustomProps {}
export interface AllowedComponentProps {
class?: unknown
style?: unknown
}

// https://github.com/microsoft/TypeScript/issues/33099
export type VNodeProps = {
key?: string | number
ref?: VNodeRef

Expand Down Expand Up @@ -104,7 +110,11 @@ export type VNodeNormalizedChildren =
| RawSlots
| null

export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
export interface VNode<
HostNode = RendererNode,
HostElement = RendererElement,
ExtraProps = { [key: string]: any }
> {
/**
* @internal
*/
Expand All @@ -114,7 +124,7 @@ export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
*/
__v_skip: true
type: VNodeTypes
props: VNodeProps | null
props: (VNodeProps & ExtraProps) | null
key: string | number | null
ref: VNodeNormalizedRef | null
scopeId: string | null // SFC only
Expand Down Expand Up @@ -597,7 +607,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
const incoming = toMerge[key]
if (existing !== incoming) {
ret[key] = existing
? [].concat(existing as any, toMerge[key])
? [].concat(existing as any, toMerge[key] as any)
: incoming
}
} else {
Expand Down
30 changes: 0 additions & 30 deletions test-dts/componentTypeExtensions.test-d.ts

This file was deleted.

57 changes: 57 additions & 0 deletions test-dts/componentTypeExtensions.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { defineComponent, expectError, expectType } from './index'

declare module '@vue/runtime-core' {
interface ComponentCustomOptions {
test?(n: number): void
}

interface ComponentCustomProperties {
state: 'stopped' | 'running'
}

interface ComponentCustomProps {
custom?: number
}
}

export const Custom = defineComponent({
props: {
bar: String,
baz: {
type: Number,
required: true
}
},

data: () => ({ counter: 0 }),

test(n) {
expectType<number>(n)
},

methods: {
aMethod() {
// @ts-expect-error
expectError(this.notExisting)
this.counter++
this.state = 'running'
// @ts-expect-error
expectError((this.state = 'not valid'))
}
}
})

expectType<JSX.Element>(<Custom baz={1} />)
expectType<JSX.Element>(<Custom custom={1} baz={1} />)
expectType<JSX.Element>(<Custom bar="bar" baz={1} />)

// @ts-expect-error
expectType<JSX.Element>(<Custom />)
// @ts-expect-error
expectError(<Custom bar="bar" />)
// @ts-expect-error
expectError(<Custom baz="baz" />)
// @ts-expect-error
expectError(<Custom baz={1} notExist={1} />)
// @ts-expect-error
expectError(<Custom baz={1} custom="custom" />)
3 changes: 2 additions & 1 deletion test-dts/defineComponent.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,9 @@ describe('with object props', () => {
eee={() => ({ a: 'eee' })}
fff={(a, b) => ({ a: a > +b })}
hhh={false}
// should allow extraneous as attrs
// should allow class/style as attrs
class="bar"
style={{ color: 'red' }}
// should allow key
key={'foo'}
// should allow ref
Expand Down

0 comments on commit 5d8a64d

Please sign in to comment.