feat(apiOptions): add warning for duplicated properties declared by options (#329)
This commit is contained in:
parent
a25b1371ab
commit
a23e03f01e
@ -575,5 +575,263 @@ describe('api: options', () => {
|
|||||||
'Computed property "foo" was assigned to but it has no setter.'
|
'Computed property "foo" was assigned to but it has no setter.'
|
||||||
).toHaveBeenWarned()
|
).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('data property is already declared in props', () => {
|
||||||
|
const Comp = {
|
||||||
|
props: { foo: Number },
|
||||||
|
data: {
|
||||||
|
foo: 1
|
||||||
|
},
|
||||||
|
render() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Data property "foo" is already defined in Props.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('computed property is already declared in data', () => {
|
||||||
|
const Comp = {
|
||||||
|
data: {
|
||||||
|
foo: 1
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
foo() {}
|
||||||
|
},
|
||||||
|
render() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Computed property "foo" is already defined in Data.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('computed property is already declared in props', () => {
|
||||||
|
const Comp = {
|
||||||
|
props: { foo: Number },
|
||||||
|
computed: {
|
||||||
|
foo() {}
|
||||||
|
},
|
||||||
|
render() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Computed property "foo" is already defined in Props.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('methods property is not a function', () => {
|
||||||
|
const Comp = {
|
||||||
|
methods: {
|
||||||
|
foo: 1
|
||||||
|
},
|
||||||
|
render() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Method "foo" has type "number" in the component definition. ` +
|
||||||
|
`Did you reference the function correctly?`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('methods property is already declared in data', () => {
|
||||||
|
const Comp = {
|
||||||
|
data: {
|
||||||
|
foo: 2
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
foo() {}
|
||||||
|
},
|
||||||
|
render() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Methods property "foo" is already defined in Data.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('methods property is already declared in props', () => {
|
||||||
|
const Comp = {
|
||||||
|
props: {
|
||||||
|
foo: Number
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
foo() {}
|
||||||
|
},
|
||||||
|
render() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Methods property "foo" is already defined in Props.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('methods property is already declared in computed', () => {
|
||||||
|
const Comp = {
|
||||||
|
computed: {
|
||||||
|
foo: {
|
||||||
|
get() {},
|
||||||
|
set() {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
foo() {}
|
||||||
|
},
|
||||||
|
render() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Methods property "foo" is already defined in Computed.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('inject property is already declared in data', () => {
|
||||||
|
const Comp = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
a: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
a: this.a
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return [h(ChildA)]
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
const ChildA = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
a: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: ['a'],
|
||||||
|
render() {
|
||||||
|
return this.a
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Inject property "a" is already defined in Data.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('inject property is already declared in props', () => {
|
||||||
|
const Comp = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
a: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
a: this.a
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return [h(ChildA)]
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
const ChildA = {
|
||||||
|
props: { a: Number },
|
||||||
|
inject: ['a'],
|
||||||
|
render() {
|
||||||
|
return this.a
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Inject property "a" is already defined in Props.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('inject property is already declared in computed', () => {
|
||||||
|
const Comp = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
a: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
a: this.a
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return [h(ChildA)]
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
const ChildA = {
|
||||||
|
computed: {
|
||||||
|
a: {
|
||||||
|
get() {},
|
||||||
|
set() {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: ['a'],
|
||||||
|
render() {
|
||||||
|
return this.a
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Inject property "a" is already defined in Computed.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('inject property is already declared in methods', () => {
|
||||||
|
const Comp = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
a: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
a: this.a
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return [h(ChildA)]
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
const ChildA = {
|
||||||
|
methods: {
|
||||||
|
a: () => null
|
||||||
|
},
|
||||||
|
inject: ['a'],
|
||||||
|
render() {
|
||||||
|
return this.a
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
expect(
|
||||||
|
`Inject property "a" is already defined in Methods.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -179,6 +179,25 @@ export interface LegacyOptions<
|
|||||||
errorCaptured?: ErrorCapturedHook
|
errorCaptured?: ErrorCapturedHook
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enum OptionTypes {
|
||||||
|
PROPS = 'Props',
|
||||||
|
DATA = 'Data',
|
||||||
|
COMPUTED = 'Computed',
|
||||||
|
METHODS = 'Methods',
|
||||||
|
INJECT = 'Inject'
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDuplicateChecker() {
|
||||||
|
const cache = Object.create(null)
|
||||||
|
return (type: OptionTypes, key: string) => {
|
||||||
|
if (cache[key]) {
|
||||||
|
warn(`${type} property "${key}" is already defined in ${cache[key]}.`)
|
||||||
|
} else {
|
||||||
|
cache[key] = type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function applyOptions(
|
export function applyOptions(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
options: ComponentOptions,
|
options: ComponentOptions,
|
||||||
@ -194,6 +213,7 @@ export function applyOptions(
|
|||||||
mixins,
|
mixins,
|
||||||
extends: extendsOptions,
|
extends: extendsOptions,
|
||||||
// state
|
// state
|
||||||
|
props: propsOptions,
|
||||||
data: dataOptions,
|
data: dataOptions,
|
||||||
computed: computedOptions,
|
computed: computedOptions,
|
||||||
methods,
|
methods,
|
||||||
@ -218,6 +238,8 @@ export function applyOptions(
|
|||||||
} = options
|
} = options
|
||||||
|
|
||||||
const globalMixins = instance.appContext.mixins
|
const globalMixins = instance.appContext.mixins
|
||||||
|
// call it only during dev
|
||||||
|
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
|
||||||
// applyOptions is called non-as-mixin once per instance
|
// applyOptions is called non-as-mixin once per instance
|
||||||
if (!asMixin) {
|
if (!asMixin) {
|
||||||
callSyncHook('beforeCreate', options, ctx, globalMixins)
|
callSyncHook('beforeCreate', options, ctx, globalMixins)
|
||||||
@ -233,12 +255,23 @@ export function applyOptions(
|
|||||||
applyMixins(instance, mixins)
|
applyMixins(instance, mixins)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__DEV__ && propsOptions) {
|
||||||
|
for (const key in propsOptions) {
|
||||||
|
checkDuplicateProperties!(OptionTypes.PROPS, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// state options
|
// state options
|
||||||
if (dataOptions) {
|
if (dataOptions) {
|
||||||
const data = isFunction(dataOptions) ? dataOptions.call(ctx) : dataOptions
|
const data = isFunction(dataOptions) ? dataOptions.call(ctx) : dataOptions
|
||||||
if (!isObject(data)) {
|
if (!isObject(data)) {
|
||||||
__DEV__ && warn(`data() should return an object.`)
|
__DEV__ && warn(`data() should return an object.`)
|
||||||
} else if (instance.data === EMPTY_OBJ) {
|
} else if (instance.data === EMPTY_OBJ) {
|
||||||
|
if (__DEV__) {
|
||||||
|
for (const key in data) {
|
||||||
|
checkDuplicateProperties!(OptionTypes.DATA, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
instance.data = reactive(data)
|
instance.data = reactive(data)
|
||||||
} else {
|
} else {
|
||||||
// existing data: this is a mixin or extends.
|
// existing data: this is a mixin or extends.
|
||||||
@ -249,6 +282,8 @@ export function applyOptions(
|
|||||||
for (const key in computedOptions) {
|
for (const key in computedOptions) {
|
||||||
const opt = (computedOptions as ComputedOptions)[key]
|
const opt = (computedOptions as ComputedOptions)[key]
|
||||||
|
|
||||||
|
__DEV__ && checkDuplicateProperties!(OptionTypes.COMPUTED, key)
|
||||||
|
|
||||||
if (isFunction(opt)) {
|
if (isFunction(opt)) {
|
||||||
renderContext[key] = computed(opt.bind(ctx))
|
renderContext[key] = computed(opt.bind(ctx))
|
||||||
} else {
|
} else {
|
||||||
@ -272,9 +307,19 @@ export function applyOptions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (methods) {
|
if (methods) {
|
||||||
for (const key in methods) {
|
for (const key in methods) {
|
||||||
renderContext[key] = (methods as MethodOptions)[key].bind(ctx)
|
const methodHandler = (methods as MethodOptions)[key]
|
||||||
|
if (isFunction(methodHandler)) {
|
||||||
|
__DEV__ && checkDuplicateProperties!(OptionTypes.METHODS, key)
|
||||||
|
renderContext[key] = methodHandler.bind(ctx)
|
||||||
|
} else if (__DEV__) {
|
||||||
|
warn(
|
||||||
|
`Method "${key}" has type "${typeof methodHandler}" in the component definition. ` +
|
||||||
|
`Did you reference the function correctly?`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (watchOptions) {
|
if (watchOptions) {
|
||||||
@ -310,10 +355,12 @@ export function applyOptions(
|
|||||||
if (isArray(injectOptions)) {
|
if (isArray(injectOptions)) {
|
||||||
for (let i = 0; i < injectOptions.length; i++) {
|
for (let i = 0; i < injectOptions.length; i++) {
|
||||||
const key = injectOptions[i]
|
const key = injectOptions[i]
|
||||||
|
__DEV__ && checkDuplicateProperties!(OptionTypes.INJECT, key)
|
||||||
renderContext[key] = inject(key)
|
renderContext[key] = inject(key)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const key in injectOptions) {
|
for (const key in injectOptions) {
|
||||||
|
__DEV__ && checkDuplicateProperties!(OptionTypes.INJECT, key)
|
||||||
const opt = injectOptions[key]
|
const opt = injectOptions[key]
|
||||||
if (isObject(opt)) {
|
if (isObject(opt)) {
|
||||||
renderContext[key] = inject(opt.from, opt.default)
|
renderContext[key] = inject(opt.from, opt.default)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user