"use strict"; const util = require("util"); const capitalize = require("capitalize"); const isConstructor = require("./is-constructor"); const isNamedFunction = require("./is-named-function"); /* TODO: Add property tracking throughout calls, to determine exactly where the wrong value was specified. */ module.exports = getValueType; function getValueType(value, withArticle = true) { let [article, description] = getValueTypeData(value); if (withArticle) { if (article != null) { return `${article} ${description}`; } else { return description; } } else { return description; } } function getValueTypeData(value) { if (value != null && value._isTypeRule) { /* _baseType boolean string number nothing? null undefined function? guardedFunction _collectionType set map array _modifierType either _constructorType (for instanceOf) _isSlotRule _isSelfRule custom type */ if (value._isSlotRule) { return [null, "(slot)"]; } else if (value._isSelfRule) { return [null, "(self)"]; } else if (value._constructorType != null) { let constructorName; if (value._constructorType.name != null) { constructorName = value._constructorType.name; } else { constructorName = "(unnamed type)"; } return ["an", `instance of ${constructorName}`]; } else if (value._baseType != null) { if (value._baseType === "guardedFunction") { let argList = value._options.args .map((arg) => getValueType(arg, false)) .join(", "); return ["a", `guarded function (${argList}) → ${getValueType(value._options.returnType, false)}`]; } else { if (value._baseType === "string") { return ["a", "string"]; } else if (value._baseType === "number") { return ["a", "number"]; } else if (value._baseType === "boolean") { return ["a", "boolean"]; } else { return [null, value._baseType]; } } } else if (value._modifierType != null) { if (value._modifierType === "either") { let typeList = value._types .map((type) => getValueType(type, false)) .join(" | "); return [null, `Either<${typeList}>`]; } else { throw new Error(`Encountered unrecognized modifier type: ${value._modifierType}`); } } else if (value._collectionType != null) { let collectionTypeName = capitalize(value._collectionType); if (value._keyType != null) { return ["a", `${collectionTypeName}<${getValueType(value._keyType, false)} → ${getValueType(value._itemType, false)}>`]; } else { return ["a", `${collectionTypeName}<${getValueType(value._itemType, false)}>`]; } } else if (value._isCustomType || value._isCustomRegistryType) { return ["an", `instance of ${value._name}`]; } else if (value._isTrait || value._isRegistryTrait) { return ["an", `instance of a type with the ${value._name} trait`]; } else { throw new Error(`Encountered unrecognized type rule: ${util.inspect(value, {breakLength: Infinity})}`); } } else { /* Assume this is a value, rather than a type rule. */ if (value === undefined) { return [null, "undefined"]; } else if (value === null) { return [null, "null"]; } else if (typeof value === "boolean") { return ["a", "boolean"]; } else if (typeof value === "number") { if (value === Number.POSITIVE_INFINITY) { return [null, "Infinity"]; } else if (value === Number.NEGATIVE_INFINITY) { return [null, "negative Infinity"]; } else if (isNaN(value)) { return [null, "NaN"]; } else { return ["a", "number"]; } } else if (typeof value === "string") { return ["a", "string"]; } else if (typeof value === "function") { if (value._guardedFunction != null) { let functionName; if (isNamedFunction(value._guardedFunction)) { functionName = `guarded function[${value._guardedFunction.name}]`; } else { functionName = "guarded function"; } /* TODO: Deduplicate this code? */ let argList = value._guardedArgs .map((arg) => getValueType(arg, false)) .join(", "); return ["a", `${functionName} (${argList}) → ${getValueType(value._guardedReturnType, false)}`]; } else if (isConstructor(value)) { if (isNamedFunction(value)) { return ["a", `constructor[${value.name}]`]; } else { return ["a", "constructor"]; } } else { if (isNamedFunction(value)) { return ["a", `function[${value.name}]`]; } else { return ["a", "function"]; } } } else if (Array.isArray(value)) { return ["an", "array"]; } else if (value instanceof RegExp) { return ["a", "regular expression"]; } else if (typeof value === "object") { if (value._guardedCollectionType === "set") { return ["a", `Set<${getValueType(value._itemType, false)}>`]; } else if (value._guardedCollectionType === "map") { return ["a", `Map<${getValueType(value._keyType, false)} → ${getValueType(value._itemType, false)}>`]; } else if (value instanceof Map) { return ["a", "Map"]; } else if (value instanceof Set) { return ["a", "Set"]; } else if (value._type != null && value._type._isCustomType != null) { return ["an", `instance of ${value._type._name}`]; } else if (value.__proto__.constructor === Object) { /* Object literal */ return ["an", "object"]; } else if (isNamedFunction(value.__proto__.constructor)) { return ["an", `instance of ${value.__proto__.constructor.name}`]; } else { return ["an", "instance of an unknown type"]; } } else { throw new Error("Encountered unrecognized value type"); } } }