Browse Source

improve warnings

Evan You 10 năm trước cách đây
mục cha
commit
38ee9670d4

+ 3 - 2
src/batcher.js

@@ -66,11 +66,12 @@ function runBatcherQueue (queue) {
     if (process.env.NODE_ENV !== 'production' && has[id] != null) {
       circular[id] = (circular[id] || 0) + 1
       if (circular[id] > config._maxUpdateCount) {
-        queue.splice(has[id], 1)
         warn(
           'You may have an infinite update loop for watcher ' +
-          'with expression: ' + watcher.expression
+          'with expression "' + watcher.expression + '"',
+          watcher.vm
         )
+        break
       }
     }
   }

+ 25 - 18
src/compiler/compile-props.js

@@ -31,10 +31,11 @@ const settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/
  *
  * @param {Element|DocumentFragment} el
  * @param {Array} propOptions
+ * @param {Vue} vm
  * @return {Function} propsLinkFn
  */
 
-export function compileProps (el, propOptions) {
+export function compileProps (el, propOptions, vm) {
   var props = []
   var names = Object.keys(propOptions)
   var i = names.length
@@ -44,7 +45,7 @@ export function compileProps (el, propOptions) {
     options = propOptions[name] || empty
 
     if (process.env.NODE_ENV !== 'production' && name === '$data') {
-      warn('Do not use $data as prop.')
+      warn('Do not use $data as prop.', vm)
       continue
     }
 
@@ -55,7 +56,8 @@ export function compileProps (el, propOptions) {
     if (!identRE.test(path)) {
       process.env.NODE_ENV !== 'production' && warn(
         'Invalid prop key: "' + name + '". Prop keys ' +
-        'must be valid identifiers.'
+        'must be valid identifiers.',
+        vm
       )
       continue
     }
@@ -98,7 +100,8 @@ export function compileProps (el, propOptions) {
           prop.mode = propBindingModes.ONE_WAY
           warn(
             'Cannot bind two-way prop with non-settable ' +
-            'parent path: ' + value
+            'parent path: ' + value,
+            vm
           )
         }
       }
@@ -111,7 +114,8 @@ export function compileProps (el, propOptions) {
         prop.mode !== propBindingModes.TWO_WAY
       ) {
         warn(
-          'Prop "' + name + '" expects a two-way binding type.'
+          'Prop "' + name + '" expects a two-way binding type.',
+          vm
         )
       }
     } else if ((value = getAttr(el, attr)) !== null) {
@@ -133,11 +137,12 @@ export function compileProps (el, propOptions) {
         warn(
           'Possible usage error for prop `' + lowerCaseName + '` - ' +
           'did you mean `' + attr + '`? HTML is case-insensitive, remember to use ' +
-          'kebab-case for props in templates.'
+          'kebab-case for props in templates.',
+          vm
         )
       } else if (options.required) {
         // warn missing required
-        warn('Missing required prop: ' + name)
+        warn('Missing required prop: ' + name, vm)
       }
     }
     // push prop
@@ -222,7 +227,7 @@ export function initProp (vm, prop, value) {
   const key = prop.path
   value = coerceProp(prop, value)
   if (value === undefined) {
-    value = getPropDefaultValue(vm, prop.options)
+    value = getPropDefaultValue(vm, prop)
   }
   if (!assertProp(prop, value, vm)) {
     value = undefined
@@ -234,12 +239,13 @@ export function initProp (vm, prop, value) {
  * Get the default value of a prop.
  *
  * @param {Vue} vm
- * @param {Object} options
+ * @param {Object} prop
  * @return {*}
  */
 
-function getPropDefaultValue (vm, options) {
+function getPropDefaultValue (vm, prop) {
   // no default, return undefined
+  const options = prop.options
   if (!hasOwn(options, 'default')) {
     // absent boolean value defaults to false
     return options.type === Boolean
@@ -250,9 +256,10 @@ function getPropDefaultValue (vm, options) {
   // warn against non-factory defaults for Object & Array
   if (isObject(def)) {
     process.env.NODE_ENV !== 'production' && warn(
-      'Object/Array as default prop values will be shared ' +
-      'across multiple instances. Use a factory function ' +
-      'to return the default value instead.'
+      'Invalid default value for prop "' + prop.name + '": ' +
+      'Props with type Object/Array must use a factory function ' +
+      'to return the default value.',
+      vm
     )
   }
   // call factory function for non-Function types
@@ -308,10 +315,10 @@ export function assertProp (prop, value, vm) {
   if (!valid) {
     if (process.env.NODE_ENV !== 'production') {
       warn(
-        'Invalid prop: type check failed for prop "' + prop.name + '"' +
-        (vm.$options.name ? ' on component <' + hyphenate(vm.$options.name) + '>.' : '.') +
+        'Invalid prop: type check failed for prop "' + prop.name + '".' +
         ' Expected ' + formatType(expectedType) +
-        ', got ' + formatValue(value) + '.'
+        ', got ' + formatValue(value) + '.',
+        vm
       )
     }
     return false
@@ -320,8 +327,8 @@ export function assertProp (prop, value, vm) {
   if (validator) {
     if (!validator(value)) {
       process.env.NODE_ENV !== 'production' && warn(
-        'Invalid prop: custom validator check failed for prop "' + prop.name + '"' +
-        (vm.$options.name ? ' on component <' + hyphenate(vm.$options.name) + '>.' : '.')
+        'Invalid prop: custom validator check failed for prop "' + prop.name + '".',
+        vm
       )
       return false
     }

+ 4 - 9
src/compiler/compile.js

@@ -14,7 +14,6 @@ import {
   checkComponentAttr,
   findRef,
   defineReactive,
-  assertAsset,
   getAttr
 } from '../util/index'
 
@@ -181,7 +180,7 @@ function teardownDirs (vm, dirs, destroying) {
  */
 
 export function compileAndLinkProps (vm, el, props, scope) {
-  var propsLinkFn = compileProps(el, props)
+  var propsLinkFn = compileProps(el, props, vm)
   var propDirs = linkAndCapture(function () {
     propsLinkFn(vm, scope)
   }, vm)
@@ -691,7 +690,8 @@ function compileDirectives (attrs, options) {
         })) {
           warn(
             'class="' + rawValue + '": Do not mix mustache interpolation ' +
-            'and v-bind for "class" on the same element. Use one or the other.'
+            'and v-bind for "class" on the same element. Use one or the other.',
+            options
           )
         }
       }
@@ -730,12 +730,7 @@ function compileDirectives (attrs, options) {
         continue
       }
 
-      dirDef = resolveAsset(options, 'directives', dirName)
-
-      if (process.env.NODE_ENV !== 'production') {
-        assertAsset(dirDef, 'directive', dirName)
-      }
-
+      dirDef = resolveAsset(options, 'directives', dirName, true)
       if (dirDef) {
         pushDir(dirName, dirDef)
       }

+ 1 - 1
src/compiler/resolve-slots.js

@@ -31,7 +31,7 @@ export function resolveSlots (vm, content) {
     }
     /* eslint-enable no-cond-assign */
     if (process.env.NODE_ENV !== 'production' && getBindAttr(el, 'slot')) {
-      warn('The "slot" attribute must be static.')
+      warn('The "slot" attribute must be static.', vm.$parent)
     }
   }
   for (name in contents) {

+ 2 - 6
src/directives/element/partial.js

@@ -4,8 +4,7 @@ import { PARTIAL } from '../priorities'
 import {
   createAnchor,
   replace,
-  resolveAsset,
-  assertAsset
+  resolveAsset
 } from '../../util/index'
 
 export default {
@@ -31,10 +30,7 @@ export default {
   },
 
   insert (id) {
-    var partial = resolveAsset(this.vm.$options, 'partials', id)
-    if (process.env.NODE_ENV !== 'production') {
-      assertAsset(partial, 'partial', id)
-    }
+    var partial = resolveAsset(this.vm.$options, 'partials', id, true)
     if (partial) {
       this.factory = new FragmentFactory(this.vm, partial)
       vIf.insert.call(this)

+ 2 - 1
src/directives/internal/component.js

@@ -239,7 +239,8 @@ export default {
           child._isFragment) {
         warn(
           'Transitions will not work on a fragment instance. ' +
-          'Template: ' + child.$options.template
+          'Template: ' + child.$options.template,
+          child
         )
       }
       return child

+ 6 - 3
src/directives/public/bind.js

@@ -52,7 +52,8 @@ export default {
         process.env.NODE_ENV !== 'production' && warn(
           attr + '="' + descriptor.raw + '": ' +
           'attribute interpolation is not allowed in Vue.js ' +
-          'directives and special attributes.'
+          'directives and special attributes.',
+          this.vm
         )
         this.el.removeAttribute(attr)
         this.invalid = true
@@ -65,7 +66,8 @@ export default {
         if (attr === 'src') {
           warn(
             raw + 'interpolation in "src" attribute will cause ' +
-            'a 404 request. Use v-bind:src instead.'
+            'a 404 request. Use v-bind:src instead.',
+            this.vm
           )
         }
 
@@ -74,7 +76,8 @@ export default {
           warn(
             raw + 'interpolation in "style" attribute will cause ' +
             'the attribute to be discarded in Internet Explorer. ' +
-            'Use v-bind:style instead.'
+            'Use v-bind:style instead.',
+            this.vm
           )
         }
       }

+ 5 - 2
src/directives/public/for.js

@@ -48,7 +48,9 @@ const vFor = {
 
     if (!this.alias) {
       process.env.NODE_ENV !== 'production' && warn(
-        'Alias is required in v-for.'
+        'Invalid v-for expression "' + this.descriptor.raw + '": ' +
+        'alias is required.',
+        this.vm
       )
       return
     }
@@ -637,7 +639,8 @@ if (process.env.NODE_ENV !== 'production') {
     warn(
       'Duplicate value found in v-for="' + this.descriptor.raw + '": ' +
       JSON.stringify(value) + '. Use track-by="$index" if ' +
-      'you are expecting duplicate values.'
+      'you are expecting duplicate values.',
+      this.vm
     )
   }
 }

+ 2 - 1
src/directives/public/if.js

@@ -28,7 +28,8 @@ export default {
     } else {
       process.env.NODE_ENV !== 'production' && warn(
         'v-if="' + this.expression + '" cannot be ' +
-        'used on an instance root element.'
+        'used on an instance root element.',
+        this.vm
       )
       this.invalid = true
     }

+ 5 - 3
src/directives/public/model/index.js

@@ -36,8 +36,9 @@ export default {
     if (this.hasRead && !this.hasWrite) {
       process.env.NODE_ENV !== 'production' && warn(
         'It seems you are using a read-only filter with ' +
-        'v-model. You might want to use a two-way filter ' +
-        'to ensure correct behavior.'
+        'v-model="' + this.descriptor.raw + '". ' +
+        'You might want to use a two-way filter to ensure correct behavior.',
+        this.vm
       )
     }
     var el = this.el
@@ -51,7 +52,8 @@ export default {
       handler = handlers.text
     } else {
       process.env.NODE_ENV !== 'production' && warn(
-        'v-model does not support element type: ' + tag
+        'v-model does not support element type: ' + tag,
+        this.vm
       )
       return
     }

+ 2 - 1
src/directives/public/on.js

@@ -94,7 +94,8 @@ export default {
       process.env.NODE_ENV !== 'production' && warn(
         'v-on:' + this.arg + '="' +
         this.expression + '" expects a function value, ' +
-        'got ' + handler
+        'got ' + handler,
+        this.vm
       )
       return
     }

+ 2 - 1
src/directives/public/ref.js

@@ -4,7 +4,8 @@ export default {
   bind () {
     process.env.NODE_ENV !== 'production' && warn(
       'v-ref:' + this.arg + ' must be used on a child ' +
-      'component. Found on <' + this.el.tagName.toLowerCase() + '>.'
+      'component. Found on <' + this.el.tagName.toLowerCase() + '>.',
+      this.vm
     )
   }
 }

+ 2 - 1
src/instance/api/lifecycle.js

@@ -15,7 +15,8 @@ export default function (Vue) {
   Vue.prototype.$mount = function (el) {
     if (this._isCompiled) {
       process.env.NODE_ENV !== 'production' && warn(
-        '$mount() should be called only once.'
+        '$mount() should be called only once.',
+        this
       )
       return
     }

+ 5 - 6
src/instance/internal/events.js

@@ -42,11 +42,9 @@ export default function (Vue) {
           vm.$on(name.replace(eventRE), handler)
         } else if (process.env.NODE_ENV !== 'production') {
           warn(
-            'v-on:' + name + '="' + attrs[i].value + '"' + (
-              vm.$options.name
-                ? ' on component <' + vm.$options.name + '>'
-                : ''
-            ) + ' expects a function value, got ' + handler
+            'v-on:' + name + '="' + attrs[i].value + '" ' +
+            'expects a function value, got ' + handler,
+            vm
           )
         }
       }
@@ -99,7 +97,8 @@ export default function (Vue) {
         process.env.NODE_ENV !== 'production' && warn(
           'Unknown method: "' + handler + '" when ' +
           'registering callback for ' + action +
-          ': "' + key + '".'
+          ': "' + key + '".',
+          vm
         )
       }
     } else if (handler && type === 'object') {

+ 2 - 9
src/instance/internal/misc.js

@@ -1,6 +1,5 @@
 import {
   resolveAsset,
-  assertAsset,
   isPlainObject,
   warn
 } from '../../util/index'
@@ -23,10 +22,7 @@ export default function (Vue) {
     var filter, fn, args, arg, offset, i, l, j, k
     for (i = 0, l = filters.length; i < l; i++) {
       filter = filters[write ? l - i - 1 : i]
-      fn = resolveAsset(this.$options, 'filters', filter.name)
-      if (process.env.NODE_ENV !== 'production') {
-        assertAsset(fn, 'filter', filter.name)
-      }
+      fn = resolveAsset(this.$options, 'filters', filter.name, true)
       if (!fn) continue
       fn = write ? fn.write : (fn.read || fn)
       if (typeof fn !== 'function') continue
@@ -61,10 +57,7 @@ export default function (Vue) {
     if (typeof value === 'function') {
       factory = value
     } else {
-      factory = resolveAsset(this.$options, 'components', value)
-      if (process.env.NODE_ENV !== 'production') {
-        assertAsset(factory, 'component', value)
-      }
+      factory = resolveAsset(this.$options, 'components', value, true)
     }
     if (!factory) {
       return

+ 6 - 3
src/instance/internal/state.js

@@ -60,7 +60,8 @@ export default function (Vue) {
     if (props && !el) {
       process.env.NODE_ENV !== 'production' && warn(
         'Props will not be compiled if no `el` option is ' +
-        'provided at instantiation.'
+        'provided at instantiation.',
+        this
       )
     }
     // make sure to convert string selectors into element now
@@ -81,7 +82,8 @@ export default function (Vue) {
     if (!isPlainObject(data)) {
       data = {}
       process.env.NODE_ENV !== 'production' && warn(
-        'data functions should return an object.'
+        'data functions should return an object.',
+        this
       )
     }
     var props = this._props
@@ -108,7 +110,8 @@ export default function (Vue) {
       } else if (process.env.NODE_ENV !== 'production') {
         warn(
           'Data field "' + key + '" is already defined ' +
-          'as a prop. Use prop default value instead.'
+          'as a prop. Use prop default value instead.',
+          this
         )
       }
     }

+ 5 - 4
src/parsers/path.js

@@ -287,12 +287,13 @@ export function getPath (obj, path) {
 
 var warnNonExistent
 if (process.env.NODE_ENV !== 'production') {
-  warnNonExistent = function (path) {
+  warnNonExistent = function (path, vm) {
     warn(
       'You are setting a non-existent path "' + path.raw + '" ' +
       'on a vm instance. Consider pre-initializing the property ' +
       'with the "data" option for more reliable reactivity ' +
-      'and better performance.'
+      'and better performance.',
+      vm
     )
   }
 }
@@ -325,7 +326,7 @@ export function setPath (obj, path, val) {
       if (!isObject(obj)) {
         obj = {}
         if (process.env.NODE_ENV !== 'production' && last._isVue) {
-          warnNonExistent(path)
+          warnNonExistent(path, last)
         }
         set(last, key, obj)
       }
@@ -336,7 +337,7 @@ export function setPath (obj, path, val) {
         obj[key] = val
       } else {
         if (process.env.NODE_ENV !== 'production' && obj._isVue) {
-          warnNonExistent(path)
+          warnNonExistent(path, obj)
         }
         set(obj, key, val)
       }

+ 2 - 1
src/transition/transition.js

@@ -78,7 +78,8 @@ export default function Transition (el, id, hooks, vm) {
     ) {
       warn(
         'invalid CSS transition type for transition="' +
-        this.id + '": ' + this.type
+        this.id + '": ' + this.type,
+        vm
       )
     }
   }

+ 13 - 11
src/util/debug.js

@@ -1,22 +1,24 @@
 import config from '../config'
+import { hyphenate } from './lang'
 
 let warn
+let formatComponentName
 
 if (process.env.NODE_ENV !== 'production') {
   const hasConsole = typeof console !== 'undefined'
-  warn = function (msg, e) {
-    if (hasConsole && (!config.silent || config.debug)) {
-      console.warn('[Vue warn]: ' + msg)
-      /* istanbul ignore if */
-      if (config.debug) {
-        if (e) {
-          throw e
-        } else {
-          console.warn((new Error('Warning Stack Trace')).stack)
-        }
-      }
+
+  warn = (msg, vm) => {
+    if (hasConsole && (!config.silent)) {
+      console.error('[Vue warn]: ' + msg + (vm ? formatComponentName(vm) : ''))
     }
   }
+
+  formatComponentName = vm => {
+    var name = vm._isVue ? vm.$options.name : vm.name
+    return name
+      ? ' (found in component: <' + hyphenate(name) + '>)'
+      : ''
+  }
 }
 
 export { warn }

+ 12 - 26
src/util/options.js

@@ -59,7 +59,8 @@ strats.data = function (parentVal, childVal, vm) {
       process.env.NODE_ENV !== 'production' && warn(
         'The "data" option should be a function ' +
         'that returns a per-instance value in component ' +
-        'definitions.'
+        'definitions.',
+        vm
       )
       return parentVal
     }
@@ -104,7 +105,8 @@ strats.el = function (parentVal, childVal, vm) {
     process.env.NODE_ENV !== 'production' && warn(
       'The "el" option should be a function ' +
       'that returns a per-instance value in component ' +
-      'definitions.'
+      'definitions.',
+      vm
     )
     return
   }
@@ -138,18 +140,6 @@ strats.activate = function (parentVal, childVal) {
     : parentVal
 }
 
-/**
- * 0.11 deprecation warning
- */
-
-strats.paramAttributes = function () {
-  /* istanbul ignore next */
-  process.env.NODE_ENV !== 'production' && warn(
-    '"paramAttributes" option has been deprecated in 0.12. ' +
-    'Use "props" instead.'
-  )
-}
-
 /**
  * Assets
  *
@@ -365,31 +355,27 @@ export function mergeOptions (parent, child, vm) {
  * @param {Object} options
  * @param {String} type
  * @param {String} id
+ * @param {Boolean} warnMissing
  * @return {Object|Function}
  */
 
-export function resolveAsset (options, type, id) {
+export function resolveAsset (options, type, id, warnMissing) {
   /* istanbul ignore if */
   if (typeof id !== 'string') {
     return
   }
   var assets = options[type]
   var camelizedId
-  return assets[id] ||
+  var res = assets[id] ||
     // camelCase ID
     assets[camelizedId = camelize(id)] ||
     // Pascal Case ID
     assets[camelizedId.charAt(0).toUpperCase() + camelizedId.slice(1)]
-}
-
-/**
- * Assert asset exists
- */
-
-export function assertAsset (val, type, id) {
-  if (!val) {
-    process.env.NODE_ENV !== 'production' && warn(
-      'Failed to resolve ' + type + ': ' + id
+  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
+    warn(
+      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
+      options
     )
   }
+  return res
 }

+ 8 - 9
src/watcher.js

@@ -83,12 +83,9 @@ Watcher.prototype.get = function () {
       config.warnExpressionErrors
     ) {
       warn(
-        'Error when evaluating expression "' +
-        this.expression + '". ' +
-        (config.debug
-          ? ''
-          : 'Turn on debug mode to see stack trace.'
-        ), e
+        'Error when evaluating expression ' +
+        '"' + this.expression + '": ' + e.toString(),
+        this.vm
       )
     }
   }
@@ -130,8 +127,9 @@ Watcher.prototype.set = function (value) {
       config.warnExpressionErrors
     ) {
       warn(
-        'Error when evaluating setter "' +
-        this.expression + '"', e
+        'Error when evaluating setter ' +
+        '"' + this.expression + '": ' + e.toString(),
+        this.vm
       )
     }
   }
@@ -144,7 +142,8 @@ Watcher.prototype.set = function (value) {
         'a v-for alias (' + this.expression + '), and the ' +
         'v-for has filters. This will not work properly. ' +
         'Either remove the filters or use an array of ' +
-        'objects and bind to object properties instead.'
+        'objects and bind to object properties instead.',
+        this.vm
       )
       return
     }

+ 1 - 1
test/unit/specs/directives/internal/prop_spec.js

@@ -233,7 +233,7 @@ describe('prop', function () {
         }
       }
     })
-    expect('Use a factory function to return the default value').toHaveBeenWarned()
+    expect('use a factory function to return the default value').toHaveBeenWarned()
     expect(getWarnCount()).toBe(2)
   })
 

+ 1 - 1
test/unit/specs/directives/public/for/for_spec.js

@@ -572,7 +572,7 @@ describe('v-for', function () {
       el: el,
       template: '<div v-for="items"></div>'
     })
-    expect('Alias is required in v-for').toHaveBeenWarned()
+    expect('alias is required').toHaveBeenWarned()
   })
 
   it('warn duplicate objects', function () {

+ 0 - 3
test/unit/specs/index.js

@@ -21,9 +21,6 @@ scope.getWarnCount = function () {
 }
 
 function hasWarned (msg) {
-  if (!_.warn.calls) {
-    console.warn('make sure to call before tests.')
-  }
   var count = _.warn.calls.count()
   var args
   while (count--) {

+ 1 - 1
test/unit/specs/instance/events_spec.js

@@ -238,7 +238,7 @@ describe('Instance Events', function () {
           comp: {}
         }
       })
-      expect('v-on:test="onThat" on component <comp> expects a function value').toHaveBeenWarned()
+      expect('v-on:test="onThat" expects a function value').toHaveBeenWarned()
     })
 
     it('passing $arguments', function () {

+ 3 - 6
test/unit/specs/util/debug_spec.js

@@ -5,24 +5,21 @@ var warnPrefix = '[Vue warn]: '
 if (typeof console !== 'undefined') {
   describe('Util - Debug', function () {
     beforeEach(function () {
-      spyOn(console, 'warn')
-      if (console.trace) {
-        spyOn(console, 'trace')
-      }
+      spyOn(console, 'error')
     })
 
     it('warn when silent is false', function () {
       config.silent = false
       _.warn.and.callThrough()
       _.warn('oops')
-      expect(console.warn).toHaveBeenCalledWith(warnPrefix + 'oops')
+      expect(console.error).toHaveBeenCalledWith(warnPrefix + 'oops')
     })
 
     it('not warn when silent is ture', function () {
       config.silent = true
       _.warn.and.callThrough()
       _.warn('oops')
-      expect(console.warn).not.toHaveBeenCalled()
+      expect(console.error).not.toHaveBeenCalled()
     })
   })
 }