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