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