Comparing version 7.4.0 to 7.5.0
@@ -5,5 +5,7 @@ const BlorkError = require("./errors/BlorkError"); | ||
// `&` ampersand with possible whitespace either side that isn't not enclosed in parenthesis or braces (via `(?!` lookahead). | ||
const R_AND_SPLIT = /\s*&+\s*(?![^(]*\))(?![^{]*\})/; | ||
const R_AND_SPLIT = /\s*&+\s*(?![^(]*\))(?![^{]*\})(?![^[]*\])/; | ||
// `|` ampersand with possible whitespace either side that isn't not enclosed in parenthesis or braces (via `(?!` lookahead). | ||
const R_OR_SPLIT = /\s*\|+\s*(?![^(]*\))(?![^{]*\})/; | ||
const R_OR_SPLIT = /\s*\|+\s*(?![^(]*\))(?![^{]*\})(?![^[]*\])/; | ||
// Split commas in a tuple. | ||
const R_TUPLE_SPLIT = /\s*,\s*/; | ||
@@ -44,4 +46,4 @@ // List of available modifiers. | ||
{ | ||
// `&` ampersand appears anywhere in the type, except when enclosed in parenthesis or braces (via `(?!` lookahead). | ||
regex: /&(?![^(]*\))(?![^{]*\})/, | ||
// `&` ampersand appears anywhere in the type, except when enclosed in any parenthesis (via `(?!` lookahead). | ||
regex: /&(?![^(]*\))(?![^{]*\})(?![^[]*\])/, | ||
callback(matches, find) { | ||
@@ -69,3 +71,3 @@ // Split type and get corresponding checker for each. | ||
// `|` pipe appears anywhere in the type, except when enclosed in parenthesis or braces (via `(?!` lookahead). | ||
regex: /\|(?![^(]*\))(?![^{]*\})/, | ||
regex: /\|(?![^(]*\))(?![^{]*\})(?![^[]*\])/, | ||
callback(matches, find) { | ||
@@ -90,2 +92,33 @@ // Split type and get corresponding checker for each. | ||
// Tuple type, e.g. `[str, num]` | ||
{ | ||
// `[]` enclosing the entire type with no [] in between. | ||
regex: /^\[\s*([^[\]]+)\s*\]$/, | ||
callback(matches, find) { | ||
// Get normal checker. | ||
const arrayChecker = find("array"); | ||
const tupleCheckers = matches[1].split(R_TUPLE_SPLIT).map(find); | ||
// Create array type checker. | ||
const arrayTupleChecker = v => { | ||
// Must be an array. | ||
if (!arrayChecker(v)) return false; | ||
// Tuple length must be exactly the same. | ||
if (v.length !== tupleCheckers.length) return false; | ||
// Tuple array | ||
// Loop through types and match each with a value recursively. | ||
return v.every((w, i) => tupleCheckers[i](w)); | ||
}; | ||
// Description message prepends "plain array containing"; | ||
const descs = tupleCheckers.map(c => c.desc).join(", "); | ||
arrayTupleChecker.desc = `${arrayChecker.desc} tuple containing: ${descs}`; | ||
// Return it. | ||
return arrayTupleChecker; | ||
} | ||
}, | ||
// Array type, e.g. `str[]` | ||
@@ -117,5 +150,5 @@ { | ||
// Object type, e.g. `{ camel: num }` or `num{}` | ||
// Object type, e.g. `{ camel: num }` | ||
{ | ||
// `{` and `}` surrounding the type or `{}` following the type | ||
// `{` and `}` surrounding the type | ||
// e.g. `{ num }`, or `{ camel: num }` (whitespace optional). | ||
@@ -122,0 +155,0 @@ regex: /^\{\s*(?:(.+):\s*)?(.+?)\s*\}$/, |
{ | ||
"name": "blork", | ||
"description": "Blork! Mini runtime type checking in Javascript", | ||
"version": "7.4.0", | ||
"version": "7.5.0", | ||
"license": "0BSD", | ||
@@ -6,0 +6,0 @@ "author": "Dave Houlbrooke <dave@shax.com>", |
@@ -431,2 +431,3 @@ # Blork! Mini runtime type checking in Javascript | ||
| `type[]` | Array type (all array entries must match type) | ||
| `[type1, type2]` | Tuple type (must match tuple exactly) | ||
| `{ type }` | Object value type (all own props must match type | ||
@@ -448,7 +449,21 @@ | `{ keyType: type }` | Object key:value type (keys and own props must match types) | ||
// Fail. | ||
check([1, 2], "str[]"); // Throws ValueError "Must be plain array containing only string (received [1, 2])" | ||
check(["a"], "int[]"); // Throws ValueError "Must be plain array containing only integer (received ["a"])" | ||
check([], "int[]+"); // Throws ValueError "Must be non-empty plain array containing only integer (received [])" | ||
check([1, 2], "str[]"); // Throws ValueError "Must be plain array containing: string (received [1, 2])" | ||
check(["a"], "int[]"); // Throws ValueError "Must be plain array containing: integer (received ["a"])" | ||
check([], "int[]+"); // Throws ValueError "Must be non-empty plain array containing: integer (received [])" | ||
``` | ||
Array tuples can be specified by surrounding types in `[]` brackets. | ||
```js | ||
// Pass. | ||
check([true, false], "[bool, bool]") // No error. | ||
check(["a", "b"], "[str, str]") // No error. | ||
check([1, 2, 3], "[num, num, num]"); // No error. | ||
// Fail. | ||
check([true, true], "[str, str]") // Throws ValueError "Must be plain array tuple containing: string, string (received [true, true])" | ||
check([true], "[bool, bool]") // Throws ValueError "Must be plain array tuple containing: boolean, boolean (received [true])" | ||
check(["a", "b", "c"], "[str, str]") // Throws ValueError "Must be plain array tuple containing: string, string (received ["a", "b", "c"])" | ||
``` | ||
Check for objects only containing strings of a specified type by surrounding the type in `{}` braces. This means the check looks for a plain object whose contents only include the specified type (whitespace is optional). | ||
@@ -464,5 +479,5 @@ | ||
// Fail. | ||
check({ a: 1, b: 2 }, "{str}"); // Throws ValueError "Must be plain object containing only string (received [1, 2])" | ||
check({ a: "a" }, "{int}"); // Throws ValueError "Must be plain object containing only integer (received ["a"])" | ||
check({}, "{int}+"); // Throws ValueError "Must be non-empty plain object containing only integer (received [])" | ||
check({ a: 1, b: 2 }, "{str}"); // Throws ValueError "Must be plain object containing: string (received [1, 2])" | ||
check({ a: "a" }, "{int}"); // Throws ValueError "Must be plain object containing: integer (received ["a"])" | ||
check({}, "{int}+"); // Throws ValueError "Must be non-empty plain object containing: integer (received [])" | ||
``` | ||
@@ -749,2 +764,4 @@ | ||
- 7.5.0 | ||
- Enable tuple arrays via `[type1, type2]` syntax | ||
- 7.4.0 | ||
@@ -751,0 +768,0 @@ - Make properties created with `props()` enumerable |
@@ -106,2 +106,15 @@ const BlorkError = require("../lib/errors/BlorkError"); | ||
}); | ||
describe("Tuple types", () => { | ||
test('Tuple types pass correctly', () => { | ||
expect(check(["abc"], "[str]")).toBe(undefined); | ||
expect(check([123], "[num]")).toBe(undefined); | ||
expect(check([123, "abc", true], "[num, str, bool]")).toBe(undefined); | ||
}); | ||
test('Tuple types fail correctly', () => { | ||
expect(() => check([123, 123, false], "[num, num, num]")).toThrow(TypeError); | ||
expect(() => check([123, 123], "[num, num, num]")).toThrow(TypeError); // Too few. | ||
expect(() => check([123, 123, 123, 123], "[num, num, num]")).toThrow(TypeError); // Too many. | ||
expect(() => check(true, "[num]")).toThrow(TypeError); // Not an array. | ||
}); | ||
}); | ||
describe("Object types", () => { | ||
@@ -108,0 +121,0 @@ test("Object types pass correctly", () => { |
150776
2529
813