ArnaudBuchholz/gpf-js

View on GitHub
src/define/check.js

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * @file Check entity definition
 * @since 0.1.6
 */
/*#ifndef(UMD)*/
"use strict";
/*global _GpfEntityDefinition*/ // Entity definition
/*global _gpfCreateAbstractFunction*/ // Build a function that throws the abstractMethod exception
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfFunc*/ // Create a new function using the source
/*global _gpfIgnore*/ // Helper to remove unused parameter warning
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*#endif*/

_gpfErrorDeclare("define/check", {
    /**
     * ### Summary
     *
     * One of the $ properties is invalid in the definition passed to {@link gpf.define}
     *
     * ### Description
     *
     * The list of possible $ properties is fixed and depends on the entity type.
     * This error is thrown when one $ property is not allowed.
     * @since 0.1.6
     */
    invalidEntity$Property: "Invalid entity $ property",

    /**
     * ### Summary
     *
     * Entity name is missing in the definition passed to {@link gpf.define}
     *
     * ### Description
     *
     * This error is thrown when the entity name is missing
     * @since 0.1.6
     */
    missingEntityName: "Missing entity name",

    /**
     * ### Summary
     *
     * Entity namespace is invalid in the definition passed to {@link gpf.define}
     *
     * ### Description
     *
     * This error is thrown when the namespace is invalid
     * @since 0.1.6
     */
    invalidEntityNamespace: "Invalid entity namespace"

});

function _gpfDefineEntityCheck$PropertyInAllowed$Properties (name, allowedList) {
    if (!allowedList.includes(name)) {
        gpf.Error.invalidEntity$Property();
    }
}

var _GPF_DEFINE_SPECIAL_PROPERTY_PREFIX = "$";

function _gpfDefineEntityCheckProperty (value, name) {
    /*jshint -W040*/ /*eslint-disable no-invalid-this*/ // bound through thisArg
    if (name.startsWith(_GPF_DEFINE_SPECIAL_PROPERTY_PREFIX)) {
        this._check$Property(name.substring(_GPF_DEFINE_SPECIAL_PROPERTY_PREFIX.length), value);
    } else {
        this._checkProperty(name, value);
    }
    /*jshint -W040*/ /*eslint-enable no-invalid-this*/
}

Object.assign(_GpfEntityDefinition.prototype, {

    /**
     * Entity type (class...)
     *
     * @readonly
     * @since 0.1.6
     */
    _type: "",

    /**
     * List of allowed $ properties
     *
     * @type {String[]}
     * @readonly
     * @since 0.1.6
     */
    _allowed$Properties: "type,name,namespace".split(","),

    /**
     * Check if the $ property is allowed
     *
     * @param {String} name $ Property name (without the starting $)
     * @param {*} value $ Property value
     * @since 0.1.6
     */
    _check$Property: function (name, value) {
        _gpfIgnore(value);
        if (name !== this._type) {
            _gpfDefineEntityCheck$PropertyInAllowed$Properties(name, this._allowed$Properties);
        }
    },

    /**
     * Throw the invalid property error
     *
     * @abstract
     * @protected
     * @since 0.1.8
     */
    _throwInvalidProperty: _gpfCreateAbstractFunction(),

    /**
     * Regular expression used to validate member name
     *
     * @type {RegExp}
     * @readonly
     * @protected
     * @since 0.1.8
     */
    _reMemberName: new RegExp(".*"),

    /**
     * Check that the member name is a valid one
     *
     * @param {String} name Member name
     * @since 0.1.8
     */
    _checkMemberName: function (name) {
        if (!this._reMemberName.exec(name)) {
            this._throwInvalidProperty();
        }
    },

    /**
     * List of reserved member names
     *
     * @type {String[]}
     * @readonly
     * @constant
     * @since 0.1.8
     */
    _reservedNames: "super,class,public,private,protected,static,mixin".split(","),

    /**
     * Check that the member name is not a reserved one
     *
     * @param {String} name Member name
     * @since 0.1.6
     */
    _checkReservedMemberName: function (name) {
        if (this._reservedNames.includes(name)) {
            this._throwInvalidProperty();
        }
    },

    /**
     * Check the value of the member
     *
     * @param {String} name Property name
     * @param {*} value Property value
     * @protected
     * @since 0.1.8
     */
    _checkMemberValue: _gpfFunc(["name", "value"], " "),

    /**
     * Check if the property is allowed
     * NOTE: $ properties are handled by {@link _check$Property}
     *
     * @param {String} name Property name
     * @param {*} value Property value
     * @since 0.1.6
     */
    _checkProperty: function (name, value) {
        this._checkMemberName(name);
        this._checkReservedMemberName(name);
        this._checkMemberValue(name, value);
    },

    /**
     * Check the properties contained in the definition passed to {@link gpf.define}
     * @since 0.1.6
     */
    _checkProperties: function () {
        _gpfObjectForEach(this._initialDefinition, _gpfDefineEntityCheckProperty, this);
    },

    /**
     * Entity name
     * @since 0.1.6
     */
    _name: "",

    /**
     * Compute name property
     * @since 0.1.6
     */
    _readName: function () {
        var definition = this._initialDefinition;
        this._name = definition["$" + this._type] || definition.$name;
    },

    /**
     * Check if name property is not empty (throw the error otherwise)
     *
     * @throws {gpf.Error.MissingEntityName}
     * @since 0.1.6
     */
    _checkNameIsNotEmpty: function () {
        if (!this._name) {
            gpf.Error.missingEntityName();
        }
    },

    /**
     * Throw the invalid name error
     *
     * @abstract
     * @protected
     * @since 0.1.8
     */
    _throwInvalidName: _gpfCreateAbstractFunction(),

    /**
     * Regular expression used to validate entity name
     *
     * @type {RegExp}
     * @readonly
     * @protected
     * @since 0.1.8
     */
    _reName: new RegExp(".*"),

    /**
     * Check name property (content)
     *
     * @since 0.1.6
     */
    _checkName: function () {
        if (!this._reName.exec(this._name)) {
            this._throwInvalidName();
        }
    },

    /**
     * Entity namespace
     * @since 0.1.6
     */
    _namespace: "",

    /**
     * If the name is prefixed with a namespace, isolate it and update name property
     *
     * @return {String|undefined} Namespace contained in the name or undefined if none
     * @since 0.1.6
     */
    _extractRelativeNamespaceFromName: function () {
        var parts = new RegExp("(.*)\\.([^\\.]+)$").exec(this._name),
            NAME_PART = 2,
            NAMESPACE_PART = 1;
        if (parts) {
            this._name = parts[NAME_PART];
            return parts[NAMESPACE_PART];
        }
    },

    /**
     * Compute namespace property
     * @since 0.1.6
     */
    _readNamespace: function () {
        var namespaces = [
            this._initialDefinition.$namespace,
            this._extractRelativeNamespaceFromName()
        ].filter(function (namespacePart) {
            return namespacePart;
        });
        if (namespaces.length) {
            this._namespace = namespaces.join(".");
        }
    },

    /**
     * Check namespace property
     *
     * @throws {gpf.Error.InvalidEntityNamespace}
     * @since 0.1.6
     */
    _checkNamespace: function () {
        if (!new RegExp("^(:?[a-z_$][a-zA-Z0-9]+(:?\\.[a-z_$][a-zA-Z0-9]+)*)?$").exec(this._namespace)) {
            gpf.Error.invalidEntityNamespace();
        }
    },

    check: function () {
        this._checkProperties();
        this._readName();
        this._checkNameIsNotEmpty();
        this._readNamespace();
        this._checkName();
        this._checkNamespace();
    }

});