ArnaudBuchholz/gpf-js

View on GitHub
src/define/class/super.js

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * @file Class method superify
 * @since 0.1.6
 */
/*#ifndef(UMD)*/
"use strict";
/*global _GpfClassDefinition*/ // Class definition
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfFunctionBuild*/ // Build function from description and context
/*global _gpfFunctionDescribe*/ // Extract function description
/*global _gpfRegExpForEach*/ // Executes the callback for each match of the regular expression
/*exported _gpfClassMethodSuperify*/ // Create a method that can use this.$super
/*#endif*/

_gpfErrorDeclare("define/class/super", {
    /**
     * ### Summary
     *
     * $super used in a member that doesn't override a method
     *
     * ### Description
     *
     * $super can't be used if the method does not override an inherited one
     * @since 0.1.7
     */
    invalidClassSuper: "Invalid class super",

    /**
     * ### Summary
     *
     * An invalid member of $super was used
     *
     * ### Description
     *
     * $super members must point to a method exposed by the inherited prototype.
     * @since 0.1.7
     */
    invalidClassSuperMember: "Invalid class super member"

});

/**
 * Used when $super points to a non existent member
 *
 * @throws {gpf.Error.InvalidClassSuper}
 * @since 0.1.7
 */
function _gpfClassNoSuper () {
    gpf.Error.invalidClassSuper();
}

/**
 * Copy super method signature and invokes it.
 * NOTE: it is required to create a new function as it will receive additional members
 *
 * @param {Function} superMethod Super method to copy
 * @return {Function} New function that wraps the super method
 * @since 0.1.7
 */
function _gpfClassSuperCreateWithSameSignature (superMethod) {
    var definition = _gpfFunctionDescribe(superMethod);
    definition.body = "return _superMethod_.apply(this, arguments);";
    return _gpfFunctionBuild(definition, {
        _superMethod_: superMethod
    });
}

/**
 * Create $super function, either based on super method or triggering an error
 *
 * @param {*} superMember Member extracted from inherited prototype
 * @return {Function} $super function
 * @since 0.1.7
 */
function _gpfClassSuperCreate (superMember) {
    var superMethod;
    if (typeof superMember === "function") {
        superMethod = superMember;
    } else {
        superMethod = _gpfClassNoSuper;
    }
    return _gpfClassSuperCreateWithSameSignature(superMethod);
}

/**
 * Copy super method signature and apply weak binding.
 *
 * @param {Object} that Object instance
 * @param {Function} $super $super member
 * @param {*} superMethod superMember Member extracted from inherited prototype
 * @return {Function} $super method
 * @since 0.1.7
 */
function _gpfClassSuperCreateWeakBoundWithSameSignature (that, $super, superMethod) {
    var definition = _gpfFunctionDescribe(superMethod);
    definition.body = "return _superMethod_.apply(this === _$super_ ? _that_ : this, arguments);";
    return _gpfFunctionBuild(definition, {
        _that_: that,
        _$super_: $super,
        _superMethod_: superMethod
    });
}

/**
 * Create $super method
 * NOTE: if the super method is not a function, an exception is thrown
 *
 * @param {Object} that Object instance
 * @param {Function} $super $super member
 * @param {*} superMethod superMember Member extracted from inherited prototype
 * @return {Function} $super method
 * @throws {gpf.Error.InvalidClassSuperMember}
 * @since 0.1.7
 */
function _gpfClassSuperCreateMember (that, $super, superMethod) {
    if (typeof superMethod !== "function") {
        gpf.Error.invalidClassSuperMember();
    }
    return _gpfClassSuperCreateWeakBoundWithSameSignature(that, $super, superMethod);
}

/**
 * Regular expression detecting .$super use
 *
 * @type {RegExp}
 * @since 0.2.1
 */
var _gpfClassSuperRegExp = new RegExp("\\.\\$super\\.(\\w+)\\b", "g"),
    _GPF_CLASS_SUPER_MATCH_MEMBER = 1;

/**
 * Extract all 'members' that are used on $super
 *
 * @param {Function} method Method to analyze
 * @return {String[]} Member names that are used
 * @since 0.1.7
 */
function _gpfClassMethodExtractSuperMembers (method) {
    return _gpfRegExpForEach(_gpfClassSuperRegExp, method)
        .map(function (match) {
            return match[_GPF_CLASS_SUPER_MATCH_MEMBER];
        });
}

Object.assign(_GpfClassDefinition.prototype, {

    /**
     * Called before invoking a that contains $super method, it is responsible of allocating the $super object
     *
     * @param {Object} that Object instance
     * @param {String} methodName Name of the method that uses $super
     * @param {String[]} superMembers Expected member names on $super
     * @return {Function} $super method
     * @since 0.1.7
     */
    _get$Super: function (that, methodName, superMembers) {
        var superProto = this._extend.prototype,
            $super = _gpfClassSuperCreate(superProto[methodName]);
        superMembers.forEach(function (memberName) {
            $super[memberName] = _gpfClassSuperCreateMember(that, $super, superProto[memberName]);
        });
        return $super;
    },

    /**
     * Body of superified method
     * @since 0.1.7
     */
    _superifiedBody: "var _super_;\n"
        + "if (Object.prototype.hasOwnProperty.call(this, \"$super\")) {\n"
        + "    _super_ = this.$super;\n"
        + "}\n"
        + "this.$super = _classDef_._get$Super(this, _methodName_, _superMembers_);\n"
        + "try{\n"
        + "    var _result_ = _method_.apply(this, arguments);\n"
        + "} finally {\n"
        + "    if (undefined === _super_) {\n"
        + "        delete this.$super;\n"
        + "    } else {\n"
        + "        this.$super = _super_;\n"
        + "    }\n"
        + "}\n"
        + "return _result_;",

    /**
     * Generates context for the superified method
     *
     * @param {Function} method Method to superify
     * @param {String} methodName Name of the method (used to search in object prototype)
     * @param {String[]} superMembers Detected $super members used in the method
     * @return {Object} Context of superified method
     * @since 0.1.7
     */
    _getSuperifiedContext: function (method, methodName, superMembers) {
        return {
            _method_: method,
            _methodName_: methodName,
            _superMembers_: superMembers,
            _classDef_: this
        };
    },

    /**
     * Generates the superified version of the method
     *
     * @param {Function} method Method to superify
     * @param {String} methodName Name of the method (used to search in object prototype)
     * @param {String[]} superMembers Detected $super members used in the method
     * @return {Function} Superified method
     * @since 0.1.7
     */
    _createSuperified: function (method, methodName, superMembers) {
        // Keep signature
        var description = _gpfFunctionDescribe(method);
        description.body = this._superifiedBody;
        return _gpfFunctionBuild(description, this._getSuperifiedContext(method, methodName, superMembers));
    },

    /**
     * Create a method that can use this.$super
     *
     * @param {Function} method Method to superify
     * @param {String} methodName Name of the method (used to search in object prototype)
     * @return {Function} Superified method
     * @since 0.1.7
     */
    _superify: function (method, methodName) {
        if (new RegExp("\\.\\$super\\b").exec(method)) {
            return this._createSuperified(method, methodName, _gpfClassMethodExtractSuperMembers(method));
        }
        return method;
    }

});