567 lines
20 KiB
Plaintext
567 lines
20 KiB
Plaintext
"use strict";
|
|
/**
|
|
* This module defines nodes used to define types and validations for objects and interfaces.
|
|
*/
|
|
// tslint:disable:no-shadowed-variable prefer-for-of
|
|
var __extends = (this && this.__extends) || (function () {
|
|
var extendStatics = function (d, b) {
|
|
extendStatics = Object.setPrototypeOf ||
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
return extendStatics(d, b);
|
|
};
|
|
return function (d, b) {
|
|
extendStatics(d, b);
|
|
function __() { this.constructor = d; }
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.basicTypes = exports.BasicType = exports.TParamList = exports.TParam = exports.param = exports.TFunc = exports.func = exports.TProp = exports.TOptional = exports.opt = exports.TIface = exports.iface = exports.TEnumLiteral = exports.enumlit = exports.TEnumType = exports.enumtype = exports.TIntersection = exports.intersection = exports.TUnion = exports.union = exports.TTuple = exports.tuple = exports.TArray = exports.array = exports.TLiteral = exports.lit = exports.TName = exports.name = exports.TType = void 0;
|
|
var util_1 = require("./util");
|
|
/** Node that represents a type. */
|
|
var TType = /** @class */ (function () {
|
|
function TType() {
|
|
}
|
|
return TType;
|
|
}());
|
|
exports.TType = TType;
|
|
/** Parses a type spec into a TType node. */
|
|
function parseSpec(typeSpec) {
|
|
return typeof typeSpec === "string" ? name(typeSpec) : typeSpec;
|
|
}
|
|
function getNamedType(suite, name) {
|
|
var ttype = suite[name];
|
|
if (!ttype) {
|
|
throw new Error("Unknown type " + name);
|
|
}
|
|
return ttype;
|
|
}
|
|
/**
|
|
* Defines a type name, either built-in, or defined in this suite. It can typically be included in
|
|
* the specs as just a plain string.
|
|
*/
|
|
function name(value) { return new TName(value); }
|
|
exports.name = name;
|
|
var TName = /** @class */ (function (_super) {
|
|
__extends(TName, _super);
|
|
function TName(name) {
|
|
var _this = _super.call(this) || this;
|
|
_this.name = name;
|
|
_this._failMsg = "is not a " + name;
|
|
return _this;
|
|
}
|
|
TName.prototype.getChecker = function (suite, strict, allowedProps) {
|
|
var _this = this;
|
|
var ttype = getNamedType(suite, this.name);
|
|
var checker = ttype.getChecker(suite, strict, allowedProps);
|
|
if (ttype instanceof BasicType || ttype instanceof TName) {
|
|
return checker;
|
|
}
|
|
// For complex types, add an additional "is not a <Type>" message on failure.
|
|
return function (value, ctx) { return checker(value, ctx) ? true : ctx.fail(null, _this._failMsg, 0); };
|
|
};
|
|
return TName;
|
|
}(TType));
|
|
exports.TName = TName;
|
|
/**
|
|
* Defines a literal value, e.g. lit('hello') or lit(123).
|
|
*/
|
|
function lit(value) { return new TLiteral(value); }
|
|
exports.lit = lit;
|
|
var TLiteral = /** @class */ (function (_super) {
|
|
__extends(TLiteral, _super);
|
|
function TLiteral(value) {
|
|
var _this = _super.call(this) || this;
|
|
_this.value = value;
|
|
_this.name = JSON.stringify(value);
|
|
_this._failMsg = "is not " + _this.name;
|
|
return _this;
|
|
}
|
|
TLiteral.prototype.getChecker = function (suite, strict) {
|
|
var _this = this;
|
|
return function (value, ctx) { return (value === _this.value) ? true : ctx.fail(null, _this._failMsg, -1); };
|
|
};
|
|
return TLiteral;
|
|
}(TType));
|
|
exports.TLiteral = TLiteral;
|
|
/**
|
|
* Defines an array type, e.g. array('number').
|
|
*/
|
|
function array(typeSpec) { return new TArray(parseSpec(typeSpec)); }
|
|
exports.array = array;
|
|
var TArray = /** @class */ (function (_super) {
|
|
__extends(TArray, _super);
|
|
function TArray(ttype) {
|
|
var _this = _super.call(this) || this;
|
|
_this.ttype = ttype;
|
|
return _this;
|
|
}
|
|
TArray.prototype.getChecker = function (suite, strict) {
|
|
var itemChecker = this.ttype.getChecker(suite, strict);
|
|
return function (value, ctx) {
|
|
if (!Array.isArray(value)) {
|
|
return ctx.fail(null, "is not an array", 0);
|
|
}
|
|
for (var i = 0; i < value.length; i++) {
|
|
var ok = itemChecker(value[i], ctx);
|
|
if (!ok) {
|
|
return ctx.fail(i, null, 1);
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
};
|
|
return TArray;
|
|
}(TType));
|
|
exports.TArray = TArray;
|
|
/**
|
|
* Defines a tuple type, e.g. tuple('string', 'number').
|
|
*/
|
|
function tuple() {
|
|
var typeSpec = [];
|
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
typeSpec[_i] = arguments[_i];
|
|
}
|
|
return new TTuple(typeSpec.map(function (t) { return parseSpec(t); }));
|
|
}
|
|
exports.tuple = tuple;
|
|
var TTuple = /** @class */ (function (_super) {
|
|
__extends(TTuple, _super);
|
|
function TTuple(ttypes) {
|
|
var _this = _super.call(this) || this;
|
|
_this.ttypes = ttypes;
|
|
return _this;
|
|
}
|
|
TTuple.prototype.getChecker = function (suite, strict) {
|
|
var itemCheckers = this.ttypes.map(function (t) { return t.getChecker(suite, strict); });
|
|
var checker = function (value, ctx) {
|
|
if (!Array.isArray(value)) {
|
|
return ctx.fail(null, "is not an array", 0);
|
|
}
|
|
for (var i = 0; i < itemCheckers.length; i++) {
|
|
var ok = itemCheckers[i](value[i], ctx);
|
|
if (!ok) {
|
|
return ctx.fail(i, null, 1);
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
if (!strict) {
|
|
return checker;
|
|
}
|
|
return function (value, ctx) {
|
|
if (!checker(value, ctx)) {
|
|
return false;
|
|
}
|
|
return value.length <= itemCheckers.length ? true :
|
|
ctx.fail(itemCheckers.length, "is extraneous", 2);
|
|
};
|
|
};
|
|
return TTuple;
|
|
}(TType));
|
|
exports.TTuple = TTuple;
|
|
/**
|
|
* Defines a union type, e.g. union('number', 'null').
|
|
*/
|
|
function union() {
|
|
var typeSpec = [];
|
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
typeSpec[_i] = arguments[_i];
|
|
}
|
|
return new TUnion(typeSpec.map(function (t) { return parseSpec(t); }));
|
|
}
|
|
exports.union = union;
|
|
var TUnion = /** @class */ (function (_super) {
|
|
__extends(TUnion, _super);
|
|
function TUnion(ttypes) {
|
|
var _this = _super.call(this) || this;
|
|
_this.ttypes = ttypes;
|
|
var names = ttypes.map(function (t) { return t instanceof TName || t instanceof TLiteral ? t.name : null; })
|
|
.filter(function (n) { return n; });
|
|
var otherTypes = ttypes.length - names.length;
|
|
if (names.length) {
|
|
if (otherTypes > 0) {
|
|
names.push(otherTypes + " more");
|
|
}
|
|
_this._failMsg = "is none of " + names.join(", ");
|
|
}
|
|
else {
|
|
_this._failMsg = "is none of " + otherTypes + " types";
|
|
}
|
|
return _this;
|
|
}
|
|
TUnion.prototype.getChecker = function (suite, strict) {
|
|
var _this = this;
|
|
var itemCheckers = this.ttypes.map(function (t) { return t.getChecker(suite, strict); });
|
|
return function (value, ctx) {
|
|
var ur = ctx.unionResolver();
|
|
for (var i = 0; i < itemCheckers.length; i++) {
|
|
var ok = itemCheckers[i](value, ur.createContext());
|
|
if (ok) {
|
|
return true;
|
|
}
|
|
}
|
|
ctx.resolveUnion(ur);
|
|
return ctx.fail(null, _this._failMsg, 0);
|
|
};
|
|
};
|
|
return TUnion;
|
|
}(TType));
|
|
exports.TUnion = TUnion;
|
|
/**
|
|
* Defines an intersection type, e.g. intersection('number', 'null').
|
|
*/
|
|
function intersection() {
|
|
var typeSpec = [];
|
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
typeSpec[_i] = arguments[_i];
|
|
}
|
|
return new TIntersection(typeSpec.map(function (t) { return parseSpec(t); }));
|
|
}
|
|
exports.intersection = intersection;
|
|
var TIntersection = /** @class */ (function (_super) {
|
|
__extends(TIntersection, _super);
|
|
function TIntersection(ttypes) {
|
|
var _this = _super.call(this) || this;
|
|
_this.ttypes = ttypes;
|
|
return _this;
|
|
}
|
|
TIntersection.prototype.getChecker = function (suite, strict) {
|
|
var allowedProps = new Set();
|
|
var itemCheckers = this.ttypes.map(function (t) { return t.getChecker(suite, strict, allowedProps); });
|
|
return function (value, ctx) {
|
|
var ok = itemCheckers.every(function (checker) { return checker(value, ctx); });
|
|
if (ok) {
|
|
return true;
|
|
}
|
|
return ctx.fail(null, null, 0);
|
|
};
|
|
};
|
|
return TIntersection;
|
|
}(TType));
|
|
exports.TIntersection = TIntersection;
|
|
/**
|
|
* Defines an enum type, e.g. enum({'A': 1, 'B': 2}).
|
|
*/
|
|
function enumtype(values) {
|
|
return new TEnumType(values);
|
|
}
|
|
exports.enumtype = enumtype;
|
|
var TEnumType = /** @class */ (function (_super) {
|
|
__extends(TEnumType, _super);
|
|
function TEnumType(members) {
|
|
var _this = _super.call(this) || this;
|
|
_this.members = members;
|
|
_this.validValues = new Set();
|
|
_this._failMsg = "is not a valid enum value";
|
|
_this.validValues = new Set(Object.keys(members).map(function (name) { return members[name]; }));
|
|
return _this;
|
|
}
|
|
TEnumType.prototype.getChecker = function (suite, strict) {
|
|
var _this = this;
|
|
return function (value, ctx) {
|
|
return (_this.validValues.has(value) ? true : ctx.fail(null, _this._failMsg, 0));
|
|
};
|
|
};
|
|
return TEnumType;
|
|
}(TType));
|
|
exports.TEnumType = TEnumType;
|
|
/**
|
|
* Defines a literal enum value, such as Direction.Up, specified as enumlit("Direction", "Up").
|
|
*/
|
|
function enumlit(name, prop) {
|
|
return new TEnumLiteral(name, prop);
|
|
}
|
|
exports.enumlit = enumlit;
|
|
var TEnumLiteral = /** @class */ (function (_super) {
|
|
__extends(TEnumLiteral, _super);
|
|
function TEnumLiteral(enumName, prop) {
|
|
var _this = _super.call(this) || this;
|
|
_this.enumName = enumName;
|
|
_this.prop = prop;
|
|
_this._failMsg = "is not " + enumName + "." + prop;
|
|
return _this;
|
|
}
|
|
TEnumLiteral.prototype.getChecker = function (suite, strict) {
|
|
var _this = this;
|
|
var ttype = getNamedType(suite, this.enumName);
|
|
if (!(ttype instanceof TEnumType)) {
|
|
throw new Error("Type " + this.enumName + " used in enumlit is not an enum type");
|
|
}
|
|
var val = ttype.members[this.prop];
|
|
if (!ttype.members.hasOwnProperty(this.prop)) {
|
|
throw new Error("Unknown value " + this.enumName + "." + this.prop + " used in enumlit");
|
|
}
|
|
return function (value, ctx) { return (value === val) ? true : ctx.fail(null, _this._failMsg, -1); };
|
|
};
|
|
return TEnumLiteral;
|
|
}(TType));
|
|
exports.TEnumLiteral = TEnumLiteral;
|
|
function makeIfaceProps(props) {
|
|
return Object.keys(props).map(function (name) { return makeIfaceProp(name, props[name]); });
|
|
}
|
|
function makeIfaceProp(name, prop) {
|
|
return prop instanceof TOptional ?
|
|
new TProp(name, prop.ttype, true) :
|
|
new TProp(name, parseSpec(prop), false);
|
|
}
|
|
/**
|
|
* Defines an interface. The first argument is an array of interfaces that it extends, and the
|
|
* second is an array of properties.
|
|
*/
|
|
function iface(bases, props) {
|
|
return new TIface(bases, makeIfaceProps(props));
|
|
}
|
|
exports.iface = iface;
|
|
var TIface = /** @class */ (function (_super) {
|
|
__extends(TIface, _super);
|
|
function TIface(bases, props) {
|
|
var _this = _super.call(this) || this;
|
|
_this.bases = bases;
|
|
_this.props = props;
|
|
_this.propSet = new Set(props.map(function (p) { return p.name; }));
|
|
return _this;
|
|
}
|
|
TIface.prototype.getChecker = function (suite, strict, allowedProps) {
|
|
var _this = this;
|
|
var baseCheckers = this.bases.map(function (b) { return getNamedType(suite, b).getChecker(suite, strict); });
|
|
var propCheckers = this.props.map(function (prop) { return prop.ttype.getChecker(suite, strict); });
|
|
var testCtx = new util_1.NoopContext();
|
|
// Consider a prop required if it's not optional AND does not allow for undefined as a value.
|
|
var isPropRequired = this.props.map(function (prop, i) {
|
|
return !prop.isOpt && !propCheckers[i](undefined, testCtx);
|
|
});
|
|
var checker = function (value, ctx) {
|
|
if (typeof value !== "object" || value === null) {
|
|
return ctx.fail(null, "is not an object", 0);
|
|
}
|
|
for (var i = 0; i < baseCheckers.length; i++) {
|
|
if (!baseCheckers[i](value, ctx)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (var i = 0; i < propCheckers.length; i++) {
|
|
var name_1 = _this.props[i].name;
|
|
var v = value[name_1];
|
|
if (v === undefined) {
|
|
if (isPropRequired[i]) {
|
|
return ctx.fail(name_1, "is missing", 1);
|
|
}
|
|
}
|
|
else {
|
|
var ok = propCheckers[i](v, ctx);
|
|
if (!ok) {
|
|
return ctx.fail(name_1, null, 1);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
if (!strict) {
|
|
return checker;
|
|
}
|
|
var propSet = this.propSet;
|
|
if (allowedProps) {
|
|
this.propSet.forEach(function (prop) { return allowedProps.add(prop); });
|
|
propSet = allowedProps;
|
|
}
|
|
// In strict mode, check also for unknown enumerable properties.
|
|
return function (value, ctx) {
|
|
if (!checker(value, ctx)) {
|
|
return false;
|
|
}
|
|
for (var prop in value) {
|
|
if (!propSet.has(prop)) {
|
|
return ctx.fail(prop, "is extraneous", 2);
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
};
|
|
return TIface;
|
|
}(TType));
|
|
exports.TIface = TIface;
|
|
/**
|
|
* Defines an optional property on an interface.
|
|
*/
|
|
function opt(typeSpec) { return new TOptional(parseSpec(typeSpec)); }
|
|
exports.opt = opt;
|
|
var TOptional = /** @class */ (function (_super) {
|
|
__extends(TOptional, _super);
|
|
function TOptional(ttype) {
|
|
var _this = _super.call(this) || this;
|
|
_this.ttype = ttype;
|
|
return _this;
|
|
}
|
|
TOptional.prototype.getChecker = function (suite, strict) {
|
|
var itemChecker = this.ttype.getChecker(suite, strict);
|
|
return function (value, ctx) {
|
|
return value === undefined || itemChecker(value, ctx);
|
|
};
|
|
};
|
|
return TOptional;
|
|
}(TType));
|
|
exports.TOptional = TOptional;
|
|
/**
|
|
* Defines a property in an interface.
|
|
*/
|
|
var TProp = /** @class */ (function () {
|
|
function TProp(name, ttype, isOpt) {
|
|
this.name = name;
|
|
this.ttype = ttype;
|
|
this.isOpt = isOpt;
|
|
}
|
|
return TProp;
|
|
}());
|
|
exports.TProp = TProp;
|
|
/**
|
|
* Defines a function. The first argument declares the function's return type, the rest declare
|
|
* its parameters.
|
|
*/
|
|
function func(resultSpec) {
|
|
var params = [];
|
|
for (var _i = 1; _i < arguments.length; _i++) {
|
|
params[_i - 1] = arguments[_i];
|
|
}
|
|
return new TFunc(new TParamList(params), parseSpec(resultSpec));
|
|
}
|
|
exports.func = func;
|
|
var TFunc = /** @class */ (function (_super) {
|
|
__extends(TFunc, _super);
|
|
function TFunc(paramList, result) {
|
|
var _this = _super.call(this) || this;
|
|
_this.paramList = paramList;
|
|
_this.result = result;
|
|
return _this;
|
|
}
|
|
TFunc.prototype.getChecker = function (suite, strict) {
|
|
return function (value, ctx) {
|
|
return typeof value === "function" ? true : ctx.fail(null, "is not a function", 0);
|
|
};
|
|
};
|
|
return TFunc;
|
|
}(TType));
|
|
exports.TFunc = TFunc;
|
|
/**
|
|
* Defines a function parameter.
|
|
*/
|
|
function param(name, typeSpec, isOpt) {
|
|
return new TParam(name, parseSpec(typeSpec), Boolean(isOpt));
|
|
}
|
|
exports.param = param;
|
|
var TParam = /** @class */ (function () {
|
|
function TParam(name, ttype, isOpt) {
|
|
this.name = name;
|
|
this.ttype = ttype;
|
|
this.isOpt = isOpt;
|
|
}
|
|
return TParam;
|
|
}());
|
|
exports.TParam = TParam;
|
|
/**
|
|
* Defines a function parameter list.
|
|
*/
|
|
var TParamList = /** @class */ (function (_super) {
|
|
__extends(TParamList, _super);
|
|
function TParamList(params) {
|
|
var _this = _super.call(this) || this;
|
|
_this.params = params;
|
|
return _this;
|
|
}
|
|
TParamList.prototype.getChecker = function (suite, strict) {
|
|
var _this = this;
|
|
var itemCheckers = this.params.map(function (t) { return t.ttype.getChecker(suite, strict); });
|
|
var testCtx = new util_1.NoopContext();
|
|
var isParamRequired = this.params.map(function (param, i) {
|
|
return !param.isOpt && !itemCheckers[i](undefined, testCtx);
|
|
});
|
|
var checker = function (value, ctx) {
|
|
if (!Array.isArray(value)) {
|
|
return ctx.fail(null, "is not an array", 0);
|
|
}
|
|
for (var i = 0; i < itemCheckers.length; i++) {
|
|
var p = _this.params[i];
|
|
if (value[i] === undefined) {
|
|
if (isParamRequired[i]) {
|
|
return ctx.fail(p.name, "is missing", 1);
|
|
}
|
|
}
|
|
else {
|
|
var ok = itemCheckers[i](value[i], ctx);
|
|
if (!ok) {
|
|
return ctx.fail(p.name, null, 1);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
if (!strict) {
|
|
return checker;
|
|
}
|
|
return function (value, ctx) {
|
|
if (!checker(value, ctx)) {
|
|
return false;
|
|
}
|
|
return value.length <= itemCheckers.length ? true :
|
|
ctx.fail(itemCheckers.length, "is extraneous", 2);
|
|
};
|
|
};
|
|
return TParamList;
|
|
}(TType));
|
|
exports.TParamList = TParamList;
|
|
/**
|
|
* Single TType implementation for all basic built-in types.
|
|
*/
|
|
var BasicType = /** @class */ (function (_super) {
|
|
__extends(BasicType, _super);
|
|
function BasicType(validator, message) {
|
|
var _this = _super.call(this) || this;
|
|
_this.validator = validator;
|
|
_this.message = message;
|
|
return _this;
|
|
}
|
|
BasicType.prototype.getChecker = function (suite, strict) {
|
|
var _this = this;
|
|
return function (value, ctx) { return _this.validator(value) ? true : ctx.fail(null, _this.message, 0); };
|
|
};
|
|
return BasicType;
|
|
}(TType));
|
|
exports.BasicType = BasicType;
|
|
/**
|
|
* Defines the suite of basic types.
|
|
*/
|
|
exports.basicTypes = {
|
|
any: new BasicType(function (v) { return true; }, "is invalid"),
|
|
number: new BasicType(function (v) { return (typeof v === "number"); }, "is not a number"),
|
|
object: new BasicType(function (v) { return (typeof v === "object" && v); }, "is not an object"),
|
|
boolean: new BasicType(function (v) { return (typeof v === "boolean"); }, "is not a boolean"),
|
|
string: new BasicType(function (v) { return (typeof v === "string"); }, "is not a string"),
|
|
symbol: new BasicType(function (v) { return (typeof v === "symbol"); }, "is not a symbol"),
|
|
void: new BasicType(function (v) { return (v == null); }, "is not void"),
|
|
undefined: new BasicType(function (v) { return (v === undefined); }, "is not undefined"),
|
|
null: new BasicType(function (v) { return (v === null); }, "is not null"),
|
|
never: new BasicType(function (v) { return false; }, "is unexpected"),
|
|
Date: new BasicType(getIsNativeChecker("[object Date]"), "is not a Date"),
|
|
RegExp: new BasicType(getIsNativeChecker("[object RegExp]"), "is not a RegExp"),
|
|
};
|
|
// This approach for checking native object types mirrors that of lodash. Its advantage over
|
|
// `isinstance` is that it can still return true for native objects created in different JS
|
|
// execution environments.
|
|
var nativeToString = Object.prototype.toString;
|
|
function getIsNativeChecker(tag) {
|
|
return function (v) { return typeof v === "object" && v && nativeToString.call(v) === tag; };
|
|
}
|
|
if (typeof Buffer !== "undefined") {
|
|
exports.basicTypes.Buffer = new BasicType(function (v) { return Buffer.isBuffer(v); }, "is not a Buffer");
|
|
}
|
|
var _loop_1 = function (array_1) {
|
|
exports.basicTypes[array_1.name] = new BasicType(function (v) { return (v instanceof array_1); }, "is not a " + array_1.name);
|
|
};
|
|
// Support typed arrays of various flavors
|
|
for (var _i = 0, _a = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array,
|
|
Int32Array, Uint32Array, Float32Array, Float64Array, ArrayBuffer]; _i < _a.length; _i++) {
|
|
var array_1 = _a[_i];
|
|
_loop_1(array_1);
|
|
}
|