Comparing version 7.6.3 to 8.0.0
@@ -70,3 +70,3 @@ const ValueError = require("../errors/ValueError"); | ||
*/ | ||
checker(type) { | ||
Blorker$checker(type) { | ||
// Check args. | ||
@@ -92,3 +92,3 @@ runChecker(this._checkers.string, type, "checker(): type", BlorkError); | ||
*/ | ||
assert(assertion, description, prefix, error) { | ||
Blorker$assert(assertion, description, prefix, error) { | ||
// Check args. | ||
@@ -114,3 +114,3 @@ runChecker(this._checkers.string, description, "check(): description", BlorkError); | ||
*/ | ||
check(value, type, prefix, error) { | ||
Blorker$check(value, type, prefix, error) { | ||
// Check args. | ||
@@ -135,3 +135,3 @@ if (prefix) runChecker(this._checkers.string, prefix, "check(): prefix", BlorkError); | ||
*/ | ||
args(args, types, prefix, error) { | ||
Blorker$args(args, types, prefix, error) { | ||
// Check args. | ||
@@ -144,3 +144,5 @@ runChecker(this._checkers.args, args, "args(): args", BlorkError); | ||
// Recurse into each type. | ||
types.forEach((t, i) => this._check(args[i], t, `${prefix || "arguments"}[${i}]`, error || this._error)); | ||
for (let i = 0; i < types.length; i++) { | ||
this._check(args[i], types[i], `${prefix || "arguments"}[${i}]`, error || this._error); | ||
} | ||
@@ -164,3 +166,3 @@ // No excess arguments. | ||
*/ | ||
add(name, checker, description, error) { | ||
Blorker$add(name, checker, description, error) { | ||
// Check args. | ||
@@ -186,3 +188,3 @@ runChecker(this._checkers.kebab, name, "add(): name", BlorkError); | ||
*/ | ||
throws(error) { | ||
Blorker$throws(error) { | ||
// Check args. | ||
@@ -196,55 +198,2 @@ runChecker(this._checkers.function, error, "throws(): error", BlorkError); | ||
/** | ||
* Define object properties that are locked to their specific Blork types. | ||
* | ||
* The properties are readable, but only writable with values matching the initial type. | ||
* This allows you to define objects with properties of guaranteed types. | ||
* | ||
* @param {object} object The object to define the property on. | ||
* @param {object} props The new locked-down props to define on the property. | ||
* @returns {void} | ||
*/ | ||
props(object, props) { | ||
// Check args. | ||
runChecker(this._checkers.objectlike, object, "props(): object", BlorkError); | ||
runChecker(this._checkers.object, props, "props(): props", BlorkError); | ||
// Loop through every property in props. | ||
Object.entries(props).forEach(([key, value]) => { | ||
// Figure out the type from the value. | ||
const type = | ||
value instanceof Object | ||
? // Object types do an 'instanceof' check, so must be an instance of the same object. | ||
Object.getPrototypeOf(value).constructor | ||
: // All other values use typeof value, e.g. "string", "number", "boolean", "symbol". | ||
typeof value; | ||
// Define the property. | ||
Object.defineProperty(object, key, { | ||
configurable: false, | ||
enumerable: true, | ||
// Return the current value. | ||
get: () => value, | ||
// Arrow function so that this refers to the _outer_ this. | ||
set: v => { | ||
// Figure out prefix. | ||
// Either ".myProp" or "MyRandomClass.myProp" | ||
const constructor = Object.getPrototypeOf(object).constructor.name; | ||
const prefix = `${constructor.name}.${key}`; | ||
// Check the value. | ||
this._check(v, type, prefix, this._error); | ||
// Update the value. | ||
value = v; | ||
} | ||
}); | ||
}); | ||
// Return object (for chaining etc). | ||
return object; | ||
} | ||
/** | ||
* Find the checker corresponding to a string. | ||
@@ -251,0 +200,0 @@ * |
const format = require("../functions/format"); | ||
const cause = require("../functions/cause"); | ||
const { EMPTY } = require("../constants"); | ||
@@ -6,3 +7,5 @@ | ||
* ValueError | ||
* | ||
* An error type that includes standardised formatting and prefixes. | ||
* ValueError semantically means "there is a problem with a value that has been passed into the current function". | ||
*/ | ||
@@ -12,14 +15,34 @@ class ValueError extends TypeError { | ||
* Constructor. | ||
* @param {string} message Message describing what went wrong, e.g. "Must be a string" | ||
* @param {mixed} value A value to debug shown at the end of the message, e.g. "Must be string (received 123)" | ||
* @param {string} prefix=undefined An optional prefix for the message e.g. the function name or the name of the value, e.g. "name: Must be string (received 123)" | ||
* @param {string} message="Invalid value" Message describing what went wrong, e.g. "Must be a string" | ||
* @param {mixed} value=EMPTY A value to debug shown at the end of the message, e.g. "Must be string (received 123)" | ||
* @param {string} prefix="" An optional prefix for the message e.g. the function name or the name of the value, e.g. "name: Must be string (received 123)" | ||
*/ | ||
constructor(message = "Invalid value", value = EMPTY, prefix = "") { | ||
// Save message through TypeError. | ||
// We format the `.message` property itself (rather than on the fly in `.toString()`) because Jest and others call it directly. | ||
super(format(message, value, prefix)); | ||
constructor(message, value, prefix) { | ||
// Defaults. | ||
if (arguments.length < 3) prefix = ""; | ||
if (arguments.length < 2) value = EMPTY; | ||
if (arguments.length < 1) message = "Invalid value"; | ||
// Save parts separately. | ||
// Super. | ||
super("******"); | ||
// Pin down the cause of the error. | ||
// This looks through the Error.stack and finds where e.g. check() or args() was actually called. | ||
const frame = cause(this.stack); | ||
/* istanbul ignore else */ | ||
if (frame) { | ||
// Prepend the caller function name to the prefix, e.g. "name" → "MyClass.myFunc(): name" | ||
/* istanbul ignore else */ | ||
if (frame.function) prefix = prefix ? `${frame.function}: ${prefix}` : frame.function; | ||
// Update file, line, column. | ||
this.fileName = frame.file; | ||
this.lineNumber = frame.line; | ||
this.stack = frame.stack; | ||
} | ||
// Save everything. | ||
this.message = format(message, value, prefix); | ||
this.prefix = prefix; | ||
this.reason = message; | ||
this.stack = this.stack.replace("******", this.message); | ||
if (value !== EMPTY) this.value = value; | ||
@@ -26,0 +49,0 @@ |
@@ -6,2 +6,3 @@ const ValueError = require("./errors/ValueError"); | ||
const format = require("./functions/format"); | ||
const destack = require("./functions/destack"); | ||
const { CLASS, KEYS, VALUES, EMPTY } = require("./constants"); | ||
@@ -16,2 +17,3 @@ | ||
module.exports.debug = debug; | ||
module.exports.destack = destack; | ||
module.exports.BlorkError = BlorkError; | ||
@@ -18,0 +20,0 @@ module.exports.ValueError = ValueError; |
@@ -18,10 +18,9 @@ const Blorker = require("../classes/Blorker"); | ||
return { | ||
assert: blorker.assert.bind(blorker), | ||
check: blorker.check.bind(blorker), | ||
args: blorker.args.bind(blorker), | ||
add: blorker.add.bind(blorker), | ||
checker: blorker.checker.bind(blorker), | ||
throws: blorker.throws.bind(blorker), | ||
props: blorker.props.bind(blorker) | ||
assert: blorker.Blorker$assert.bind(blorker), | ||
check: blorker.Blorker$check.bind(blorker), | ||
args: blorker.Blorker$args.bind(blorker), | ||
add: blorker.Blorker$add.bind(blorker), | ||
checker: blorker.Blorker$checker.bind(blorker), | ||
throws: blorker.Blorker$throws.bind(blorker) | ||
}; | ||
}; |
@@ -139,7 +139,7 @@ // Constants. | ||
if (typeof value.name === "string" && value.name.length > 0) { | ||
// Named function, e.g. myFunc() | ||
// Named function, e.g. "myFunc()" | ||
return `${value.name}()`; | ||
} else { | ||
// Unnamed function, e.g. function() | ||
return "anonymous function()"; | ||
// Unnamed function, e.g. "function ()" | ||
return "function ()"; | ||
} | ||
@@ -146,0 +146,0 @@ } |
@@ -13,3 +13,7 @@ const { EMPTY } = require("../constants"); | ||
*/ | ||
function format(message, value = EMPTY, prefix = "") { | ||
function format(message, value, prefix) { | ||
// Defaults. | ||
if (arguments.length < 3) prefix = ""; | ||
if (arguments.length < 2) value = EMPTY; | ||
// e.g. MyPrefix: Must be string (received 123) | ||
@@ -16,0 +20,0 @@ return ( |
@@ -65,3 +65,3 @@ const BlorkError = require("./errors/BlorkError"); | ||
// Loop through and call each checker. | ||
for (const c of ands) if (!c(v)) return false; // Fail. | ||
for (let i = 0; i < ands.length; i++) if (!ands[i](v)) return false; // Fail. | ||
return true; // Otherwise pass. | ||
@@ -95,3 +95,3 @@ }; | ||
// Loop through and call each checker. | ||
for (const c of ors) if (c(v)) return true; // Pass. | ||
for (let i = 0; i < ors.length; i++) if (ors[i](v)) return true; // Pass. | ||
return false; // Otherwise fail. | ||
@@ -98,0 +98,0 @@ }; |
{ | ||
"name": "blork", | ||
"description": "Blork! Mini runtime type checking in Javascript", | ||
"version": "7.6.3", | ||
"version": "8.0.0", | ||
"license": "0BSD", | ||
@@ -6,0 +6,0 @@ "author": "Dave Houlbrooke <dave@shax.com>", |
@@ -9,3 +9,3 @@ # Blork! Mini runtime type checking in Javascript | ||
Blork is fully unit tested and 100% covered (if you're into that!). | ||
Blork is fully unit tested and 100% covered (if you're into that!). Heaps of love has been put into the _niceness_ and consistency of error messages, so hopefully you'll enjoy that too. | ||
@@ -47,6 +47,6 @@ ## Installation | ||
// Call with invalid args. | ||
myFunc(123); // Throws ValueError "arguments[0]: Must be string (received 123)" | ||
myFunc("abc", "abc"); // Throws ValueError "arguments[1]: Must be number (received "abc")" | ||
myFunc(); // Throws ValueError "arguments[0]: Must be string (received undefined)" | ||
myFunc("abc", 123, true); // Throws ValueError "arguments: Too many arguments (expected 2) (received 3)" | ||
myFunc(123); // Throws ValueError "myFunc(): arguments[0]: Must be string (received 123)" | ||
myFunc("abc", "abc"); // Throws ValueError "myFunc(): arguments[1]: Must be number (received "abc")" | ||
myFunc(); // Throws ValueError "myFunc(): arguments[0]: Must be string (received undefined)" | ||
myFunc("abc", 123, true); // Throws ValueError "myFunc(): arguments: Too many arguments (expected 2) (received 3)" | ||
``` | ||
@@ -93,4 +93,10 @@ | ||
// Check all the options with a literal type (note that keepAlive is optional). | ||
check(options, { name: "string", required: "boolean", keepAlive: "number?" }); | ||
check(options, { name: "string", required: "boolean", keepAlive: "number?" }, "options"); | ||
} | ||
// Checks that pass. | ||
myFunc({ name: "Dog", required: true }); // No error. | ||
// Checks that fail. | ||
myFunc({ name: 123, required: false }); // Throws ValueError "myFunc(): options.name: Must be string (received 123)" | ||
``` | ||
@@ -249,3 +255,3 @@ | ||
// Fails. | ||
myFunc("A dog sits over there"); // Throws ValueError "arguments[1]: Must be string containing "cat" (received "A dog sits over there")" | ||
myFunc("A dog sits over there"); // Throws ValueError "myFunc(): arguments[1]: Must be string containing "cat" (received "A dog sits over there")" | ||
``` | ||
@@ -292,46 +298,2 @@ | ||
### props(): Define Blork-checked object properties | ||
The `props()` function can define an object properties (like `Object.defineProperties()`) that are readable and writable, BUT the value must always match the type it was initially defined with. | ||
This allows you to create objects with properties that have a guaranteed type. This makes your object more robust and removes the need to check the type of the property before using it. | ||
`props()` accepts two arguments: | ||
1. `object` The object to define the property on | ||
2. `props` A set of properties to define on the object and lock down | ||
```js | ||
import { prop } from "blork"; | ||
// Make an object. | ||
const obj = {}; | ||
// Define typed properties on the object. | ||
props(obj, { | ||
"name": "Mel", | ||
"coords": { lat: 0, lng: 0 }, | ||
"map": new Map() | ||
}); | ||
// Setting the value to an allowed type is fine. | ||
obj.name = "John"; | ||
obj.coords = { lat: 28.20, lng: 12.00 }; | ||
obj.map = new Map(); | ||
// Setting the value to a disallowed type is not fine. | ||
obj.name = 123; // Throws TypeError "name: Must be string (received 123)" | ||
obj.coords = 123; // Throws TypeError "coords: Must be plain object (received 123)" | ||
obj.coords = { lat: "abc", lng: 0 }; // Throws TypeError "coords.lat: Must be number (received "abc")" | ||
obj.map = new Set(); // Throws TypeError "map: must be instance of Map (received Set) | ||
``` | ||
```js | ||
import { prop } from "blork"; | ||
// Make an object. | ||
const obj = {}; | ||
``` | ||
### debug(): Debug any value as a string. | ||
@@ -795,2 +757,6 @@ | ||
- 8.0.0 | ||
- Remove `props()` functionality (bloat) | ||
- Prepend function name to `ValueError` errors, e.g. `MyClass.myFunc(): Must be string...` | ||
- Add `destack()` method that parses `Error.stack` across major browsers | ||
- 7.6.0 | ||
@@ -797,0 +763,0 @@ - Allow `prefix` and `error` arguments for `check()` and `args()` |
@@ -11,4 +11,4 @@ const BlorkError = require("../../lib/errors/BlorkError"); | ||
test("Return correct error with message only", () => { | ||
expect(new BlorkError("Message")).toHaveProperty("message", "Message"); | ||
expect(new BlorkError("Message")).toHaveProperty("prefix", ""); | ||
expect(new BlorkError("Message")).toHaveProperty("message", "Object.test(): Message"); | ||
expect(new BlorkError("Message")).toHaveProperty("prefix", "Object.test()"); | ||
expect(new BlorkError("Message")).toHaveProperty("reason", "Message"); | ||
@@ -18,4 +18,4 @@ expect(new BlorkError("Message")).not.toHaveProperty("value"); | ||
test("Return correct error with message and value", () => { | ||
expect(new BlorkError("Message", 123)).toHaveProperty("message", "Message (received 123)"); | ||
expect(new BlorkError("Message", 123)).toHaveProperty("prefix", ""); | ||
expect(new BlorkError("Message", 123)).toHaveProperty("message", "Object.test(): Message (received 123)"); | ||
expect(new BlorkError("Message", 123)).toHaveProperty("prefix", "Object.test()"); | ||
expect(new BlorkError("Message", 123)).toHaveProperty("reason", "Message"); | ||
@@ -25,4 +25,7 @@ expect(new BlorkError("Message", 123)).toHaveProperty("value", 123); | ||
test("Return correct error with message, value, and prefix", () => { | ||
expect(new BlorkError("Message", 123, "Prefix")).toHaveProperty("message", "Prefix: Message (received 123)"); | ||
expect(new BlorkError("Message", 123, "Prefix")).toHaveProperty("prefix", "Prefix"); | ||
expect(new BlorkError("Message", 123, "Prefix")).toHaveProperty( | ||
"message", | ||
"Object.test(): Prefix: Message (received 123)" | ||
); | ||
expect(new BlorkError("Message", 123, "Prefix")).toHaveProperty("prefix", "Object.test(): Prefix"); | ||
expect(new BlorkError("Message", 123, "Prefix")).toHaveProperty("reason", "Message"); | ||
@@ -29,0 +32,0 @@ expect(new BlorkError("Message", 123, "Prefix")).toHaveProperty("value", 123); |
@@ -7,4 +7,4 @@ const { ValueError, EMPTY } = require("../../lib/exports"); | ||
expect(new ValueError()).toHaveProperty("name", "ValueError"); | ||
expect(new ValueError()).toHaveProperty("message", "Invalid value"); | ||
expect(new ValueError()).toHaveProperty("prefix", ""); | ||
expect(new ValueError()).toHaveProperty("message", "Object.test(): Invalid value"); | ||
expect(new ValueError()).toHaveProperty("prefix", "Object.test()"); | ||
expect(new ValueError()).toHaveProperty("reason", "Invalid value"); | ||
@@ -15,20 +15,47 @@ expect(new ValueError()).not.toHaveProperty("value"); | ||
expect(new ValueError("Message")).toHaveProperty("name", "ValueError"); | ||
expect(new ValueError("Message")).toHaveProperty("message", "Message"); | ||
expect(new ValueError("Message")).toHaveProperty("prefix", ""); | ||
expect(new ValueError("Message")).toHaveProperty("message", "Object.test(): Message"); | ||
expect(new ValueError("Message")).toHaveProperty("prefix", "Object.test()"); | ||
expect(new ValueError("Message")).toHaveProperty("reason", "Message"); | ||
expect(new ValueError("Message")).not.toHaveProperty("value"); | ||
function abc() { | ||
return new ValueError("Message"); | ||
} | ||
expect(abc()).toHaveProperty("name", "ValueError"); | ||
expect(abc()).toHaveProperty("message", "abc(): Message"); | ||
expect(abc()).toHaveProperty("prefix", "abc()"); | ||
expect(abc()).toHaveProperty("reason", "Message"); | ||
expect(abc()).not.toHaveProperty("value"); | ||
}); | ||
test("Return correct error with message and value", () => { | ||
expect(new ValueError("Message", 123)).toHaveProperty("name", "ValueError"); | ||
expect(new ValueError("Message", 123)).toHaveProperty("message", "Message (received 123)"); | ||
expect(new ValueError("Message", 123)).toHaveProperty("prefix", ""); | ||
expect(new ValueError("Message", 123)).toHaveProperty("message", "Object.test(): Message (received 123)"); | ||
expect(new ValueError("Message", 123)).toHaveProperty("prefix", "Object.test()"); | ||
expect(new ValueError("Message", 123)).toHaveProperty("reason", "Message"); | ||
expect(new ValueError("Message", 123)).toHaveProperty("value", 123); | ||
function abc() { | ||
return new ValueError("Message", 123); | ||
} | ||
expect(abc()).toHaveProperty("name", "ValueError"); | ||
expect(abc()).toHaveProperty("message", "abc(): Message (received 123)"); | ||
expect(abc()).toHaveProperty("prefix", "abc()"); | ||
expect(abc()).toHaveProperty("reason", "Message"); | ||
expect(abc()).toHaveProperty("value", 123); | ||
}); | ||
test("Return correct error with message, value, and prefix", () => { | ||
expect(new ValueError("Message", 123, "Prefix")).toHaveProperty("name", "ValueError"); | ||
expect(new ValueError("Message", 123, "Prefix")).toHaveProperty("message", "Prefix: Message (received 123)"); | ||
expect(new ValueError("Message", 123, "Prefix")).toHaveProperty("prefix", "Prefix"); | ||
expect(new ValueError("Message", 123, "Prefix")).toHaveProperty( | ||
"message", | ||
"Object.test(): Prefix: Message (received 123)" | ||
); | ||
expect(new ValueError("Message", 123, "Prefix")).toHaveProperty("prefix", "Object.test(): Prefix"); | ||
expect(new ValueError("Message", 123, "Prefix")).toHaveProperty("reason", "Message"); | ||
expect(new ValueError("Message", 123, "Prefix")).toHaveProperty("value", 123); | ||
function abc() { | ||
return new ValueError("Message", 123, "Prefix"); | ||
} | ||
expect(abc()).toHaveProperty("name", "ValueError"); | ||
expect(abc()).toHaveProperty("message", "abc(): Prefix: Message (received 123)"); | ||
expect(abc()).toHaveProperty("prefix", "abc(): Prefix"); | ||
expect(abc()).toHaveProperty("reason", "Message"); | ||
expect(abc()).toHaveProperty("value", 123); | ||
}); | ||
@@ -35,0 +62,0 @@ test("Does not show value in message if value is EMPTY", () => { |
@@ -45,9 +45,11 @@ const { args, BlorkError, ValueError } = require("../lib/exports"); | ||
const argsObj = { "0": true, "1": true, "2": true, length: 3 }; | ||
expect(() => args(argsObj, [Boolean, Boolean])).toThrow(/^arguments/); | ||
expect(() => args(argsObj, [String, String, String])).toThrow(/^arguments/); | ||
// Still includes "expect(): " because we use the stack to calculate the function prefix | ||
expect(() => args(argsObj, [Boolean, Boolean])).toThrow(/^expect\(\): arguments:/); | ||
expect(() => args(argsObj, [String, String, String])).toThrow(/^expect\(\): arguments\[0\]:/); | ||
}); | ||
test("Error prefix can be altered by setting prefix argument", () => { | ||
const argsObj = { "0": true, "1": true, "2": true, length: 3 }; | ||
expect(() => args(argsObj, [Boolean, Boolean], "myprefix")).toThrow(/^myprefix/); | ||
expect(() => args(argsObj, [String, String, String], "myprefix")).toThrow(/^myprefix/); | ||
// Still includes "expect(): " because we use the stack to calculate the function prefix | ||
expect(() => args(argsObj, [Boolean, Boolean], "myprefix")).toThrow(/^expect\(\): myprefix:/); | ||
expect(() => args(argsObj, [String, String, String], "myprefix")).toThrow(/^expect\(\): myprefix\[0\]:/); | ||
}); | ||
@@ -54,0 +56,0 @@ }); |
@@ -24,6 +24,8 @@ const { assert, BlorkError, ValueError } = require("../lib/exports"); | ||
test("Error prefix defaults to no prefix", () => { | ||
expect(() => assert(false, "be assertively true")).toThrow(/^Must/); | ||
// Still includes "expect(): " because we use the stack to calculate the function prefix | ||
expect(() => assert(false, "be assertively true")).toThrow(/^expect\(\): Must/); | ||
}); | ||
test("Error prefix can be altered by setting prefix argument", () => { | ||
expect(() => assert(false, "be assertively true", "myprefix")).toThrow(/^myprefix/); | ||
// Still includes "expect(): " because we use the stack to calculate the function prefix | ||
expect(() => assert(false, "be assertively true", "myprefix")).toThrow(/^expect\(\): myprefix/); | ||
}); | ||
@@ -30,0 +32,0 @@ }); |
@@ -181,2 +181,3 @@ const BlorkError = require("../lib/errors/BlorkError"); | ||
expect(() => check(1, "string | string & string")).toThrow(/Must be \(string or string\) and string/); | ||
expect(() => check(1, "{ string } | null")).toThrow(/Must be \(plain object containing string\) or null/); | ||
}); | ||
@@ -183,0 +184,0 @@ }); |
@@ -32,6 +32,8 @@ const { check, BlorkError, ValueError } = require("../lib/exports"); | ||
test("Error prefix defaults to no prefix", () => { | ||
expect(() => check(123, String)).toThrow(/^Must/); | ||
// Still includes "expect(): " because we use the stack to calculate the function prefix | ||
expect(() => check(123, String)).toThrow(/^expect\(\): Must/); | ||
}); | ||
test("Error prefix can be altered by setting prefix argument", () => { | ||
expect(() => check(123, String, "myprefix")).toThrow(/^myprefix/); | ||
// Still includes "expect(): " because we use the stack to calculate the function prefix | ||
expect(() => check(123, String, "myprefix")).toThrow(/^expect\(\): myprefix/); | ||
}); | ||
@@ -38,0 +40,0 @@ }); |
@@ -70,3 +70,3 @@ const { debug } = require("../lib/exports"); | ||
test("Return correct debug string for functions", () => { | ||
expect(debug(function() {})).toBe("anonymous function()"); | ||
expect(debug(function() {})).toBe("function ()"); | ||
expect(debug(function dog() {})).toBe("dog()"); | ||
@@ -73,0 +73,0 @@ }); |
172117
46
3026
811