mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Add typedefs and tests for custom instance properties and methods
This commit is contained in:
parent
9fe3b02d71
commit
1aa6aef679
6 changed files with 255 additions and 0 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -8,6 +8,26 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `implementProperty` and `implementMethod` to the `roblox` built-in library to fill in missing functionality that Lune does not aim to implement itself.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local roblox = require("@lune/roblox")
|
||||||
|
|
||||||
|
local part = roblox.Instance.new("Part")
|
||||||
|
|
||||||
|
roblox.implementMethod("BasePart", "TestMethod", function(_, ...)
|
||||||
|
print("Tried to call TestMethod with", ...)
|
||||||
|
end)
|
||||||
|
|
||||||
|
part:TestMethod("Hello", "world!")
|
||||||
|
```
|
||||||
|
|
||||||
## `0.7.8` - October 5th, 2023
|
## `0.7.8` - October 5th, 2023
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -164,6 +164,10 @@ create_tests! {
|
||||||
roblox_instance_classes_workspace: "roblox/instance/classes/Workspace",
|
roblox_instance_classes_workspace: "roblox/instance/classes/Workspace",
|
||||||
roblox_instance_classes_terrain: "roblox/instance/classes/Terrain",
|
roblox_instance_classes_terrain: "roblox/instance/classes/Terrain",
|
||||||
|
|
||||||
|
roblox_instance_custom_async: "roblox/instance/custom/async",
|
||||||
|
roblox_instance_custom_methods: "roblox/instance/custom/methods",
|
||||||
|
roblox_instance_custom_properties: "roblox/instance/custom/properties",
|
||||||
|
|
||||||
roblox_instance_methods_clear_all_children: "roblox/instance/methods/ClearAllChildren",
|
roblox_instance_methods_clear_all_children: "roblox/instance/methods/ClearAllChildren",
|
||||||
roblox_instance_methods_clone: "roblox/instance/methods/Clone",
|
roblox_instance_methods_clone: "roblox/instance/methods/Clone",
|
||||||
roblox_instance_methods_destroy: "roblox/instance/methods/Destroy",
|
roblox_instance_methods_destroy: "roblox/instance/methods/Destroy",
|
||||||
|
|
12
tests/roblox/instance/custom/async.luau
Normal file
12
tests/roblox/instance/custom/async.luau
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
local roblox = require("@lune/roblox")
|
||||||
|
|
||||||
|
local game = roblox.Instance.new("DataModel")
|
||||||
|
local http = game:GetService("HttpService") :: any
|
||||||
|
|
||||||
|
roblox.implementMethod("HttpService", "GetAsync", function()
|
||||||
|
-- TODO: Fill in method body
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- TODO: Fill in rest of test cases here
|
||||||
|
|
||||||
|
http:GetAsync()
|
48
tests/roblox/instance/custom/methods.luau
Normal file
48
tests/roblox/instance/custom/methods.luau
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
local roblox = require("@lune/roblox")
|
||||||
|
|
||||||
|
local inst = roblox.Instance.new("Instance") :: any
|
||||||
|
local part = roblox.Instance.new("Part") :: any
|
||||||
|
|
||||||
|
-- Basic sanity checks for callbacks
|
||||||
|
|
||||||
|
local success = pcall(function()
|
||||||
|
inst:Wat()
|
||||||
|
end)
|
||||||
|
assert(not success, "Nonexistent methods should error")
|
||||||
|
|
||||||
|
roblox.implementMethod("Instance", "Wat", function() end)
|
||||||
|
|
||||||
|
local success2 = pcall(function()
|
||||||
|
inst:Wat()
|
||||||
|
end)
|
||||||
|
assert(success2, "Nonexistent methods should error, unless implemented")
|
||||||
|
|
||||||
|
-- Instance should be passed to callback
|
||||||
|
|
||||||
|
roblox.implementMethod("Instance", "PassingInstanceTest", function(instance)
|
||||||
|
assert(instance == inst, "Invalid instance was passed to callback")
|
||||||
|
end)
|
||||||
|
roblox.implementMethod("Part", "PassingPartTest", function(instance)
|
||||||
|
assert(instance == part, "Invalid instance was passed to callback")
|
||||||
|
end)
|
||||||
|
inst:PassingInstanceTest()
|
||||||
|
part:PassingPartTest()
|
||||||
|
|
||||||
|
-- Any number of args passed & returned should work
|
||||||
|
|
||||||
|
roblox.implementMethod("Instance", "Echo", function(_, ...)
|
||||||
|
return ...
|
||||||
|
end)
|
||||||
|
|
||||||
|
local one, two, three = inst:Echo("one", "two", "three")
|
||||||
|
assert(one == "one", "implementMethod callback should return proper values")
|
||||||
|
assert(two == "two", "implementMethod callback should return proper values")
|
||||||
|
assert(three == "three", "implementMethod callback should return proper values")
|
||||||
|
|
||||||
|
-- Methods implemented by Lune should take precedence
|
||||||
|
|
||||||
|
roblox.implementMethod("Instance", "FindFirstChild", function()
|
||||||
|
error("unreachable")
|
||||||
|
end)
|
||||||
|
inst:FindFirstChild("Test")
|
||||||
|
part:FindFirstChild("Test")
|
64
tests/roblox/instance/custom/properties.luau
Normal file
64
tests/roblox/instance/custom/properties.luau
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
local roblox = require("@lune/roblox")
|
||||||
|
|
||||||
|
local inst = roblox.Instance.new("Instance") :: any
|
||||||
|
local part = roblox.Instance.new("Part") :: any
|
||||||
|
|
||||||
|
-- Basic sanity checks for callbacks
|
||||||
|
|
||||||
|
local success = pcall(function()
|
||||||
|
local _ = inst.Wat
|
||||||
|
end)
|
||||||
|
assert(not success, "Nonexistent properties should error")
|
||||||
|
|
||||||
|
roblox.implementProperty("Instance", "Wat", function()
|
||||||
|
return nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
local success2 = pcall(function()
|
||||||
|
local _ = inst.Wat
|
||||||
|
end)
|
||||||
|
assert(success2, "Nonexistent properties should error, unless implemented")
|
||||||
|
|
||||||
|
-- Instance should be passed to callback
|
||||||
|
|
||||||
|
roblox.implementProperty("Instance", "PassingInstanceTest", function(instance)
|
||||||
|
assert(instance == inst, "Invalid instance was passed to callback")
|
||||||
|
return nil
|
||||||
|
end)
|
||||||
|
roblox.implementProperty("Part", "PassingPartTest", function(instance)
|
||||||
|
assert(instance == part, "Invalid instance was passed to callback")
|
||||||
|
return nil
|
||||||
|
end)
|
||||||
|
local _ = inst.PassingInstanceTest
|
||||||
|
local _ = part.PassingPartTest
|
||||||
|
|
||||||
|
-- Any number of args passed & returned should work
|
||||||
|
|
||||||
|
local counters = {}
|
||||||
|
roblox.implementProperty("Instance", "Counter", function(instance)
|
||||||
|
-- FIXME: Instances do not make for unique table keys for some reason ...
|
||||||
|
local value = counters[tostring(instance)] or 0
|
||||||
|
value += 1
|
||||||
|
counters[tostring(instance)] = value
|
||||||
|
return value
|
||||||
|
end, function(instance, value)
|
||||||
|
counters[tostring(instance)] = value
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert(inst.Counter == 1, "implementProperty callback should return proper values")
|
||||||
|
assert(inst.Counter == 2, "implementProperty callback should return proper values")
|
||||||
|
assert(inst.Counter == 3, "implementProperty callback should return proper values")
|
||||||
|
|
||||||
|
inst.Counter = 10
|
||||||
|
|
||||||
|
assert(inst.Counter == 11, "implementProperty callback should set proper values")
|
||||||
|
assert(inst.Counter == 12, "implementProperty callback should return proper values")
|
||||||
|
assert(inst.Counter == 13, "implementProperty callback should return proper values")
|
||||||
|
|
||||||
|
-- Properties implemented by Lune should take precedence
|
||||||
|
|
||||||
|
roblox.implementProperty("Instance", "Parent", function()
|
||||||
|
error("unreachable")
|
||||||
|
end)
|
||||||
|
local _ = inst.Parent
|
||||||
|
local _ = part.Parent
|
|
@ -391,6 +391,113 @@ function roblox.getReflectionDatabase(): Database
|
||||||
return nil :: any
|
return nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Roblox
|
||||||
|
|
||||||
|
Implements a property for all instances of the given `className`.
|
||||||
|
|
||||||
|
This takes into account class hierarchies, so implementing a property
|
||||||
|
for the `BasePart` class will also implement it for `Part` and others,
|
||||||
|
unless a more specific implementation is added to the `Part` class directly.
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
The given `getter` callback will be called each time the property is
|
||||||
|
indexed, with the instance as its one and only argument. The `setter`
|
||||||
|
callback, if given, will be called each time the property should be set,
|
||||||
|
with the instance as the first argument and the property value as second.
|
||||||
|
|
||||||
|
### Example usage
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local roblox = require("@lune/roblox")
|
||||||
|
|
||||||
|
local part = roblox.Instance.new("Part")
|
||||||
|
|
||||||
|
local propertyValues = {}
|
||||||
|
roblox.implementProperty(
|
||||||
|
"BasePart",
|
||||||
|
"CoolProp",
|
||||||
|
function(instance)
|
||||||
|
if propertyValues[instance] == nil then
|
||||||
|
propertyValues[instance] = 0
|
||||||
|
end
|
||||||
|
propertyValues[instance] += 1
|
||||||
|
return propertyValues[instance]
|
||||||
|
end,
|
||||||
|
function(instance, value)
|
||||||
|
propertyValues[instance] = value
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
print(part.CoolProp) --> 1
|
||||||
|
print(part.CoolProp) --> 2
|
||||||
|
print(part.CoolProp) --> 3
|
||||||
|
|
||||||
|
part.CoolProp = 10
|
||||||
|
|
||||||
|
print(part.CoolProp) --> 11
|
||||||
|
print(part.CoolProp) --> 12
|
||||||
|
print(part.CoolProp) --> 13
|
||||||
|
```
|
||||||
|
|
||||||
|
@param className The class to implement the property for.
|
||||||
|
@param propertyName The name of the property to implement.
|
||||||
|
@param getter The function which will be called to get the property value when indexed.
|
||||||
|
@param setter The function which will be called to set the property value when indexed. Defaults to a function that will error with a message saying the property is read-only.
|
||||||
|
]=]
|
||||||
|
function roblox.implementProperty<T>(
|
||||||
|
className: string,
|
||||||
|
propertyName: string,
|
||||||
|
getter: (instance: Instance) -> T,
|
||||||
|
setter: ((instance: Instance, value: T) -> ())?
|
||||||
|
)
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Roblox
|
||||||
|
|
||||||
|
Implements a method for all instances of the given `className`.
|
||||||
|
|
||||||
|
This takes into account class hierarchies, so implementing a method
|
||||||
|
for the `BasePart` class will also implement it for `Part` and others,
|
||||||
|
unless a more specific implementation is added to the `Part` class directly.
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
The given `callback` will be called every time the method is called,
|
||||||
|
and will receive the instance it was called on as its first argument.
|
||||||
|
The remaining arguments will be what the caller passed to the method, and
|
||||||
|
all values returned from the callback will then be returned to the caller.
|
||||||
|
|
||||||
|
### Example usage
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local roblox = require("@lune/roblox")
|
||||||
|
|
||||||
|
local part = roblox.Instance.new("Part")
|
||||||
|
|
||||||
|
roblox.implementMethod("BasePart", "TestMethod", function(instance, ...)
|
||||||
|
print("Called TestMethod on instance", instance, "with", ...)
|
||||||
|
end)
|
||||||
|
|
||||||
|
part:TestMethod("Hello", "world!")
|
||||||
|
--> Called TestMethod on instance Part with Hello, world!
|
||||||
|
```
|
||||||
|
|
||||||
|
@param className The class to implement the method for.
|
||||||
|
@param methodName The name of the method to implement.
|
||||||
|
@param callback The function which will be called when the method is called.
|
||||||
|
]=]
|
||||||
|
function roblox.implementMethod(
|
||||||
|
className: string,
|
||||||
|
methodName: string,
|
||||||
|
callback: (instance: Instance, ...any) -> ...any
|
||||||
|
)
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
-- TODO: Make typedefs for all of the datatypes as well...
|
-- TODO: Make typedefs for all of the datatypes as well...
|
||||||
roblox.Instance = (nil :: any) :: {
|
roblox.Instance = (nil :: any) :: {
|
||||||
new: ((className: "DataModel") -> DataModel) & ((className: string) -> Instance),
|
new: ((className: "DataModel") -> DataModel) & ((className: string) -> Instance),
|
||||||
|
|
Loading…
Reference in a new issue