Repository: vuejs/vue
Pull request: Warn when component option should be an object, but is not (#5605)

https://github.com/vuejs/vue/issues/5605

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [ ] Bugfix
- [x] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [x] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [x] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [x] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [x] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included

If adding a **new feature**, the PR's description includes:
- [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

**Other information:**


Changed files:
filename: src/core/instance/state.js
patch: @@ -60,6 +60,16 @@ const isReservedProp = {
   slot: 1
 }
 
+function checkOptionType (vm: Component, name: string) {
+  const option = vm.$options[name]
+  if (!isPlainObject(option)) {
+    warn(
+      `component option "${name}" should be an object.`,
+      vm
+    )
+  }
+}
+
 function initProps (vm: Component, propsOptions: Object) {
   const propsData = vm.$options.propsData || {}
   const props = vm._props = {}
@@ -148,6 +158,7 @@ function getData (data: Function, vm: Component): any {
 const computedWatcherOptions = { lazy: true }
 
 function initComputed (vm: Component, computed: Object) {
+  process.env.NODE_ENV !== 'production' && checkOptionType(vm, 'computed')
   const watchers = vm._computedWatchers = Object.create(null)
 
   for (const key in computed) {
@@ -213,6 +224,7 @@ function createComputedGetter (key) {
 }
 
 function initMethods (vm: Component, methods: Object) {
+  process.env.NODE_ENV !== 'production' && checkOptionType(vm, 'methods')
   const props = vm.$options.props
   for (const key in methods) {
     vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
@@ -235,6 +247,7 @@ function initMethods (vm: Component, methods: Object) {
 }
 
 function initWatch (vm: Component, watch: Object) {
+  process.env.NODE_ENV !== 'production' && checkOptionType(vm, 'watch')
   for (const key in watch) {
     const handler = watch[key]
     if (Array.isArray(handler)) {

filename: test/helpers/test-object-option.js
patch: @@ -0,0 +1,17 @@
+import Vue from 'vue'
+
+export default function testObjectOption (name: string) {
+  it('should warn non object', () => {
+    const options = {}
+    options[name] = () => {}
+    new Vue(options)
+    expect(`component option "${name}" should be an object`).toHaveBeenWarned()
+  })
+
+  it('don\'t warn when is an object', () => {
+    const options = {}
+    options[name] = {}
+    new Vue(options)
+    expect(`component option "${name}" should be an object`).not.toHaveBeenWarned()
+  })
+}

filename: test/unit/features/options/computed.spec.js
patch: @@ -1,4 +1,5 @@
 import Vue from 'vue'
+import testObjectOption from '../../../helpers/test-object-option'
 
 describe('Options computed', () => {
   it('basic usage', done => {
@@ -48,6 +49,8 @@ describe('Options computed', () => {
     }).then(done)
   })
 
+  testObjectOption('computed')
+
   it('warn with setter and no getter', () => {
     const vm = new Vue({
       template: `

filename: test/unit/features/options/methods.spec.js
patch: @@ -1,4 +1,5 @@
 import Vue from 'vue'
+import testObjectOption from '../../../helpers/test-object-option'
 
 describe('Options methods', () => {
   it('should have correct context', () => {
@@ -16,6 +17,8 @@ describe('Options methods', () => {
     expect(vm.a).toBe(2)
   })
 
+  testObjectOption('methods')
+
   it('should warn undefined methods', () => {
     new Vue({
       methods: {

filename: test/unit/features/options/watch.spec.js
patch: @@ -1,4 +1,5 @@
 import Vue from 'vue'
+import testObjectOption from '../../../helpers/test-object-option'
 
 describe('Options watch', () => {
   let spy
@@ -23,6 +24,8 @@ describe('Options watch', () => {
     }).then(done)
   })
 
+  testObjectOption('watch')
+
   it('string method name', done => {
     const vm = new Vue({
       data: {