massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / @humanwhocodes / object-schema / src / object-schema.js
diff --git a/.config/coc/extensions/node_modules/coc-prettier/node_modules/@humanwhocodes/object-schema/src/object-schema.js b/.config/coc/extensions/node_modules/coc-prettier/node_modules/@humanwhocodes/object-schema/src/object-schema.js
new file mode 100644 (file)
index 0000000..b663236
--- /dev/null
@@ -0,0 +1,235 @@
+/**
+ * @filedescription Object Schema
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const { MergeStrategy } = require("./merge-strategy");
+const { ValidationStrategy } = require("./validation-strategy");
+
+//-----------------------------------------------------------------------------
+// Private
+//-----------------------------------------------------------------------------
+
+const strategies = Symbol("strategies");
+const requiredKeys = Symbol("requiredKeys");
+
+/**
+ * Validates a schema strategy.
+ * @param {string} name The name of the key this strategy is for.
+ * @param {Object} strategy The strategy for the object key.
+ * @param {boolean} [strategy.required=true] Whether the key is required.
+ * @param {string[]} [strategy.requires] Other keys that are required when
+ *      this key is present.
+ * @param {Function} strategy.merge A method to call when merging two objects
+ *      with the same key.
+ * @param {Function} strategy.validate A method to call when validating an
+ *      object with the key.
+ * @returns {void}
+ * @throws {Error} When the strategy is missing a name.
+ * @throws {Error} When the strategy is missing a merge() method.
+ * @throws {Error} When the strategy is missing a validate() method.
+ */
+function validateDefinition(name, strategy) {
+
+    let hasSchema = false;
+    if (strategy.schema) {
+        if (typeof strategy.schema === "object") {
+            hasSchema = true;
+        } else {
+            throw new TypeError("Schema must be an object.");
+        }
+    }
+
+    if (typeof strategy.merge === "string") {
+        if (!(strategy.merge in MergeStrategy)) {
+            throw new TypeError(`Definition for key "${name}" missing valid merge strategy.`);
+        }
+    } else if (!hasSchema && typeof strategy.merge !== "function") {
+        throw new TypeError(`Definition for key "${name}" must have a merge property.`);
+    }
+
+    if (typeof strategy.validate === "string") {
+        if (!(strategy.validate in ValidationStrategy)) {
+            throw new TypeError(`Definition for key "${name}" missing valid validation strategy.`);
+        }
+    } else if (!hasSchema && typeof strategy.validate !== "function") {
+        throw new TypeError(`Definition for key "${name}" must have a validate() method.`);
+    }
+}
+
+
+//-----------------------------------------------------------------------------
+// Class
+//-----------------------------------------------------------------------------
+
+/**
+ * Represents an object validation/merging schema.
+ */
+class ObjectSchema {
+
+    /**
+     * Creates a new instance.
+     */
+    constructor(definitions) {
+
+        if (!definitions) {
+            throw new Error("Schema definitions missing.");
+        }
+
+        /**
+         * Track all strategies in the schema by key.
+         * @type {Map}
+         * @property strategies
+         */
+        this[strategies] = new Map();
+
+        /**
+         * Separately track any keys that are required for faster validation.
+         * @type {Map}
+         * @property requiredKeys
+         */
+        this[requiredKeys] = new Map();
+
+        // add in all strategies
+        for (const key of Object.keys(definitions)) {
+            validateDefinition(key, definitions[key]);
+
+            // normalize merge and validate methods if subschema is present
+            if (typeof definitions[key].schema === "object") {
+                const schema = new ObjectSchema(definitions[key].schema);
+                definitions[key] = {
+                    ...definitions[key],
+                    merge(first = {}, second = {}) {
+                        return schema.merge(first, second);
+                    },
+                    validate(value) {
+                        ValidationStrategy.object(value);
+                        schema.validate(value);
+                    }
+                };
+            }
+
+            // normalize the merge method in case there's a string
+            if (typeof definitions[key].merge === "string") {
+                definitions[key] = {
+                    ...definitions[key],
+                    merge: MergeStrategy[definitions[key].merge]
+                };
+            };
+
+            // normalize the validate method in case there's a string
+            if (typeof definitions[key].validate === "string") {
+                definitions[key] = {
+                    ...definitions[key],
+                    validate: ValidationStrategy[definitions[key].validate]
+                };
+            };
+
+            this[strategies].set(key, definitions[key]);
+
+            if (definitions[key].required) {
+                this[requiredKeys].set(key, definitions[key]);
+            }
+        }
+    }
+
+    /**
+     * Determines if a strategy has been registered for the given object key.
+     * @param {string} key The object key to find a strategy for.
+     * @returns {boolean} True if the key has a strategy registered, false if not. 
+     */
+    hasKey(key) {
+        return this[strategies].has(key);
+    }
+
+    /**
+     * Merges objects together to create a new object comprised of the keys
+     * of the all objects. Keys are merged based on the each key's merge
+     * strategy.
+     * @param {...Object} objects The objects to merge.
+     * @returns {Object} A new object with a mix of all objects' keys.
+     * @throws {Error} If any object is invalid.
+     */
+    merge(...objects) {
+
+        // double check arguments
+        if (objects.length < 2) {
+            throw new Error("merge() requires at least two arguments.");
+        }
+
+        if (objects.some(object => (object == null || typeof object !== "object"))) {
+            throw new Error("All arguments must be objects.");
+        }
+
+        return objects.reduce((result, object) => {
+            
+            this.validate(object);
+            
+            for (const [key, strategy] of this[strategies]) {
+                try {
+                    if (key in result || key in object) {
+                        const value = strategy.merge.call(this, result[key], object[key]);
+                        if (value !== undefined) {
+                            result[key] = value;
+                        }
+                    }
+                } catch (ex) {
+                    ex.message = `Key "${key}": ` + ex.message;
+                    throw ex;
+                }
+            }
+            return result;
+        }, {});
+    }
+
+    /**
+     * Validates an object's keys based on the validate strategy for each key.
+     * @param {Object} object The object to validate.
+     * @returns {void}
+     * @throws {Error} When the object is invalid. 
+     */
+    validate(object) {
+
+        // check existing keys first
+        for (const key of Object.keys(object)) {
+
+            // check to see if the key is defined
+            if (!this.hasKey(key)) {
+                throw new Error(`Unexpected key "${key}" found.`);
+            }
+
+            // validate existing keys
+            const strategy = this[strategies].get(key);
+
+            // first check to see if any other keys are required
+            if (Array.isArray(strategy.requires)) {
+                if (!strategy.requires.every(otherKey => otherKey in object)) {
+                    throw new Error(`Key "${key}" requires keys "${strategy.requires.join("\", \"")}".`);
+                }
+            }
+
+            // now apply remaining validation strategy
+            try {
+                strategy.validate.call(strategy, object[key]);
+            } catch (ex) {
+                ex.message = `Key "${key}": ` + ex.message;
+                throw ex;
+            }
+        }
+
+        // ensure required keys aren't missing
+        for (const [key] of this[requiredKeys]) {
+            if (!(key in object)) {
+                throw new Error(`Missing required key "${key}".`);
+            }
+        }
+
+    }
+}
+
+exports.ObjectSchema = ObjectSchema;