You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
5.5 KiB
JavaScript

"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");
}
}
}