code: 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
      }
    }
  }
docs: _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