Comparing version 1.0.0-pre.4 to 1.0.0-pre.5
@@ -7,10 +7,13 @@ var canSymbol = require("can-symbol"); | ||
var newSymbol = canSymbol.for("can.new"); | ||
var isMemberSymbol = canSymbol.for("can.isMember"); | ||
QUnit.module('can-type - Type methods'); | ||
function equal(assert, result, expected) { | ||
assert.equal(expected, result, "Result matches expected"); | ||
assert.equal(result, expected, "Result matches expected"); | ||
} | ||
function strictEqual(assert, result, expected) { | ||
assert.strictEqual(expected, result, "Result matches expected strictly"); | ||
assert.strictEqual(result, expected, "Result matches expected strictly"); | ||
} | ||
@@ -28,2 +31,6 @@ | ||
function notOk(assert, reason) { | ||
assert.ok(false, reason || "Expected to throw"); | ||
} | ||
function throwsBecauseOfWrongType(assert) { | ||
@@ -33,2 +40,6 @@ ok(assert, "Throws when the wrong type is provided"); | ||
function shouldHaveThrownBecauseOfWrongType(assert) { | ||
notOk(assert, "Should have thrown because the wrong type was provided"); | ||
} | ||
var checkIsNaN = { | ||
@@ -44,2 +55,10 @@ check: isNaN | ||
var checkValue = function(comparison) { | ||
return { | ||
check: function(assert, result) { | ||
assert.strictEqual(result, comparison, "value has been correctly converted"); | ||
} | ||
}; | ||
}; | ||
var checkBoolean = function (comparison) { | ||
@@ -87,7 +106,8 @@ return { | ||
var Integer = {}; | ||
Integer[canSymbol.for("can.new")] = function(val) { | ||
Integer[newSymbol] = function(val) { | ||
return parseInt(val); | ||
}; | ||
Integer[canSymbol.for("can.isMember")] = function(val) { | ||
return Number.isInteger(val); | ||
Integer[isMemberSymbol] = function(value) { | ||
// “polyfill” for Number.isInteger because it’s not supported in IE11 | ||
return typeof value === "number" && isFinite(value) && Math.floor(value) === value; | ||
}; | ||
@@ -135,2 +155,48 @@ canReflect.setName(Integer, "Integer"); | ||
maybeConvert: checkNumber(44) | ||
}, | ||
{ | ||
Type: type.check(Number), | ||
value: "44", | ||
convert: checkNumber(44), | ||
maybeConvert: checkNumber(44), | ||
check: { | ||
check: shouldHaveThrownBecauseOfWrongType, | ||
throws: throwsBecauseOfWrongType | ||
} | ||
}, | ||
{ | ||
Type: type.maybe(Number), | ||
value: "44", | ||
convert: checkNumber(44), | ||
check: throwsBecauseOfWrongType, | ||
maybe: throwsBecauseOfWrongType | ||
}, | ||
{ | ||
Type: type.maybe(Number), | ||
value: null, | ||
convert: checkValue(null), | ||
check: checkValue(null) | ||
}, | ||
{ | ||
Type: type.convert(Number), | ||
value: "33", | ||
check: throwsBecauseOfWrongType, | ||
maybe: throwsBecauseOfWrongType, | ||
convert: checkNumber(33), | ||
maybeConvert: checkNumber(33) | ||
}, | ||
{ | ||
Type: type.convert(Number), | ||
value: null, | ||
check: throwsBecauseOfWrongType, | ||
maybe: throwsBecauseOfWrongType, | ||
convert: checkNumber(0), | ||
maybeConvert: checkValue(null) | ||
}, | ||
{ | ||
Type: type.check(Integer), value: 44.4, | ||
convert: checkNumber(44), | ||
maybeConvert: checkNumber(44), | ||
check: throwsBecauseOfWrongType, | ||
maybe: throwsBecauseOfWrongType | ||
} | ||
@@ -243,1 +309,31 @@ ]; | ||
}); | ||
QUnit.test("Type equality", function(assert) { | ||
assert.strictEqual(type.convert(type.check(String)), type.convert(type.check(String))); | ||
assert.strictEqual(type.maybe(String), type.maybe(String)); | ||
}); | ||
QUnit.test("TypeObjects do not need to throw themselves", function(assert) { | ||
assert.expect(2); | ||
function isABC(str) { | ||
return "ABC".indexOf(str.toString()) !== -1; | ||
} | ||
var OnlyABC = {}; | ||
OnlyABC[newSymbol] = function() { | ||
return "A"; | ||
}; | ||
OnlyABC[isMemberSymbol] = isABC; | ||
var StrictABC = type.check(OnlyABC); | ||
try { | ||
canReflect.convert("D", StrictABC); | ||
} catch(e) { | ||
assert.ok(true, "Throw because isMember failed"); | ||
} | ||
var NotStrictABC = type.convert(StrictABC); | ||
var val = canReflect.convert("D", NotStrictABC); | ||
assert.equal(val, "A", "converted"); | ||
}); |
259
can-type.js
@@ -6,18 +6,5 @@ var canReflect = require("can-reflect"); | ||
var newSymbol = canSymbol.for("can.new"); | ||
var getSchemaSymbol = canSymbol.for("can.getSchema"); | ||
var type = exports; | ||
var primitives = new Map(); | ||
canReflect.each({ | ||
"boolean": Boolean, | ||
"number": Number, | ||
"string": String | ||
}, function(Type, typeString) { | ||
primitives.set(Type, { | ||
isMember: function(val) { | ||
return typeof val === typeString; | ||
} | ||
}); | ||
}); | ||
function makeSchema(values) { | ||
@@ -32,100 +19,182 @@ return function(){ | ||
function makeTypeFactory(createSchema) { | ||
return function makeTypeWithAction(action) { | ||
var typeCache = new Map(); | ||
// Make an isMember function that prefers a isMemberSymbol on the Type. | ||
function makeIsMember(check) { | ||
return function isMember(value) { | ||
var Type = this.Type; | ||
if(Type[isMemberSymbol]) { | ||
return Type[isMemberSymbol](value); | ||
} | ||
if(check.call(this, value)) { | ||
return true; | ||
} | ||
return false; | ||
}; | ||
} | ||
return function createType(Type) { | ||
if(typeCache.has(Type)) { | ||
return typeCache.get(Type); | ||
} | ||
// Default isMember for non-maybe, non-primitives | ||
function isMember(value) { | ||
return value instanceof this.Type; | ||
} | ||
var isMember = function() { return false; }; | ||
if(primitives.has(Type)) { | ||
isMember = primitives.get(Type).isMember; | ||
} | ||
// isMember for maybe non-primitives | ||
function maybeIsMember(value) { | ||
return value == null || value instanceof this.Type; | ||
} | ||
var createTypeWithSchema = createSchema(Type, action, isMember); | ||
typeCache.set(Type, createTypeWithSchema); | ||
return createTypeWithSchema; | ||
}; | ||
}; | ||
// Default "can.new" | ||
function canNew(value) { | ||
if(this.isStrict && !this[isMemberSymbol](value)) { | ||
return check(this.Type, value); | ||
} | ||
return canReflect.convert(value, this.Type); | ||
} | ||
var createMaybe = makeTypeFactory(function createMaybe(Type, action, isMember) { | ||
var typeObject = {}; | ||
// "can.new" for Booleans | ||
function booleanNew(value) { | ||
if(this.isStrict && !this[isMemberSymbol](value)) { | ||
return check(Boolean, value); | ||
} | ||
if (value === "false" || value=== "0") { | ||
return false; | ||
} | ||
return Boolean(value); | ||
} | ||
var values = [Type, null, undefined]; | ||
if (Type === Boolean) { | ||
values = [true, false, null, undefined]; | ||
var maybeValues = Object.freeze([null, undefined]); | ||
function check(Type, val) { | ||
throw new Error('Type value ' + typeof val === "string" ? '"' + val + '"' : val + ' is not of type ' + canReflect.getName(Type) + '.' ); | ||
} | ||
/* Base converting proto */ | ||
var baseType = {}; | ||
canReflect.assignSymbols(baseType, { | ||
isStrict: false, | ||
"can.new": canNew, | ||
"can.isMember": makeIsMember(isMember) | ||
}); | ||
/* Descriptor for applying strictness */ | ||
var strictDescriptor = { | ||
isStrict: { | ||
enumerable: true, | ||
value: true | ||
} | ||
}; | ||
strictDescriptor[newSymbol] = { | ||
enumerable: true, | ||
value: canNew | ||
}; | ||
return canReflect.assignSymbols(typeObject, { | ||
"can.new": function(val) { | ||
if (val == null) { | ||
return val; | ||
} | ||
var isInstance = typeof Type === "function" && val instanceof Type; | ||
if (isInstance || isMember(val)) { | ||
return val; | ||
} | ||
// Convert `'false'` into `false` | ||
if (Type === Boolean && (val === 'false' || val === '0')) { | ||
return false; | ||
} | ||
return action(Type, val); | ||
}, | ||
"can.getSchema": makeSchema(values), | ||
"can.getName": function(){ | ||
return canReflect.getName(Type); | ||
}, | ||
"can.isMember": function(value) { | ||
if(Type[isMemberSymbol]) { | ||
return Type[isMemberSymbol](value); | ||
} | ||
return value == null || value instanceof Type || isMember(value); | ||
/* Descriptor for applying nonstrictness */ | ||
var unStrictDescriptor = { | ||
isStrict: { | ||
enumerable: true, | ||
value: false | ||
} | ||
}; | ||
/* Descriptor for maybe types */ | ||
var maybeDescriptors = {}; | ||
maybeDescriptors[isMemberSymbol] = { | ||
enumerable: false, | ||
value: makeIsMember(maybeIsMember) | ||
}; | ||
/* Base maybe type */ | ||
var baseMaybeType = Object.create(baseType, maybeDescriptors); | ||
var strictMaybeDescriptor = { | ||
isStrict: strictDescriptor.isStrict | ||
}; | ||
strictMaybeDescriptor[isMemberSymbol] = maybeDescriptors[isMemberSymbol]; | ||
strictMaybeDescriptor[newSymbol] = { | ||
enumerable: true, | ||
value: canNew | ||
}; | ||
var unStrictMaybeDescriptor = { | ||
isStrict: unStrictDescriptor.isStrict | ||
}; | ||
unStrictMaybeDescriptor[isMemberSymbol] = maybeDescriptors[isMemberSymbol]; | ||
var primitiveBaseTypes = new Map(); | ||
canReflect.each({ | ||
"boolean": Boolean, | ||
"number": Number, | ||
"string": String | ||
}, function(Type, typeString) { | ||
var noMaybeDescriptor = {}; | ||
var maybeDescriptor = {}; | ||
noMaybeDescriptor[isMemberSymbol] = { | ||
enumerable: true, | ||
value: function isMember(val) { | ||
return typeof val === typeString; | ||
} | ||
}); | ||
}); | ||
}; | ||
var createNoMaybe = makeTypeFactory(function createNoMaybe(Type, action, isMember) { | ||
var typeObject = {}; | ||
maybeDescriptor[isMemberSymbol] = { | ||
enumerable: true, | ||
value: function isMaybeMember(val) { | ||
return val == null || typeof val === typeString; | ||
} | ||
}; | ||
var values = [Type]; | ||
if (Type === Boolean) { | ||
values = [true, false]; | ||
if(Type === Boolean) { | ||
noMaybeDescriptor[newSymbol] = maybeDescriptor[newSymbol] = { | ||
enumerable: true, | ||
value: booleanNew | ||
}; | ||
maybeDescriptor[getSchemaSymbol] = makeSchema([true, false, null, undefined]); | ||
noMaybeDescriptor[getSchemaSymbol] = makeSchema([true, false]); | ||
} | ||
return canReflect.assignSymbols(typeObject, { | ||
"can.new": function(val) { | ||
var isInstance = typeof Type === "function" && val instanceof Type; | ||
if (isInstance || isMember(val)) { | ||
return val; | ||
} | ||
// Convert `'false'` into `false` | ||
if (Type === Boolean && (val === 'false' || val === '0')) { | ||
return false; | ||
} | ||
return action(Type, val); | ||
}, | ||
"can.getSchema": makeSchema(values), | ||
"can.getName": function(){ | ||
return canReflect.getName(Type); | ||
}, | ||
"can.isMember": function(value) { | ||
if(Type[isMemberSymbol]) { | ||
return Type[isMemberSymbol](value); | ||
} | ||
return value instanceof Type || isMember(value); | ||
} | ||
primitiveBaseTypes.set(Type, { | ||
noMaybe: Object.create(baseType, noMaybeDescriptor), | ||
maybe: Object.create(baseMaybeType, maybeDescriptor) | ||
}); | ||
}); | ||
function check(Type, val) { | ||
throw new Error('Type value ' + typeof val === "string" ? '"' + val + '"' : val + ' is not of type ' + canReflect.getName(Type) + '.' ); | ||
function addType(typeObject, Type) { | ||
if(!('Type' in typeObject)) { | ||
Object.defineProperty(typeObject, 'Type', { | ||
value: Type | ||
}); | ||
} | ||
} | ||
function convert(Type, val) { | ||
return canReflect.convert(val, Type); | ||
function getBase(Type, baseType, basePrimitiveName) { | ||
if(primitiveBaseTypes.has(Type)) { | ||
return primitiveBaseTypes.get(Type)[basePrimitiveName]; | ||
} else if(isTypeObject(Type)) { | ||
return Type; | ||
} else { | ||
return baseType; | ||
} | ||
} | ||
function makeTypeFactory(name, baseType, childDescriptors, primitiveMaybe, schemaValues) { | ||
var typeCache = new WeakMap(); | ||
return function(Type) { | ||
if(typeCache.has(Type)) { | ||
return typeCache.get(Type); | ||
} | ||
var base = getBase(Type, baseType, primitiveMaybe); | ||
var typeObject = Object.create(base, childDescriptors); | ||
addType(typeObject, Type); | ||
typeObject[getSchemaSymbol] = makeSchema([Type].concat(schemaValues)); | ||
canReflect.setName(typeObject, "type." + name + "(" + canReflect.getName(Type) + ")"); | ||
typeCache.set(Type, typeObject); | ||
return typeObject; | ||
}; | ||
} | ||
exports.check = makeTypeFactory("check", baseType, strictDescriptor, "noMaybe", []); | ||
exports.convert = makeTypeFactory("convert", baseType, unStrictDescriptor, "noMaybe", []); | ||
exports.maybe = makeTypeFactory("maybe", baseMaybeType, strictMaybeDescriptor, "maybe", maybeValues); | ||
exports.maybeConvert = makeTypeFactory("maybeConvert", baseMaybeType, unStrictMaybeDescriptor, "maybe", maybeValues); | ||
function isTypeObject(Type) { | ||
@@ -172,8 +241,2 @@ if(canReflect.isPrimitive(Type)) { | ||
exports.check = createNoMaybe(check); | ||
exports.maybe = createMaybe(check); | ||
exports.convert = createNoMaybe(convert); | ||
exports.maybeConvert = createMaybe(convert); | ||
// type checking should not throw in production | ||
@@ -180,0 +243,0 @@ if(process.env.NODE_ENV === 'production') { |
@@ -198,1 +198,31 @@ @module {Object} can-type | ||
- `type.convert` and `type.maybeConvert` will convert the value using [can-reflect.convert]. | ||
## Applying multiple type functions | ||
The type functions [can-type/check], [can-type/convert], [can-type/maybe], and [can-type/maybeConvert] all return a [can-type.typeobject]. Since they also can take a TypeObject as an argument, this means you can apply multiple type functions. | ||
For example, using [can-type/convert] and [can-type/maybe] is equivalent to using [can-type/maybeConvert]: | ||
```js | ||
import { Reflect, type } from "can"; | ||
const MaybeConvertString = type.convert(type.maybe(String)); | ||
console.log(2, Reflect.convert(2, MaybeConvertString)); // "2" | ||
console.log(null, Reflect.convert(2, MaybeConvertString)); // null | ||
``` | ||
@codepen | ||
Another example is taking a strict type and making it a converter type by wrapping with [can-type/convert]: | ||
```js | ||
import { Reflect, can } from "can"; | ||
const StrictString = type.check(String); | ||
const NonStrictString = type.convert(StrictString); | ||
console.log("Converting: ", Reflect.convert(5, NonStrictString)); // "5" | ||
``` | ||
@codepen | ||
This works because the type functions all keep a reference to the underlying type and simply toggle the *strictness* of the newly created TypeObject. When [can-symbol/symbols/new] is called the strictness is checked. |
{ | ||
"name": "can-type", | ||
"version": "1.0.0-pre.4", | ||
"version": "1.0.0-pre.5", | ||
"description": "Type definitions", | ||
@@ -5,0 +5,0 @@ "homepage": "https://canjs.com/doc/can-type.html", |
39422
564