sanctuary-def
Advanced tools
Comparing version 0.4.0 to 0.5.0
834
index.js
@@ -8,3 +8,3 @@ /* global define, self */ | ||
/* istanbul ignore else */ | ||
if (typeof module !== 'undefined') { | ||
if (typeof module === 'object' && typeof module.exports === 'object') { | ||
module.exports = f(); | ||
@@ -47,10 +47,2 @@ } else if (typeof define === 'function' && define.amd != null) { | ||
// any :: ([a], (a -> Boolean)) -> Boolean | ||
var any = function(xs, pred) { | ||
for (var idx = 0; idx < xs.length; idx += 1) { | ||
if (pred(xs[idx])) return true; | ||
} | ||
return false; | ||
}; | ||
// chain :: ([a], (a -> [b])) -> [b] | ||
@@ -65,9 +57,11 @@ var chain = function(xs, f) { | ||
// eqProps :: String -> Object -> Object -> Boolean | ||
var eqProps = function(key) { | ||
return function(o1) { | ||
return function(o2) { | ||
return o1[key] === o2[key]; | ||
}; | ||
}; | ||
// filter :: ([a], (a -> Boolean)) -> [a] | ||
var filter = function(xs, pred) { | ||
var result = []; | ||
for (var idx = 0; idx < xs.length; idx += 1) { | ||
if (pred(xs[idx])) { | ||
result.push(xs[idx]); | ||
} | ||
} | ||
return result; | ||
}; | ||
@@ -91,2 +85,5 @@ | ||
// last :: [a] -> a | ||
var last = function(xs) { return xs[xs.length - 1]; }; | ||
// map :: ([a], (a -> b)) -> [b] | ||
@@ -132,2 +129,46 @@ var map = function(xs, f) { | ||
// reduce :: ([a], b, (b, a) -> b) -> b | ||
var reduce = function(xs, y, f) { | ||
var result = y; | ||
for (var idx = 0; idx < xs.length; idx += 1) result = f(result, xs[idx]); | ||
return result; | ||
}; | ||
// strRepeat :: (String, Integer) -> String | ||
var strRepeat = function(s, times) { | ||
return Array(times + 1).join(s); | ||
}; | ||
// r :: Char -> String -> String | ||
var r = function(c) { | ||
return function(s) { | ||
return strRepeat(c, s.length); | ||
}; | ||
}; | ||
// _ :: String -> String | ||
var _ = r(' '); | ||
// toPairs :: StrMap a -> [Pair String a] | ||
var toPairs = function(obj) { | ||
return map(keys(obj), function(k) { return [k, obj[k]]; }); | ||
}; | ||
// trimTrailingSpaces :: String -> String | ||
var trimTrailingSpaces = function(s) { | ||
return s.replace(/[ ]+$/gm, ''); | ||
}; | ||
// unlines :: [String] -> String | ||
var unlines = function(lines) { | ||
var s = ''; | ||
for (var idx = 0; idx < lines.length; idx += 1) s += lines[idx] + '\n'; | ||
return s; | ||
}; | ||
// when :: (Boolean, (a -> a), a) -> a | ||
var when = function(bool, f, x) { | ||
return bool ? f(x) : x; | ||
}; | ||
// stripNamespace :: String -> String | ||
@@ -204,10 +245,2 @@ var stripNamespace = function(s) { return s.slice(s.indexOf('/') + 1); }; | ||
// Any :: Type | ||
var Any = { | ||
'@@type': 'sanctuary-def/Type', | ||
type: 'ANY', | ||
test: K(true), | ||
toString: always('Any') | ||
}; | ||
// Unknown :: Type | ||
@@ -221,2 +254,10 @@ var Unknown = { | ||
// Inconsistent :: Type | ||
var Inconsistent = { | ||
'@@type': 'sanctuary-def/Inconsistent', | ||
type: 'INCONSISTENT', | ||
test: K(false), | ||
toString: always('???') | ||
}; | ||
// TypeVariable :: String -> Type | ||
@@ -247,2 +288,5 @@ $.TypeVariable = function(name) { | ||
return function($1) { | ||
var format = function(f, f$1) { | ||
return f('(' + stripNamespace(name) + ' ') + f$1(String($1)) + f(')'); | ||
}; | ||
return { | ||
@@ -252,4 +296,5 @@ '@@type': 'sanctuary-def/Type', | ||
name: name, | ||
test: function(x) { return test(x) && all(_1(x), $1.test); }, | ||
toString: always('(' + stripNamespace(name) + ' ' + $1 + ')'), | ||
test: test, | ||
format: format, | ||
toString: always(format(id, id)), | ||
_1: _1, | ||
@@ -261,3 +306,3 @@ $1: $1 | ||
// UnaryType.from :: Type -> Type | ||
// UnaryType.from :: Type -> (Type -> Type) | ||
UnaryType.from = function(t) { | ||
@@ -271,2 +316,6 @@ return UnaryType(t.name, t.test, t._1); | ||
return function($1, $2) { | ||
var format = function(f, f$1, f$2) { | ||
return f('(' + stripNamespace(name) + ' ') + | ||
f$1(String($1)) + f(' ') + f$2(String($2)) + f(')'); | ||
}; | ||
return { | ||
@@ -276,6 +325,5 @@ '@@type': 'sanctuary-def/Type', | ||
name: name, | ||
test: function(x) { return test(x) && all(_1(x), $1.test) && | ||
all(_2(x), $2.test); }, | ||
toString: | ||
always('(' + stripNamespace(name) + ' ' + $1 + ' ' + $2 + ')'), | ||
test: test, | ||
format: format, | ||
toString: always(format(id, id, id)), | ||
_1: _1, | ||
@@ -289,3 +337,3 @@ _2: _2, | ||
// BinaryType.from :: Type -> Type | ||
// BinaryType.from :: Type -> ((Type, Type) -> Type) | ||
BinaryType.from = function(t) { | ||
@@ -337,9 +385,11 @@ return BinaryType(t.name, t.test, t._1, t._2); | ||
if (!isEmpty(invalidMappings)) { | ||
throw new TypeError( | ||
'Invalid values\n\n' + | ||
'The argument to ‘RecordType’ must be an object mapping field name ' + | ||
'to type.\n\n' + | ||
'The following mappings are invalid:\n\n' + | ||
throw new TypeError(trimTrailingSpaces(unlines([ | ||
'Invalid values', | ||
'', | ||
'The argument to ‘RecordType’ must be an object mapping field name to type.', | ||
'', | ||
'The following mappings are invalid:', | ||
'', | ||
map(invalidMappings, prefix(' - ')).join('\n') | ||
); | ||
]))); | ||
} | ||
@@ -354,3 +404,5 @@ | ||
var name = names[idx]; | ||
if (!has(name, x) || !fields[name].test(x[name])) return false; | ||
if (!has(name, x) || !test(fields[name], x[name]).valid) { | ||
return false; | ||
} | ||
} | ||
@@ -380,2 +432,11 @@ return true; | ||
// StrMap :: Type -> Type | ||
var StrMap = UnaryType( | ||
'sanctuary-def/StrMap', | ||
function(x) { return $.Object.test(x); }, | ||
function(strMap) { | ||
return map(keys(strMap), function(k) { return strMap[k]; }); | ||
} | ||
); | ||
// $$type :: a -> String | ||
@@ -407,3 +468,2 @@ var $$type = function(x) { | ||
$.env = [ | ||
($.Any = Any), | ||
($.Array = type1('Array', id)), | ||
@@ -418,2 +478,3 @@ ($.Boolean = type0('Boolean')), | ||
($.RegExp = type0('RegExp')), | ||
($.StrMap = StrMap), | ||
($.String = type0('String')), | ||
@@ -423,2 +484,8 @@ ($.Undefined = type0('Undefined')) | ||
// Any :: Type | ||
$.Any = NullaryType( | ||
'sanctuary-def/Any', | ||
K(true) | ||
); | ||
// ValidDate :: Type | ||
@@ -573,38 +640,15 @@ $.ValidDate = NullaryType( | ||
// replaceTypeVars :: {[Type]} -> Type -> [Type] | ||
var replaceTypeVars = function(typeVarMap) { | ||
return function recur(t) { | ||
switch (t.type) { | ||
case 'VARIABLE': | ||
return has(t.name, typeVarMap) ? typeVarMap[t.name] : [t]; | ||
case 'UNARY': | ||
return map(recur(t.$1), UnaryType.from(t)); | ||
case 'BINARY': | ||
return BinaryType.xprod(t, recur(t.$1), recur(t.$2)); | ||
default: | ||
return [t]; | ||
} | ||
}; | ||
// unexpectedType :: Any -> TypeError | ||
var unexpectedType = /* istanbul ignore next */ function(x) { | ||
return new TypeError( | ||
'Unexpected type ' + | ||
LEFT_SINGLE_QUOTATION_MARK + x + RIGHT_SINGLE_QUOTATION_MARK | ||
); | ||
}; | ||
// rejectInconsistent :: Type -> [Type] | ||
var rejectInconsistent = function recur(t) { | ||
switch (t.type) { | ||
case 'ANY': | ||
return []; | ||
case 'UNARY': | ||
return map(recur(t.$1), UnaryType.from(t)); | ||
case 'BINARY': | ||
return BinaryType.xprod(t, recur(t.$1), recur(t.$2)); | ||
default: | ||
return [t]; | ||
} | ||
}; | ||
// equalTypes :: (Type, Type) -> Boolean | ||
var equalTypes = function equalTypes(t1, t2) { | ||
// equalTypes :: (Type, Type, Boolean) -> Boolean | ||
var equalTypes = function equalTypes(t1, t2, loose) { | ||
if (t1.type === 'INCONSISTENT' || t2.type === 'INCONSISTENT') return loose; | ||
if (t1.type === 'UNKNOWN' || t2.type === 'UNKNOWN') return true; | ||
switch (t1.type) { | ||
case 'ANY': | ||
return t1.type === t2.type && t1.name === t2.name; | ||
case 'NULLARY': | ||
@@ -614,7 +658,7 @@ return t1.type === t2.type && t1.name === t2.name; | ||
return t1.type === t2.type && t1.name === t2.name && | ||
equalTypes(t1.$1, t2.$1); | ||
equalTypes(t1.$1, t2.$1, loose); | ||
case 'BINARY': | ||
return t1.type === t2.type && t1.name === t2.name && | ||
equalTypes(t1.$1, t2.$1) && | ||
equalTypes(t1.$2, t2.$2); | ||
equalTypes(t1.$1, t2.$1, loose) && | ||
equalTypes(t1.$2, t2.$2, loose); | ||
case 'ENUM': | ||
@@ -626,25 +670,84 @@ return t1.type === t2.type && show(t1) === show(t2); | ||
default: | ||
throw new TypeError( | ||
'Unexpected type ' + | ||
LEFT_SINGLE_QUOTATION_MARK + t1.type + RIGHT_SINGLE_QUOTATION_MARK | ||
); | ||
throw unexpectedType(t1.type); | ||
} | ||
}; | ||
// commonTypes :: [[Type]] -> [Type] | ||
var commonTypes = function(typeses) { | ||
if (isEmpty(typeses)) return []; | ||
// chooseType :: (Type, Type) -> Type | ||
var chooseType = function(t1, t2) { | ||
return t1.type === 'UNKNOWN' ? t2 : t1; | ||
}; | ||
return chain(typeses[0], function(t) { | ||
var common = true; | ||
for (var idx = 1; idx < typeses.length; idx += 1) { | ||
common = false; | ||
for (var idx2 = 0; idx2 < typeses[idx].length; idx2 += 1) { | ||
if (equalTypes(t, typeses[idx][idx2])) { | ||
common = true; | ||
break; | ||
} | ||
} | ||
// mergeTypes :: (Type, Type) -> Type | ||
// | ||
// Either String ??? `mergeTypes` Either ??? Number = Either String Number | ||
var mergeTypes = function(t1, t2) { | ||
return ( | ||
t1.type === 'UNARY' ? | ||
UnaryType.from(t1)(chooseType(t1.$1, t2.$1)) : | ||
t1.type === 'BINARY' ? | ||
BinaryType.from(t1)(chooseType(t1.$1, t2.$1), | ||
chooseType(t1.$2, t2.$2)) : | ||
// else | ||
chooseType(t1, t2) | ||
); | ||
}; | ||
// commonTypes :: ([[Type]], Boolean) -> [Type] | ||
// | ||
// [[String, RegexFlags], [String, RegexFlags]] ---> [String, RegexFlags] | ||
// | ||
// [[Boolean], [Boolean], [Boolean], [Number]] ---> [] | ||
// | ||
// [[Array ???], [Array String], [Array ???]] ---> [Array String] | ||
// | ||
// [[Either String ???], [Either ??? Number]] ---> [Either String Number] | ||
var commonTypes = function(typeses, loose) { | ||
return reduce(typeses[0], [], function(types, t) { | ||
var st = reduce(typeses, {ok: true, type: t}, function(st, types) { | ||
var st$ = reduce(types, {ok: false, type: st.type}, function(st, t) { | ||
var equal = equalTypes(st.type, t, loose); | ||
return {ok: equal || st.ok, | ||
type: equal ? mergeTypes(st.type, t) : st.type}; | ||
}); | ||
return {type: st$.type, ok: st.ok && st$.ok}; | ||
}); | ||
return st.ok ? types.concat([st.type]) : types; | ||
}); | ||
}; | ||
// testNested :: (Type, Any, Integer) -> Result | ||
var testNested = function(t, x, position) { | ||
var _ = '_' + String(position); | ||
var $ = '$' + String(position); | ||
for (var idx = 0, xs = t[_](x); idx < xs.length; idx += 1) { | ||
var result = test(t[$], xs[idx]); | ||
if (!result.valid) { | ||
result.typePath.unshift(t); | ||
result.propPath.unshift($); | ||
return result; | ||
} | ||
return common ? [t] : []; | ||
} | ||
return {valid: true, value: x}; | ||
}; | ||
// test :: (Type, Any) -> Result | ||
var test = function(t, x) { | ||
if (!t.test(x)) { | ||
return {valid: false, value: x, typePath: [t], propPath: []}; | ||
} else if (t.type === 'UNARY') { | ||
return testNested(t, x, 1); | ||
} else if (t.type === 'BINARY') { | ||
var lhs = testNested(t, x, 1); | ||
return lhs.valid ? testNested(t, x, 2) : lhs; | ||
} else { | ||
return {valid: true, value: x}; | ||
} | ||
}; | ||
// filterTypesByValues :: ([Type], [Any]) -> [Type] | ||
var filterTypesByValues = function(types, values) { | ||
return filter(types, function(t) { | ||
return all(values, function(x) { | ||
return test(t, x).valid; | ||
}); | ||
}); | ||
@@ -674,11 +777,2 @@ }; | ||
var typeNotInEnvironment = function(env, name, type) { | ||
return new TypeError( | ||
'Definition of ' + LEFT_SINGLE_QUOTATION_MARK + name + | ||
RIGHT_SINGLE_QUOTATION_MARK + ' references ' + type.name + | ||
' which is not in the environment:\n\n' + | ||
map(chain(env, rejectInconsistent), prefix(' - ')).join('\n') | ||
); | ||
}; | ||
var invalidArgument = function(name, types, value, index) { | ||
@@ -692,37 +786,241 @@ return new TypeError( | ||
var orphanArgument = function(env, name, value, index) { | ||
return new TypeError( | ||
LEFT_SINGLE_QUOTATION_MARK + name + RIGHT_SINGLE_QUOTATION_MARK + | ||
' received ' + show(value) + ' as its ' + ordinals[index] + | ||
' argument, but this value is not a member of any of the types ' + | ||
'in the environment:\n\n' + | ||
map(chain(env, rejectInconsistent), prefix(' - ')).join('\n') | ||
); | ||
// constraintsRepr :: StrMap [TypeClass] -> String | ||
var constraintsRepr = function(constraints) { | ||
var reprs = chain(toPairs(constraints), function(pair) { | ||
return map(pair[1], function(typeClass) { | ||
return stripNamespace(typeClass.name) + ' ' + pair[0]; | ||
}); | ||
}); | ||
return when(reprs.length > 0, | ||
function(s) { return s + ' => '; }, | ||
when(reprs.length > 1, | ||
function(s) { return '(' + s + ')'; }, | ||
reprs.join(', '))); | ||
}; | ||
var invalidReturnValue = function(name, types, value) { | ||
return new TypeError( | ||
LEFT_SINGLE_QUOTATION_MARK + name + RIGHT_SINGLE_QUOTATION_MARK + | ||
' is expected to return a value of type ' + types.join(' or ') + | ||
'; returned ' + show(value) | ||
); | ||
// label :: String -> String -> String | ||
var label = function(label) { | ||
return function(s) { | ||
var delta = s.length - label.length; | ||
return strRepeat(' ', Math.floor(delta / 2)) + label + | ||
strRepeat(' ', Math.ceil(delta / 2)); | ||
}; | ||
}; | ||
var invalidValue = function(name, types, value, index) { | ||
return isNaN(index) ? | ||
invalidReturnValue(name, types, value) : | ||
invalidArgument(name, types, value, index); | ||
// arrowJoin :: [String] -> String | ||
var arrowJoin = function(xs) { | ||
return xs.join(' -> '); | ||
}; | ||
var constraintViolation = function(name, typeVarName, typeClasses, _types) { | ||
var types = chain(_types, rejectInconsistent); | ||
return new TypeError( | ||
LEFT_SINGLE_QUOTATION_MARK + name + RIGHT_SINGLE_QUOTATION_MARK + | ||
' requires ' + LEFT_SINGLE_QUOTATION_MARK + typeVarName + | ||
RIGHT_SINGLE_QUOTATION_MARK + ' to implement ' + | ||
typeClasses.join(' and ') + '; ' + types.join(' and ') + ' ' + | ||
(types.length === 1 ? 'does' : 'do') + ' not' | ||
// isParameterizedType :: Object -> Boolean | ||
var isParameterizedType = function(t) { | ||
return t.type === 'UNARY' || t.type === 'BINARY'; | ||
}; | ||
// showType :: Type -> String | ||
var showType = function(t) { | ||
var s = String(t); | ||
return isParameterizedType(t) ? s.slice(1, -1) : s; | ||
}; | ||
// showTypeSig :: [Type] -> String | ||
var showTypeSig = function(types) { | ||
return arrowJoin(map(types, showType)); | ||
}; | ||
// showTypeSig_ :: [Type] -> String | ||
var showTypeSig_ = function(types) { | ||
return arrowJoin(map(types, showType).concat([''])); | ||
}; | ||
// _showTypeSig :: [Type] -> String | ||
var _showTypeSig = function(types) { | ||
return arrowJoin([''].concat(map(types, showType))); | ||
}; | ||
// _showTypeSig_ :: [Type] -> String | ||
var _showTypeSig_ = function(types) { | ||
return arrowJoin([''].concat(map(types, showType)).concat([''])); | ||
}; | ||
// showValueAndType :: Pair Any [Type] -> String | ||
var showValueAndType = function(pair) { | ||
return show(pair[0]) + ' :: ' + map(pair[1], showType).join(', '); | ||
}; | ||
// underline :: Type -> [String] -> (String -> String) -> String | ||
var underline = function(type) { | ||
return function(propPath) { | ||
return function(f) { | ||
var t = type; | ||
var types = [t]; | ||
for (var idx = 0; idx < propPath.length; idx += 1) { | ||
types.push(t = t[propPath[idx]]); | ||
} | ||
var s = f(String(last(types))); | ||
for (idx = types.length - 2; idx >= 0; idx -= 1) { | ||
t = types[idx]; | ||
s = t.type === 'UNARY' ? | ||
t.format(_, K(s)) : | ||
t.type === 'BINARY' && propPath[idx] === '$1' ? | ||
t.format(_, K(s), _) : | ||
// else | ||
t.format(_, _, K(s)); | ||
} | ||
return isParameterizedType(type) ? s.slice(1, -1) : s; | ||
}; | ||
}; | ||
}; | ||
// Info = { index :: Integer | ||
// , pairs :: [Pair Any [Type]] | ||
// , propPath :: [String] | ||
// , typePath :: [Type] } | ||
// typeClassConstraintViolation :: ... -> Error | ||
var typeClassConstraintViolation = function( | ||
name, // :: String | ||
constraints, // :: StrMap [TypeClass] | ||
expTypes, // :: [Type] | ||
typeClass, // :: TypeClass | ||
info // :: Info | ||
) { | ||
var typeVarName = last(info.typePath).name; | ||
var reprs = chain(toPairs(constraints), function(pair) { | ||
return map(pair[1], function(tc) { | ||
var match = tc.name === typeClass.name && pair[0] === typeVarName; | ||
return r(match ? '^' : ' ')(stripNamespace(tc.name) + ' ' + pair[0]); | ||
}); | ||
}); | ||
var carets = when(reprs.length > 1, | ||
function(s) { return _('(') + s + _(')'); }, | ||
reprs.join(_(', '))); | ||
var padding = _(showTypeSig_(expTypes.slice(0, info.index))); | ||
var f = underline(info.typePath[0])(info.propPath); | ||
return new TypeError(trimTrailingSpaces(unlines([ | ||
'Type-class constraint violation', | ||
'', | ||
name + ' :: ' + constraintsRepr(constraints) + showTypeSig(expTypes), | ||
_(name + ' :: ') + carets + _(' => ') + padding + f(r('^')), | ||
_(name + ' :: ' + carets + ' => ') + padding + f(label('1')), | ||
'', | ||
'1) ' + map(info.pairs, showValueAndType).join('\n '), | ||
'', | ||
LEFT_SINGLE_QUOTATION_MARK + name + RIGHT_SINGLE_QUOTATION_MARK + ' requires ' + | ||
LEFT_SINGLE_QUOTATION_MARK + typeVarName + RIGHT_SINGLE_QUOTATION_MARK + | ||
' to satisfy the ' + typeClass + ' type-class constraint;' + | ||
' the value at position 1 does not.' | ||
]))); | ||
}; | ||
// annotateSig :: ... -> String | ||
var annotateSig = function( | ||
types, // :: [Type] | ||
fst, // :: Info | ||
snd, // :: Info | ||
f, // :: String -> String | ||
g // :: String -> String | ||
) { | ||
return _(_showTypeSig((types.slice(0, fst.index)))) + | ||
underline(fst.typePath[0])(fst.propPath)(f) + | ||
_(_showTypeSig_(types.slice(fst.index + 1, snd.index))) + | ||
underline(snd.typePath[0])(snd.propPath)(g); | ||
}; | ||
// _typeVarConstraintViolation :: ... -> Error | ||
var _typeVarConstraintViolation = function( | ||
name, // :: String | ||
constraints, // :: StrMap [TypeClass] | ||
expTypes, // :: [Type] | ||
carets, // :: String | ||
numbers, // :: String | ||
pairss // :: [[Pair Any [Type]]] | ||
) { | ||
var nameAndConstraints = name + ' :: ' + constraintsRepr(constraints); | ||
var lines = []; | ||
lines.push('Type-variable constraint violation'); | ||
lines.push(''); | ||
lines.push(nameAndConstraints + showTypeSig(expTypes)); | ||
lines.push(_(nameAndConstraints) + carets); | ||
lines.push(_(nameAndConstraints) + numbers); | ||
for (var idx = 0; idx < pairss.length; idx += 1) { | ||
lines.push(''); | ||
lines.push(String(idx + 1) + ') ' + | ||
map(pairss[idx], showValueAndType).join('\n ')); | ||
} | ||
lines.push(''); | ||
lines.push('Since there is no type of which all the above values are ' + | ||
'members, the type-variable constraint has been violated.'); | ||
return new TypeError(trimTrailingSpaces(unlines(lines))); | ||
}; | ||
// typeVarConstraintViolation :: ... -> Error | ||
var typeVarConstraintViolation = function( | ||
name, // :: String | ||
constraints, // :: StrMap [TypeClass] | ||
expTypes, // :: [Type] | ||
info // :: Info | ||
) { | ||
var padding = _(_showTypeSig(expTypes.slice(0, info.index))); | ||
var f = underline(expTypes[info.index])(info.propPath); | ||
return _typeVarConstraintViolation( | ||
name, | ||
constraints, | ||
expTypes, | ||
padding + f(r('^')), | ||
padding + f(label('1')), | ||
[info.pairs] | ||
); | ||
}; | ||
// typeVarConstraintViolation2 :: ... -> Error | ||
var typeVarConstraintViolation2 = function( | ||
name, // :: String | ||
constraints, // :: StrMap [TypeClass] | ||
expTypes, // :: [Type] | ||
_fst, // :: Info | ||
_snd // :: Info | ||
) { | ||
var fst = _fst.index < _snd.index ? _fst : _snd; | ||
var snd = _fst.index < _snd.index ? _snd : _fst; | ||
return _typeVarConstraintViolation( | ||
name, | ||
constraints, | ||
expTypes, | ||
annotateSig(expTypes, fst, snd, r('^'), r('^')), | ||
annotateSig(expTypes, fst, snd, label('1'), label('2')), | ||
[fst.pairs, snd.pairs] | ||
); | ||
}; | ||
// invalidValue :: ... -> Error | ||
var invalidValue = function( | ||
name, // :: String | ||
constraints, // :: StrMap [TypeClass] | ||
expTypes, // :: [Type] | ||
info // :: Info | ||
) { | ||
var nameAndConstraints = name + ' :: ' + constraintsRepr(constraints); | ||
var padding = _(_showTypeSig(expTypes.slice(0, info.index))); | ||
var f = underline(info.typePath[0])(info.propPath); | ||
return new TypeError(trimTrailingSpaces(unlines([ | ||
'Invalid value', | ||
'', | ||
nameAndConstraints + showTypeSig(expTypes), | ||
_(nameAndConstraints) + padding + f(r('^')), | ||
_(nameAndConstraints) + padding + f(label('1')), | ||
'', | ||
'1) ' + map(info.pairs, showValueAndType).join('\n '), | ||
'', | ||
'The value at position 1 is not a member of ' + | ||
LEFT_SINGLE_QUOTATION_MARK + showType(last(info.typePath)) + RIGHT_SINGLE_QUOTATION_MARK + '.' | ||
]))); | ||
}; | ||
// create :: (Boolean, [Type]) -> Function | ||
@@ -737,47 +1035,85 @@ $.create = function(checkTypes, _env) { | ||
// assertExpectedTypesInEnvironment :: String -> [Type] -> Undefined | ||
var assertExpectedTypesInEnvironment = function(name) { | ||
return function recur(expTypes) { | ||
for (var idx = 0; idx < expTypes.length; idx += 1) { | ||
var expType = expTypes[idx]; | ||
if (expType.type !== 'VARIABLE') { | ||
if (!any(env, eqProps('name')(expType))) { | ||
throw typeNotInEnvironment(env, name, expType); | ||
} | ||
if (expType.type === 'UNARY') { | ||
recur([expType.$1]); | ||
} else if (expType.type === 'BINARY') { | ||
recur([expType.$1, expType.$2]); | ||
} | ||
} | ||
// _determineActualTypes :: (Boolean, [Object], [Any]) -> [Type] | ||
var _determineActualTypes = function recur(loose, seen, values) { | ||
if (isEmpty(values)) return [Unknown]; | ||
// typeses :: [[Type]] | ||
var typeses = map(values, function(value) { | ||
var seen$; | ||
if (typeof value === 'object' && value != null || | ||
typeof value === 'function') { | ||
// Abort if a circular reference is encountered; add the current | ||
// object to the list of seen objects otherwise. | ||
if (seen.indexOf(value) >= 0) return []; | ||
seen$ = seen.concat([value]); | ||
} else { | ||
seen$ = seen; | ||
} | ||
}; | ||
return chain(env, function(t) { | ||
return ( | ||
t.name === 'sanctuary-def/Nullable' || !test(t, value).valid ? | ||
[] : | ||
t.type === 'UNARY' ? | ||
map(recur(loose, seen$, t._1(value)), UnaryType.from(t)) : | ||
t.type === 'BINARY' ? | ||
BinaryType.xprod(t, | ||
recur(loose, seen$, t._1(value)), | ||
recur(loose, seen$, t._2(value))) : | ||
// else | ||
[t] | ||
); | ||
}); | ||
}); | ||
// common :: [Type] | ||
var common = commonTypes(typeses, loose); | ||
if (!isEmpty(common)) return common; | ||
// If none of the values is a member of a type in the environment, | ||
// and all the values have the same type identifier, the values are | ||
// members of a "foreign" type. | ||
if (isEmpty(filterTypesByValues(env, values)) && | ||
all(values.slice(1), $$typeEq($$type(values[0])))) { | ||
// Create a nullary type for the foreign type. | ||
return [type0($$type(values[0]))]; | ||
} | ||
return [Inconsistent]; | ||
}; | ||
// determineActualTypes :: a -> [Type] | ||
var determineActualTypes = function recur(value) { | ||
return chain(env, function(t) { | ||
return ( | ||
t.name === 'sanctuary-def/Nullable' || !t.test(value) ? | ||
[] : | ||
t.type === 'UNARY' ? | ||
map(commonTypes(or(map(t._1(value), recur), [[Unknown]])), | ||
UnaryType.from(t)) : | ||
t.type === 'BINARY' ? | ||
BinaryType.xprod( | ||
t, | ||
commonTypes(or(map(t._1(value), recur), [[Unknown]])), | ||
commonTypes(or(map(t._2(value), recur), [[Unknown]])) | ||
) : | ||
// else | ||
[t] | ||
); | ||
// rejectInconsistent :: [Type] -> [Type] | ||
var rejectInconsistent = function(types) { | ||
return filter(types, function(t) { | ||
return t.type !== 'INCONSISTENT' && t.type !== 'UNKNOWN'; | ||
}); | ||
}; | ||
var _satisfactoryTypes = | ||
function(name, constraints, $typeVarMap, _value, index) { | ||
return function recur(expType, values, nest) { | ||
// determineActualTypesStrict :: [Any] -> [Type] | ||
var determineActualTypesStrict = function(values) { | ||
return rejectInconsistent(_determineActualTypes(false, [], values)); | ||
}; | ||
// determineActualTypesLoose :: [Any] -> [Type] | ||
var determineActualTypesLoose = function(values) { | ||
return rejectInconsistent(_determineActualTypes(true, [], values)); | ||
}; | ||
// valuesToPairs :: [Any] -> [Pair Any [Type]] | ||
var valuesToPairs = function(values) { | ||
return map(values, function(x) { | ||
return [x, determineActualTypesLoose([x])]; | ||
}); | ||
}; | ||
// _satisfactoryTypes :: ... -> [Type] | ||
var _satisfactoryTypes = function( | ||
name, // :: String | ||
constraints, // :: StrMap [TypeClass] | ||
expTypes, // :: [Type] | ||
$typeVarMap, // :: StrMap { info :: Info, types :: [Type] } | ||
index // :: Integer | ||
) { | ||
return function recur( | ||
expType, // :: Type | ||
values, // :: [Any] | ||
typePath, // :: [Type] | ||
propPath // :: [String] | ||
) { | ||
var $1s, $2s, idx, okTypes; | ||
switch (expType.type) { | ||
@@ -792,7 +1128,11 @@ | ||
if (!typeClasses[idx2].test(values[idx])) { | ||
throw constraintViolation( | ||
throw typeClassConstraintViolation( | ||
name, | ||
typeVarName, | ||
typeClasses, | ||
commonTypes(map(values, determineActualTypes)) | ||
constraints, | ||
expTypes, | ||
typeClasses[idx2], | ||
{index: index, | ||
pairs: valuesToPairs([values[idx]]), | ||
propPath: propPath, | ||
typePath: typePath.concat([expType])} | ||
); | ||
@@ -804,17 +1144,41 @@ } | ||
if (has(typeVarName, $typeVarMap)) { | ||
var types = $typeVarMap[typeVarName]; | ||
okTypes = chain(types, function(t) { | ||
return all(values, t.test) ? [t] : []; | ||
}); | ||
okTypes = filterTypesByValues($typeVarMap[typeVarName].types, | ||
values); | ||
if (isEmpty(okTypes)) { | ||
throw invalidValue(name, map(types, nest), _value, index); | ||
throw typeVarConstraintViolation2( | ||
name, | ||
constraints, | ||
expTypes, | ||
{index: index, | ||
pairs: valuesToPairs(values), | ||
propPath: propPath, | ||
typePath: typePath.concat([expType])}, | ||
$typeVarMap[typeVarName].info | ||
); | ||
} | ||
} else { | ||
okTypes = chain(commonTypes(map(values, determineActualTypes)), | ||
rejectInconsistent); | ||
okTypes = determineActualTypesStrict(values); | ||
if (isEmpty(okTypes) && !isEmpty(values)) { | ||
throw orphanArgument(env, name, _value, index); | ||
throw typeVarConstraintViolation( | ||
name, | ||
constraints, | ||
expTypes, | ||
{index: index, | ||
pairs: valuesToPairs(values), | ||
propPath: propPath, | ||
typePath: typePath.concat([expType])} | ||
); | ||
} | ||
} | ||
if (!isEmpty(okTypes)) $typeVarMap[typeVarName] = okTypes; | ||
if (!isEmpty(okTypes)) { | ||
$typeVarMap[typeVarName] = { | ||
types: okTypes, | ||
info: { | ||
index: index, | ||
pairs: valuesToPairs(values), | ||
propPath: propPath, | ||
typePath: typePath.concat([expType]) | ||
} | ||
}; | ||
} | ||
return okTypes; | ||
@@ -825,13 +1189,15 @@ | ||
chain(values, expType._1), | ||
UnaryType.from(expType)); | ||
typePath.concat([expType]), | ||
propPath.concat(['$1'])); | ||
return map(or($1s, [expType.$1]), UnaryType.from(expType)); | ||
case 'BINARY': | ||
var specialize = BinaryType.from(expType); | ||
$1s = recur(expType.$1, | ||
chain(values, expType._1), | ||
function($1) { return specialize($1, expType.$2); }); | ||
typePath.concat([expType]), | ||
propPath.concat(['$1'])); | ||
$2s = recur(expType.$2, | ||
chain(values, expType._2), | ||
function($2) { return specialize(expType.$1, $2); }); | ||
typePath.concat([expType]), | ||
propPath.concat(['$2'])); | ||
return BinaryType.xprod(expType, | ||
@@ -842,3 +1208,3 @@ or($1s, [expType.$1]), | ||
default: | ||
return commonTypes(map(values, determineActualTypes)); | ||
return determineActualTypesStrict(values); | ||
} | ||
@@ -848,11 +1214,34 @@ }; | ||
var satisfactoryTypes = | ||
function(name, constraints, $typeVarMap, expType, value, index) { | ||
return _satisfactoryTypes(name, constraints, $typeVarMap, value, index) | ||
(expType, [value], id); | ||
// satisfactoryTypes :: ... -> [Type] | ||
var satisfactoryTypes = function( | ||
name, // :: String | ||
constraints, // :: StrMap [TypeClass] | ||
expTypes, // :: [Type] | ||
$typeVarMap, // :: StrMap { info :: Info, types :: [Type] } | ||
value, // :: Any | ||
index // :: Integer | ||
) { | ||
return _satisfactoryTypes(name, | ||
constraints, | ||
expTypes, | ||
$typeVarMap, | ||
index) | ||
(expTypes[index], | ||
[value], | ||
[], | ||
[]); | ||
}; | ||
var curry = function(name, constraints, expArgTypes, expRetType, | ||
_typeVarMap, _values, _indexes, impl) { | ||
// curry :: ... -> Function | ||
var curry = function( | ||
name, // :: String | ||
constraints, // :: StrMap [TypeClass] | ||
expTypes, // :: [Type] | ||
_typeVarMap, // :: StrMap { info :: Info, types :: [Type] } | ||
_values, // :: [Any] | ||
_indexes, // :: [Integer] | ||
impl // :: Function | ||
) { | ||
return arity(_indexes.length, function() { | ||
var result; | ||
if (checkTypes) { | ||
@@ -862,4 +1251,4 @@ var delta = _indexes.length - arguments.length; | ||
throw invalidArgumentsLength(name, | ||
expArgTypes.length, | ||
expArgTypes.length - delta); | ||
expTypes.length - 1, | ||
expTypes.length - 1 - delta); | ||
} | ||
@@ -869,3 +1258,3 @@ } | ||
for (var typeVarName in _typeVarMap) { | ||
$typeVarMap[typeVarName] = _typeVarMap[typeVarName].slice(); | ||
$typeVarMap[typeVarName] = _typeVarMap[typeVarName]; | ||
} | ||
@@ -884,11 +1273,18 @@ var values = _values.slice(); | ||
if (checkTypes) { | ||
var expType = expArgTypes[index]; | ||
if (!expType.test(value) || | ||
isEmpty(satisfactoryTypes(name, constraints, $typeVarMap, | ||
expType, value, index))) { | ||
result = test(expTypes[index], value); | ||
if (!result.valid) { | ||
throw invalidValue(name, | ||
replaceTypeVars($typeVarMap)(expType), | ||
value, | ||
index); | ||
constraints, | ||
expTypes, | ||
{index: index, | ||
pairs: valuesToPairs([result.value]), | ||
propPath: result.propPath, | ||
typePath: result.typePath}); | ||
} | ||
satisfactoryTypes(name, | ||
constraints, | ||
expTypes, | ||
$typeVarMap, | ||
value, | ||
index); | ||
} | ||
@@ -903,12 +1299,28 @@ values[index] = value; | ||
if (checkTypes) { | ||
if (!expRetType.test(returnValue)) { | ||
throw invalidReturnValue(name, [expRetType], returnValue); | ||
result = test(last(expTypes), returnValue); | ||
if (!result.valid) { | ||
throw invalidValue(name, | ||
constraints, | ||
expTypes, | ||
{index: _indexes.length, | ||
pairs: valuesToPairs([result.value]), | ||
propPath: result.propPath, | ||
typePath: result.typePath}); | ||
} | ||
satisfactoryTypes(name, constraints, $typeVarMap, | ||
expRetType, returnValue, NaN); | ||
satisfactoryTypes(name, | ||
constraints, | ||
expTypes, | ||
$typeVarMap, | ||
returnValue, | ||
expTypes.length - 1); | ||
} | ||
return returnValue; | ||
} else { | ||
return curry(name, constraints, expArgTypes, expRetType, | ||
$typeVarMap, values, indexes, impl); | ||
return curry(name, | ||
constraints, | ||
expTypes, | ||
$typeVarMap, | ||
values, | ||
indexes, | ||
impl); | ||
} | ||
@@ -927,3 +1339,3 @@ }); | ||
for (var idx = 0; idx < types.length; idx += 1) { | ||
if (!types[idx].test(arguments[idx])) { | ||
if (!test(types[idx], arguments[idx]).valid) { | ||
throw invalidArgument('def', [types[idx]], arguments[idx], idx); | ||
@@ -934,4 +1346,3 @@ } | ||
var expArgTypes = expTypes.slice(0, -1); | ||
var arity = expArgTypes.length; | ||
var arity = expTypes.length - 1; | ||
if (arity > 9) { | ||
@@ -944,8 +1355,5 @@ throw new RangeError( | ||
if (checkTypes) assertExpectedTypesInEnvironment(name)(expTypes); | ||
return curry(name, | ||
constraints, | ||
expArgTypes, | ||
expTypes[expTypes.length - 1], | ||
expTypes, | ||
{}, | ||
@@ -952,0 +1360,0 @@ new Array(arity), |
{ | ||
"name": "sanctuary-def", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "Run-time type system for JavaScript", | ||
@@ -19,3 +19,3 @@ "license": "MIT", | ||
"mocha": "2.x.x", | ||
"ramda": "~0.19.1", | ||
"ramda": "0.21.x", | ||
"xyz": "0.5.x" | ||
@@ -22,0 +22,0 @@ }, |
174
README.md
@@ -124,3 +124,11 @@ # sanctuary-def | ||
add('2', '2'); | ||
// ! TypeError: ‘add’ expected a value of type Number as its first argument; received "2" | ||
// ! TypeError: Invalid value | ||
// | ||
// add :: Number -> Number -> Number | ||
// ^^^^^^ | ||
// 1 | ||
// | ||
// 1) "2" :: String | ||
// | ||
// The value at position 1 is not a member of ‘Number’. | ||
``` | ||
@@ -133,3 +141,11 @@ | ||
add('X'); | ||
// ! TypeError: ‘add’ expected a value of type Number as its first argument; received "X" | ||
// ! TypeError: Invalid value | ||
// | ||
// add :: Number -> Number -> Number | ||
// ^^^^^^ | ||
// 1 | ||
// | ||
// 1) "X" :: String | ||
// | ||
// The value at position 1 is not a member of ‘Number’. | ||
``` | ||
@@ -346,2 +362,13 @@ | ||
#### `StrMap` | ||
```haskell | ||
$.StrMap :: Type -> Type | ||
``` | ||
Constructor for homogeneous Object types. | ||
`{foo: 1, bar: 2, baz: 3}`, for example, is a member of `StrMap Number`; | ||
`{foo: 1, bar: 2, baz: 'XXX'}` is not. | ||
#### `String` | ||
@@ -384,3 +411,2 @@ | ||
- [`$.Any`](#any) | ||
- [`$.Array`](#array) | ||
@@ -395,2 +421,3 @@ - [`$.Boolean`](#boolean) | ||
- [`$.RegExp`](#regexp) | ||
- [`$.StrMap`](#strmap) | ||
- [`$.String`](#string) | ||
@@ -447,3 +474,13 @@ - [`$.Undefined`](#undefined) | ||
cmp(0, '1'); | ||
// ! TypeError: ‘cmp’ expected a value of type Number as its second argument; received "1" | ||
// ! TypeError: Type-variable constraint violation | ||
// | ||
// cmp :: a -> a -> Number | ||
// ^ ^ | ||
// 1 2 | ||
// | ||
// 1) 0 :: Number | ||
// | ||
// 2) "1" :: String | ||
// | ||
// Since there is no type of which all the above values are members, the type-variable constraint has been violated. | ||
``` | ||
@@ -494,6 +531,22 @@ | ||
rem(0.5); | ||
// ! TypeError: ‘rem’ expected a value of type Integer as its first argument; received 0.5 | ||
// ! TypeError: Invalid value | ||
// | ||
// rem :: Integer -> NonZeroInteger -> Integer | ||
// ^^^^^^^ | ||
// 1 | ||
// | ||
// 1) 0.5 :: Number | ||
// | ||
// The value at position 1 is not a member of ‘Integer’. | ||
rem(42, 0); | ||
// ! TypeError: ‘rem’ expected a value of type NonZeroInteger as its second argument; received 0 | ||
// ! TypeError: Invalid value | ||
// | ||
// rem :: Integer -> NonZeroInteger -> Integer | ||
// ^^^^^^^^^^^^^^ | ||
// 1 | ||
// | ||
// 1) 0 :: Number | ||
// | ||
// The value at position 1 is not a member of ‘NonZeroInteger’. | ||
``` | ||
@@ -515,3 +568,11 @@ | ||
sum(['1', '2', '3', '4']); | ||
// ! TypeError: ‘sum’ expected a value of type (Array Number) as its first argument; received ["1", "2", "3", "4"] | ||
// ! TypeError: Invalid value | ||
// | ||
// sum :: Array Number -> Number | ||
// ^^^^^^ | ||
// 1 | ||
// | ||
// 1) "1" :: String | ||
// | ||
// The value at position 1 is not a member of ‘Number’. | ||
``` | ||
@@ -574,3 +635,13 @@ | ||
fromMaybe(0, Just('XXX')); | ||
// ! TypeError: ‘fromMaybe’ expected a value of type (Maybe Number) as its second argument; received Just("XXX") | ||
// ! TypeError: Type-variable constraint violation | ||
// | ||
// fromMaybe :: a -> Maybe a -> a | ||
// ^ ^ | ||
// 1 2 | ||
// | ||
// 1) 0 :: Number | ||
// | ||
// 2) "XXX" :: String | ||
// | ||
// Since there is no type of which all the above values are members, the type-variable constraint has been violated. | ||
``` | ||
@@ -649,3 +720,11 @@ | ||
showCard(Pair('X', '♠')); | ||
// ! TypeError: ‘showCard’ expected a value of type (Pair Rank Suit) as its first argument; received Pair("X", "♠") | ||
// ! TypeError: Invalid value | ||
// | ||
// showCard :: Pair Rank Suit -> String | ||
// ^^^^ | ||
// 1 | ||
// | ||
// 1) "X" :: String | ||
// | ||
// The value at position 1 is not a member of ‘Rank’. | ||
``` | ||
@@ -671,7 +750,2 @@ | ||
// env :: [Type] | ||
const env = $.env.concat([TimeUnit, $.ValidDate, $.ValidNumber]); | ||
const def = $.create(true, env); | ||
// convertTo :: TimeUnit -> ValidDate -> ValidNumber | ||
@@ -695,3 +769,11 @@ const convertTo = | ||
convertTo('days', new Date(1000)); | ||
// ! TypeError: ‘convertTo’ expected a value of type ("milliseconds" | "seconds" | "minutes" | "hours") as its first argument; received "days" | ||
// ! TypeError: Invalid value | ||
// | ||
// convertTo :: ("milliseconds" | "seconds" | "minutes" | "hours") -> ValidDate -> ValidNumber | ||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
// 1 | ||
// | ||
// 1) "days" :: String | ||
// | ||
// The value at position 1 is not a member of ‘("milliseconds" | "seconds" | "minutes" | "hours")’. | ||
``` | ||
@@ -715,14 +797,8 @@ | ||
```javascript | ||
// FiniteNumber :: Type | ||
const FiniteNumber = $.NullaryType( | ||
'my-package/FiniteNumber', | ||
x => Object.prototype.toString.call(x) === '[object Number]' && isFinite(x) | ||
); | ||
// Point :: Type | ||
const Point = $.RecordType({x: FiniteNumber, y: FiniteNumber}); | ||
const Point = $.RecordType({x: $.FiniteNumber, y: $.FiniteNumber}); | ||
// dist :: Point -> Point -> FiniteNumber | ||
const dist = | ||
def('dist', {}, [Point, Point, FiniteNumber], | ||
def('dist', {}, [Point, Point, $.FiniteNumber], | ||
(p, q) => Math.sqrt(Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2))); | ||
@@ -737,6 +813,22 @@ | ||
dist({x: 0, y: 0}, {x: NaN, y: NaN}); | ||
// ! TypeError: ‘dist’ expected a value of type { x :: FiniteNumber, y :: FiniteNumber } as its second argument; received {"x": NaN, "y": NaN} | ||
// ! TypeError: Invalid value | ||
// | ||
// dist :: { x :: FiniteNumber, y :: FiniteNumber } -> { x :: FiniteNumber, y :: FiniteNumber } -> FiniteNumber | ||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
// 1 | ||
// | ||
// 1) {"x": NaN, "y": NaN} :: Object, StrMap Number | ||
// | ||
// The value at position 1 is not a member of ‘{ x :: FiniteNumber, y :: FiniteNumber }’. | ||
dist(0); | ||
// ! TypeError: ‘dist’ expected a value of type { x :: FiniteNumber, y :: FiniteNumber } as its first argument; received 0 | ||
// ! TypeError: Invalid value | ||
// | ||
// dist :: { x :: FiniteNumber, y :: FiniteNumber } -> { x :: FiniteNumber, y :: FiniteNumber } -> FiniteNumber | ||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
// 1 | ||
// | ||
// 1) 0 :: Number | ||
// | ||
// The value at position 1 is not a member of ‘{ x :: FiniteNumber, y :: FiniteNumber }’. | ||
``` | ||
@@ -764,3 +856,13 @@ | ||
_concat([1, 2], 'buzz'); | ||
// ! TypeError: ‘_concat’ expected a value of type (Array Number) as its second argument; received "buzz" | ||
// ! TypeError: Type-variable constraint violation | ||
// | ||
// _concat :: a -> a -> a | ||
// ^ ^ | ||
// 1 2 | ||
// | ||
// 1) [1, 2] :: Array Number | ||
// | ||
// 2) "buzz" :: String | ||
// | ||
// Since there is no type of which all the above values are members, the type-variable constraint has been violated. | ||
``` | ||
@@ -800,6 +902,22 @@ | ||
concat({}, {}); | ||
// ! TypeError: ‘concat’ requires ‘a’ to implement Semigroup; Object does not | ||
// ! TypeError: Type-class constraint violation | ||
// | ||
// concat :: Semigroup a => a -> a -> a | ||
// ^^^^^^^^^^^ ^ | ||
// 1 | ||
// | ||
// 1) {} :: Object, StrMap ??? | ||
// | ||
// ‘concat’ requires ‘a’ to satisfy the Semigroup type-class constraint; the value at position 1 does not. | ||
concat(null, null); | ||
// ! TypeError: ‘concat’ requires ‘a’ to implement Semigroup; Null does not | ||
// ! TypeError: Type-class constraint violation | ||
// | ||
// concat :: Semigroup a => a -> a -> a | ||
// ^^^^^^^^^^^ ^ | ||
// 1 | ||
// | ||
// 1) null :: Null | ||
// | ||
// ‘concat’ requires ‘a’ to satisfy the Semigroup type-class constraint; the value at position 1 does not. | ||
``` | ||
@@ -806,0 +924,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
64579
1197
923