function _decorator( methodFn, ...args ) {
  // check the first argument to see if it's a user-supplied keycode or array
  // of keycodes, or if it's the wrapped class or method
  const testArg = args[0];
  const isArray = Array.isArray( testArg );

  // if the test argument is not an object or function, it is user-supplied
  // keycodes. else there are no arguments and it's just the wrapped class
  if ( isArray || ~[ 'string', 'number', 'symbol' ].indexOf( typeof testArg ) ) {
    const keys = isArray ? testArg : args;

    // return the decorator function, which on the next call will look for
    // the presence of a method name to determine if this is a wrapped method
    // or component
    return ( target, methodName, descriptor ) => {
      return methodName ?
        methodFn( { target, descriptor, keys } ) :
        classWrapper( target, keys );
    };
  } else {
    const WrappedComponent = args[0];
    const methodName = args[1];

    // method decorators without keycode (which) arguments are not allowed.
    if ( WrappedComponent && !methodName ) {
      return classWrapper( ...args );
    } else {
      console.warn( `${methodName}: Method decorators must have keycode arguments, so the decorator for this method will not do anything` );
      return noopDecorator
    }
  }
}
_decorator

@access private
@param {Function} methodFn The method wrapper to delegate to, based on whether user has specified a scoped decorator or not
@param {Array} ...args Remainder of arguments passed in
@return {Function} The decorated class or method