add gs
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,560 @@
|
||||
return function()
|
||||
local t = require(script.Parent)
|
||||
|
||||
it("should support basic types", function()
|
||||
assert(t.any(""))
|
||||
assert(t.boolean(true))
|
||||
assert(t.none(nil))
|
||||
assert(t.number(1))
|
||||
assert(t.string("foo"))
|
||||
assert(t.table({}))
|
||||
|
||||
assert(not (t.any(nil)))
|
||||
assert(not (t.boolean("true")))
|
||||
assert(not (t.none(1)))
|
||||
assert(not (t.number(true)))
|
||||
assert(not (t.string(true)))
|
||||
assert(not (t.table(82)))
|
||||
end)
|
||||
|
||||
it("should support special number types", function()
|
||||
local maxTen = t.numberMax(10)
|
||||
local minTwo = t.numberMin(2)
|
||||
local maxTenEx = t.numberMaxExclusive(10)
|
||||
local minTwoEx = t.numberMinExclusive(2)
|
||||
local constrainedEightToEleven = t.numberConstrained(8, 11)
|
||||
local constrainedEightToElevenEx = t.numberConstrainedExclusive(8, 11)
|
||||
|
||||
assert(maxTen(5))
|
||||
assert(maxTen(10))
|
||||
assert(not (maxTen(11)))
|
||||
assert(not (maxTen()))
|
||||
|
||||
assert(minTwo(5))
|
||||
assert(minTwo(2))
|
||||
assert(not (minTwo(1)))
|
||||
assert(not (minTwo()))
|
||||
|
||||
assert(maxTenEx(5))
|
||||
assert(maxTenEx(9))
|
||||
assert(not (maxTenEx(10)))
|
||||
assert(not (maxTenEx()))
|
||||
|
||||
assert(minTwoEx(5))
|
||||
assert(minTwoEx(3))
|
||||
assert(not (minTwoEx(2)))
|
||||
assert(not (minTwoEx()))
|
||||
|
||||
assert(not (constrainedEightToEleven(7)))
|
||||
assert(constrainedEightToEleven(8))
|
||||
assert(constrainedEightToEleven(9))
|
||||
assert(constrainedEightToEleven(11))
|
||||
assert(not (constrainedEightToEleven(12)))
|
||||
assert(not (constrainedEightToEleven()))
|
||||
|
||||
assert(not (constrainedEightToElevenEx(7)))
|
||||
assert(not (constrainedEightToElevenEx(8)))
|
||||
assert(constrainedEightToElevenEx(9))
|
||||
assert(not (constrainedEightToElevenEx(11)))
|
||||
assert(not (constrainedEightToElevenEx(12)))
|
||||
assert(not (constrainedEightToElevenEx()))
|
||||
end)
|
||||
|
||||
it("should support optional types", function()
|
||||
local check = t.optional(t.string)
|
||||
assert(check(""))
|
||||
assert(check())
|
||||
assert(not (check(1)))
|
||||
end)
|
||||
|
||||
it("should support tuple types", function()
|
||||
local myTupleCheck = t.tuple(t.number, t.string, t.optional(t.number))
|
||||
assert(myTupleCheck(1, "2", 3))
|
||||
assert(myTupleCheck(1, "2"))
|
||||
assert(not (myTupleCheck(1, "2", "3")))
|
||||
end)
|
||||
|
||||
it("should support union types", function()
|
||||
local numberOrString = t.union(t.number, t.string)
|
||||
assert(numberOrString(1))
|
||||
assert(numberOrString("1"))
|
||||
assert(not (numberOrString(nil)))
|
||||
end)
|
||||
|
||||
it("should support literal types", function()
|
||||
local checkSingle = t.literal("foo")
|
||||
local checkUnion = t.union(t.literal("foo"), t.literal("bar"), t.literal("oof"))
|
||||
|
||||
assert(checkSingle("foo"))
|
||||
assert(checkUnion("foo"))
|
||||
assert(checkUnion("bar"))
|
||||
assert(checkUnion("oof"))
|
||||
|
||||
assert(not (checkSingle("FOO")))
|
||||
assert(not (checkUnion("FOO")))
|
||||
assert(not (checkUnion("BAR")))
|
||||
assert(not (checkUnion("OOF")))
|
||||
end)
|
||||
|
||||
it("should support multiple literal types", function()
|
||||
local checkSingle = t.literal("foo")
|
||||
local checkUnion = t.literal("foo", "bar", "oof")
|
||||
|
||||
assert(checkSingle("foo"))
|
||||
assert(checkUnion("foo"))
|
||||
assert(checkUnion("bar"))
|
||||
assert(checkUnion("oof"))
|
||||
|
||||
assert(not (checkSingle("FOO")))
|
||||
assert(not (checkUnion("FOO")))
|
||||
assert(not (checkUnion("BAR")))
|
||||
assert(not (checkUnion("OOF")))
|
||||
end)
|
||||
|
||||
it("should support intersection types", function()
|
||||
local integerMax5000 = t.intersection(t.integer, t.numberMax(5000))
|
||||
assert(integerMax5000(1))
|
||||
assert(not (integerMax5000(5001)))
|
||||
assert(not (integerMax5000(1.1)))
|
||||
assert(not (integerMax5000("1")))
|
||||
end)
|
||||
|
||||
describe("array", function()
|
||||
it("should support array types", function()
|
||||
local stringArray = t.array(t.string)
|
||||
local anyArray = t.array(t.any)
|
||||
local stringValues = t.values(t.string)
|
||||
assert(not (anyArray("foo")))
|
||||
assert(anyArray({1, "2", 3}))
|
||||
assert(not (stringArray({1, "2", 3})))
|
||||
assert(not (stringArray()))
|
||||
assert(not (stringValues()))
|
||||
assert(anyArray({"1", "2", "3"}, t.string))
|
||||
assert(not (anyArray({
|
||||
foo = "bar"
|
||||
})))
|
||||
assert(not (anyArray({
|
||||
[1] = "non",
|
||||
[5] = "sequential"
|
||||
})))
|
||||
end)
|
||||
|
||||
it("should not be fooled by sparse arrays", function()
|
||||
local anyArray = t.array(t.any)
|
||||
|
||||
assert(not (anyArray({
|
||||
[1] = 1,
|
||||
[2] = 2,
|
||||
[4] = 4,
|
||||
})))
|
||||
end)
|
||||
end)
|
||||
|
||||
it("should support map types", function()
|
||||
local stringNumberMap = t.map(t.string, t.number)
|
||||
assert(stringNumberMap({}))
|
||||
assert(stringNumberMap({a = 1}))
|
||||
assert(not (stringNumberMap({[1] = "a"})))
|
||||
assert(not (stringNumberMap({a = "a"})))
|
||||
assert(not (stringNumberMap()))
|
||||
end)
|
||||
|
||||
it("should support interface types", function()
|
||||
local IVector3 = t.interface({
|
||||
x = t.number,
|
||||
y = t.number,
|
||||
z = t.number,
|
||||
})
|
||||
|
||||
assert(IVector3({
|
||||
w = 0,
|
||||
x = 1,
|
||||
y = 2,
|
||||
z = 3,
|
||||
}))
|
||||
|
||||
assert(not (IVector3({
|
||||
w = 0,
|
||||
x = 1,
|
||||
y = 2,
|
||||
})))
|
||||
end)
|
||||
|
||||
it("should support strict interface types", function()
|
||||
local IVector3 = t.strictInterface({
|
||||
x = t.number,
|
||||
y = t.number,
|
||||
z = t.number,
|
||||
})
|
||||
|
||||
assert(not (IVector3(0)))
|
||||
|
||||
assert(not (IVector3({
|
||||
w = 0,
|
||||
x = 1,
|
||||
y = 2,
|
||||
z = 3,
|
||||
})))
|
||||
|
||||
assert(not (IVector3({
|
||||
w = 0,
|
||||
x = 1,
|
||||
y = 2,
|
||||
})))
|
||||
|
||||
assert(IVector3({
|
||||
x = 1,
|
||||
y = 2,
|
||||
z = 3,
|
||||
}))
|
||||
end)
|
||||
|
||||
it("should support deep interface types", function()
|
||||
local IPlayer = t.interface({
|
||||
name = t.string,
|
||||
inventory = t.interface({
|
||||
size = t.number
|
||||
})
|
||||
})
|
||||
|
||||
assert(IPlayer({
|
||||
name = "TestPlayer",
|
||||
inventory = {
|
||||
size = 1
|
||||
}
|
||||
}))
|
||||
|
||||
assert(not (IPlayer({
|
||||
inventory = {
|
||||
size = 1
|
||||
}
|
||||
})))
|
||||
|
||||
assert(not (IPlayer({
|
||||
name = "TestPlayer",
|
||||
inventory = {
|
||||
}
|
||||
})))
|
||||
|
||||
assert(not (IPlayer({
|
||||
name = "TestPlayer",
|
||||
})))
|
||||
end)
|
||||
|
||||
it("should support deep optional interface types", function()
|
||||
local IPlayer = t.interface({
|
||||
name = t.string,
|
||||
inventory = t.optional(t.interface({
|
||||
size = t.number
|
||||
}))
|
||||
})
|
||||
|
||||
assert(IPlayer({
|
||||
name = "TestPlayer"
|
||||
}))
|
||||
|
||||
assert(not (IPlayer({
|
||||
name = "TestPlayer",
|
||||
inventory = {
|
||||
}
|
||||
})))
|
||||
|
||||
assert(IPlayer({
|
||||
name = "TestPlayer",
|
||||
inventory = {
|
||||
size = 1
|
||||
}
|
||||
}))
|
||||
end)
|
||||
|
||||
it("should support Roblox Instance types", function()
|
||||
local stringValueCheck = t.instanceOf("StringValue")
|
||||
local stringValue = Instance.new("StringValue")
|
||||
local boolValue = Instance.new("BoolValue")
|
||||
|
||||
assert(stringValueCheck(stringValue))
|
||||
assert(not (stringValueCheck(boolValue)))
|
||||
assert(not (stringValueCheck()))
|
||||
end)
|
||||
|
||||
it("should support Roblox Instance types inheritance", function()
|
||||
local guiObjectCheck = t.instanceIsA("GuiObject")
|
||||
local frame = Instance.new("Frame")
|
||||
local textLabel = Instance.new("TextLabel")
|
||||
local stringValue = Instance.new("StringValue")
|
||||
|
||||
assert(guiObjectCheck(frame))
|
||||
assert(guiObjectCheck(textLabel))
|
||||
assert(not (guiObjectCheck(stringValue)))
|
||||
assert(not (guiObjectCheck()))
|
||||
end)
|
||||
|
||||
it("should support Roblox Enum types", function()
|
||||
local sortOrderEnumCheck = t.enum(Enum.SortOrder)
|
||||
assert(t.Enum(Enum.SortOrder))
|
||||
assert(not (t.Enum("Enum.SortOrder")))
|
||||
|
||||
assert(t.EnumItem(Enum.SortOrder.Name))
|
||||
assert(not (t.EnumItem("Enum.SortOrder.Name")))
|
||||
|
||||
assert(sortOrderEnumCheck(Enum.SortOrder.Name))
|
||||
assert(sortOrderEnumCheck(Enum.SortOrder.Custom))
|
||||
assert(not (sortOrderEnumCheck(Enum.EasingStyle.Linear)))
|
||||
assert(not (sortOrderEnumCheck()))
|
||||
end)
|
||||
|
||||
it("should support Roblox RBXScriptSignal", function()
|
||||
assert(t.RBXScriptSignal(game.ChildAdded))
|
||||
assert(not (t.RBXScriptSignal(nil)))
|
||||
assert(not (t.RBXScriptSignal(Vector3.new())))
|
||||
end)
|
||||
|
||||
-- TODO: Add this back when Lemur supports it
|
||||
-- it("should support Roblox RBXScriptConnection", function()
|
||||
-- local conn = game.ChildAdded:Connect(function() end)
|
||||
-- assert(t.RBXScriptConnection(conn))
|
||||
-- assert(not (t.RBXScriptConnection(nil)))
|
||||
-- assert(not (t.RBXScriptConnection(Vector3.new())))
|
||||
-- end)
|
||||
|
||||
it("should support wrapping function types", function()
|
||||
local checkFoo = t.tuple(t.string, t.number, t.optional(t.string))
|
||||
local foo = t.wrap(function(a, b, c)
|
||||
local result = string.format("%s %d", a, b)
|
||||
if c then
|
||||
result = result .. " " .. c
|
||||
end
|
||||
return result
|
||||
end, checkFoo)
|
||||
|
||||
assert(not (pcall(foo)))
|
||||
assert(not (pcall(foo, "a")))
|
||||
assert(not (pcall(foo, 2)))
|
||||
assert(pcall(foo, "a", 1))
|
||||
assert(pcall(foo, "a", 1, "b"))
|
||||
end)
|
||||
|
||||
it("should support strict types", function()
|
||||
local myType = t.strict(t.tuple(t.string, t.number))
|
||||
assert(not (pcall(function()
|
||||
myType("a", "b")
|
||||
end)))
|
||||
assert(pcall(function()
|
||||
myType("a", 1)
|
||||
end))
|
||||
end)
|
||||
|
||||
it("should support common OOP types", function()
|
||||
local MyClass = {}
|
||||
MyClass.__index = MyClass
|
||||
|
||||
function MyClass.new()
|
||||
local self = setmetatable({}, MyClass)
|
||||
return self
|
||||
end
|
||||
|
||||
local function instanceOfClass(class)
|
||||
return function(value)
|
||||
local tableSuccess, tableErrMsg = t.table(value)
|
||||
if not tableSuccess then
|
||||
return false, tableErrMsg or ""
|
||||
end
|
||||
|
||||
local mt = getmetatable(value)
|
||||
if not mt or mt.__index ~= class then
|
||||
return false, "bad member of class"
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local instanceOfMyClass = instanceOfClass(MyClass)
|
||||
|
||||
local myObject = MyClass.new()
|
||||
assert(instanceOfMyClass(myObject))
|
||||
assert(not (instanceOfMyClass({})))
|
||||
assert(not (instanceOfMyClass()))
|
||||
end)
|
||||
|
||||
it("should not treat NaN as numbers", function()
|
||||
assert(t.number(1))
|
||||
assert(not (t.number(0/0)))
|
||||
assert(not (t.number("1")))
|
||||
end)
|
||||
|
||||
it("should not treat numbers as NaN", function()
|
||||
assert(not (t.nan(1)))
|
||||
assert(t.nan(0/0))
|
||||
assert(not (t.nan("1")))
|
||||
end)
|
||||
|
||||
it("should allow union of number and NaN", function()
|
||||
local numberOrNaN = t.union(t.number, t.nan)
|
||||
assert(numberOrNaN(1))
|
||||
assert(numberOrNaN(0/0))
|
||||
assert(not (numberOrNaN("1")))
|
||||
end)
|
||||
|
||||
it("should support non-string keys for interfaces", function()
|
||||
local key = {}
|
||||
local myInterface = t.interface({ [key] = t.number })
|
||||
assert(myInterface({ [key] = 1 }))
|
||||
assert(not (myInterface({ [key] = "1" })))
|
||||
end)
|
||||
|
||||
it("should support failing on non-string keys for strict interfaces", function()
|
||||
local myInterface = t.strictInterface({ a = t.number })
|
||||
assert(not (myInterface({ a = 1, [{}] = 2 })))
|
||||
end)
|
||||
|
||||
it("should support children", function()
|
||||
local myInterface = t.interface({
|
||||
buttonInFrame = t.intersection(t.instanceOf("Frame"), t.children({
|
||||
MyButton = t.instanceOf("ImageButton")
|
||||
}))
|
||||
})
|
||||
|
||||
assert(not (t.children({})(5)))
|
||||
assert(not (myInterface({ buttonInFrame = Instance.new("Frame") })))
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button = Instance.new("ImageButton", frame)
|
||||
button.Name = "MyButton"
|
||||
assert(myInterface({ buttonInFrame = frame }))
|
||||
end
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button = Instance.new("ImageButton", frame)
|
||||
button.Name = "NotMyButton"
|
||||
assert(not (myInterface({ buttonInFrame = frame })))
|
||||
end
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button = Instance.new("TextButton", frame)
|
||||
button.Name = "MyButton"
|
||||
assert(not (myInterface({ buttonInFrame = frame })))
|
||||
end
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button1 = Instance.new("ImageButton", frame)
|
||||
button1.Name = "MyButton"
|
||||
local button2 = Instance.new("ImageButton", frame)
|
||||
button2.Name = "MyButton"
|
||||
assert(not (myInterface({ buttonInFrame = frame })))
|
||||
end
|
||||
end)
|
||||
|
||||
it("should support t.instanceOf shorthand", function()
|
||||
local myInterface = t.interface({
|
||||
buttonInFrame = t.instanceOf("Frame", {
|
||||
MyButton = t.instanceOf("ImageButton")
|
||||
})
|
||||
})
|
||||
|
||||
assert(not (t.children({})(5)))
|
||||
assert(not (myInterface({ buttonInFrame = Instance.new("Frame") })))
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button = Instance.new("ImageButton", frame)
|
||||
button.Name = "MyButton"
|
||||
assert(myInterface({ buttonInFrame = frame }))
|
||||
end
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button = Instance.new("ImageButton", frame)
|
||||
button.Name = "NotMyButton"
|
||||
assert(not (myInterface({ buttonInFrame = frame })))
|
||||
end
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button = Instance.new("TextButton", frame)
|
||||
button.Name = "MyButton"
|
||||
assert(not (myInterface({ buttonInFrame = frame })))
|
||||
end
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button1 = Instance.new("ImageButton", frame)
|
||||
button1.Name = "MyButton"
|
||||
local button2 = Instance.new("ImageButton", frame)
|
||||
button2.Name = "MyButton"
|
||||
assert(not (myInterface({ buttonInFrame = frame })))
|
||||
end
|
||||
end)
|
||||
|
||||
it("should support t.instanceIsA shorthand", function()
|
||||
local myInterface = t.interface({
|
||||
buttonInFrame = t.instanceIsA("Frame", {
|
||||
MyButton = t.instanceIsA("ImageButton")
|
||||
})
|
||||
})
|
||||
|
||||
assert(not (t.children({})(5)))
|
||||
assert(not (myInterface({ buttonInFrame = Instance.new("Frame") })))
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button = Instance.new("ImageButton", frame)
|
||||
button.Name = "MyButton"
|
||||
assert(myInterface({ buttonInFrame = frame }))
|
||||
end
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button = Instance.new("ImageButton", frame)
|
||||
button.Name = "NotMyButton"
|
||||
assert(not (myInterface({ buttonInFrame = frame })))
|
||||
end
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button = Instance.new("TextButton", frame)
|
||||
button.Name = "MyButton"
|
||||
assert(not (myInterface({ buttonInFrame = frame })))
|
||||
end
|
||||
|
||||
do
|
||||
local frame = Instance.new("Frame")
|
||||
local button1 = Instance.new("ImageButton", frame)
|
||||
button1.Name = "MyButton"
|
||||
local button2 = Instance.new("ImageButton", frame)
|
||||
button2.Name = "MyButton"
|
||||
assert(not (myInterface({ buttonInFrame = frame })))
|
||||
end
|
||||
end)
|
||||
|
||||
it("should support t.match", function()
|
||||
local check = t.match("%d+")
|
||||
assert(check("123"))
|
||||
assert(not (check("abc")))
|
||||
assert(not (check()))
|
||||
end)
|
||||
|
||||
it("should support t.keyOf", function()
|
||||
local myNewEnum = {
|
||||
OptionA = {},
|
||||
OptionB = {},
|
||||
}
|
||||
local check = t.keyOf(myNewEnum)
|
||||
assert(check("OptionA"))
|
||||
assert(not (check("OptionC")))
|
||||
end)
|
||||
|
||||
it("should support t.valueOf", function()
|
||||
local myNewEnum = {
|
||||
OptionA = {},
|
||||
OptionB = {},
|
||||
}
|
||||
local check = t.valueOf(myNewEnum)
|
||||
assert(check(myNewEnum.OptionA))
|
||||
assert(not (check(1010)))
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,214 @@
|
||||
/** checks to see if `value` is a T */
|
||||
type check<T> = (value: unknown) => value is T;
|
||||
|
||||
interface t {
|
||||
// lua types
|
||||
/** checks to see if `value` is an any */
|
||||
any: check<any>;
|
||||
/** checks to see if `value` is a boolean */
|
||||
boolean: check<boolean>;
|
||||
/** checks to see if `value` is a thread */
|
||||
thread: check<thread>;
|
||||
/** checks to see if `value` is a Function */
|
||||
callback: check<Function>;
|
||||
/** checks to see if `value` is undefined */
|
||||
none: check<undefined>;
|
||||
/** checks to see if `value` is a number, will _not_ match NaN */
|
||||
number: check<number>;
|
||||
/** checks to see if `value` is NaN */
|
||||
nan: check<number>;
|
||||
/** checks to see if `value` is a string */
|
||||
string: check<string>;
|
||||
/** checks to see if `value` is an object */
|
||||
table: check<object>;
|
||||
/** checks to see if `value` is a userdata */
|
||||
userdata: check<object>;
|
||||
|
||||
// roblox types
|
||||
/** checks to see if `value` is an Axes */
|
||||
Axes: check<Axes>;
|
||||
/** checks to see if `value` is a BrickColor */
|
||||
BrickColor: check<BrickColor>;
|
||||
/** checks to see if `value` is a CFrame */
|
||||
CFrame: check<CFrame>;
|
||||
/** checks to see if `value` is a Color3 */
|
||||
Color3: check<Color3>;
|
||||
/** checks to see if `value` is a ColorSequence */
|
||||
ColorSequence: check<ColorSequence>;
|
||||
/** checks to see if `value` is a ColorSequenceKeypoint */
|
||||
ColorSequenceKeypoint: check<ColorSequenceKeypoint>;
|
||||
/** checks to see if `value` is a DockWidgetPluginGuiInfo */
|
||||
DockWidgetPluginGuiInfo: check<DockWidgetPluginGuiInfo>;
|
||||
/** checks to see if `value` is a Faces */
|
||||
Faces: check<Faces>;
|
||||
/** checks to see if `value` is an Instance */
|
||||
Instance: check<Instance>;
|
||||
/** checks to see if `value` is a NumberRange */
|
||||
NumberRange: check<NumberRange>;
|
||||
/** checks to see if `value` is a NumberSequence */
|
||||
NumberSequence: check<NumberSequence>;
|
||||
/** checks to see if `value` is a NumberSequenceKeypoint */
|
||||
NumberSequenceKeypoint: check<NumberSequenceKeypoint>;
|
||||
/** checks to see if `value` is a PathWaypoint */
|
||||
PathWaypoint: check<PathWaypoint>;
|
||||
/** checks to see if `value` is a PhysicalProperties */
|
||||
PhysicalProperties: check<PhysicalProperties>;
|
||||
/** checks to see if `value` is a Random */
|
||||
Random: check<Random>;
|
||||
/** checks to see if `value` is a Ray */
|
||||
Ray: check<Ray>;
|
||||
/** checks to see if `value` is a Rect */
|
||||
Rect: check<Rect>;
|
||||
/** checks to see if `value` is a Region3 */
|
||||
Region3: check<Region3>;
|
||||
/** checks to see if `value` is a Region3int16 */
|
||||
Region3int16: check<Region3int16>;
|
||||
/** checks to see if `value` is a TweenInfo */
|
||||
TweenInfo: check<TweenInfo>;
|
||||
/** checks to see if `value` is a UDim */
|
||||
UDim: check<UDim>;
|
||||
/** checks to see if `value` is a UDim2 */
|
||||
UDim2: check<UDim2>;
|
||||
/** checks to see if `value` is a Vector2 */
|
||||
Vector2: check<Vector2>;
|
||||
/** checks to see if `value` is a Vector3 */
|
||||
Vector3: check<Vector3>;
|
||||
/** checks to see if `value` is a Vector3int16 */
|
||||
Vector3int16: check<Vector3int16>;
|
||||
/** checks to see if `value` is a RBXScriptSignal */
|
||||
RBXScriptSignal: check<RBXScriptSignal>;
|
||||
/** checks to see if `value` is a RBXScriptConnection */
|
||||
RBXScriptConnection: check<RBXScriptConnection>;
|
||||
|
||||
/**
|
||||
* checks to see if `value == literalValue`
|
||||
*/
|
||||
literal<T extends string | number | boolean | undefined>(this: void, literalValue: T): check<T>;
|
||||
literal<T extends Array<any>>(
|
||||
this: void,
|
||||
...args: T
|
||||
): T extends [infer A]
|
||||
? (value: unknown) => value is A
|
||||
: T extends [infer A, infer B]
|
||||
? check<A | B>
|
||||
: T extends [infer A, infer B, infer C]
|
||||
? check<A | B | C>
|
||||
: T extends [infer A, infer B, infer C, infer D]
|
||||
? check<A | B | C | D>
|
||||
: T extends [infer A, infer B, infer C, infer D, infer E]
|
||||
? check<A | B | C | D | E>
|
||||
: T extends [infer A, infer B, infer C, infer D, infer E, infer F]
|
||||
? check<A | B | C | D | E | F>
|
||||
: never;
|
||||
literal<T>(this: void, literalValue: T): (value: unknown) => value is T;
|
||||
|
||||
/** Returns a t.union of each key in the table as a t.literal */
|
||||
keyOf: <T>(valueTable: T) => check<keyof T>;
|
||||
|
||||
/** Returns a t.union of each value in the table as a t.literal */
|
||||
valueOf: <T>(valueTable: T) => T extends { [P in keyof T]: infer U } ? check<U> : never;
|
||||
|
||||
/** checks to see if `value` is an integer */
|
||||
integer: (value: unknown) => value is number;
|
||||
/** checks to see if `value` is a number and is more than or equal to `min` */
|
||||
numberMin: (min: number) => (value: unknown) => value is number;
|
||||
/** checks to see if `value` is a number and is less than or equal to `max` */
|
||||
numberMax: (max: number) => (value: unknown) => value is number;
|
||||
/** checks to see if `value` is a number and is more than `min` */
|
||||
numberMinExclusive: (min: number) => (value: unknown) => value is number;
|
||||
/** checks to see if `value` is a number and is less than `max` */
|
||||
numberMaxExclusive: (max: number) => (value: unknown) => value is number;
|
||||
/** checks to see if `value` is a number and is more than 0 */
|
||||
numberPositive: (value: unknown) => value is number;
|
||||
/** checks to see if `value` is a number and is less than 0 */
|
||||
numberNegative: (value: unknown) => value is number;
|
||||
/** checks to see if `value` is a number and `min <= value <= max` */
|
||||
numberConstrained: (min: number, max: number) => (value: unknown) => value is number;
|
||||
/** checks to see if `value` is a number and `min < value < max` */
|
||||
numberConstrainedExclusive: (min: number, max: number) => (value: unknown) => value is number;
|
||||
/** checks `t.string` and determines if value matches the pattern via `string.match(value, pattern)` */
|
||||
match: (pattern: string) => check<string>;
|
||||
/** checks to see if `value` is either nil or passes `check` */
|
||||
optional: <T>(check: (value: unknown) => value is T) => check<T | undefined>;
|
||||
/** checks to see if `value` is a table and if its keys match against `check */
|
||||
keys: <T>(check: (value: unknown) => value is T) => check<Map<T, unknown>>;
|
||||
/** checks to see if `value` is a table and if its values match against `check` */
|
||||
values: <T>(check: (value: unknown) => value is T) => check<Map<unknown, T>>;
|
||||
/** checks to see if `value` is a table and all of its keys match against `keyCheck` and all of its values match against `valueCheck` */
|
||||
map: <K, V>(
|
||||
keyCheck: (value: unknown) => value is K,
|
||||
valueCheck: (value: unknown) => value is V
|
||||
) => check<Map<K, V>>;
|
||||
/** checks to see if `value` is an array and all of its keys are sequential integers and all of its values match `check` */
|
||||
array: <T>(check: (value: unknown) => value is T) => check<Array<T>>;
|
||||
|
||||
/** checks to see if `value` matches any given check */
|
||||
union: <T extends Array<any>>(
|
||||
...args: T
|
||||
) => T extends [check<infer A>]
|
||||
? (value: unknown) => value is A
|
||||
: T extends [check<infer A>, check<infer B>]
|
||||
? check<A | B>
|
||||
: T extends [check<infer A>, check<infer B>, check<infer C>]
|
||||
? check<A | B | C>
|
||||
: T extends [check<infer A>, check<infer B>, check<infer C>, check<infer D>]
|
||||
? check<A | B | C | D>
|
||||
: T extends [check<infer A>, check<infer B>, check<infer C>, check<infer D>, check<infer E>]
|
||||
? check<A | B | C | D | E>
|
||||
: T extends [check<infer A>, check<infer B>, check<infer C>, check<infer D>, check<infer E>, check<infer F>]
|
||||
? check<A | B | C | D | E | F>
|
||||
: never;
|
||||
|
||||
/** checks to see if `value` matches all given checks */
|
||||
intersection: <T extends Array<any>>(
|
||||
...args: T
|
||||
) => T extends [check<infer A>]
|
||||
? (value: unknown) => value is A
|
||||
: T extends [check<infer A>, check<infer B>]
|
||||
? check<A & B>
|
||||
: T extends [check<infer A>, check<infer B>, check<infer C>]
|
||||
? check<A & B & C>
|
||||
: T extends [check<infer A>, check<infer B>, check<infer C>, check<infer D>]
|
||||
? check<A & B & C & D>
|
||||
: T extends [check<infer A>, check<infer B>, check<infer C>, check<infer D>, check<infer E>]
|
||||
? check<A & B & C & D & E>
|
||||
: T extends [check<infer A>, check<infer B>, check<infer C>, check<infer D>, check<infer E>, check<infer F>]
|
||||
? check<A & B & C & D & E & F>
|
||||
: never;
|
||||
|
||||
/** checks to see if `value` matches a given interface definition */
|
||||
interface: <T extends { [index: string]: (value: unknown) => value is any }>(
|
||||
checkTable: T
|
||||
) => check<{ [P in keyof T]: t.static<T[P]> }>;
|
||||
|
||||
/** checks to see if `value` matches a given interface definition with no extra members */
|
||||
strictInterface: <T extends { [index: string]: (value: unknown) => value is any }>(
|
||||
checkTable: T
|
||||
) => check<{ [P in keyof T]: t.static<T[P]> }>;
|
||||
|
||||
instanceOf<S extends keyof Instances>(this: void, className: S): check<Instances[S]>;
|
||||
instanceOf<S extends keyof Instances, T extends { [index: string]: (value: unknown) => value is any }>(
|
||||
this: void,
|
||||
className: S,
|
||||
checkTable: T
|
||||
): check<Instances[S] & { [P in keyof T]: t.static<T[P]> }>;
|
||||
|
||||
instanceIsA<S extends keyof Instances>(this: void, className: S): check<Instances[S]>;
|
||||
instanceIsA<S extends keyof Instances, T extends { [index: string]: (value: unknown) => value is any }>(
|
||||
this: void,
|
||||
className: S,
|
||||
checkTable: T
|
||||
): check<Instances[S] & { [P in keyof T]: t.static<T[P]> }>;
|
||||
|
||||
children: <T extends { [index: string]: (value: unknown) => value is any }>(
|
||||
checkTable: T
|
||||
) => check<Instance & { [P in keyof T]: t.static<T[P]> }>;
|
||||
}
|
||||
|
||||
declare namespace t {
|
||||
/** creates a static type from a t-defined type */
|
||||
export type static<T> = T extends check<infer U> ? U : never;
|
||||
}
|
||||
|
||||
declare const t: t;
|
||||
export = t;
|
||||
@@ -0,0 +1,554 @@
|
||||
-- t: a runtime typechecker for Roblox
|
||||
|
||||
-- regular lua compatibility
|
||||
local typeof = typeof or type
|
||||
|
||||
local function primitive(typeName)
|
||||
return function(value)
|
||||
local valueType = typeof(value)
|
||||
if valueType == typeName then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local t = {}
|
||||
|
||||
function t.any(value)
|
||||
if value ~= nil then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--Lua primitives
|
||||
|
||||
t.boolean = primitive("boolean")
|
||||
t.thread = primitive("thread")
|
||||
t.callback = primitive("function")
|
||||
t.none = primitive("nil")
|
||||
t.string = primitive("string")
|
||||
t.table = primitive("table")
|
||||
t.userdata = primitive("userdata")
|
||||
|
||||
function t.number(value)
|
||||
local valueType = typeof(value)
|
||||
if valueType == "number" then
|
||||
if value == value then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function t.nan(value)
|
||||
if value ~= value then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- roblox types
|
||||
|
||||
t.Axes = primitive("Axes")
|
||||
t.BrickColor = primitive("BrickColor")
|
||||
t.CFrame = primitive("CFrame")
|
||||
t.Color3 = primitive("Color3")
|
||||
t.ColorSequence = primitive("ColorSequence")
|
||||
t.ColorSequenceKeypoint = primitive("ColorSequenceKeypoint")
|
||||
t.DockWidgetPluginGuiInfo = primitive("DockWidgetPluginGuiInfo")
|
||||
t.Faces = primitive("Faces")
|
||||
t.Instance = primitive("Instance")
|
||||
t.NumberRange = primitive("NumberRange")
|
||||
t.NumberSequence = primitive("NumberSequence")
|
||||
t.NumberSequenceKeypoint = primitive("NumberSequenceKeypoint")
|
||||
t.PathWaypoint = primitive("PathWaypoint")
|
||||
t.PhysicalProperties = primitive("PhysicalProperties")
|
||||
t.Random = primitive("Random")
|
||||
t.Ray = primitive("Ray")
|
||||
t.Rect = primitive("Rect")
|
||||
t.Region3 = primitive("Region3")
|
||||
t.Region3int16 = primitive("Region3int16")
|
||||
t.TweenInfo = primitive("TweenInfo")
|
||||
t.UDim = primitive("UDim")
|
||||
t.UDim2 = primitive("UDim2")
|
||||
t.Vector2 = primitive("Vector2")
|
||||
t.Vector3 = primitive("Vector3")
|
||||
t.Vector3int16 = primitive("Vector3int16")
|
||||
t.Enum = primitive("Enum")
|
||||
t.EnumItem = primitive("EnumItem")
|
||||
t.RBXScriptSignal = primitive("RBXScriptSignal")
|
||||
t.RBXScriptConnection = primitive("RBXScriptConnection")
|
||||
|
||||
function t.literal(...)
|
||||
local size = select("#", ...)
|
||||
if size == 1 then
|
||||
local literal = ...
|
||||
return function(value)
|
||||
if value ~= literal then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
else
|
||||
local literals = {}
|
||||
for i = 1, size do
|
||||
local value = select(i, ...)
|
||||
literals[i] = t.literal(value)
|
||||
end
|
||||
return t.union(unpack(literals))
|
||||
end
|
||||
end
|
||||
|
||||
t.exactly = t.literal
|
||||
|
||||
function t.keyOf(keyTable)
|
||||
local keys = {}
|
||||
for key in pairs(keyTable) do
|
||||
keys[#keys + 1] = key
|
||||
end
|
||||
return t.literal(unpack(keys))
|
||||
end
|
||||
|
||||
function t.valueOf(valueTable)
|
||||
local values = {}
|
||||
for _, value in pairs(valueTable) do
|
||||
values[#values + 1] = value
|
||||
end
|
||||
return t.literal(unpack(values))
|
||||
end
|
||||
|
||||
function t.integer(value)
|
||||
local success = t.number(value)
|
||||
if not success then
|
||||
return false
|
||||
end
|
||||
if value%1 == 0 then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function t.numberMin(min)
|
||||
return function(value)
|
||||
local success = t.number(value)
|
||||
if not success then
|
||||
return false
|
||||
end
|
||||
if value >= min then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function t.numberMax(max)
|
||||
return function(value)
|
||||
local success = t.number(value)
|
||||
if not success then
|
||||
return false
|
||||
end
|
||||
if value <= max then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function t.numberMinExclusive(min)
|
||||
return function(value)
|
||||
local success = t.number(value)
|
||||
if not success then
|
||||
return false
|
||||
end
|
||||
if min < value then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function t.numberMaxExclusive(max)
|
||||
return function(value)
|
||||
local success = t.number(value)
|
||||
if not success then
|
||||
return false
|
||||
end
|
||||
if value < max then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
t.numberPositive = t.numberMinExclusive(0)
|
||||
t.numberNegative = t.numberMaxExclusive(0)
|
||||
|
||||
function t.numberConstrained(min, max)
|
||||
assert(t.number(min) and t.number(max))
|
||||
local minCheck = t.numberMin(min)
|
||||
local maxCheck = t.numberMax(max)
|
||||
return function(value)
|
||||
local minSuccess = minCheck(value)
|
||||
if not minSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
local maxSuccess = maxCheck(value)
|
||||
if not maxSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function t.numberConstrainedExclusive(min, max)
|
||||
assert(t.number(min) and t.number(max))
|
||||
local minCheck = t.numberMinExclusive(min)
|
||||
local maxCheck = t.numberMaxExclusive(max)
|
||||
return function(value)
|
||||
local minSuccess = minCheck(value)
|
||||
if not minSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
local maxSuccess = maxCheck(value)
|
||||
if not maxSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function t.match(pattern)
|
||||
assert(t.string(pattern))
|
||||
return function(value)
|
||||
local stringSuccess = t.string(value)
|
||||
if not stringSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
if string.match(value, pattern) == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function t.optional(check)
|
||||
assert(t.callback(check))
|
||||
return function(value)
|
||||
if value == nil then
|
||||
return true
|
||||
end
|
||||
local success = check(value)
|
||||
if success then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function t.tuple(...)
|
||||
local checks = {...}
|
||||
return function(...)
|
||||
local args = {...}
|
||||
for i = 1, #checks do
|
||||
local success = checks[i](args[i])
|
||||
if success == false then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function t.keys(check)
|
||||
assert(t.callback(check))
|
||||
return function(value)
|
||||
local tableSuccess = t.table(value)
|
||||
if tableSuccess == false then
|
||||
return false
|
||||
end
|
||||
|
||||
for key in pairs(value) do
|
||||
local success = check(key)
|
||||
if success == false then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function t.values(check)
|
||||
assert(t.callback(check))
|
||||
return function(value)
|
||||
local tableSuccess = t.table(value)
|
||||
if tableSuccess == false then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, val in pairs(value) do
|
||||
local success = check(val)
|
||||
if success == false then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function t.map(keyCheck, valueCheck)
|
||||
assert(t.callback(keyCheck), t.callback(valueCheck))
|
||||
local keyChecker = t.keys(keyCheck)
|
||||
local valueChecker = t.values(valueCheck)
|
||||
return function(value)
|
||||
local keySuccess = keyChecker(value)
|
||||
if not keySuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
local valueSuccess = valueChecker(value)
|
||||
if not valueSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local arrayKeysCheck = t.keys(t.integer)
|
||||
|
||||
function t.array(check)
|
||||
assert(t.callback(check))
|
||||
local valuesCheck = t.values(check)
|
||||
return function(value)
|
||||
local keySuccess = arrayKeysCheck(value)
|
||||
if keySuccess == false then
|
||||
return false
|
||||
end
|
||||
|
||||
-- # is unreliable for sparse arrays
|
||||
-- Count upwards using ipairs to avoid false positives from the behavior of #
|
||||
local arraySize = 0
|
||||
|
||||
for _, _ in ipairs(value) do
|
||||
arraySize = arraySize + 1
|
||||
end
|
||||
|
||||
for key in pairs(value) do
|
||||
if key < 1 or key > arraySize then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local valueSuccess = valuesCheck(value)
|
||||
if not valueSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local callbackArray = t.array(t.callback)
|
||||
|
||||
function t.union(...)
|
||||
local checks = {...}
|
||||
assert(callbackArray(checks))
|
||||
return function(value)
|
||||
for _, check in pairs(checks) do
|
||||
if check(value) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function t.intersection(...)
|
||||
local checks = {...}
|
||||
assert(callbackArray(checks))
|
||||
return function(value)
|
||||
for _, check in pairs(checks) do
|
||||
local success = check(value)
|
||||
if not success then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local checkInterface = t.map(t.any, t.callback)
|
||||
|
||||
function t.interface(checkTable)
|
||||
assert(checkInterface(checkTable))
|
||||
return function(value)
|
||||
local tableSuccess = t.table(value)
|
||||
if tableSuccess == false then
|
||||
return false
|
||||
end
|
||||
|
||||
for key, check in pairs(checkTable) do
|
||||
local success = check(value[key])
|
||||
if success == false then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function t.strictInterface(checkTable)
|
||||
assert(checkInterface(checkTable))
|
||||
return function(value)
|
||||
local tableSuccess = t.table(value)
|
||||
if tableSuccess == false then
|
||||
return false
|
||||
end
|
||||
|
||||
for key, check in pairs(checkTable) do
|
||||
local success = check(value[key])
|
||||
if success == false then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for key in pairs(value) do
|
||||
if not checkTable[key] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function t.instanceOf(className)
|
||||
assert(t.string(className))
|
||||
return function(value)
|
||||
local instanceSuccess = t.Instance(value)
|
||||
if not instanceSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
if value.ClassName ~= className then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
t.instance = t.instanceOf
|
||||
|
||||
function t.instanceIsA(className)
|
||||
assert(t.string(className))
|
||||
return function(value)
|
||||
local instanceSuccess = t.Instance(value)
|
||||
if not instanceSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
if not value:IsA(className) then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function t.enum(enum)
|
||||
assert(t.Enum(enum))
|
||||
return function(value)
|
||||
local enumItemSuccess = t.EnumItem(value)
|
||||
if not enumItemSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
if value.EnumType == enum then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local checkWrap = t.tuple(t.callback, t.callback)
|
||||
|
||||
function t.wrap(callback, checkArgs)
|
||||
assert(checkWrap(callback, checkArgs))
|
||||
return function(...)
|
||||
assert(checkArgs(...))
|
||||
return callback(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function t.strict(check)
|
||||
return function(...)
|
||||
assert(check(...))
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local checkChildren = t.map(t.string, t.callback)
|
||||
function t.children(checkTable)
|
||||
assert(checkChildren(checkTable))
|
||||
|
||||
return function(value)
|
||||
local instanceSuccess = t.Instance(value)
|
||||
if not instanceSuccess then
|
||||
return false
|
||||
end
|
||||
|
||||
local childrenByName = {}
|
||||
for _, child in pairs(value:GetChildren()) do
|
||||
local name = child.Name
|
||||
if checkTable[name] then
|
||||
if childrenByName[name] then
|
||||
return false
|
||||
end
|
||||
childrenByName[name] = child
|
||||
end
|
||||
end
|
||||
|
||||
for name, check in pairs(checkTable) do
|
||||
local success = check(childrenByName[name])
|
||||
if not success then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
Reference in New Issue
Block a user