├── .gitignore ├── .luaurc ├── .moonwave └── static │ └── favicon.ico ├── .pesde ├── roblox_sync_config_generator.luau └── sourcemap_generator.luau ├── CHANGELOG.md ├── LICENSE ├── README.md ├── WallyPatches └── jsdotlua_jest-roblox-shared@3.6.1-rc.2.patch ├── aftman.toml ├── default.project.json ├── lune ├── build-lune-zip.luau ├── enable-loadmodule.luau ├── moonwave.luau ├── prepare-lune-version.luau ├── publish-all.luau ├── set-version.luau └── wally-install.luau ├── moonwave.toml ├── pages └── DEVELOPMENT.md ├── pesde.toml ├── rokit.toml ├── selene.toml ├── src-t-standalone-pesde ├── pesde.toml └── t.luau ├── src-t-standalone-wally ├── init.luau └── wally.toml ├── src ├── GreenTea.luau ├── InstanceClasses.luau ├── init.luau └── tCompat.luau ├── test.project.json ├── test ├── GreenTea.spec.luau ├── bench │ ├── lineLengthOf.bench.luau │ └── spaceLengthOf.bench.luau ├── init.server.luau ├── jest.config.luau └── tCompat.spec.luau └── wally.toml /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/settings.json 2 | 3 | /Packages 4 | /ServerPackages 5 | /DevPackages 6 | 7 | roblox_packages 8 | lune_packages 9 | pesde.lock 10 | 11 | /sourcemap.json 12 | /wally.lock 13 | 14 | /build 15 | /moonwave -------------------------------------------------------------------------------- /.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "languageMode": "strict", 3 | "lint": { 4 | "*": true, 5 | "LocalShadow": false 6 | } 7 | } -------------------------------------------------------------------------------- /.moonwave/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Corecii/GreenTea/99016875c7c71cc96cb79b9e56cdcc8f96592f65/.moonwave/static/favicon.ico -------------------------------------------------------------------------------- /.pesde/roblox_sync_config_generator.luau: -------------------------------------------------------------------------------- 1 | local process = require("@lune/process") 2 | local home_dir = if process.os == "windows" then process.env.userprofile else process.env.HOME 3 | 4 | require(home_dir .. "/.pesde/scripts/lune/rojo/roblox_sync_config_generator.luau") 5 | -------------------------------------------------------------------------------- /.pesde/sourcemap_generator.luau: -------------------------------------------------------------------------------- 1 | local process = require("@lune/process") 2 | local home_dir = if process.os == "windows" then process.env.userprofile else process.env.HOME 3 | 4 | require(home_dir .. "/.pesde/scripts/lune/rojo/sourcemap_generator.luau") -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | Nothing yet! 6 | 7 | ## 0.4.11 8 | 9 | - Add `GreenTea.wrapFn`, `GreenTea.wrapFnArgs`, and `GreenTea.wrapFnReturns` 10 | - Deprecate `Type.wrapFn` 11 | - Update various docs 12 | 13 | ## 0.4.10 14 | 15 | - Handle missing `utf8.graphemes` function for Lune support. 16 | - Added lune support. 17 | 18 | ## 0.4.9+pesde 19 | 20 | - Added pesde support. No code changes or package version changes. 21 | 22 | ## 0.4.9 23 | 24 | - Remove class which was removed from the engine. 25 | - Add docs for what to do when a class is removed from the engine. 26 | 27 | ## 0.4.8 28 | 29 | - Optimizations 30 | - Internal change: only use `\` in markdown comments for indication newlines. Cleans up moonwave docs some. 31 | - Internal change: wrap moonwave in a helper script that makes corrections for moonwave. 32 | - Internal change: format with Stylua. 33 | 34 | ## 0.4.7 35 | 36 | - Fix Type:assert and add tests for it 37 | - Fix tests for table type to check __index 38 | 39 | ## 0.4.6 40 | 41 | - Fix buggy type definition for `GreenTea.fn` 42 | - Make `GreenTea.build` handle tuples properly 43 | - Export a type for `GreenTea.build`'s "BuiltType" values 44 | 45 | ## 0.4.5 46 | 47 | - Fix type signature of `__call` 48 | - Fix typechecking failure in `.meta` 49 | 50 | ## 0.4.4 51 | 52 | - Fix: meta did not return input 53 | 54 | ## 0.4.3 55 | 56 | - Add `meta` to exports 57 | 58 | ## 0.4.2 59 | 60 | - Make metadata type more permissive 61 | 62 | ## 0.4.1 63 | 64 | - Add user-specified metadata 65 | 66 | ## 0.4.0 67 | 68 | - Change behavior around tables to respect `__iter`, `__index` 69 | - Change behavior around tables to not check if ``getmetatable(input) == getmetable(typedef)` 70 | 71 | ## 0.3.1 72 | 73 | - Only show simplified input types in error messages 74 | 75 | ## 0.3.0 76 | 77 | - Change `Type.__call` to return a string cause instead of an object for `t` and `assert` compatibility. 78 | 79 | ## 0.2.1 80 | 81 | - Add missing `t` members 82 | 83 | ## 0.2.0 84 | 85 | - Catch edge case where a GreenTea constructor is passed into a GreenTea constructor 86 | - Freeze GreenTea and its child tables so they can't be modified 87 | - Change `basic` types to have a `typeof` or `type` to differentiate them _[breaking change]_ 88 | - Fix some docs issues 89 | 90 | Pushing this is a normal breaking change because no one's using this library yet so there are no ecosystem concerns about a breaking change! 91 | 92 | ## 0.1.1 93 | 94 | - Fixed formatting for error cases where 3+ errors occur on one line. 95 | 96 | ## 0.1.0 97 | 98 | Initial release. 99 | Expect future breaking changes as ergonomics are figured out. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Shae A. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🍵 GreenTea 2 | An experimental runtime typechecker for Roblox Luau, with matching 'tooling-time' Luau types. 3 | 4 | [Docs](https://corecii.github.io/GreenTea/) 5 | 6 | ## Features 7 | - Check types at runtime (like [the t package](https://github.com/osyrisrblx/t)) 8 | - Build Luau types and runtime types simultaneously (so you have less duplicate code!) 9 | - Pretty errors that tell you exactly where you went wrong, even for unions and intersections 10 | - Inspect types at runtime, so you can procedurally check types in weird scenarios, such as when checking a set of Instance attributes. 11 | - Infer types from values 12 | - `t` compatibility (via `GreenTea.t` — all `t` tests pass!) 13 | 14 | ## Experimental 15 | While this package has had a lot of work put into it, it's largely an experiment. It's a first iteration, so the API can use some improvement — especially the structure of the generated Type objects, which could have a better shape. 16 | 17 | Please leave feedback on the issues page. Thank you! 18 | 19 | ## Install 20 | 21 | ### with Wally (for Rojo) 22 | 1. [Install Wally](https://wally.run/install) 23 | 2. Add `GreenTea = "corecii/greentea@0.4.11"` to your `wally.toml` 24 | 25 | ### with pesde (for Rojo or Lune) 26 | 1. [Install pesde](https://docs.pesde.daimond113.com/installation) 27 | 2. Run `pesde add corecii/greentea`\ 28 | or add `GreenTea = { name = "corecii/greentea", version = "^0.4.11" }` to your `pesde.toml` 29 | 30 | ### Standalone (for non-Rojo) 31 | - Download from [the Releases page](https://github.com/corecii/greentea/releases) 32 | 33 | ## How Does It Work? 34 | 35 | GreenTea's runtime code looks like a typical runtime typechecker: 36 | you call functions to create typecheckers and compose them together. 37 | 38 | It has two tricks: 39 | 1. Its function type definitions _lie_ and say that it's returning _the type that you're checking for_ instead of a GreenTea.Type object. 40 | So while you're composing a runtime type, you're _also_ composing a Luau type definition! 41 | 2. The typechecker it produces is actually an _object_, which gives you a runtime-inspectable 42 | type definition you can do more advanced stuff with. This also means prettier errors that look a lot like Luau type errors. 43 | 44 | ## Examples 45 | 46 | ### Easy Mode 47 | 48 | ```lua 49 | local gt = require(path.to.GreenTea) 50 | 51 | local stringType = gt.build(gt.string()) 52 | 53 | -- equivalent to takesString(value: string) 54 | local function takesString(value: typeof(stringType:type())) 55 | -- will error if value is not a string 56 | stringType:assert(value) 57 | 58 | -- ... 59 | end 60 | ``` 61 | 62 | Using `gt` is recommended, because `GreenTea` is a lot to type and `gt` is not! 63 | 64 | ### How it Works 65 | 66 | ```lua 67 | local stringTypeRaw = gt.string() 68 | 69 | -- stringTypeRaw is typed in Luau as `string` 70 | -- but at runtime, it's really a GreenTea.Type object. 71 | -- This makes it easy to split it up into a runtime 72 | -- typechecker and a Luau type: 73 | 74 | -- give a name to the type 75 | type stringTypeLuau = typeof(stringTypeRaw:type()) 76 | 77 | -- cast the object to the runtime typechecker type it really is 78 | local stringTypeChecker = gt.typecast(stringTypeRaw) 79 | 80 | -- You can think of this like "tricking" Luau into 81 | -- "giving" us a `string` type. 82 | -- This is all `GreenTea.build` does -- it typecasts 83 | -- the value into a GreenTea.Type object, but includes 84 | -- the original type os you can use `typeof`. 85 | 86 | -- But the magic is this also works for more complex types! 87 | 88 | local catTypeRaw = gt.table({ 89 | age = gt.number(), 90 | meowSound = gt.string(), 91 | breed = { 92 | [gt.string()] = gt.number(), 93 | }, 94 | }) 95 | 96 | type catTypeLuau = typeof(catTypeRaw) 97 | -- which is the same as... 98 | type catTypeSame = { 99 | age: number, 100 | meowSound: string, 101 | breed: { [string]: number }, 102 | } 103 | 104 | -- and we can get a runtime typechecker... 105 | local catTypeChecker = gt.typecast(catTypeRaw) 106 | 107 | -- OR we can make it really easy on ourselves if we just use `build` from the beginning: 108 | 109 | local catType = gt.build(gt.table{ 110 | age = gt.number(), 111 | meowSound = gt.string(), 112 | breed = { 113 | [gt.indexer(gt.string())] = gt.number(), 114 | }, 115 | })) 116 | 117 | -- catType == catTypeChecker 118 | -- catTypeLuau == typeof(catTypeChecker:type()) 119 | ``` 120 | 121 | Using `build` is recommended because it makes this mostly type-safe: 122 | - The returned value is properly typed a GreenTea Type object, so you 123 | have typechecked access to GreenTea.Type's properties and methods. 124 | - TypeObject.type() can still be passed to GreenTea constructors to 125 | pass the GreenTea type in. 126 | - `build` runs `GreenTea.typeof` internally, so you can pass it simple tables 127 | and they'll be converted to the proper `GreenTea.table` type. 128 | 129 | In this way, "built" types are for direct use, and "unbuilt" types are for composition 130 | with other GreenTea constructors. 131 | 132 | ### Taking in GreenTea types 133 | 134 | GreenTea is great for libraries that want to do typechecking, like RemoteEvent wrappers, because you can make the API very ergonomic: 135 | 136 | ```lua 137 | -- RemoteWrapper.luau 138 | 139 | function RemoteWrapper.new(greenTeaType: T): RemoteWrapper 140 | local self = { 141 | typechecker = GreenTea.build(greenTeaType), 142 | ... 143 | } 144 | 145 | return setmetatable(self, RemoteWrapper) 146 | end 147 | 148 | function RemoteWrapper.onServerEvent(self: RemoteWrapper, fn: (player: Player, params: T) -> ()) 149 | self.event:OnServerEvent(function(player: Player, paramsMaybe: any?) 150 | local params = self.typechecker:assert(params) 151 | fn(player, params) 152 | end) 153 | end 154 | 155 | ``` 156 | ```lua 157 | -- CatSpawner.luau 158 | 159 | local SpawnCat = RemoteWrapper.new({ 160 | cat = { 161 | age = gt.number(), 162 | meowSound = gt.string(), 163 | ... 164 | }, 165 | location = gt.CFrame(), 166 | ... 167 | }) 168 | 169 | SpawnVehicle:onServerEvent(function(player, params) 170 | -- params is properly typed as { cat: { age: number, ...}, location: CFrame, ... } 171 | end) 172 | ``` 173 | 174 | ## Lune Support 175 | 176 | GreenTea has not been extensively tested on Lune. 177 | Some Roblox-specific typecheckers might not work, and there may be other issues. 178 | Please report any issues here: https://github.com/corecii/greentea/issues 179 | 180 | ## `t` Compatibility 181 | 182 | `GreenTea.t` includes all of the methods from the `t` package. See [the t package's readme](https://github.com/osyrisrblx/t/blob/master/README.md) for more details. 183 | 184 | ### As a drop-in replacement 185 | 186 | You can include the `greentea-t-standalone` package as an easy, drop-in replacement for a codebase which already uses `t`. Just add it to your `wally.toml`: 187 | 188 | > **with Wally (for Rojo)** 189 | > 1. [Install Wally](https://wally.run/install) 190 | > 2. Add `t = "corecii/greentea-t-standalone@0.4.11"` to your `wally.toml` 191 | 192 | > **with pesde (for Rojo)** 193 | > 1. [Install pesde](https://docs.pesde.daimond113.com/installation) 194 | > 2. Add `t = { name = "corecii/greentea_t_standalone", version = "^0.4.11" }` to your `pesde.toml` 195 | 196 | This package just exports `GreenTea.t` to make drop-in replacement easy. 197 | 198 | ## Inspiration 199 | 200 | This library was largely inspired by `t`, but also my experience working with t types, working with Luau types, and working with systems that need inspectable type definitions. This is my first attempt at solving all of these problems. I'd really like to see the inversion of this library -- where inspectable runtime type definitions are built from Luau types. But that's more work, so I'll leave it as a TODO! 201 | 202 | ## Future Plans 203 | - Split up `GreenTea.luau` into multiple files. It's too long. But I'm tired and want to move on and actually use this library for cool things. 204 | - Stop lying to the typechecker once it's able to handle all of GreenTea's features. 205 | - Inverted GreenTea, where runtime typecheckers are built from Luau types. 206 | -------------------------------------------------------------------------------- /WallyPatches/jsdotlua_jest-roblox-shared@3.6.1-rc.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/jest-roblox-shared/src/Writeable.lua b/jest-roblox-shared/src/Writeable.lua 2 | index fb54b45..379f8b8 100644 3 | --- a/jest-roblox-shared/src/Writeable.lua 4 | +++ b/jest-roblox-shared/src/Writeable.lua 5 | @@ -30,6 +30,8 @@ function Writeable.new(options: { write: (data: string) -> () }?): Writeable 6 | end 7 | 8 | function Writeable:write(data: string) 9 | + -- strip terminal formatting sequences which display badly in Roblox Studio 10 | + data = string.gsub(data, "\27%[%d+m", "") 11 | self._writeFn(data) 12 | end 13 | 14 | -------------------------------------------------------------------------------- /aftman.toml: -------------------------------------------------------------------------------- 1 | 2 | [tools] 3 | rojo = "upliftgames/rojo@7.4.0-uplift.syncback.rc.9" 4 | wally = "upliftgames/wally@0.3.2" 5 | wally-package-types = "JohnnyMorganz/wally-package-types@1.3.1" 6 | wally-patch-package="Barocena/wally-patch-package@1.2.1" 7 | lune = "filiptibell/lune@0.8.2" 8 | -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "greentea", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /lune/build-lune-zip.luau: -------------------------------------------------------------------------------- 1 | local process = require("@lune/process") 2 | local fs = require("@lune/fs") 3 | 4 | local function exec(cmdline: string | { string }, options: process.SpawnOptions?) 5 | local pieces = {} 6 | if typeof(cmdline) == "string" then 7 | for piece in string.gmatch(" " .. cmdline, "%s+([^%s]+)") do 8 | table.insert(pieces, piece) 9 | end 10 | else 11 | pieces = cmdline 12 | end 13 | 14 | local options = table.clone(options or {}) 15 | if options.stdio == nil then 16 | options.stdio = "inherit" 17 | end 18 | 19 | local program = table.remove(pieces, 1) 20 | assert(program ~= nil, "cmdline must include a program") 21 | local result = process.spawn(program, pieces, options :: any) 22 | if not result.ok then 23 | process.exit(result.code) 24 | end 25 | 26 | return result.stdout:gsub("^[\r\n]*", ""):gsub("[\r\n]*$", "") 27 | end 28 | 29 | local function zip(out: string, items: { string }) 30 | local result 31 | if process.os == "windows" then 32 | result = process.spawn("C:\\Windows\\system32\\tar.exe", { "-a", "-cf", out, unpack(items) }) 33 | else 34 | result = process.spawn("zip", { "-r", out, unpack(items) }) 35 | end 36 | 37 | if not result.ok then 38 | warn(`Failed to zip: ({result.code}) {result.stderr}`) 39 | process.exit(result.code) 40 | end 41 | end 42 | 43 | exec("lune run prepare-lune-version", { shell = true }) 44 | 45 | if fs.isDir("lune-build") then 46 | fs.removeDir("lune-build") 47 | end 48 | 49 | fs.writeDir("lune-build") 50 | 51 | fs.copy("src", "lune-build/GreenTea") 52 | zip("GreenTea-lune.zip", { "-C", "lune-build", "GreenTea" }) 53 | 54 | fs.removeDir("lune-build") 55 | -------------------------------------------------------------------------------- /lune/enable-loadmodule.luau: -------------------------------------------------------------------------------- 1 | 2 | local fs = require("@lune/fs") 3 | local process = require("@lune/process") 4 | local serde = require("@lune/serde") 5 | 6 | if process.env.windir ~= nil then 7 | -- Windows 8 | 9 | local appDataPath = process.env.LOCALAPPDATA 10 | local robloxVersionsPath = `{appDataPath}\\Roblox\\Versions` 11 | 12 | if not fs.isDir(robloxVersionsPath) then 13 | error(`Roblox versions folder not found. Please install Roblox. Checked here: {robloxVersionsPath}`) 14 | end 15 | 16 | local mostRecentStudioVersionPath 17 | for _, versionDirName in fs.readDir(robloxVersionsPath) do 18 | local versionDirPath = `{robloxVersionsPath}\\{versionDirName}` 19 | 20 | if not fs.isDir(versionDirPath) then 21 | continue 22 | end 23 | if not fs.isFile(`{versionDirPath}\\RobloxStudioBeta.exe`) then 24 | continue 25 | end 26 | 27 | mostRecentStudioVersionPath = versionDirPath 28 | break 29 | end 30 | 31 | if mostRecentStudioVersionPath == nil then 32 | error(`Roblox Studio not found. Please install Roblox. Checked here: {robloxVersionsPath}`) 33 | end 34 | 35 | local clientSettingsDir = `{mostRecentStudioVersionPath}\\ClientSettings` 36 | if not fs.isDir(clientSettingsDir) then 37 | fs.writeDir(clientSettingsDir) 38 | end 39 | 40 | local clientAppSettingsPath = `{clientSettingsDir}\\ClientAppSettings.json` 41 | local clientAppSettings 42 | 43 | if fs.isFile(clientAppSettingsPath) then 44 | clientAppSettings = serde.decode("json", fs.readFile(clientAppSettingsPath)) 45 | 46 | local metadata = fs.metadata(clientAppSettingsPath) 47 | if metadata.permissions and metadata.permissions.readOnly then 48 | local result = process.spawn("attrib", {"-r", clientAppSettingsPath }) 49 | assert(result.ok, `Failed to remove read-only attribute:\n{result.stderr}`) 50 | end 51 | else 52 | clientAppSettings = {} 53 | end 54 | 55 | clientAppSettings["FFlagEnableLoadModule"] = true 56 | 57 | fs.writeFile(clientAppSettingsPath, serde.encode("json", clientAppSettings)) 58 | 59 | local result = process.spawn("attrib", {"+r", clientAppSettingsPath }) 60 | assert(result.ok, `Failed to add read-only attribute:\n{result.stderr}`) 61 | 62 | print(`Wrote FFlagEnableLoadModule to ClientAppSettings.json at {clientAppSettingsPath}`) 63 | else 64 | error("This script only supports windows. Please implement support for your OS. I can't because I only have a Windows machine, sorry.") 65 | end -------------------------------------------------------------------------------- /lune/moonwave.luau: -------------------------------------------------------------------------------- 1 | local fs = require("@lune/fs") 2 | local task = require("@lune/task") 3 | local process = require("@lune/process") 4 | 5 | export type Path = string 6 | 7 | local function getModifiedAt(path: Path): number 8 | local meta = fs.metadata(path) 9 | return assert(meta.modifiedAt).unixTimestampMillis 10 | end 11 | 12 | local function rebuild_item(options: { 13 | base: Path, 14 | out: Path, 15 | file: Path 16 | }) 17 | local basePath = `{options.base}/{options.file}` 18 | local outPath = `{options.out}/{options.file}` 19 | 20 | local baseMeta = fs.metadata(basePath) 21 | if not baseMeta.exists then 22 | local outMeta = fs.metadata(outPath) 23 | if outMeta.exists then 24 | fs.removeFile(outPath) 25 | end 26 | return 27 | end 28 | 29 | local outParent = string.match(outPath, "(.+)[/\\][^/\\]*$") 30 | if outParent then 31 | fs.writeDir(outParent) 32 | end 33 | 34 | local baseFile = fs.readFile(basePath) 35 | local outFile = string.gsub( 36 | baseFile, 37 | "(%-%-%-[^\n]*)\\ *(\r?\n)", 38 | "%1 %2" 39 | ) 40 | 41 | fs.writeFile(outPath, outFile) 42 | end 43 | 44 | local function rebuild_directory(options: { 45 | base: Path, 46 | out: Path, 47 | dir: Path, 48 | watch: { [Path]: number }?, 49 | }) 50 | for _, child in fs.readDir(`{options.base}/{options.dir}`) do 51 | if fs.metadata(`{options.base}/{options.dir}/{child}`).kind == "dir" then 52 | rebuild_directory({ 53 | base = options.base, 54 | out = options.out, 55 | dir = `{options.dir}/{child}`, 56 | watch = options.watch, 57 | }) 58 | else 59 | rebuild_item({ 60 | base = options.base, 61 | out = options.out, 62 | file = `{options.dir}/{child}`, 63 | }) 64 | 65 | if options.watch then 66 | options.watch[`{options.dir}/{child}`] = getModifiedAt(`{options.base}/{options.dir}/{child}`) 67 | end 68 | end 69 | end 70 | end 71 | 72 | local function watch(items: { [Path]: number }, onChanged: (Path) -> ()) 73 | local thread = task.spawn(function() 74 | while true do 75 | for path, timestamp in items do 76 | local meta = fs.metadata(path) 77 | if meta.exists and meta.modifiedAt.unixTimestampMillis > timestamp then 78 | onChanged(path) 79 | end 80 | task.wait(1/60) 81 | end 82 | end 83 | end) 84 | return function() 85 | task.cancel(thread) 86 | end 87 | end 88 | 89 | local function buildWatchlist(options: { 90 | base: Path, 91 | out: Path, 92 | dir: Path, 93 | watch: { [Path]: number }, 94 | }) 95 | for _, child in fs.readDir(`{options.base}/{options.dir}`) do 96 | if fs.metadata(`{options.base}/{options.dir}/{child}`).kind == "dir" then 97 | buildWatchlist({ 98 | base = options.base, 99 | out = options.out, 100 | dir = `{options.dir}/{child}`, 101 | watch = options.watch, 102 | }) 103 | else 104 | options.watch[`{options.dir}/{child}`] = getModifiedAt(`{options.base}/{options.dir}/{child}`) 105 | end 106 | end 107 | end 108 | 109 | local function copy(options: { 110 | base: Path, 111 | out: Path, 112 | items: { Path }, 113 | watch: { [Path]: number }?, 114 | }) 115 | for _, item in options.items do 116 | fs.copy(`{options.base}/{item}`, `{options.out}/{item}`, { 117 | overwrite = true, 118 | }) 119 | 120 | if options.watch then 121 | if fs.metadata(`{options.base}/{item}`).kind == "file" then 122 | options.watch[item] = getModifiedAt(`{options.base}/{item}`) 123 | else 124 | buildWatchlist({ 125 | base = options.base, 126 | out = options.out, 127 | dir = item, 128 | watch = options.watch, 129 | }) 130 | end 131 | end 132 | end 133 | end 134 | 135 | local codeWatchList = {} 136 | rebuild_directory({ 137 | base = "src", 138 | out = "moonwave/src", 139 | dir = ".", 140 | watch = codeWatchList, 141 | }) 142 | 143 | local copyWatchList = {} 144 | copy({ 145 | base = ".", 146 | out = "moonwave", 147 | items = { 148 | "pages", 149 | "CHANGELOG.md", 150 | "README.md", 151 | "moonwave.toml", 152 | }, 153 | watch = copyWatchList, 154 | }) 155 | 156 | if process.args[1] == "dev" then 157 | print("watching for changes to existing files...") 158 | watch(codeWatchList, function(filePath) 159 | print(` rebuilding {filePath}`) 160 | rebuild_item({ 161 | base = "src", 162 | out = "moonwave/src", 163 | file = filePath, 164 | }) 165 | end) 166 | watch(copyWatchList, function(filePath) 167 | print(` copying {filePath}`) 168 | copy({ 169 | base = ".", 170 | out = "moonwave", 171 | items = { 172 | filePath, 173 | }, 174 | }) 175 | end) 176 | 177 | process.spawn("moonwave", process.args, { 178 | stdio = "inherit", 179 | cwd = "moonwave", 180 | shell = true, 181 | }) 182 | elseif process.args[1] == "build" then 183 | process.spawn("moonwave", process.args, { 184 | stdio = "inherit", 185 | cwd = "moonwave", 186 | shell = true, 187 | }) 188 | else 189 | warn(`unknown command: {process.args[1]}`) 190 | end 191 | 192 | -------------------------------------------------------------------------------- /lune/prepare-lune-version.luau: -------------------------------------------------------------------------------- 1 | --[[ 2 | This is a simplified Roblox -> Lune converter specifically for GreenTea. 3 | I have chosen this approach over DarkLua and other because... 4 | 1. String requires are coming to Roblox soon, which makes this less necessary. 5 | 2. DarkLua and similar add a lot of complexity for something that is really 6 | simple in this case due to GreenTea being such a small library. 7 | ]] 8 | 9 | local fs = require("@lune/fs") 10 | local serde = require("@lune/serde") 11 | 12 | local function rewrite(path: string, process: (str: string) -> string) 13 | return fs.writeFile(path, process(fs.readFile(path))) 14 | end 15 | 16 | for _, name in fs.readDir("src") do 17 | rewrite(`src/{name}`, function(str) 18 | return str:gsub('require%(([^"].-)%)', function(inner) 19 | local target = inner:gsub("script%.Parent%.", "./"):gsub("script%.", "./") 20 | 21 | return `require("{target}")` 22 | end) 23 | end) 24 | end 25 | 26 | rewrite(`src-t-standalone-pesde/t.luau`, function(str) 27 | return str:gsub("require%(script.Parent.roblox_packages.GreenTea%)", 'require("./lune_packages/GreenTea")') 28 | end) 29 | 30 | rewrite("pesde.toml", function(str) 31 | local data = serde.decode("toml", str) 32 | data.target.environment = "lune" 33 | data.target.build_files = nil 34 | 35 | return serde.encode("toml", data, true) 36 | end) 37 | 38 | rewrite("src-t-standalone-pesde/pesde.toml", function(str) 39 | local data = serde.decode("toml", str) 40 | data.target.environment = "lune" 41 | data.target.build_files = nil 42 | 43 | return serde.encode("toml", data, true) 44 | end) 45 | -------------------------------------------------------------------------------- /lune/publish-all.luau: -------------------------------------------------------------------------------- 1 | local process = require("@lune/process") 2 | local fs = require("@lune/fs") 3 | local serde = require("@lune/serde") 4 | 5 | local function exec(cmdline: string | { string }, options: process.SpawnOptions?) 6 | local pieces = {} 7 | if typeof(cmdline) == "string" then 8 | for piece in string.gmatch(" " .. cmdline, "%s+([^%s]+)") do 9 | table.insert(pieces, piece) 10 | end 11 | else 12 | pieces = cmdline 13 | end 14 | 15 | local options = table.clone(options or {}) 16 | if options.stdio == nil then 17 | options.stdio = "inherit" 18 | end 19 | 20 | local program = table.remove(pieces, 1) 21 | assert(program ~= nil, "cmdline must include a program") 22 | local result = process.spawn(program, pieces, options :: any) 23 | if not result.ok then 24 | process.exit(result.code) 25 | end 26 | 27 | return result.stdout:gsub("^[\r\n]*", ""):gsub("[\r\n]*$", "") 28 | end 29 | 30 | local function zip(out: string, items: { string }) 31 | local result 32 | if process.os == "windows" then 33 | result = process.spawn("C:\\Windows\\system32\\tar.exe", { "-a", "-cf", out, unpack(items) }) 34 | else 35 | result = process.spawn("zip", { "-r", out, unpack(items) }) 36 | end 37 | 38 | if not result.ok then 39 | warn(`Failed to zip: ({result.code}) {result.stderr}`) 40 | process.exit(result.code) 41 | end 42 | end 43 | 44 | local function sanitizePattern(str: string) 45 | return string.gsub(str, "([%.%-%*%+%?%%])", "%%%1") 46 | end 47 | 48 | exec("git remote update") 49 | 50 | if exec("git symbolic-ref --short HEAD") ~= "main" then 51 | warn("Cannot publish.") 52 | warn("You must be on the main branch to publish.") 53 | process.exit(1) 54 | end 55 | 56 | if exec("git rev-parse HEAD") ~= exec("git rev-parse main") then 57 | warn("Cannot publish.") 58 | warn("You must be up-to-date with GitHub to publish.") 59 | process.exit(1) 60 | end 61 | 62 | if exec("git status --porcelain") ~= "" then 63 | warn("Cannot publish.") 64 | warn("There are uncommitted changes. Commit or stash them before publishing.") 65 | process.exit(1) 66 | end 67 | 68 | local changelogRaw = fs.readFile("CHANGELOG.md") 69 | local wally = serde.decode("toml", fs.readFile("wally.toml")) 70 | 71 | local changelogEntry = changelogRaw:match(`## {sanitizePattern(wally.package.version)}.-\n+(.-)\n+##`) 72 | 73 | if not changelogEntry then 74 | warn("Cannot publish.") 75 | warn(`No changelog entry found for {wally.package.version}`) 76 | process.exit(1) 77 | end 78 | 79 | exec("rojo build -o GreenTea.rbxm default.project.json") 80 | 81 | exec({ 82 | "gh", 83 | "release", 84 | "create", 85 | `v{wally.package.version}`, 86 | "-t", 87 | `v{wally.package.version}`, 88 | "-n", 89 | changelogEntry, 90 | "GreenTea.rbxm", 91 | }) 92 | 93 | fs.removeFile("GreenTea.rbxm") 94 | 95 | exec("wally publish") 96 | exec("wally publish", { cwd = "src-t-standalone-wally" }) 97 | 98 | exec("pesde install") 99 | exec("pesde publish --yes") 100 | 101 | exec("pesde install", { cwd = "src-t-standalone-pesde" }) 102 | exec("pesde publish --yes", { cwd = "src-t-standalone-pesde" }) 103 | 104 | -- Publish lune version 105 | -- (makes temporary changes which we use Git to undo) 106 | 107 | exec("lune run prepare-lune-version", { shell = true }) 108 | 109 | exec("pesde install") 110 | exec("pesde publish --yes") 111 | 112 | exec("pesde install", { cwd = "src-t-standalone-pesde" }) 113 | exec("pesde publish --yes", { cwd = "src-t-standalone-pesde" }) 114 | 115 | -- Add lune to release 116 | 117 | if fs.isDir("lune-build") then 118 | fs.removeDir("lune-build") 119 | end 120 | 121 | fs.writeDir("lune-build") 122 | 123 | fs.copy("src", "lune-build/GreenTea") 124 | zip("GreenTea-lune.zip", { "-C", "lune-build", "GreenTea" }) 125 | 126 | exec(`gh release upload v{wally.package.version} GreenTea-lune.zip`) 127 | 128 | fs.removeDir("lune-build") 129 | fs.removeFile("GreenTea-lune.zip") 130 | 131 | -- Reset FS changes 132 | 133 | exec("git reset --hard HEAD", { shell = true }) 134 | 135 | -- Publish docs 136 | 137 | exec("lune run moonwave -- build --publish", { shell = true }) 138 | -------------------------------------------------------------------------------- /lune/set-version.luau: -------------------------------------------------------------------------------- 1 | local fs = require("@lune/fs") 2 | local serde = require("@lune/serde") 3 | local process = require("@lune/process") 4 | 5 | local wally = serde.decode("toml", fs.readFile("wally.toml")) 6 | 7 | local lastVersion = wally.package.version 8 | local nextVersion = assert(process.args[1], "version required!\n\nusage:\n lune set-version -- ") 9 | 10 | assert( 11 | string.match(nextVersion, "^[0-9]+%.[0-9]+%.[0-9]+$") 12 | or string.match(nextVersion, "^[0-9]+%.[0-9]+%.[0-9]+%-[%w%._]+$") 13 | or string.match(nextVersion, "^[0-9]+%.[0-9]+%.[0-9]+%-[%w%._]+%+[%w%._]+$") 14 | or string.match(nextVersion, "^[0-9]+%.[0-9]+%.[0-9]+%+[%w%._]+$"), 15 | "version must be a semver" 16 | ) 17 | 18 | local function sanitizePattern(str: string) 19 | return string.gsub(str, "([%.%-%*%+%?%%])", "%%%1") 20 | end 21 | 22 | local function replaceVersion(file: string, from: string, to: string) 23 | fs.writeFile(file, (string.gsub(fs.readFile(file), sanitizePattern(from), to))) 24 | end 25 | 26 | replaceVersion("README.md", `@{lastVersion}`, `@{nextVersion}`) 27 | replaceVersion("README.md", `version = "^{lastVersion}"`, `version = "^{nextVersion}"`) 28 | 29 | replaceVersion("wally.toml", `version = "{lastVersion}"`, `version = "{nextVersion}"`) 30 | replaceVersion("src-t-standalone-wally/wally.toml", `version = "{lastVersion}"`, `version = "{nextVersion}"`) 31 | replaceVersion("src-t-standalone-wally/wally.toml", `@={lastVersion}`, `@={nextVersion}`) 32 | 33 | replaceVersion("pesde.toml", `version = "{lastVersion}"`, `version = "{nextVersion}"`) 34 | replaceVersion("src-t-standalone-pesde/pesde.toml", `version = "{lastVersion}"`, `version = "{nextVersion}"`) 35 | replaceVersion("src-t-standalone-pesde/pesde.toml", `version = "={lastVersion}"`, `version = "={nextVersion}"`) 36 | -------------------------------------------------------------------------------- /lune/wally-install.luau: -------------------------------------------------------------------------------- 1 | local process = require("@lune/process") 2 | 3 | local function exec(cmdline: string) 4 | local pieces = {} 5 | for piece in string.gmatch(" " .. cmdline, "%s+([^%s]+)") do 6 | table.insert(pieces, piece) 7 | end 8 | 9 | local program = table.remove(pieces, 1) 10 | assert(program ~= nil, "cmdline must include a program") 11 | local result = process.spawn(program, pieces, { 12 | stdio = "inherit", 13 | }) 14 | if not result.ok then 15 | process.exit(result.code) 16 | end 17 | end 18 | 19 | exec("wally install") 20 | exec("rojo sourcemap -o sourcemap.json ./test.project.json") 21 | exec("wally-package-types --sourcemap sourcemap.json ./DevPackages/") 22 | exec("wally-patch-package") -------------------------------------------------------------------------------- /moonwave.toml: -------------------------------------------------------------------------------- 1 | title = "GreenTea" # From Git 2 | gitRepoUrl = "https://github.com/corecii/greentea" # From Git 3 | 4 | gitSourceBranch = "main" 5 | changelog = true 6 | classOrder = [] 7 | 8 | [docusaurus] 9 | onBrokenLinks = "throw" 10 | onBrokenMarkdownLinks = "warn" 11 | favicon = "favicon.ico" 12 | 13 | # From git: 14 | organizationName = "Shae A." 15 | projectName = "GreenTea" 16 | url = "https://corecii.github.io" 17 | baseUrl = "/GreenTea" 18 | tagline = "" 19 | 20 | [navbar] 21 | # hideableSidebar = true 22 | 23 | [[navbar.items]] 24 | href = "/Development" 25 | label = "Development" 26 | position = "left" 27 | 28 | [footer] 29 | style = "dark" 30 | copyright = "Copyright © 2024 Shae A. Built with Moonwave and Docusaurus" 31 | 32 | # [[footer.links]] 33 | # title = "examples" 34 | 35 | # [[footer.links]] 36 | # label = "(the / before newlines is not a bug)" 37 | # href = "Development#doc-comments" 38 | -------------------------------------------------------------------------------- /pages/DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development and Contribution Info 2 | 3 | ## Workspace Setup 4 | 5 | - This project uses aftman to manage tooling. 6 | - This project uses rojo to build and test the library. 7 | - This project uses lune to run development scripts. 8 | - This project uses wally to manage dependencies, plus wally-package-types and wally-patch-package. 9 | 10 | ## Upgrading 11 | 12 | Ideally all major versions of GreenTea should use the same internal objects so that 13 | `GreenTea.isGreenTeaType` is true across all objects. This creates a split between major versions: 14 | - "major-major" versions: these have breaking changes that don't allow us to unify the objects types 15 | - "major-minor" versions: these have breaking changes that allow us to unify the objects types 16 | 17 | ### Major-minor Versions 18 | 19 | We can use a [semver-trick](https://github.com/dtolnay/semver-trick) to help unify internal objects. 20 | 21 | For example, say we want to change `GreenTea.string({ graphemes = "..." })` from not requiring `bytes` to be defined to requiring it to be defined (as it is now): 22 | - `v1.0.0` _does not_ require the user to specify `bytes`. 23 | - `v2.0.0` _does_ require the user to specify `bytes`. 24 | - `v1.0.1` takes `V2.0.0` as a dependency, and returns a copy of `v2.0.0` with `GreenTea.string` modified to 25 | include a default value for `bytes` if it is not specified. 26 | 27 | In this manner, `v1.0.1`'s internal objects are exactly the same as `v2.0.0`'s, and they'll be 28 | `GreenTea.isGreenTeaType` compatible. 29 | 30 | ### Major-major Versions 31 | 32 | For these, it's mostly like a typical major upgrade, except we should make `GreenTea.typeof` take in previous versions' types and convert them. This gives an option for libraries expecting GreenTea types 33 | to compose with libraries that use previous versions. 34 | 35 | Ideally, we should also do the inverse: make the previous version able to take in a newer GreenTea's Type and compose with it. 36 | 37 | ## Wally 38 | 39 | Wally is used to manage dependencies, but we also need types and patches. 40 | 41 | The easiest way to do this is to run the lune script: `lune run wally-install` . 42 | Alternatively, go read the lune script and see what it does. 43 | 44 | We only use package patches to fix some output issues with jest-lua. 45 | 46 | ## Tests 47 | 48 | Tests are ran using jest-lua. These require a fflag to be set. 49 | 50 | A Lune script exists to set this flag: `lune run enable-loadmodule` . This... 51 | - Finds the Roblox install directory, and the Roblox Studio's version folder 52 | - Finds or creates `ClientSettings/ClientAppSettings.json` 53 | - Sets the flag in that file 54 | - Sets the file to read-only, so Roblox doesn't overwrite it on startup. 55 | 56 | This is only implemented for Windows, because I only have a Windows machine. 57 | If you have a Mac feel free to implement this for your platform in the lune script. 58 | 59 | ## Luau Rules 60 | 61 | We disable LocalShadow because it's the only way to redefine a variable's types. 62 | We'd like to remove this when it's not longer necessary. 63 | 64 | ## Doc Comments and Running Moonwave 65 | 66 | Luau-lsp and Moonwave have conflicting definitions of explicit newlines in doc comments: 67 | - Luau-lsp only works with lines ending in a backslash (`\`) 68 | - Moonwave only works with lines ending in two spaces (` `) 69 | 70 | In source, we use a backslash to indicate a newline in a doc comment. 71 | We then process the source with our moonwave helper Lune script, `moonwave`, 72 | which replaces the backslashes with two spaces. 73 | 74 | Generally, running moonwave should always go through the `moonwave` Lune script. 75 | The script prepares a whole pseudo-repo for Moonwave to work in with automated 76 | fixes and all necessary files. 77 | 78 | The moonwave script can be run like the moonwave command once moonwave is 79 | globally installed: 80 | - `lunar moonwave dev`, `lunar moonwave build --publish`, etc. if [lunar](https://github.com/corecii/lunar) is installed (via rokit) 81 | - `lune run moonwave -- dev`, `lune run moonwave -- build --publish`, etc. otherwise 82 | 83 | ## Removed Classes 84 | 85 | When Roblox removes an instance class which is present in InstanceClasses, we 86 | _cannot_ just remove it from the InstanceClasses table because this may break code 87 | which previously worked. 88 | 89 | Instead, we should leave the runtime type definition for the class present, but 90 | alias its Luau type to `never`. This will trigger a type error in any code that 91 | uses it, but won't change runtime behavior. Type-erroring is wanted behavior: 92 | this class is removed from the engine, so anything referring to it both is 93 | already type-erroring and needs to be changed. Changing runtime behavior is 94 | _not_ wanted in non-major releases. 95 | 96 | ## License Credits 97 | 98 | ### Favicon 99 | 100 | The favicon is [from Microsoft's emoji set, under the MIT license.](https://github.com/microsoft/fluentui-emoji/blob/main/LICENSE) 101 | 102 |
See the license 103 | 104 | MIT License 105 | 106 | Copyright (c) Microsoft Corporation. 107 | 108 | Permission is hereby granted, free of charge, to any person obtaining a copy 109 | of this software and associated documentation files (the "Software"), to deal 110 | in the Software without restriction, including without limitation the rights 111 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 112 | copies of the Software, and to permit persons to whom the Software is 113 | furnished to do so, subject to the following conditions: 114 | 115 | The above copyright notice and this permission notice shall be included in all 116 | copies or substantial portions of the Software. 117 | 118 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 119 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 120 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 121 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 122 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 123 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 124 | SOFTWARE 125 | 126 |
-------------------------------------------------------------------------------- /pesde.toml: -------------------------------------------------------------------------------- 1 | name = "corecii/greentea" 2 | version = "0.4.11" 3 | description = "A runtime typechecker with Luau types, pretty errors, and inspection" 4 | authors = ["Corecii (Shae A.) "] 5 | repository = "https://github.com/Corecii/GreenTea" 6 | license = "MIT" 7 | 8 | includes = [ 9 | "pesde.toml", 10 | "README.md", 11 | "CHANGELOG.md", 12 | "LICENSE", 13 | "src", 14 | "selene.toml", 15 | ".luaurc", 16 | ] 17 | 18 | [target] 19 | environment = "roblox" 20 | lib = "src/init.luau" 21 | build_files = ["src"] 22 | 23 | [scripts] 24 | roblox_sync_config_generator = ".pesde/roblox_sync_config_generator.luau" 25 | sourcemap_generator = ".pesde/sourcemap_generator.luau" 26 | 27 | [indices] 28 | default = "https://github.com/daimond113/pesde-index" 29 | -------------------------------------------------------------------------------- /rokit.toml: -------------------------------------------------------------------------------- 1 | 2 | # This file lists tools managed by Rokit, a toolchain manager for Roblox projects. 3 | # For more information, see https://github.com/filiptibell/rokit 4 | 5 | # New tools can be added by running `rokit add ` in a terminal. 6 | [tools] 7 | rojo = "upliftgames/rojo@7.4.0-uplift.syncback.rc.17" 8 | wally = "upliftgames/wally@0.3.2" 9 | wally-package-types = "JohnnyMorganz/wally-package-types@1.3.1" 10 | wally-patch-package = "Barocena/wally-patch-package@1.2.1" 11 | lune = "filiptibell/lune@0.8.2" 12 | lunar = "corecii/lunar@0.1.0" 13 | 14 | # This is against recommendation but it's easy and works for now. 15 | pesde = "daimond113/pesde@0.5.3+registry.0.1.2" 16 | -------------------------------------------------------------------------------- /selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" 2 | 3 | exclude = [] 4 | 5 | [lints] 6 | shadowing = "allow" 7 | incorrect_standard_library_use = "allow" 8 | unused_variable = "allow" 9 | -------------------------------------------------------------------------------- /src-t-standalone-pesde/pesde.toml: -------------------------------------------------------------------------------- 1 | name = "corecii/greentea_t_standalone" 2 | version = "0.4.11" 3 | description = "A t-compatible API for GreenTea" 4 | authors = ["Corecii (Shae A.) "] 5 | repository = "https://github.com/Corecii/GreenTea" 6 | license = "MIT" 7 | 8 | includes = ["pesde.toml", "t.luau"] 9 | 10 | [target] 11 | environment = "roblox" 12 | lib = "t.luau" 13 | build_files = ["t.luau"] 14 | 15 | [scripts] 16 | roblox_sync_config_generator = "../.pesde/roblox_sync_config_generator.luau" 17 | sourcemap_generator = "../.pesde/sourcemap_generator.luau" 18 | 19 | [indices] 20 | default = "https://github.com/daimond113/pesde-index" 21 | 22 | [dependencies] 23 | GreenTea = { name = "corecii/greentea", version = "=0.4.11" } 24 | -------------------------------------------------------------------------------- /src-t-standalone-pesde/t.luau: -------------------------------------------------------------------------------- 1 | local GreenTea = require(script.Parent.roblox_packages.GreenTea) 2 | 3 | export type Cause = GreenTea.Cause 4 | export type Type = GreenTea.Type 5 | export type TuplePacked = GreenTea.TuplePacked 6 | 7 | return GreenTea.t 8 | -------------------------------------------------------------------------------- /src-t-standalone-wally/init.luau: -------------------------------------------------------------------------------- 1 | local GreenTea = require(script.Parent.GreenTea) 2 | 3 | export type Cause = GreenTea.Cause 4 | export type Type = GreenTea.Type 5 | export type TuplePacked = GreenTea.TuplePacked 6 | 7 | return GreenTea.t -------------------------------------------------------------------------------- /src-t-standalone-wally/wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "corecii/greentea-t-standalone" 3 | version = "0.4.11" 4 | registry = "https://github.com/UpliftGames/wally-index" 5 | realm = "shared" 6 | 7 | [dependencies] 8 | GreenTea = "corecii/greentea@=0.4.11" 9 | -------------------------------------------------------------------------------- /src/InstanceClasses.luau: -------------------------------------------------------------------------------- 1 | local GreenTea = require(script.Parent.GreenTea) 2 | 3 | local highlightWrap = GreenTea.__highlightWrap 4 | local Cause = GreenTea.__Cause 5 | local Type = GreenTea.__Type 6 | 7 | local InstanceClasses = {} 8 | 9 | setmetatable(InstanceClasses, { 10 | __call = function(self: any, class: string): any 11 | local self 12 | self = { 13 | kind = "InstanceIsA", 14 | instanceIsA = { 15 | class = class, 16 | }, 17 | _matches = function(input: any, ...: any): () 18 | if typeof(input) == "Instance" and input:IsA(class) then 19 | return Cause.ok() 20 | else 21 | return Cause.err(self, input, `expected an instance of {class}, got $input`) 22 | end 23 | end, 24 | _format = function(highlight: { [any]: string }, maxLineLength: number, recurse: { [any]: any }) 25 | return highlightWrap(class, highlight[self]) 26 | end, 27 | } 28 | return setmetatable(self, Type) :: any 29 | end, 30 | }) 31 | 32 | local function class(name: string): any 33 | return function(): any 34 | local self 35 | self = { 36 | kind = "InstanceIsA", 37 | class = class, 38 | _matches = function(input: any) 39 | if typeof(input) == "Instance" then 40 | if input:IsA(name) then 41 | return Cause.ok() 42 | else 43 | return Cause.err(self, input, `expected {name}, got {input.ClassName}`) 44 | end 45 | else 46 | return Cause.err(self, input, `expected {name}, got {typeof(input)}`) 47 | end 48 | end, 49 | _format = function(highlight: { [any]: string }, maxLineLength: number, recurse: { [any]: any }) 50 | return highlightWrap(name, highlight[self]) 51 | end, 52 | } 53 | return setmetatable(self, Type) :: any 54 | end 55 | end 56 | 57 | InstanceClasses.Instance = class("Instance") :: () -> Instance 58 | InstanceClasses.AccessoryDescription = class("AccessoryDescription") :: () -> AccessoryDescription 59 | InstanceClasses.AccountService = class("AccountService") :: () -> AccountService 60 | InstanceClasses.Accoutrement = class("Accoutrement") :: () -> Accoutrement 61 | InstanceClasses.Accessory = class("Accessory") :: () -> Accessory 62 | InstanceClasses.Hat = class("Hat") :: () -> Hat 63 | InstanceClasses.AdPortal = class("AdPortal") :: () -> AdPortal 64 | InstanceClasses.AdService = class("AdService") :: () -> AdService 65 | InstanceClasses.AdvancedDragger = class("AdvancedDragger") :: () -> AdvancedDragger 66 | InstanceClasses.AnalyticsService = class("AnalyticsService") :: () -> AnalyticsService 67 | InstanceClasses.Animation = class("Animation") :: () -> Animation 68 | InstanceClasses.AnimationClip = class("AnimationClip") :: () -> AnimationClip 69 | InstanceClasses.CurveAnimation = class("CurveAnimation") :: () -> CurveAnimation 70 | InstanceClasses.KeyframeSequence = class("KeyframeSequence") :: () -> KeyframeSequence 71 | InstanceClasses.AnimationClipProvider = class("AnimationClipProvider") :: () -> AnimationClipProvider 72 | InstanceClasses.AnimationController = class("AnimationController") :: () -> AnimationController 73 | InstanceClasses.AnimationFromVideoCreatorService = 74 | class("AnimationFromVideoCreatorService") :: () -> AnimationFromVideoCreatorService 75 | InstanceClasses.AnimationFromVideoCreatorStudioService = 76 | class("AnimationFromVideoCreatorStudioService") :: () -> AnimationFromVideoCreatorStudioService 77 | InstanceClasses.AnimationRigData = class("AnimationRigData") :: () -> AnimationRigData 78 | InstanceClasses.AnimationStreamTrack = class("AnimationStreamTrack") :: () -> AnimationStreamTrack 79 | InstanceClasses.AnimationTrack = class("AnimationTrack") :: () -> AnimationTrack 80 | InstanceClasses.Animator = class("Animator") :: () -> Animator 81 | InstanceClasses.AppUpdateService = class("AppUpdateService") :: () -> AppUpdateService 82 | InstanceClasses.AssetCounterService = class("AssetCounterService") :: () -> AssetCounterService 83 | InstanceClasses.AssetDeliveryProxy = class("AssetDeliveryProxy") :: () -> AssetDeliveryProxy 84 | InstanceClasses.AssetImportService = class("AssetImportService") :: () -> AssetImportService 85 | InstanceClasses.AssetImportSession = class("AssetImportSession") :: () -> AssetImportSession 86 | InstanceClasses.AssetManagerService = class("AssetManagerService") :: () -> AssetManagerService 87 | InstanceClasses.AssetPatchSettings = class("AssetPatchSettings") :: () -> AssetPatchSettings 88 | InstanceClasses.AssetService = class("AssetService") :: () -> AssetService 89 | InstanceClasses.Atmosphere = class("Atmosphere") :: () -> Atmosphere 90 | InstanceClasses.Attachment = class("Attachment") :: () -> Attachment 91 | InstanceClasses.Bone = class("Bone") :: () -> Bone 92 | InstanceClasses.AudioAnalyzer = class("AudioAnalyzer") :: () -> AudioAnalyzer 93 | InstanceClasses.AudioChorus = class("AudioChorus") :: () -> AudioChorus 94 | InstanceClasses.AudioCompressor = class("AudioCompressor") :: () -> AudioCompressor 95 | InstanceClasses.AudioDeviceInput = class("AudioDeviceInput") :: () -> AudioDeviceInput 96 | InstanceClasses.AudioDeviceOutput = class("AudioDeviceOutput") :: () -> AudioDeviceOutput 97 | InstanceClasses.AudioDistortion = class("AudioDistortion") :: () -> AudioDistortion 98 | InstanceClasses.AudioEcho = class("AudioEcho") :: () -> AudioEcho 99 | InstanceClasses.AudioEmitter = class("AudioEmitter") :: () -> AudioEmitter 100 | InstanceClasses.AudioEqualizer = class("AudioEqualizer") :: () -> AudioEqualizer 101 | InstanceClasses.AudioFader = class("AudioFader") :: () -> AudioFader 102 | InstanceClasses.AudioFlanger = class("AudioFlanger") :: () -> AudioFlanger 103 | InstanceClasses.AudioListener = class("AudioListener") :: () -> AudioListener 104 | InstanceClasses.AudioPitchShifter = class("AudioPitchShifter") :: () -> AudioPitchShifter 105 | InstanceClasses.AudioPlayer = class("AudioPlayer") :: () -> AudioPlayer 106 | InstanceClasses.AudioReverb = class("AudioReverb") :: () -> AudioReverb 107 | InstanceClasses.AudioSearchParams = class("AudioSearchParams") :: () -> AudioSearchParams 108 | InstanceClasses.AvatarChatService = class("AvatarChatService") :: () -> AvatarChatService 109 | InstanceClasses.AvatarCreationService = class("AvatarCreationService") :: () -> AvatarCreationService 110 | InstanceClasses.AvatarEditorService = class("AvatarEditorService") :: () -> AvatarEditorService 111 | InstanceClasses.AvatarImportService = class("AvatarImportService") :: () -> AvatarImportService 112 | InstanceClasses.Backpack = class("Backpack") :: () -> Backpack 113 | InstanceClasses.BadgeService = class("BadgeService") :: () -> BadgeService 114 | InstanceClasses.BaseImportData = class("BaseImportData") :: () -> BaseImportData 115 | InstanceClasses.AnimationImportData = class("AnimationImportData") :: () -> AnimationImportData 116 | InstanceClasses.FacsImportData = class("FacsImportData") :: () -> FacsImportData 117 | InstanceClasses.GroupImportData = class("GroupImportData") :: () -> GroupImportData 118 | InstanceClasses.JointImportData = class("JointImportData") :: () -> JointImportData 119 | InstanceClasses.MaterialImportData = class("MaterialImportData") :: () -> MaterialImportData 120 | InstanceClasses.MeshImportData = class("MeshImportData") :: () -> MeshImportData 121 | InstanceClasses.RootImportData = class("RootImportData") :: () -> RootImportData 122 | InstanceClasses.BasePlayerGui = class("BasePlayerGui") :: () -> BasePlayerGui 123 | InstanceClasses.CoreGui = class("CoreGui") :: () -> CoreGui 124 | InstanceClasses.PlayerGui = class("PlayerGui") :: () -> PlayerGui 125 | InstanceClasses.StarterGui = class("StarterGui") :: () -> StarterGui 126 | InstanceClasses.BaseRemoteEvent = class("BaseRemoteEvent") :: () -> BaseRemoteEvent 127 | InstanceClasses.RemoteEvent = class("RemoteEvent") :: () -> RemoteEvent 128 | InstanceClasses.UnreliableRemoteEvent = class("UnreliableRemoteEvent") :: () -> UnreliableRemoteEvent 129 | InstanceClasses.BaseWrap = class("BaseWrap") :: () -> BaseWrap 130 | InstanceClasses.WrapLayer = class("WrapLayer") :: () -> WrapLayer 131 | InstanceClasses.WrapTarget = class("WrapTarget") :: () -> WrapTarget 132 | InstanceClasses.Beam = class("Beam") :: () -> Beam 133 | InstanceClasses.BindableEvent = class("BindableEvent") :: () -> BindableEvent 134 | InstanceClasses.BindableFunction = class("BindableFunction") :: () -> BindableFunction 135 | InstanceClasses.BodyMover = class("BodyMover") :: () -> BodyMover 136 | InstanceClasses.BodyAngularVelocity = class("BodyAngularVelocity") :: () -> BodyAngularVelocity 137 | InstanceClasses.BodyForce = class("BodyForce") :: () -> BodyForce 138 | InstanceClasses.BodyGyro = class("BodyGyro") :: () -> BodyGyro 139 | InstanceClasses.BodyPosition = class("BodyPosition") :: () -> BodyPosition 140 | InstanceClasses.BodyThrust = class("BodyThrust") :: () -> BodyThrust 141 | InstanceClasses.BodyVelocity = class("BodyVelocity") :: () -> BodyVelocity 142 | InstanceClasses.RocketPropulsion = class("RocketPropulsion") :: () -> RocketPropulsion 143 | InstanceClasses.BodyPartDescription = class("BodyPartDescription") :: () -> BodyPartDescription 144 | InstanceClasses.Breakpoint = class("Breakpoint") :: () -> Breakpoint 145 | InstanceClasses.BrowserService = class("BrowserService") :: () -> BrowserService 146 | InstanceClasses.BubbleChatMessageProperties = class("BubbleChatMessageProperties") :: () -> BubbleChatMessageProperties 147 | InstanceClasses.BulkImportService = class("BulkImportService") :: () -> BulkImportService 148 | InstanceClasses.CacheableContentProvider = class("CacheableContentProvider") :: () -> CacheableContentProvider 149 | InstanceClasses.HSRDataContentProvider = class("HSRDataContentProvider") :: () -> HSRDataContentProvider 150 | InstanceClasses.MeshContentProvider = class("MeshContentProvider") :: () -> MeshContentProvider 151 | InstanceClasses.SolidModelContentProvider = class("SolidModelContentProvider") :: () -> SolidModelContentProvider 152 | InstanceClasses.CalloutService = class("CalloutService") :: () -> CalloutService 153 | InstanceClasses.Camera = class("Camera") :: () -> Camera 154 | InstanceClasses.CaptureService = class("CaptureService") :: () -> CaptureService 155 | InstanceClasses.ChangeHistoryService = class("ChangeHistoryService") :: () -> ChangeHistoryService 156 | InstanceClasses.CharacterAppearance = class("CharacterAppearance") :: () -> CharacterAppearance 157 | InstanceClasses.BodyColors = class("BodyColors") :: () -> BodyColors 158 | InstanceClasses.CharacterMesh = class("CharacterMesh") :: () -> CharacterMesh 159 | InstanceClasses.Clothing = class("Clothing") :: () -> Clothing 160 | InstanceClasses.Pants = class("Pants") :: () -> Pants 161 | InstanceClasses.Shirt = class("Shirt") :: () -> Shirt 162 | InstanceClasses.ShirtGraphic = class("ShirtGraphic") :: () -> ShirtGraphic 163 | InstanceClasses.Skin = class("Skin") :: () -> Skin 164 | InstanceClasses.Chat = class("Chat") :: () -> Chat 165 | InstanceClasses.ChatbotUIService = class("ChatbotUIService") :: () -> ChatbotUIService 166 | InstanceClasses.ClickDetector = class("ClickDetector") :: () -> ClickDetector 167 | InstanceClasses.DragDetector = class("DragDetector") :: () -> DragDetector 168 | InstanceClasses.Clouds = class("Clouds") :: () -> Clouds 169 | InstanceClasses.ClusterPacketCache = class("ClusterPacketCache") :: () -> ClusterPacketCache 170 | InstanceClasses.Collaborator = class("Collaborator") :: () -> Collaborator 171 | InstanceClasses.CollaboratorsService = class("CollaboratorsService") :: () -> CollaboratorsService 172 | InstanceClasses.CollectionService = class("CollectionService") :: () -> CollectionService 173 | InstanceClasses.CommandInstance = class("CommandInstance") :: () -> CommandInstance 174 | InstanceClasses.CommandService = class("CommandService") :: () -> CommandService 175 | InstanceClasses.Configuration = class("Configuration") :: () -> Configuration 176 | InstanceClasses.ConfigureServerService = class("ConfigureServerService") :: () -> ConfigureServerService 177 | InstanceClasses.Constraint = class("Constraint") :: () -> Constraint 178 | InstanceClasses.AlignOrientation = class("AlignOrientation") :: () -> AlignOrientation 179 | InstanceClasses.AlignPosition = class("AlignPosition") :: () -> AlignPosition 180 | InstanceClasses.AngularVelocity = class("AngularVelocity") :: () -> AngularVelocity 181 | InstanceClasses.AnimationConstraint = class("AnimationConstraint") :: () -> AnimationConstraint 182 | InstanceClasses.BallSocketConstraint = class("BallSocketConstraint") :: () -> BallSocketConstraint 183 | InstanceClasses.HingeConstraint = class("HingeConstraint") :: () -> HingeConstraint 184 | InstanceClasses.LineForce = class("LineForce") :: () -> LineForce 185 | InstanceClasses.LinearVelocity = class("LinearVelocity") :: () -> LinearVelocity 186 | InstanceClasses.PlaneConstraint = class("PlaneConstraint") :: () -> PlaneConstraint 187 | InstanceClasses.Plane = class("Plane") :: () -> Plane 188 | InstanceClasses.RigidConstraint = class("RigidConstraint") :: () -> RigidConstraint 189 | InstanceClasses.RodConstraint = class("RodConstraint") :: () -> RodConstraint 190 | InstanceClasses.RopeConstraint = class("RopeConstraint") :: () -> RopeConstraint 191 | InstanceClasses.SlidingBallConstraint = class("SlidingBallConstraint") :: () -> SlidingBallConstraint 192 | InstanceClasses.CylindricalConstraint = class("CylindricalConstraint") :: () -> CylindricalConstraint 193 | InstanceClasses.PrismaticConstraint = class("PrismaticConstraint") :: () -> PrismaticConstraint 194 | InstanceClasses.SpringConstraint = class("SpringConstraint") :: () -> SpringConstraint 195 | InstanceClasses.Torque = class("Torque") :: () -> Torque 196 | InstanceClasses.TorsionSpringConstraint = class("TorsionSpringConstraint") :: () -> TorsionSpringConstraint 197 | InstanceClasses.UniversalConstraint = class("UniversalConstraint") :: () -> UniversalConstraint 198 | InstanceClasses.VectorForce = class("VectorForce") :: () -> VectorForce 199 | InstanceClasses.ContentProvider = class("ContentProvider") :: () -> ContentProvider 200 | InstanceClasses.ContextActionService = class("ContextActionService") :: () -> ContextActionService 201 | InstanceClasses.Controller = class("Controller") :: () -> Controller 202 | InstanceClasses.HumanoidController = class("HumanoidController") :: () -> HumanoidController 203 | InstanceClasses.SkateboardController = class("SkateboardController") :: () -> SkateboardController 204 | InstanceClasses.VehicleController = class("VehicleController") :: () -> VehicleController 205 | InstanceClasses.ControllerBase = class("ControllerBase") :: () -> ControllerBase 206 | InstanceClasses.AirController = class("AirController") :: () -> AirController 207 | InstanceClasses.ClimbController = class("ClimbController") :: () -> ClimbController 208 | InstanceClasses.GroundController = class("GroundController") :: () -> GroundController 209 | InstanceClasses.SwimController = class("SwimController") :: () -> SwimController 210 | InstanceClasses.ControllerManager = class("ControllerManager") :: () -> ControllerManager 211 | InstanceClasses.ControllerService = class("ControllerService") :: () -> ControllerService 212 | InstanceClasses.CookiesService = class("CookiesService") :: () -> CookiesService 213 | InstanceClasses.CorePackages = class("CorePackages") :: () -> CorePackages 214 | InstanceClasses.CoreScriptDebuggingManagerHelper = 215 | class("CoreScriptDebuggingManagerHelper") :: () -> CoreScriptDebuggingManagerHelper 216 | InstanceClasses.CoreScriptSyncService = class("CoreScriptSyncService") :: () -> CoreScriptSyncService 217 | InstanceClasses.CreationDBService = class("CreationDBService") :: () -> CreationDBService 218 | InstanceClasses.CrossDMScriptChangeListener = class("CrossDMScriptChangeListener") :: () -> CrossDMScriptChangeListener 219 | InstanceClasses.CustomEvent = class("CustomEvent") :: () -> CustomEvent 220 | InstanceClasses.CustomEventReceiver = class("CustomEventReceiver") :: () -> CustomEventReceiver 221 | InstanceClasses.DataModelMesh = class("DataModelMesh") :: () -> DataModelMesh 222 | InstanceClasses.BevelMesh = class("BevelMesh") :: () -> BevelMesh 223 | InstanceClasses.CylinderMesh = class("CylinderMesh") :: () -> CylinderMesh 224 | InstanceClasses.EditableMesh = class("EditableMesh") :: () -> EditableMesh 225 | InstanceClasses.FileMesh = class("FileMesh") :: () -> FileMesh 226 | InstanceClasses.SpecialMesh = class("SpecialMesh") :: () -> SpecialMesh 227 | InstanceClasses.DataModelPatchService = class("DataModelPatchService") :: () -> DataModelPatchService 228 | InstanceClasses.DataModelSession = class("DataModelSession") :: () -> DataModelSession 229 | InstanceClasses.DataStoreGetOptions = class("DataStoreGetOptions") :: () -> DataStoreGetOptions 230 | InstanceClasses.DataStoreIncrementOptions = class("DataStoreIncrementOptions") :: () -> DataStoreIncrementOptions 231 | InstanceClasses.DataStoreInfo = class("DataStoreInfo") :: () -> DataStoreInfo 232 | InstanceClasses.DataStoreKey = class("DataStoreKey") :: () -> DataStoreKey 233 | InstanceClasses.DataStoreKeyInfo = class("DataStoreKeyInfo") :: () -> DataStoreKeyInfo 234 | InstanceClasses.DataStoreObjectVersionInfo = class("DataStoreObjectVersionInfo") :: () -> DataStoreObjectVersionInfo 235 | InstanceClasses.DataStoreOptions = class("DataStoreOptions") :: () -> DataStoreOptions 236 | InstanceClasses.DataStoreService = class("DataStoreService") :: () -> DataStoreService 237 | InstanceClasses.DataStoreSetOptions = class("DataStoreSetOptions") :: () -> DataStoreSetOptions 238 | InstanceClasses.Debris = class("Debris") :: () -> Debris 239 | InstanceClasses.DebugSettings = class("DebugSettings") :: () -> DebugSettings 240 | InstanceClasses.DebuggablePluginWatcher = class("DebuggablePluginWatcher") :: () -> DebuggablePluginWatcher 241 | InstanceClasses.DebuggerBreakpoint = class("DebuggerBreakpoint") :: () -> DebuggerBreakpoint 242 | InstanceClasses.DebuggerConnection = class("DebuggerConnection") :: () -> DebuggerConnection 243 | InstanceClasses.LocalDebuggerConnection = class("LocalDebuggerConnection") :: () -> LocalDebuggerConnection 244 | InstanceClasses.DebuggerConnectionManager = class("DebuggerConnectionManager") :: () -> DebuggerConnectionManager 245 | InstanceClasses.DebuggerLuaResponse = class("DebuggerLuaResponse") :: () -> DebuggerLuaResponse 246 | InstanceClasses.DebuggerManager = class("DebuggerManager") :: () -> DebuggerManager 247 | InstanceClasses.DebuggerUIService = class("DebuggerUIService") :: () -> DebuggerUIService 248 | InstanceClasses.DebuggerVariable = class("DebuggerVariable") :: () -> DebuggerVariable 249 | InstanceClasses.DebuggerWatch = class("DebuggerWatch") :: () -> DebuggerWatch 250 | InstanceClasses.DeviceIdService = class("DeviceIdService") :: () -> DeviceIdService 251 | InstanceClasses.Dialog = class("Dialog") :: () -> Dialog 252 | InstanceClasses.DialogChoice = class("DialogChoice") :: () -> DialogChoice 253 | InstanceClasses.DraftsService = class("DraftsService") :: () -> DraftsService 254 | InstanceClasses.Dragger = class("Dragger") :: () -> Dragger 255 | InstanceClasses.DraggerService = class("DraggerService") :: () -> DraggerService 256 | InstanceClasses.EditableImage = class("EditableImage") :: () -> EditableImage 257 | InstanceClasses.EngineAPICloudProcessingService = class("EngineAPICloudProcessingService") :: () -> never -- removed class 258 | InstanceClasses.EulerRotationCurve = class("EulerRotationCurve") :: () -> EulerRotationCurve 259 | InstanceClasses.EventIngestService = class("EventIngestService") :: () -> EventIngestService 260 | InstanceClasses.ExperienceAuthService = class("ExperienceAuthService") :: () -> ExperienceAuthService 261 | InstanceClasses.ExperienceInviteOptions = class("ExperienceInviteOptions") :: () -> ExperienceInviteOptions 262 | InstanceClasses.ExperienceNotificationService = 263 | class("ExperienceNotificationService") :: () -> ExperienceNotificationService 264 | InstanceClasses.ExperienceService = class("ExperienceService") :: () -> ExperienceService 265 | InstanceClasses.ExperienceStateCaptureService = 266 | class("ExperienceStateCaptureService") :: () -> ExperienceStateCaptureService 267 | InstanceClasses.Explosion = class("Explosion") :: () -> Explosion 268 | InstanceClasses.FaceAnimatorService = class("FaceAnimatorService") :: () -> FaceAnimatorService 269 | InstanceClasses.FaceControls = class("FaceControls") :: () -> FaceControls 270 | InstanceClasses.FaceInstance = class("FaceInstance") :: () -> FaceInstance 271 | InstanceClasses.Decal = class("Decal") :: () -> Decal 272 | InstanceClasses.Texture = class("Texture") :: () -> Texture 273 | InstanceClasses.FacialAnimationRecordingService = 274 | class("FacialAnimationRecordingService") :: () -> FacialAnimationRecordingService 275 | InstanceClasses.FacialAnimationStreamingServiceStats = 276 | class("FacialAnimationStreamingServiceStats") :: () -> FacialAnimationStreamingServiceStats 277 | InstanceClasses.FacialAnimationStreamingServiceV2 = 278 | class("FacialAnimationStreamingServiceV2") :: () -> FacialAnimationStreamingServiceV2 279 | InstanceClasses.FacialAnimationStreamingSubsessionStats = 280 | class("FacialAnimationStreamingSubsessionStats") :: () -> FacialAnimationStreamingSubsessionStats 281 | InstanceClasses.Feature = class("Feature") :: () -> Feature 282 | InstanceClasses.Hole = class("Hole") :: () -> Hole 283 | InstanceClasses.MotorFeature = class("MotorFeature") :: () -> MotorFeature 284 | InstanceClasses.File = class("File") :: () -> File 285 | InstanceClasses.Fire = class("Fire") :: () -> Fire 286 | InstanceClasses.FlagStandService = class("FlagStandService") :: () -> FlagStandService 287 | InstanceClasses.FloatCurve = class("FloatCurve") :: () -> FloatCurve 288 | InstanceClasses.FlyweightService = class("FlyweightService") :: () -> FlyweightService 289 | InstanceClasses.CSGDictionaryService = class("CSGDictionaryService") :: () -> CSGDictionaryService 290 | InstanceClasses.NonReplicatedCSGDictionaryService = 291 | class("NonReplicatedCSGDictionaryService") :: () -> NonReplicatedCSGDictionaryService 292 | InstanceClasses.Folder = class("Folder") :: () -> Folder 293 | InstanceClasses.ForceField = class("ForceField") :: () -> ForceField 294 | InstanceClasses.FriendService = class("FriendService") :: () -> FriendService 295 | InstanceClasses.FunctionalTest = class("FunctionalTest") :: () -> FunctionalTest 296 | InstanceClasses.GamePassService = class("GamePassService") :: () -> GamePassService 297 | InstanceClasses.GameSettings = class("GameSettings") :: () -> GameSettings 298 | InstanceClasses.GamepadService = class("GamepadService") :: () -> GamepadService 299 | InstanceClasses.Geometry = class("Geometry") :: () -> Geometry 300 | InstanceClasses.GeometryService = class("GeometryService") :: () -> GeometryService 301 | InstanceClasses.GetTextBoundsParams = class("GetTextBoundsParams") :: () -> GetTextBoundsParams 302 | InstanceClasses.GlobalDataStore = class("GlobalDataStore") :: () -> GlobalDataStore 303 | InstanceClasses.DataStore = class("DataStore") :: () -> DataStore 304 | InstanceClasses.OrderedDataStore = class("OrderedDataStore") :: () -> OrderedDataStore 305 | InstanceClasses.GoogleAnalyticsConfiguration = 306 | class("GoogleAnalyticsConfiguration") :: () -> GoogleAnalyticsConfiguration 307 | InstanceClasses.GroupService = class("GroupService") :: () -> GroupService 308 | InstanceClasses.GuiBase = class("GuiBase") :: () -> GuiBase 309 | InstanceClasses.GuiBase2d = class("GuiBase2d") :: () -> GuiBase2d 310 | InstanceClasses.GuiObject = class("GuiObject") :: () -> GuiObject 311 | InstanceClasses.CanvasGroup = class("CanvasGroup") :: () -> CanvasGroup 312 | InstanceClasses.Frame = class("Frame") :: () -> Frame 313 | InstanceClasses.GuiButton = class("GuiButton") :: () -> GuiButton 314 | InstanceClasses.ImageButton = class("ImageButton") :: () -> ImageButton 315 | InstanceClasses.TextButton = class("TextButton") :: () -> TextButton 316 | InstanceClasses.GuiLabel = class("GuiLabel") :: () -> GuiLabel 317 | InstanceClasses.ImageLabel = class("ImageLabel") :: () -> ImageLabel 318 | InstanceClasses.TextLabel = class("TextLabel") :: () -> TextLabel 319 | InstanceClasses.ScrollingFrame = class("ScrollingFrame") :: () -> ScrollingFrame 320 | InstanceClasses.TextBox = class("TextBox") :: () -> TextBox 321 | InstanceClasses.VideoFrame = class("VideoFrame") :: () -> VideoFrame 322 | InstanceClasses.ViewportFrame = class("ViewportFrame") :: () -> ViewportFrame 323 | InstanceClasses.LayerCollector = class("LayerCollector") :: () -> LayerCollector 324 | InstanceClasses.BillboardGui = class("BillboardGui") :: () -> BillboardGui 325 | InstanceClasses.PluginGui = class("PluginGui") :: () -> PluginGui 326 | InstanceClasses.DockWidgetPluginGui = class("DockWidgetPluginGui") :: () -> DockWidgetPluginGui 327 | InstanceClasses.QWidgetPluginGui = class("QWidgetPluginGui") :: () -> QWidgetPluginGui 328 | InstanceClasses.ScreenGui = class("ScreenGui") :: () -> ScreenGui 329 | InstanceClasses.GuiMain = class("GuiMain") :: () -> GuiMain 330 | InstanceClasses.SurfaceGuiBase = class("SurfaceGuiBase") :: () -> SurfaceGuiBase 331 | InstanceClasses.AdGui = class("AdGui") :: () -> AdGui 332 | InstanceClasses.SurfaceGui = class("SurfaceGui") :: () -> SurfaceGui 333 | InstanceClasses.GuiBase3d = class("GuiBase3d") :: () -> GuiBase3d 334 | InstanceClasses.FloorWire = class("FloorWire") :: () -> FloorWire 335 | InstanceClasses.InstanceAdornment = class("InstanceAdornment") :: () -> InstanceAdornment 336 | InstanceClasses.SelectionBox = class("SelectionBox") :: () -> SelectionBox 337 | InstanceClasses.PVAdornment = class("PVAdornment") :: () -> PVAdornment 338 | InstanceClasses.HandleAdornment = class("HandleAdornment") :: () -> HandleAdornment 339 | InstanceClasses.BoxHandleAdornment = class("BoxHandleAdornment") :: () -> BoxHandleAdornment 340 | InstanceClasses.ConeHandleAdornment = class("ConeHandleAdornment") :: () -> ConeHandleAdornment 341 | InstanceClasses.CylinderHandleAdornment = class("CylinderHandleAdornment") :: () -> CylinderHandleAdornment 342 | InstanceClasses.ImageHandleAdornment = class("ImageHandleAdornment") :: () -> ImageHandleAdornment 343 | InstanceClasses.LineHandleAdornment = class("LineHandleAdornment") :: () -> LineHandleAdornment 344 | InstanceClasses.SphereHandleAdornment = class("SphereHandleAdornment") :: () -> SphereHandleAdornment 345 | InstanceClasses.WireframeHandleAdornment = class("WireframeHandleAdornment") :: () -> WireframeHandleAdornment 346 | InstanceClasses.ParabolaAdornment = class("ParabolaAdornment") :: () -> ParabolaAdornment 347 | InstanceClasses.SelectionSphere = class("SelectionSphere") :: () -> SelectionSphere 348 | InstanceClasses.PartAdornment = class("PartAdornment") :: () -> PartAdornment 349 | InstanceClasses.HandlesBase = class("HandlesBase") :: () -> HandlesBase 350 | InstanceClasses.ArcHandles = class("ArcHandles") :: () -> ArcHandles 351 | InstanceClasses.Handles = class("Handles") :: () -> Handles 352 | InstanceClasses.SurfaceSelection = class("SurfaceSelection") :: () -> SurfaceSelection 353 | InstanceClasses.SelectionLasso = class("SelectionLasso") :: () -> SelectionLasso 354 | InstanceClasses.SelectionPartLasso = class("SelectionPartLasso") :: () -> SelectionPartLasso 355 | InstanceClasses.SelectionPointLasso = class("SelectionPointLasso") :: () -> SelectionPointLasso 356 | InstanceClasses.Path2D = class("Path2D") :: () -> Path2D 357 | InstanceClasses.GuiService = class("GuiService") :: () -> GuiService 358 | InstanceClasses.GuidRegistryService = class("GuidRegistryService") :: () -> GuidRegistryService 359 | InstanceClasses.HapticService = class("HapticService") :: () -> HapticService 360 | InstanceClasses.HeightmapImporterService = class("HeightmapImporterService") :: () -> HeightmapImporterService 361 | InstanceClasses.HiddenSurfaceRemovalAsset = class("HiddenSurfaceRemovalAsset") :: () -> HiddenSurfaceRemovalAsset 362 | InstanceClasses.Highlight = class("Highlight") :: () -> Highlight 363 | InstanceClasses.Hopper = class("Hopper") :: () -> Hopper 364 | InstanceClasses.HttpRbxApiService = class("HttpRbxApiService") :: () -> HttpRbxApiService 365 | InstanceClasses.HttpRequest = class("HttpRequest") :: () -> HttpRequest 366 | InstanceClasses.HttpService = class("HttpService") :: () -> HttpService 367 | InstanceClasses.Humanoid = class("Humanoid") :: () -> Humanoid 368 | InstanceClasses.HumanoidDescription = class("HumanoidDescription") :: () -> HumanoidDescription 369 | InstanceClasses.IKControl = class("IKControl") :: () -> IKControl 370 | InstanceClasses.ILegacyStudioBridge = class("ILegacyStudioBridge") :: () -> ILegacyStudioBridge 371 | InstanceClasses.LegacyStudioBridge = class("LegacyStudioBridge") :: () -> LegacyStudioBridge 372 | InstanceClasses.IXPService = class("IXPService") :: () -> IXPService 373 | InstanceClasses.IncrementalPatchBuilder = class("IncrementalPatchBuilder") :: () -> IncrementalPatchBuilder 374 | InstanceClasses.InputObject = class("InputObject") :: () -> InputObject 375 | InstanceClasses.InsertService = class("InsertService") :: () -> InsertService 376 | InstanceClasses.JointInstance = class("JointInstance") :: () -> JointInstance 377 | InstanceClasses.DynamicRotate = class("DynamicRotate") :: () -> DynamicRotate 378 | InstanceClasses.RotateP = class("RotateP") :: () -> RotateP 379 | InstanceClasses.RotateV = class("RotateV") :: () -> RotateV 380 | InstanceClasses.Glue = class("Glue") :: () -> Glue 381 | InstanceClasses.ManualSurfaceJointInstance = class("ManualSurfaceJointInstance") :: () -> ManualSurfaceJointInstance 382 | InstanceClasses.ManualGlue = class("ManualGlue") :: () -> ManualGlue 383 | InstanceClasses.ManualWeld = class("ManualWeld") :: () -> ManualWeld 384 | InstanceClasses.Motor = class("Motor") :: () -> Motor 385 | InstanceClasses.Motor6D = class("Motor6D") :: () -> Motor6D 386 | InstanceClasses.Rotate = class("Rotate") :: () -> Rotate 387 | InstanceClasses.Snap = class("Snap") :: () -> Snap 388 | InstanceClasses.VelocityMotor = class("VelocityMotor") :: () -> VelocityMotor 389 | InstanceClasses.Weld = class("Weld") :: () -> Weld 390 | InstanceClasses.JointsService = class("JointsService") :: () -> JointsService 391 | InstanceClasses.KeyboardService = class("KeyboardService") :: () -> KeyboardService 392 | InstanceClasses.Keyframe = class("Keyframe") :: () -> Keyframe 393 | InstanceClasses.KeyframeMarker = class("KeyframeMarker") :: () -> KeyframeMarker 394 | InstanceClasses.KeyframeSequenceProvider = class("KeyframeSequenceProvider") :: () -> KeyframeSequenceProvider 395 | InstanceClasses.LSPFileSyncService = class("LSPFileSyncService") :: () -> LSPFileSyncService 396 | InstanceClasses.LanguageService = class("LanguageService") :: () -> LanguageService 397 | InstanceClasses.Light = class("Light") :: () -> Light 398 | InstanceClasses.PointLight = class("PointLight") :: () -> PointLight 399 | InstanceClasses.SpotLight = class("SpotLight") :: () -> SpotLight 400 | InstanceClasses.SurfaceLight = class("SurfaceLight") :: () -> SurfaceLight 401 | InstanceClasses.Lighting = class("Lighting") :: () -> Lighting 402 | InstanceClasses.LiveScriptingService = class("LiveScriptingService") :: () -> LiveScriptingService 403 | InstanceClasses.LocalStorageService = class("LocalStorageService") :: () -> LocalStorageService 404 | InstanceClasses.AppStorageService = class("AppStorageService") :: () -> AppStorageService 405 | InstanceClasses.UserStorageService = class("UserStorageService") :: () -> UserStorageService 406 | InstanceClasses.LocalizationService = class("LocalizationService") :: () -> LocalizationService 407 | InstanceClasses.LocalizationTable = class("LocalizationTable") :: () -> LocalizationTable 408 | InstanceClasses.CloudLocalizationTable = class("CloudLocalizationTable") :: () -> CloudLocalizationTable 409 | InstanceClasses.LodDataEntity = class("LodDataEntity") :: () -> LodDataEntity 410 | InstanceClasses.LodDataService = class("LodDataService") :: () -> LodDataService 411 | InstanceClasses.LogReporterService = class("LogReporterService") :: () -> LogReporterService 412 | InstanceClasses.LogService = class("LogService") :: () -> LogService 413 | InstanceClasses.LoginService = class("LoginService") :: () -> LoginService 414 | InstanceClasses.LuaSettings = class("LuaSettings") :: () -> LuaSettings 415 | InstanceClasses.LuaSourceContainer = class("LuaSourceContainer") :: () -> LuaSourceContainer 416 | InstanceClasses.BaseScript = class("BaseScript") :: () -> BaseScript 417 | InstanceClasses.CoreScript = class("CoreScript") :: () -> CoreScript 418 | InstanceClasses.Script = class("Script") :: () -> Script 419 | InstanceClasses.LocalScript = class("LocalScript") :: () -> LocalScript 420 | InstanceClasses.ModuleScript = class("ModuleScript") :: () -> ModuleScript 421 | InstanceClasses.LuaWebService = class("LuaWebService") :: () -> LuaWebService 422 | InstanceClasses.LuauScriptAnalyzerService = class("LuauScriptAnalyzerService") :: () -> LuauScriptAnalyzerService 423 | InstanceClasses.MarkerCurve = class("MarkerCurve") :: () -> MarkerCurve 424 | InstanceClasses.MarketplaceService = class("MarketplaceService") :: () -> MarketplaceService 425 | InstanceClasses.MaterialGenerationService = class("MaterialGenerationService") :: () -> MaterialGenerationService 426 | InstanceClasses.MaterialGenerationSession = class("MaterialGenerationSession") :: () -> MaterialGenerationSession 427 | InstanceClasses.MaterialService = class("MaterialService") :: () -> MaterialService 428 | InstanceClasses.MaterialVariant = class("MaterialVariant") :: () -> MaterialVariant 429 | InstanceClasses.MemStorageConnection = class("MemStorageConnection") :: () -> MemStorageConnection 430 | InstanceClasses.MemStorageService = class("MemStorageService") :: () -> MemStorageService 431 | InstanceClasses.MemoryStoreHashMap = class("MemoryStoreHashMap") :: () -> MemoryStoreHashMap 432 | InstanceClasses.MemoryStoreQueue = class("MemoryStoreQueue") :: () -> MemoryStoreQueue 433 | InstanceClasses.MemoryStoreService = class("MemoryStoreService") :: () -> MemoryStoreService 434 | InstanceClasses.MemoryStoreSortedMap = class("MemoryStoreSortedMap") :: () -> MemoryStoreSortedMap 435 | InstanceClasses.Message = class("Message") :: () -> Message 436 | InstanceClasses.Hint = class("Hint") :: () -> Hint 437 | InstanceClasses.MessageBusConnection = class("MessageBusConnection") :: () -> MessageBusConnection 438 | InstanceClasses.MessageBusService = class("MessageBusService") :: () -> MessageBusService 439 | InstanceClasses.MessagingService = class("MessagingService") :: () -> MessagingService 440 | InstanceClasses.MetaBreakpoint = class("MetaBreakpoint") :: () -> MetaBreakpoint 441 | InstanceClasses.MetaBreakpointContext = class("MetaBreakpointContext") :: () -> MetaBreakpointContext 442 | InstanceClasses.MetaBreakpointManager = class("MetaBreakpointManager") :: () -> MetaBreakpointManager 443 | InstanceClasses.Mouse = class("Mouse") :: () -> Mouse 444 | InstanceClasses.PlayerMouse = class("PlayerMouse") :: () -> PlayerMouse 445 | InstanceClasses.PluginMouse = class("PluginMouse") :: () -> PluginMouse 446 | InstanceClasses.MouseService = class("MouseService") :: () -> MouseService 447 | InstanceClasses.MultipleDocumentInterfaceInstance = 448 | class("MultipleDocumentInterfaceInstance") :: () -> MultipleDocumentInterfaceInstance 449 | InstanceClasses.NetworkMarker = class("NetworkMarker") :: () -> NetworkMarker 450 | InstanceClasses.NetworkPeer = class("NetworkPeer") :: () -> NetworkPeer 451 | InstanceClasses.NetworkClient = class("NetworkClient") :: () -> NetworkClient 452 | InstanceClasses.NetworkServer = class("NetworkServer") :: () -> NetworkServer 453 | InstanceClasses.NetworkReplicator = class("NetworkReplicator") :: () -> NetworkReplicator 454 | InstanceClasses.ClientReplicator = class("ClientReplicator") :: () -> ClientReplicator 455 | InstanceClasses.ServerReplicator = class("ServerReplicator") :: () -> ServerReplicator 456 | InstanceClasses.NetworkSettings = class("NetworkSettings") :: () -> NetworkSettings 457 | InstanceClasses.NoCollisionConstraint = class("NoCollisionConstraint") :: () -> NoCollisionConstraint 458 | InstanceClasses.NotificationService = class("NotificationService") :: () -> NotificationService 459 | InstanceClasses.OmniRecommendationsService = class("OmniRecommendationsService") :: () -> OmniRecommendationsService 460 | InstanceClasses.OpenCloudApiV1 = class("OpenCloudApiV1") :: () -> OpenCloudApiV1 461 | InstanceClasses.OpenCloudService = class("OpenCloudService") :: () -> OpenCloudService 462 | InstanceClasses.OperationGraph = class("OperationGraph") :: () -> OperationGraph 463 | InstanceClasses.PVInstance = class("PVInstance") :: () -> PVInstance 464 | InstanceClasses.BasePart = class("BasePart") :: () -> BasePart 465 | InstanceClasses.CornerWedgePart = class("CornerWedgePart") :: () -> CornerWedgePart 466 | InstanceClasses.FormFactorPart = class("FormFactorPart") :: () -> FormFactorPart 467 | InstanceClasses.Part = class("Part") :: () -> Part 468 | InstanceClasses.FlagStand = class("FlagStand") :: () -> FlagStand 469 | InstanceClasses.Platform = class("Platform") :: () -> Platform 470 | InstanceClasses.Seat = class("Seat") :: () -> Seat 471 | InstanceClasses.SkateboardPlatform = class("SkateboardPlatform") :: () -> SkateboardPlatform 472 | InstanceClasses.SpawnLocation = class("SpawnLocation") :: () -> SpawnLocation 473 | InstanceClasses.WedgePart = class("WedgePart") :: () -> WedgePart 474 | InstanceClasses.Terrain = class("Terrain") :: () -> Terrain 475 | InstanceClasses.TriangleMeshPart = class("TriangleMeshPart") :: () -> TriangleMeshPart 476 | InstanceClasses.MeshPart = class("MeshPart") :: () -> MeshPart 477 | InstanceClasses.PartOperation = class("PartOperation") :: () -> PartOperation 478 | InstanceClasses.IntersectOperation = class("IntersectOperation") :: () -> IntersectOperation 479 | InstanceClasses.NegateOperation = class("NegateOperation") :: () -> NegateOperation 480 | InstanceClasses.UnionOperation = class("UnionOperation") :: () -> UnionOperation 481 | InstanceClasses.TrussPart = class("TrussPart") :: () -> TrussPart 482 | InstanceClasses.VehicleSeat = class("VehicleSeat") :: () -> VehicleSeat 483 | InstanceClasses.Model = class("Model") :: () -> Model 484 | InstanceClasses.Actor = class("Actor") :: () -> Actor 485 | InstanceClasses.BackpackItem = class("BackpackItem") :: () -> BackpackItem 486 | InstanceClasses.HopperBin = class("HopperBin") :: () -> HopperBin 487 | InstanceClasses.Tool = class("Tool") :: () -> Tool 488 | InstanceClasses.Flag = class("Flag") :: () -> Flag 489 | InstanceClasses.Status = class("Status") :: () -> Status 490 | InstanceClasses.WorldRoot = class("WorldRoot") :: () -> WorldRoot 491 | InstanceClasses.Workspace = class("Workspace") :: () -> Workspace 492 | InstanceClasses.WorldModel = class("WorldModel") :: () -> WorldModel 493 | InstanceClasses.PackageLink = class("PackageLink") :: () -> PackageLink 494 | InstanceClasses.PackageService = class("PackageService") :: () -> PackageService 495 | InstanceClasses.PackageUIService = class("PackageUIService") :: () -> PackageUIService 496 | InstanceClasses.Pages = class("Pages") :: () -> Pages 497 | InstanceClasses.AudioPages = class("AudioPages") :: () -> AudioPages 498 | InstanceClasses.CatalogPages = class("CatalogPages") :: () -> CatalogPages 499 | InstanceClasses.DataStoreKeyPages = class("DataStoreKeyPages") :: () -> DataStoreKeyPages 500 | InstanceClasses.DataStoreListingPages = class("DataStoreListingPages") :: () -> DataStoreListingPages 501 | InstanceClasses.DataStorePages = class("DataStorePages") :: () -> DataStorePages 502 | InstanceClasses.DataStoreVersionPages = class("DataStoreVersionPages") :: () -> DataStoreVersionPages 503 | InstanceClasses.FriendPages = class("FriendPages") :: () -> FriendPages 504 | InstanceClasses.InventoryPages = class("InventoryPages") :: () -> InventoryPages 505 | InstanceClasses.EmotesPages = class("EmotesPages") :: () -> EmotesPages 506 | InstanceClasses.MemoryStoreHashMapPages = class("MemoryStoreHashMapPages") :: () -> MemoryStoreHashMapPages 507 | InstanceClasses.OutfitPages = class("OutfitPages") :: () -> OutfitPages 508 | InstanceClasses.StandardPages = class("StandardPages") :: () -> StandardPages 509 | InstanceClasses.PartOperationAsset = class("PartOperationAsset") :: () -> PartOperationAsset 510 | InstanceClasses.ParticleEmitter = class("ParticleEmitter") :: () -> ParticleEmitter 511 | InstanceClasses.PatchBundlerFileWatch = class("PatchBundlerFileWatch") :: () -> PatchBundlerFileWatch 512 | InstanceClasses.PatchMapping = class("PatchMapping") :: () -> PatchMapping 513 | InstanceClasses.Path = class("Path") :: () -> Path 514 | InstanceClasses.PathfindingLink = class("PathfindingLink") :: () -> PathfindingLink 515 | InstanceClasses.PathfindingModifier = class("PathfindingModifier") :: () -> PathfindingModifier 516 | InstanceClasses.PathfindingService = class("PathfindingService") :: () -> PathfindingService 517 | InstanceClasses.PausedState = class("PausedState") :: () -> PausedState 518 | InstanceClasses.PausedStateBreakpoint = class("PausedStateBreakpoint") :: () -> PausedStateBreakpoint 519 | InstanceClasses.PausedStateException = class("PausedStateException") :: () -> PausedStateException 520 | InstanceClasses.PermissionsService = class("PermissionsService") :: () -> PermissionsService 521 | InstanceClasses.PhysicsService = class("PhysicsService") :: () -> PhysicsService 522 | InstanceClasses.PhysicsSettings = class("PhysicsSettings") :: () -> PhysicsSettings 523 | InstanceClasses.PlaceStatsService = class("PlaceStatsService") :: () -> PlaceStatsService 524 | InstanceClasses.PlacesService = class("PlacesService") :: () -> PlacesService 525 | InstanceClasses.PlatformCloudStorageService = class("PlatformCloudStorageService") :: () -> PlatformCloudStorageService 526 | InstanceClasses.PlatformFriendsService = class("PlatformFriendsService") :: () -> PlatformFriendsService 527 | InstanceClasses.Player = class("Player") :: () -> Player 528 | InstanceClasses.PlayerEmulatorService = class("PlayerEmulatorService") :: () -> PlayerEmulatorService 529 | InstanceClasses.PlayerScripts = class("PlayerScripts") :: () -> PlayerScripts 530 | InstanceClasses.PlayerViewService = class("PlayerViewService") :: () -> PlayerViewService 531 | InstanceClasses.Players = class("Players") :: () -> Players 532 | InstanceClasses.Plugin = class("Plugin") :: () -> Plugin 533 | InstanceClasses.PluginAction = class("PluginAction") :: () -> PluginAction 534 | InstanceClasses.PluginCapabilities = class("PluginCapabilities") :: () -> PluginCapabilities 535 | InstanceClasses.PluginDebugService = class("PluginDebugService") :: () -> PluginDebugService 536 | InstanceClasses.PluginDragEvent = class("PluginDragEvent") :: () -> PluginDragEvent 537 | InstanceClasses.PluginGuiService = class("PluginGuiService") :: () -> PluginGuiService 538 | InstanceClasses.PluginManagementService = class("PluginManagementService") :: () -> PluginManagementService 539 | InstanceClasses.PluginManager = class("PluginManager") :: () -> PluginManager 540 | InstanceClasses.PluginManagerInterface = class("PluginManagerInterface") :: () -> PluginManagerInterface 541 | InstanceClasses.PluginMenu = class("PluginMenu") :: () -> PluginMenu 542 | InstanceClasses.PluginPolicyService = class("PluginPolicyService") :: () -> PluginPolicyService 543 | InstanceClasses.PluginToolbar = class("PluginToolbar") :: () -> PluginToolbar 544 | InstanceClasses.PluginToolbarButton = class("PluginToolbarButton") :: () -> PluginToolbarButton 545 | InstanceClasses.PointsService = class("PointsService") :: () -> PointsService 546 | InstanceClasses.PolicyService = class("PolicyService") :: () -> PolicyService 547 | InstanceClasses.PoseBase = class("PoseBase") :: () -> PoseBase 548 | InstanceClasses.NumberPose = class("NumberPose") :: () -> NumberPose 549 | InstanceClasses.Pose = class("Pose") :: () -> Pose 550 | InstanceClasses.PostEffect = class("PostEffect") :: () -> PostEffect 551 | InstanceClasses.BloomEffect = class("BloomEffect") :: () -> BloomEffect 552 | InstanceClasses.BlurEffect = class("BlurEffect") :: () -> BlurEffect 553 | InstanceClasses.ColorCorrectionEffect = class("ColorCorrectionEffect") :: () -> ColorCorrectionEffect 554 | InstanceClasses.DepthOfFieldEffect = class("DepthOfFieldEffect") :: () -> DepthOfFieldEffect 555 | InstanceClasses.SunRaysEffect = class("SunRaysEffect") :: () -> SunRaysEffect 556 | InstanceClasses.ProcessInstancePhysicsService = 557 | class("ProcessInstancePhysicsService") :: () -> ProcessInstancePhysicsService 558 | InstanceClasses.ProximityPrompt = class("ProximityPrompt") :: () -> ProximityPrompt 559 | InstanceClasses.ProximityPromptService = class("ProximityPromptService") :: () -> ProximityPromptService 560 | InstanceClasses.PublishService = class("PublishService") :: () -> PublishService 561 | InstanceClasses.RbxAnalyticsService = class("RbxAnalyticsService") :: () -> RbxAnalyticsService 562 | InstanceClasses.ReflectionMetadata = class("ReflectionMetadata") :: () -> ReflectionMetadata 563 | InstanceClasses.ReflectionMetadataCallbacks = class("ReflectionMetadataCallbacks") :: () -> ReflectionMetadataCallbacks 564 | InstanceClasses.ReflectionMetadataClasses = class("ReflectionMetadataClasses") :: () -> ReflectionMetadataClasses 565 | InstanceClasses.ReflectionMetadataEnums = class("ReflectionMetadataEnums") :: () -> ReflectionMetadataEnums 566 | InstanceClasses.ReflectionMetadataEvents = class("ReflectionMetadataEvents") :: () -> ReflectionMetadataEvents 567 | InstanceClasses.ReflectionMetadataFunctions = class("ReflectionMetadataFunctions") :: () -> ReflectionMetadataFunctions 568 | InstanceClasses.ReflectionMetadataItem = class("ReflectionMetadataItem") :: () -> ReflectionMetadataItem 569 | InstanceClasses.ReflectionMetadataClass = class("ReflectionMetadataClass") :: () -> ReflectionMetadataClass 570 | InstanceClasses.ReflectionMetadataEnum = class("ReflectionMetadataEnum") :: () -> ReflectionMetadataEnum 571 | InstanceClasses.ReflectionMetadataEnumItem = class("ReflectionMetadataEnumItem") :: () -> ReflectionMetadataEnumItem 572 | InstanceClasses.ReflectionMetadataMember = class("ReflectionMetadataMember") :: () -> ReflectionMetadataMember 573 | InstanceClasses.ReflectionMetadataProperties = 574 | class("ReflectionMetadataProperties") :: () -> ReflectionMetadataProperties 575 | InstanceClasses.ReflectionMetadataYieldFunctions = 576 | class("ReflectionMetadataYieldFunctions") :: () -> ReflectionMetadataYieldFunctions 577 | InstanceClasses.ReflectionService = class("ReflectionService") :: () -> ReflectionService 578 | InstanceClasses.RemoteCursorService = class("RemoteCursorService") :: () -> RemoteCursorService 579 | InstanceClasses.RemoteDebuggerServer = class("RemoteDebuggerServer") :: () -> RemoteDebuggerServer 580 | InstanceClasses.RemoteFunction = class("RemoteFunction") :: () -> RemoteFunction 581 | InstanceClasses.RenderSettings = class("RenderSettings") :: () -> RenderSettings 582 | InstanceClasses.RenderingTest = class("RenderingTest") :: () -> RenderingTest 583 | InstanceClasses.ReplicatedFirst = class("ReplicatedFirst") :: () -> ReplicatedFirst 584 | InstanceClasses.ReplicatedStorage = class("ReplicatedStorage") :: () -> ReplicatedStorage 585 | InstanceClasses.RibbonNotificationService = class("RibbonNotificationService") :: () -> RibbonNotificationService 586 | InstanceClasses.RobloxPluginGuiService = class("RobloxPluginGuiService") :: () -> RobloxPluginGuiService 587 | InstanceClasses.RobloxReplicatedStorage = class("RobloxReplicatedStorage") :: () -> RobloxReplicatedStorage 588 | InstanceClasses.RobloxServerStorage = class("RobloxServerStorage") :: () -> RobloxServerStorage 589 | InstanceClasses.RomarkService = class("RomarkService") :: () -> RomarkService 590 | InstanceClasses.RotationCurve = class("RotationCurve") :: () -> RotationCurve 591 | InstanceClasses.RtMessagingService = class("RtMessagingService") :: () -> RtMessagingService 592 | InstanceClasses.RunService = class("RunService") :: () -> RunService 593 | InstanceClasses.RuntimeScriptService = class("RuntimeScriptService") :: () -> RuntimeScriptService 594 | InstanceClasses.SafetyService = class("SafetyService") :: () -> SafetyService 595 | InstanceClasses.ScreenshotHud = class("ScreenshotHud") :: () -> ScreenshotHud 596 | InstanceClasses.ScriptBuilder = class("ScriptBuilder") :: () -> ScriptBuilder 597 | InstanceClasses.SyncScriptBuilder = class("SyncScriptBuilder") :: () -> SyncScriptBuilder 598 | InstanceClasses.ScriptChangeService = class("ScriptChangeService") :: () -> ScriptChangeService 599 | InstanceClasses.ScriptCloneWatcher = class("ScriptCloneWatcher") :: () -> ScriptCloneWatcher 600 | InstanceClasses.ScriptCloneWatcherHelper = class("ScriptCloneWatcherHelper") :: () -> ScriptCloneWatcherHelper 601 | InstanceClasses.ScriptCommitService = class("ScriptCommitService") :: () -> ScriptCommitService 602 | InstanceClasses.ScriptContext = class("ScriptContext") :: () -> ScriptContext 603 | InstanceClasses.ScriptDebugger = class("ScriptDebugger") :: () -> ScriptDebugger 604 | InstanceClasses.ScriptDocument = class("ScriptDocument") :: () -> ScriptDocument 605 | InstanceClasses.ScriptEditorService = class("ScriptEditorService") :: () -> ScriptEditorService 606 | InstanceClasses.ScriptRegistrationService = class("ScriptRegistrationService") :: () -> ScriptRegistrationService 607 | InstanceClasses.ScriptRuntime = class("ScriptRuntime") :: () -> ScriptRuntime 608 | InstanceClasses.ScriptService = class("ScriptService") :: () -> ScriptService 609 | InstanceClasses.Selection = class("Selection") :: () -> Selection 610 | InstanceClasses.SelectionHighlightManager = class("SelectionHighlightManager") :: () -> SelectionHighlightManager 611 | InstanceClasses.SensorBase = class("SensorBase") :: () -> SensorBase 612 | InstanceClasses.BuoyancySensor = class("BuoyancySensor") :: () -> BuoyancySensor 613 | InstanceClasses.ControllerSensor = class("ControllerSensor") :: () -> ControllerSensor 614 | InstanceClasses.ControllerPartSensor = class("ControllerPartSensor") :: () -> ControllerPartSensor 615 | InstanceClasses.ServerScriptService = class("ServerScriptService") :: () -> ServerScriptService 616 | InstanceClasses.ServerStorage = class("ServerStorage") :: () -> ServerStorage 617 | InstanceClasses.ServiceProvider = class("ServiceProvider") :: () -> ServiceProvider 618 | InstanceClasses.DataModel = class("DataModel") :: () -> DataModel 619 | InstanceClasses.GenericSettings = class("GenericSettings") :: () -> GenericSettings 620 | InstanceClasses.AnalysticsSettings = class("AnalysticsSettings") :: () -> AnalysticsSettings 621 | InstanceClasses.GlobalSettings = class("GlobalSettings") :: () -> GlobalSettings 622 | InstanceClasses.UserSettings = class("UserSettings") :: () -> UserSettings 623 | InstanceClasses.ServiceVisibilityService = class("ServiceVisibilityService") :: () -> ServiceVisibilityService 624 | InstanceClasses.SessionService = class("SessionService") :: () -> SessionService 625 | InstanceClasses.SharedTableRegistry = class("SharedTableRegistry") :: () -> SharedTableRegistry 626 | InstanceClasses.ShorelineUpgraderService = class("ShorelineUpgraderService") :: () -> ShorelineUpgraderService 627 | InstanceClasses.Sky = class("Sky") :: () -> Sky 628 | InstanceClasses.Smoke = class("Smoke") :: () -> Smoke 629 | InstanceClasses.SmoothVoxelsUpgraderService = class("SmoothVoxelsUpgraderService") :: () -> SmoothVoxelsUpgraderService 630 | InstanceClasses.SnippetService = class("SnippetService") :: () -> SnippetService 631 | InstanceClasses.SocialService = class("SocialService") :: () -> SocialService 632 | InstanceClasses.Sound = class("Sound") :: () -> Sound 633 | InstanceClasses.SoundEffect = class("SoundEffect") :: () -> SoundEffect 634 | InstanceClasses.ChorusSoundEffect = class("ChorusSoundEffect") :: () -> ChorusSoundEffect 635 | InstanceClasses.CompressorSoundEffect = class("CompressorSoundEffect") :: () -> CompressorSoundEffect 636 | InstanceClasses.CustomSoundEffect = class("CustomSoundEffect") :: () -> CustomSoundEffect 637 | InstanceClasses.AssetSoundEffect = class("AssetSoundEffect") :: () -> AssetSoundEffect 638 | InstanceClasses.ChannelSelectorSoundEffect = class("ChannelSelectorSoundEffect") :: () -> ChannelSelectorSoundEffect 639 | InstanceClasses.DistortionSoundEffect = class("DistortionSoundEffect") :: () -> DistortionSoundEffect 640 | InstanceClasses.EchoSoundEffect = class("EchoSoundEffect") :: () -> EchoSoundEffect 641 | InstanceClasses.EqualizerSoundEffect = class("EqualizerSoundEffect") :: () -> EqualizerSoundEffect 642 | InstanceClasses.FlangeSoundEffect = class("FlangeSoundEffect") :: () -> FlangeSoundEffect 643 | InstanceClasses.PitchShiftSoundEffect = class("PitchShiftSoundEffect") :: () -> PitchShiftSoundEffect 644 | InstanceClasses.ReverbSoundEffect = class("ReverbSoundEffect") :: () -> ReverbSoundEffect 645 | InstanceClasses.TremoloSoundEffect = class("TremoloSoundEffect") :: () -> TremoloSoundEffect 646 | InstanceClasses.SoundGroup = class("SoundGroup") :: () -> SoundGroup 647 | InstanceClasses.SoundService = class("SoundService") :: () -> SoundService 648 | InstanceClasses.Sparkles = class("Sparkles") :: () -> Sparkles 649 | InstanceClasses.SpawnerService = class("SpawnerService") :: () -> SpawnerService 650 | InstanceClasses.StackFrame = class("StackFrame") :: () -> StackFrame 651 | InstanceClasses.StandalonePluginScripts = class("StandalonePluginScripts") :: () -> StandalonePluginScripts 652 | InstanceClasses.StarterGear = class("StarterGear") :: () -> StarterGear 653 | InstanceClasses.StarterPack = class("StarterPack") :: () -> StarterPack 654 | InstanceClasses.StarterPlayer = class("StarterPlayer") :: () -> StarterPlayer 655 | InstanceClasses.StarterPlayerScripts = class("StarterPlayerScripts") :: () -> StarterPlayerScripts 656 | InstanceClasses.StarterCharacterScripts = class("StarterCharacterScripts") :: () -> StarterCharacterScripts 657 | InstanceClasses.Stats = class("Stats") :: () -> Stats 658 | InstanceClasses.StatsItem = class("StatsItem") :: () -> StatsItem 659 | InstanceClasses.RunningAverageItemDouble = class("RunningAverageItemDouble") :: () -> RunningAverageItemDouble 660 | InstanceClasses.RunningAverageItemInt = class("RunningAverageItemInt") :: () -> RunningAverageItemInt 661 | InstanceClasses.RunningAverageTimeIntervalItem = 662 | class("RunningAverageTimeIntervalItem") :: () -> RunningAverageTimeIntervalItem 663 | InstanceClasses.TotalCountTimeIntervalItem = class("TotalCountTimeIntervalItem") :: () -> TotalCountTimeIntervalItem 664 | InstanceClasses.StopWatchReporter = class("StopWatchReporter") :: () -> StopWatchReporter 665 | InstanceClasses.StreamingService = class("StreamingService") :: () -> StreamingService 666 | InstanceClasses.Studio = class("Studio") :: () -> Studio 667 | InstanceClasses.StudioAssetService = class("StudioAssetService") :: () -> StudioAssetService 668 | InstanceClasses.StudioAttachment = class("StudioAttachment") :: () -> StudioAttachment 669 | InstanceClasses.StudioCallout = class("StudioCallout") :: () -> StudioCallout 670 | InstanceClasses.StudioData = class("StudioData") :: () -> StudioData 671 | InstanceClasses.StudioDeviceEmulatorService = class("StudioDeviceEmulatorService") :: () -> StudioDeviceEmulatorService 672 | InstanceClasses.StudioObjectBase = class("StudioObjectBase") :: () -> StudioObjectBase 673 | InstanceClasses.StudioWidget = class("StudioWidget") :: () -> StudioWidget 674 | InstanceClasses.StudioPublishService = class("StudioPublishService") :: () -> StudioPublishService 675 | InstanceClasses.StudioScriptDebugEventListener = 676 | class("StudioScriptDebugEventListener") :: () -> StudioScriptDebugEventListener 677 | InstanceClasses.StudioSdkService = class("StudioSdkService") :: () -> StudioSdkService 678 | InstanceClasses.StudioService = class("StudioService") :: () -> StudioService 679 | InstanceClasses.StudioTheme = class("StudioTheme") :: () -> StudioTheme 680 | InstanceClasses.StudioWidgetsService = class("StudioWidgetsService") :: () -> StudioWidgetsService 681 | InstanceClasses.StyleBase = class("StyleBase") :: () -> StyleBase 682 | InstanceClasses.StyleRule = class("StyleRule") :: () -> StyleRule 683 | InstanceClasses.StyleSheet = class("StyleSheet") :: () -> StyleSheet 684 | InstanceClasses.StyleDerive = class("StyleDerive") :: () -> StyleDerive 685 | InstanceClasses.StyleLink = class("StyleLink") :: () -> StyleLink 686 | InstanceClasses.StylingService = class("StylingService") :: () -> StylingService 687 | InstanceClasses.SurfaceAppearance = class("SurfaceAppearance") :: () -> SurfaceAppearance 688 | InstanceClasses.TaskScheduler = class("TaskScheduler") :: () -> TaskScheduler 689 | InstanceClasses.Team = class("Team") :: () -> Team 690 | InstanceClasses.TeamCreateData = class("TeamCreateData") :: () -> TeamCreateData 691 | InstanceClasses.TeamCreatePublishService = class("TeamCreatePublishService") :: () -> TeamCreatePublishService 692 | InstanceClasses.TeamCreateService = class("TeamCreateService") :: () -> TeamCreateService 693 | InstanceClasses.Teams = class("Teams") :: () -> Teams 694 | InstanceClasses.TeleportAsyncResult = class("TeleportAsyncResult") :: () -> TeleportAsyncResult 695 | InstanceClasses.TeleportOptions = class("TeleportOptions") :: () -> TeleportOptions 696 | InstanceClasses.TeleportService = class("TeleportService") :: () -> TeleportService 697 | InstanceClasses.TemporaryCageMeshProvider = class("TemporaryCageMeshProvider") :: () -> TemporaryCageMeshProvider 698 | InstanceClasses.TemporaryScriptService = class("TemporaryScriptService") :: () -> TemporaryScriptService 699 | InstanceClasses.TerrainDetail = class("TerrainDetail") :: () -> TerrainDetail 700 | InstanceClasses.TerrainRegion = class("TerrainRegion") :: () -> TerrainRegion 701 | InstanceClasses.TestService = class("TestService") :: () -> TestService 702 | InstanceClasses.TextBoxService = class("TextBoxService") :: () -> TextBoxService 703 | InstanceClasses.TextChannel = class("TextChannel") :: () -> TextChannel 704 | InstanceClasses.TextChatCommand = class("TextChatCommand") :: () -> TextChatCommand 705 | InstanceClasses.TextChatConfigurations = class("TextChatConfigurations") :: () -> TextChatConfigurations 706 | InstanceClasses.BubbleChatConfiguration = class("BubbleChatConfiguration") :: () -> BubbleChatConfiguration 707 | InstanceClasses.ChatInputBarConfiguration = class("ChatInputBarConfiguration") :: () -> ChatInputBarConfiguration 708 | InstanceClasses.ChatWindowConfiguration = class("ChatWindowConfiguration") :: () -> ChatWindowConfiguration 709 | InstanceClasses.TextChatMessage = class("TextChatMessage") :: () -> TextChatMessage 710 | InstanceClasses.TextChatMessageProperties = class("TextChatMessageProperties") :: () -> TextChatMessageProperties 711 | InstanceClasses.TextChatService = class("TextChatService") :: () -> TextChatService 712 | InstanceClasses.TextFilterResult = class("TextFilterResult") :: () -> TextFilterResult 713 | InstanceClasses.TextFilterTranslatedResult = class("TextFilterTranslatedResult") :: () -> TextFilterTranslatedResult 714 | InstanceClasses.TextService = class("TextService") :: () -> TextService 715 | InstanceClasses.TextSource = class("TextSource") :: () -> TextSource 716 | InstanceClasses.ThirdPartyUserService = class("ThirdPartyUserService") :: () -> ThirdPartyUserService 717 | InstanceClasses.ThreadState = class("ThreadState") :: () -> ThreadState 718 | InstanceClasses.TimerService = class("TimerService") :: () -> TimerService 719 | InstanceClasses.ToastNotificationService = class("ToastNotificationService") :: () -> ToastNotificationService 720 | InstanceClasses.TouchInputService = class("TouchInputService") :: () -> TouchInputService 721 | InstanceClasses.TouchTransmitter = class("TouchTransmitter") :: () -> TouchTransmitter 722 | InstanceClasses.TracerService = class("TracerService") :: () -> TracerService 723 | InstanceClasses.TrackerLodController = class("TrackerLodController") :: () -> TrackerLodController 724 | InstanceClasses.TrackerStreamAnimation = class("TrackerStreamAnimation") :: () -> TrackerStreamAnimation 725 | InstanceClasses.Trail = class("Trail") :: () -> Trail 726 | InstanceClasses.Translator = class("Translator") :: () -> Translator 727 | InstanceClasses.TutorialService = class("TutorialService") :: () -> TutorialService 728 | InstanceClasses.TweenBase = class("TweenBase") :: () -> TweenBase 729 | InstanceClasses.Tween = class("Tween") :: () -> Tween 730 | InstanceClasses.TweenService = class("TweenService") :: () -> TweenService 731 | InstanceClasses.UGCAvatarService = class("UGCAvatarService") :: () -> UGCAvatarService 732 | InstanceClasses.UGCValidationService = class("UGCValidationService") :: () -> UGCValidationService 733 | InstanceClasses.UIBase = class("UIBase") :: () -> UIBase 734 | InstanceClasses.UIComponent = class("UIComponent") :: () -> UIComponent 735 | InstanceClasses.UIConstraint = class("UIConstraint") :: () -> UIConstraint 736 | InstanceClasses.UIAspectRatioConstraint = class("UIAspectRatioConstraint") :: () -> UIAspectRatioConstraint 737 | InstanceClasses.UISizeConstraint = class("UISizeConstraint") :: () -> UISizeConstraint 738 | InstanceClasses.UITextSizeConstraint = class("UITextSizeConstraint") :: () -> UITextSizeConstraint 739 | InstanceClasses.UICorner = class("UICorner") :: () -> UICorner 740 | InstanceClasses.UIFlexItem = class("UIFlexItem") :: () -> UIFlexItem 741 | InstanceClasses.UIGradient = class("UIGradient") :: () -> UIGradient 742 | InstanceClasses.UILayout = class("UILayout") :: () -> UILayout 743 | InstanceClasses.UIGridStyleLayout = class("UIGridStyleLayout") :: () -> UIGridStyleLayout 744 | InstanceClasses.UIGridLayout = class("UIGridLayout") :: () -> UIGridLayout 745 | InstanceClasses.UIListLayout = class("UIListLayout") :: () -> UIListLayout 746 | InstanceClasses.UIPageLayout = class("UIPageLayout") :: () -> UIPageLayout 747 | InstanceClasses.UITableLayout = class("UITableLayout") :: () -> UITableLayout 748 | InstanceClasses.UIPadding = class("UIPadding") :: () -> UIPadding 749 | InstanceClasses.UIScale = class("UIScale") :: () -> UIScale 750 | InstanceClasses.UIStroke = class("UIStroke") :: () -> UIStroke 751 | InstanceClasses.UnvalidatedAssetService = class("UnvalidatedAssetService") :: () -> UnvalidatedAssetService 752 | InstanceClasses.UserGameSettings = class("UserGameSettings") :: () -> UserGameSettings 753 | InstanceClasses.UserInputService = class("UserInputService") :: () -> UserInputService 754 | InstanceClasses.UserService = class("UserService") :: () -> UserService 755 | InstanceClasses.VRService = class("VRService") :: () -> VRService 756 | InstanceClasses.VRStatusService = class("VRStatusService") :: () -> VRStatusService 757 | InstanceClasses.ValueBase = class("ValueBase") :: () -> ValueBase 758 | InstanceClasses.BinaryStringValue = class("BinaryStringValue") :: () -> BinaryStringValue 759 | InstanceClasses.BoolValue = class("BoolValue") :: () -> BoolValue 760 | InstanceClasses.BrickColorValue = class("BrickColorValue") :: () -> BrickColorValue 761 | InstanceClasses.CFrameValue = class("CFrameValue") :: () -> CFrameValue 762 | InstanceClasses.Color3Value = class("Color3Value") :: () -> Color3Value 763 | InstanceClasses.DoubleConstrainedValue = class("DoubleConstrainedValue") :: () -> DoubleConstrainedValue 764 | InstanceClasses.IntConstrainedValue = class("IntConstrainedValue") :: () -> IntConstrainedValue 765 | InstanceClasses.IntValue = class("IntValue") :: () -> IntValue 766 | InstanceClasses.NumberValue = class("NumberValue") :: () -> NumberValue 767 | InstanceClasses.ObjectValue = class("ObjectValue") :: () -> ObjectValue 768 | InstanceClasses.RayValue = class("RayValue") :: () -> RayValue 769 | InstanceClasses.StringValue = class("StringValue") :: () -> StringValue 770 | InstanceClasses.Vector3Value = class("Vector3Value") :: () -> Vector3Value 771 | InstanceClasses.Vector3Curve = class("Vector3Curve") :: () -> Vector3Curve 772 | InstanceClasses.VersionControlService = class("VersionControlService") :: () -> VersionControlService 773 | InstanceClasses.VideoCaptureService = class("VideoCaptureService") :: () -> VideoCaptureService 774 | InstanceClasses.VideoService = class("VideoService") :: () -> VideoService 775 | InstanceClasses.VirtualInputManager = class("VirtualInputManager") :: () -> VirtualInputManager 776 | InstanceClasses.VirtualUser = class("VirtualUser") :: () -> VirtualUser 777 | InstanceClasses.VisibilityCheckDispatcher = class("VisibilityCheckDispatcher") :: () -> VisibilityCheckDispatcher 778 | InstanceClasses.Visit = class("Visit") :: () -> Visit 779 | InstanceClasses.VoiceChatInternal = class("VoiceChatInternal") :: () -> VoiceChatInternal 780 | InstanceClasses.VoiceChatService = class("VoiceChatService") :: () -> VoiceChatService 781 | InstanceClasses.WeldConstraint = class("WeldConstraint") :: () -> WeldConstraint 782 | InstanceClasses.Wire = class("Wire") :: () -> Wire 783 | InstanceClasses.PathWaypoint = class("PathWaypoint") :: () -> PathWaypoint 784 | 785 | return InstanceClasses 786 | -------------------------------------------------------------------------------- /src/init.luau: -------------------------------------------------------------------------------- 1 | local GreenTea = require(script.GreenTea) 2 | local InstanceClasses = require(script.InstanceClasses) 3 | local tCompat = require(script.tCompat) 4 | 5 | export type Cause = GreenTea.Cause 6 | export type Type = GreenTea.Type 7 | export type TuplePacked = GreenTea.TuplePacked 8 | 9 | local export = {} 10 | 11 | export.t = tCompat 12 | 13 | export.isGreenTeaType = GreenTea.isGreenTeaType 14 | export.isGtType = GreenTea.isGtType 15 | 16 | export.any = GreenTea.any 17 | export.unknown = GreenTea.unknown 18 | export.never = GreenTea.never 19 | 20 | export.boolean = GreenTea.boolean 21 | export.bool = GreenTea.bool 22 | 23 | export.Instance = GreenTea.Instance 24 | export.isA = InstanceClasses 25 | export.IsA = InstanceClasses 26 | 27 | export.coroutine = GreenTea.coroutine 28 | export.thread = GreenTea.thread 29 | 30 | export.buffer = GreenTea.buffer 31 | 32 | export.userdata = GreenTea.userdata 33 | 34 | export.Vector2 = GreenTea.Vector2 35 | export.vector = GreenTea.vector 36 | export.Vector3 = GreenTea.Vector3 37 | export.CFrame = GreenTea.CFrame 38 | export.Color3 = GreenTea.Color3 39 | export.UDim = GreenTea.UDim 40 | export.UDim2 = GreenTea.UDim2 41 | export.Ray = GreenTea.Ray 42 | export.Rect = GreenTea.Rect 43 | export.Region3 = GreenTea.Region3 44 | export.BrickColor = GreenTea.BrickColor 45 | export.Font = GreenTea.Font 46 | 47 | export.Enum = GreenTea.Enum 48 | export.EnumItem = GreenTea.EnumItem 49 | 50 | export.none = GreenTea.none 51 | 52 | export.literal = GreenTea.literal 53 | 54 | export.withCustom = GreenTea.withCustom 55 | export.custom = GreenTea.custom 56 | 57 | export.number = GreenTea.number 58 | 59 | export.string = GreenTea.string 60 | 61 | export.isTypeof = GreenTea.isTypeof 62 | export.isType = GreenTea.isType 63 | 64 | export.vararg = GreenTea.vararg 65 | export.tuple = GreenTea.tuple 66 | 67 | export.args = GreenTea.args 68 | export.returns = GreenTea.returns 69 | export.fn = GreenTea.fn 70 | export.anyfn = GreenTea.anyfn 71 | 72 | export.tuplePacked = GreenTea.tuplePacked 73 | 74 | export.table = GreenTea.table 75 | export.struct = GreenTea.struct 76 | 77 | export.anyTable = GreenTea.anyTable 78 | export.array = GreenTea.array 79 | export.dictionary = GreenTea.dictionary 80 | 81 | export.union = GreenTea.union 82 | export.oneOf = GreenTea.oneOf 83 | 84 | export.intersection = GreenTea.intersection 85 | export.allOf = GreenTea.allOf 86 | 87 | export.optional = GreenTea.optional 88 | export.opt = GreenTea.opt 89 | 90 | export.typeof = GreenTea.typeof 91 | 92 | export.typecast = GreenTea.typecast 93 | export.asGreenTeaType = GreenTea.asGreenTeaType 94 | export.asGtType = GreenTea.asGtType 95 | 96 | export.build = GreenTea.build 97 | 98 | export.meta = GreenTea.meta 99 | 100 | export.wrapFn = GreenTea.wrapFn 101 | export.wrapFnArgs = GreenTea.wrapFnArgs 102 | export.wrapFnReturns = GreenTea.wrapFnReturns 103 | 104 | table.freeze(export) 105 | table.freeze(export.isA) 106 | table.freeze(export.t) 107 | table.freeze(GreenTea.__Cause) 108 | table.freeze(GreenTea.__Type) 109 | 110 | for key, value in export do 111 | GreenTea.__greenteaConstructorsSet[value] = `GreenTea.{key}` 112 | end 113 | 114 | for key: string, value: any in pairs(export.isA :: any) do 115 | GreenTea.__greenteaConstructorsSet[value] = `GreenTea.isA.{key}` 116 | end 117 | 118 | for key: string, value: any in pairs(export.t) do 119 | GreenTea.__greenteaConstructorsSet[value] = `GreenTea.t.{key}` 120 | end 121 | 122 | return export 123 | -------------------------------------------------------------------------------- /src/tCompat.luau: -------------------------------------------------------------------------------- 1 | local GreenTea = require(script.Parent.GreenTea) 2 | local isA = require(script.Parent.InstanceClasses) 3 | 4 | local t = {} 5 | 6 | type Typechecker = (...any) -> (boolean, any?) 7 | 8 | type TypecheckerConstr = (T...) -> Typechecker 9 | 10 | local function asPlainFn(fn: (...any) -> ...any): Typechecker 11 | return function() 12 | return fn() 13 | end 14 | end 15 | 16 | local function asGreenTeaType(thing: any): any 17 | if GreenTea.isGtType(thing) then 18 | return thing 19 | elseif typeof(thing) == "function" then 20 | return GreenTea.custom(thing) 21 | elseif thing == nil then 22 | return GreenTea.none() 23 | else 24 | return GreenTea.typeof(thing) 25 | end 26 | end 27 | 28 | local implicitConstructors = { 29 | boolean = GreenTea.boolean :: Typechecker, 30 | buffer = (GreenTea.buffer :: any) :: Typechecker, 31 | callback = (GreenTea.anyfn :: any) :: Typechecker, 32 | ["function"] = (GreenTea.anyfn :: any) :: Typechecker, 33 | none = (GreenTea.none :: any) :: Typechecker, 34 | ["nil"] = (GreenTea.none :: any) :: Typechecker, 35 | string = asPlainFn(GreenTea.string), 36 | table = asPlainFn(GreenTea.anyTable), 37 | userdata = asPlainFn(GreenTea.userdata), 38 | vector = asPlainFn(GreenTea.vector), 39 | number = asPlainFn(GreenTea.number), 40 | thread = asPlainFn(GreenTea.coroutine), 41 | any = function() 42 | return GreenTea.any() 43 | end :: Typechecker, 44 | nan = function() 45 | return GreenTea.withCustom(GreenTea.number({ nan = true }), function(item) 46 | return item ~= item 47 | end) :: any 48 | end :: Typechecker, 49 | integer = function() 50 | return GreenTea.number({ integer = true }) :: any 51 | end :: Typechecker, 52 | numberPositive = function() 53 | return GreenTea.number({ range = "(0, inf]" }) :: any 54 | end :: Typechecker, 55 | numberNegative = function() 56 | return GreenTea.number({ range = "[-inf, 0)" }) :: any 57 | end :: Typechecker, 58 | Enum = (GreenTea.Enum :: any) :: Typechecker, 59 | EnumItem = (GreenTea.EnumItem :: any) :: Typechecker, 60 | } 61 | 62 | local function basicType(name: string): Typechecker 63 | return GreenTea.__newBasicType(name) 64 | end 65 | 66 | implicitConstructors.Axes = basicType("Axes") 67 | implicitConstructors.BrickColor = basicType("BrickColor") 68 | implicitConstructors.CatalogSearchParams = basicType("CatalogSearchParams") 69 | implicitConstructors.CFrame = basicType("CFrame") 70 | implicitConstructors.Color3 = basicType("Color3") 71 | implicitConstructors.ColorSequence = basicType("ColorSequence") 72 | implicitConstructors.ColorSequenceKeypoint = basicType("ColorSequenceKeypoint") 73 | implicitConstructors.DateTime = basicType("DateTime") 74 | implicitConstructors.DockWidgetPluginGuiInfo = basicType("DockWidgetPluginGuiInfo") 75 | implicitConstructors.Enums = basicType("Enums") 76 | implicitConstructors.Faces = basicType("Faces") 77 | implicitConstructors.FloatCurveKey = basicType("FloatCurveKey") 78 | implicitConstructors.Font = basicType("Font") 79 | implicitConstructors.Instance = basicType("Instance") 80 | implicitConstructors.NumberRange = basicType("NumberRange") 81 | implicitConstructors.NumberSequence = basicType("NumberSequence") 82 | implicitConstructors.NumberSequenceKeypoint = basicType("NumberSequenceKeypoint") 83 | implicitConstructors.OverlapParams = basicType("OverlapParams") 84 | implicitConstructors.PathWaypoint = basicType("PathWaypoint") 85 | implicitConstructors.PhysicalProperties = basicType("PhysicalProperties") 86 | implicitConstructors.Random = basicType("Random") 87 | implicitConstructors.Ray = basicType("Ray") 88 | implicitConstructors.RaycastParams = basicType("RaycastParams") 89 | implicitConstructors.RaycastResult = basicType("RaycastResult") 90 | implicitConstructors.RBXScriptConnection = basicType("RBXScriptConnection") 91 | implicitConstructors.RBXScriptSignal = basicType("RBXScriptSignal") 92 | implicitConstructors.Rect = basicType("Rect") 93 | implicitConstructors.Region3 = basicType("Region3") 94 | implicitConstructors.Region3int16 = basicType("Region3int16") 95 | implicitConstructors.TweenInfo = basicType("TweenInfo") 96 | implicitConstructors.UDim = basicType("UDim") 97 | implicitConstructors.UDim2 = basicType("UDim2") 98 | implicitConstructors.Vector2 = basicType("Vector2") 99 | implicitConstructors.Vector2int16 = basicType("Vector2int16") 100 | implicitConstructors.Vector3 = basicType("Vector3") 101 | implicitConstructors.Vector3int16 = basicType("Vector3int16") 102 | 103 | t.type = function(typename: string): any 104 | return GreenTea.isType(typename) 105 | end :: TypecheckerConstr 106 | 107 | t.typeof = function(typename: string): any 108 | return GreenTea.isTypeof(typename) 109 | end :: TypecheckerConstr 110 | 111 | t.literal = function(...: any): any 112 | local count = select("#", ...) 113 | if count == 0 then 114 | return GreenTea.any() 115 | elseif count == 1 then 116 | return GreenTea.literal(...) 117 | else 118 | local literals = {} 119 | for index = 1, count do 120 | table.insert(literals, GreenTea.literal(select(index, ...))) 121 | end 122 | 123 | return GreenTea.union(table.unpack(literals)) 124 | end 125 | end :: TypecheckerConstr<...any> 126 | 127 | t.exactly = t.literal 128 | 129 | t.keyOf = function(keyType: { [any]: any }): any 130 | local union = {} 131 | for key, _value in pairs(keyType) do 132 | table.insert(union, GreenTea.literal(key)) 133 | end 134 | return GreenTea.union(table.unpack(union)) 135 | end :: TypecheckerConstr<{ [any]: any }> 136 | 137 | t.valueOf = function(valueType: { [any]: any }): any 138 | local union = {} 139 | for _key, value in pairs(valueType) do 140 | table.insert(union, GreenTea.typeof(value)) 141 | end 142 | return GreenTea.union(table.unpack(union)) 143 | end :: TypecheckerConstr<{ [any]: any }> 144 | 145 | t.optional = function(thins: any): any 146 | return GreenTea.optional(asGreenTeaType(thins)) 147 | end :: TypecheckerConstr 148 | 149 | t.tuple = function(...: any): any 150 | local tupleArgs = table.pack(...) 151 | for index = 1, tupleArgs.n do 152 | tupleArgs[index] = asGreenTeaType(tupleArgs[index]) 153 | end 154 | 155 | return GreenTea.tuple(table.unpack(tupleArgs)) 156 | end :: TypecheckerConstr<...any> 157 | 158 | t.union = function(...: any) 159 | local unionArgs = table.pack(...) 160 | for index = 1, unionArgs.n do 161 | unionArgs[index] = asGreenTeaType(unionArgs[index]) 162 | end 163 | return GreenTea.union(table.unpack(unionArgs)) 164 | end :: TypecheckerConstr<...any> 165 | 166 | t.some = t.union 167 | 168 | t.intersection = function(...: any): any 169 | local intersectionArgs = table.pack(...) 170 | for index = 1, intersectionArgs.n do 171 | intersectionArgs[index] = asGreenTeaType(intersectionArgs[index]) 172 | end 173 | return GreenTea.intersection(table.unpack(intersectionArgs)) 174 | end :: TypecheckerConstr<...any> 175 | 176 | t.every = t.intersection 177 | 178 | t.keys = function(check: any): any 179 | return GreenTea.dictionary(asGreenTeaType(check), GreenTea.any()) 180 | end :: TypecheckerConstr 181 | 182 | t.values = function(check: any): any 183 | return GreenTea.dictionary(GreenTea.any(), asGreenTeaType(check)) 184 | end :: TypecheckerConstr 185 | 186 | t.map = function(key: any, value: any): any 187 | return GreenTea.dictionary(asGreenTeaType(key), asGreenTeaType(value)) 188 | end :: TypecheckerConstr 189 | 190 | t.set = function(key: any): any 191 | return GreenTea.dictionary(asGreenTeaType(key), GreenTea.literal(true)) 192 | end :: TypecheckerConstr 193 | 194 | t.numberMin = function(min: number): any 195 | return GreenTea.number({ range = `[{min}, inf]` }) 196 | end :: TypecheckerConstr 197 | t.numberMax = function(max: number): any 198 | return GreenTea.number({ range = `[-inf, {max}]` }) 199 | end :: TypecheckerConstr 200 | t.numberConstrained = function(min: number, max: number): any 201 | return GreenTea.number({ range = `[{min}, {max}]` }) 202 | end :: TypecheckerConstr 203 | t.numberMinExclusive = function(min: number): any 204 | return GreenTea.number({ range = `({min}, inf]` }) 205 | end :: TypecheckerConstr 206 | t.numberMaxExclusive = function(max: number): any 207 | return GreenTea.number({ range = `[-inf, {max})` }) 208 | end :: TypecheckerConstr 209 | t.numberConstrainedExclusive = function(min: number, max: number): any 210 | return GreenTea.number({ range = `({min}, {max})` }) 211 | end :: TypecheckerConstr 212 | 213 | t.match = function(pattern: string): any 214 | return GreenTea.string({ pattern = pattern }) 215 | end :: TypecheckerConstr 216 | 217 | t.array = function(typechecker): any 218 | return GreenTea.array(asGreenTeaType(typechecker)) 219 | end :: TypecheckerConstr 220 | 221 | t.strictArray = function(...: any): any 222 | local arrayTypes = { ... } 223 | arrayTypes[GreenTea.any()] = GreenTea.never() 224 | 225 | local count = select("#", ...) 226 | for index = 1, count do 227 | arrayTypes[index] = asGreenTeaType(arrayTypes[index]) 228 | end 229 | 230 | return GreenTea.table(arrayTypes, { raw = true, count = { min = 0, max = count } }) 231 | end :: TypecheckerConstr<...any> 232 | 233 | t.interface = function(interface: { [any]: any }): any 234 | local interfaceCleaned = {} 235 | for key, value in pairs(interface) do 236 | interfaceCleaned[key] = asGreenTeaType(value) 237 | end 238 | 239 | return GreenTea.table(interfaceCleaned, { raw = true }) 240 | end :: TypecheckerConstr<{ [any]: any }> 241 | 242 | t.strictInterface = function(interface: { [string]: any }): any 243 | local interfaceCleaned = {} 244 | for key, value in pairs(interface) do 245 | interfaceCleaned[key] = asGreenTeaType(value) 246 | end 247 | 248 | interfaceCleaned[GreenTea.any()] = GreenTea.never() 249 | 250 | return GreenTea.table(interfaceCleaned, { raw = true }) 251 | end :: TypecheckerConstr<{ [string]: any }> 252 | 253 | local function tChildren(children: { [string]: any }) 254 | local childrenCleaned = {} 255 | for key, type in pairs(children) do 256 | assert(typeof(key) == "string", "children keys must be strings") 257 | childrenCleaned[key] = asGreenTeaType(type) 258 | end 259 | return function(thing): (boolean, string?) 260 | if not thing or typeof(thing) ~= "Instance" then 261 | return false, "expected an instance" 262 | end 263 | 264 | for key, typechecker in pairs(childrenCleaned) do 265 | if not thing:FindFirstChild(key) then 266 | return false, "missing child " .. key 267 | end 268 | 269 | local success, err = typechecker(thing:FindFirstChild(key)) 270 | if not success then 271 | return false, err 272 | end 273 | end 274 | 275 | local existing = {} 276 | for _, child in thing:GetChildren() do 277 | if existing[child.Name] then 278 | return false, "duplicate child " .. child.Name 279 | end 280 | 281 | existing[child.Name] = true 282 | end 283 | 284 | return true, nil 285 | end 286 | end 287 | 288 | t.instanceOf = function(className: string, children: { [string]: any }?): any 289 | local childrenCheck = children and tChildren(children) 290 | return GreenTea.withCustom(isA(className), function(thing) 291 | if thing.ClassName ~= className then 292 | return false, "expected an instance of " .. className 293 | else 294 | if childrenCheck then 295 | return childrenCheck(thing) 296 | else 297 | return true 298 | end 299 | end 300 | end, "InstanceOf") 301 | end :: TypecheckerConstr 302 | 303 | t.instance = t.instanceOf 304 | 305 | t.instanceIsA = function(className: string, children: { [string]: any }?): any 306 | if children then 307 | return GreenTea.withCustom(isA(className), tChildren(children), "Children") 308 | else 309 | return isA(className) 310 | end 311 | end :: TypecheckerConstr 312 | 313 | t.children = function(children: { [string]: any }): any 314 | return GreenTea.custom(tChildren(children), "Children") 315 | end :: TypecheckerConstr<{ [string]: any }> 316 | 317 | t.enum = function(enum: Enum): any 318 | return GreenTea.custom(function(thing) 319 | if typeof(thing) ~= "EnumItem" then 320 | return false, "expected an enum item" 321 | end 322 | if thing.EnumType ~= enum then 323 | return false, "expected an enum item of type " .. tostring(enum) 324 | end 325 | return true 326 | end, `{enum}`) 327 | end :: TypecheckerConstr 328 | 329 | t.wrap = function(fn: (Args...) -> Returns..., argsCheck: any): (Args...) -> Returns... 330 | local argsChecker: GreenTea.Type = asGreenTeaType(argsCheck) 331 | return function(...: any) 332 | argsChecker:assert(...) 333 | 334 | return fn(...) 335 | end 336 | end 337 | 338 | t.strict = function(typechecker: T): T 339 | local typechecker = asGreenTeaType(typechecker) 340 | return function(...) 341 | typechecker:assert(...) 342 | end :: any 343 | end 344 | 345 | type exportedTypes = typeof(t) & typeof(implicitConstructors) 346 | 347 | local exportedRuntime = setmetatable(t :: any, { 348 | __index = function(_self, key) 349 | if implicitConstructors[key] then 350 | return implicitConstructors[key]() 351 | else 352 | return nil 353 | end 354 | end, 355 | }) 356 | 357 | return exportedRuntime :: exportedTypes 358 | -------------------------------------------------------------------------------- /test.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "greentea-test", 3 | "tree": { 4 | "$className": "DataModel", 5 | "ServerScriptService": { 6 | "$className": "ServerScriptService", 7 | "test": { 8 | "$path": "test" 9 | }, 10 | "DevPackages": { 11 | "$path": "DevPackages" 12 | }, 13 | "GreenTea": { 14 | "$path": "src" 15 | } 16 | } 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /test/GreenTea.spec.luau: -------------------------------------------------------------------------------- 1 | local JestGlobals = require(script.Parent.Parent.DevPackages.JestGlobals) 2 | local expectRaw = JestGlobals.expect 3 | local describe = JestGlobals.describe 4 | local it = JestGlobals.it 5 | 6 | local expect: typeof(expectRaw) = function(input: any): any 7 | -- remove 2nd+ args which would otherwise be implicitly passed 8 | return expectRaw(input) 9 | end :: any 10 | 11 | local gt: any = require(script.Parent.Parent.GreenTea) 12 | 13 | local anyValuesTable: { any } = { 14 | { value = "hello", name = "string 'hello'" }, 15 | { value = 5, name = "number 5" }, 16 | { value = true, name = "boolean true" }, 17 | { value = false, name = "boolean false" }, 18 | { value = nil, name = "nil" }, 19 | { value = {}, name = "empty table" }, 20 | { value = { foo = "bar" }, name = "foobar table" }, 21 | { value = Instance.new("Folder"), name = "Folder Instance" }, 22 | { value = CFrame.new(), name = "CFrame" }, 23 | { value = newproxy(true), name = "newproxy(true)" }, 24 | { value = function() end, name = "function" }, 25 | } 26 | 27 | local function anyValues(): () -> (string, any) 28 | return coroutine.wrap(function() 29 | for index, value in anyValuesTable do 30 | coroutine.yield(value.name, value.value) 31 | end 32 | end) 33 | end 34 | 35 | local function oneNil(): () -> (any, nil) 36 | return coroutine.wrap(function() 37 | coroutine.yield(1, nil) 38 | end) 39 | end 40 | 41 | describe("gt.isGtType", function() 42 | it("should return true for GreenTea Types and false otherwise", function() 43 | expect(gt.isGtType(gt.string())).toBe(true) 44 | 45 | expect(gt.isGtType({})).toBe(false) 46 | expect(gt.isGtType(nil)).toBe(false) 47 | end) 48 | end) 49 | 50 | describe("gt.any", function() 51 | for name, value in anyValues() do 52 | if value == nil then 53 | it(`should match value {name}`, function() 54 | expect(gt.any({ allowNil = true }):matches(value)).toBe(true) 55 | expect(gt.any({ allowNil = false }):matches(value)).toBe(false) 56 | expect(gt.any():matches(value)).toBe(false) 57 | end) 58 | continue 59 | end 60 | it(`should match value {name}`, function() 61 | expect(gt.any():matches(value)).toBe(true) 62 | end) 63 | end 64 | 65 | it("should format as 'any'", function() 66 | expect(gt.any():format()).toBe("any") 67 | end) 68 | 69 | it("should have a kind of 'any'", function() 70 | expect(gt.any().kind).toBe("any") 71 | end) 72 | end) 73 | 74 | describe("gt.unknown", function() 75 | for name, value in anyValues() do 76 | if value == nil then 77 | it(`should match value {name}`, function() 78 | expect(gt.unknown({ allowNil = true }):matches(value)).toBe(true) 79 | expect(gt.unknown({ allowNil = false }):matches(value)).toBe(false) 80 | expect(gt.unknown():matches(value)).toBe(false) 81 | end) 82 | continue 83 | end 84 | it(`should match value {name}`, function() 85 | expect(gt.unknown():matches(value)).toBe(true) 86 | end) 87 | end 88 | 89 | it("should format as 'unknown'", function() 90 | expect(gt.unknown():format()).toBe("unknown") 91 | end) 92 | 93 | it("should have a kind of 'unknown'", function() 94 | expect(gt.unknown().kind).toBe("unknown") 95 | end) 96 | end) 97 | 98 | describe("gt.never", function() 99 | for name, value in anyValues() do 100 | it(`should not match value {name}`, function() 101 | expect(gt.never():matches(value)).toBe(false) 102 | end) 103 | end 104 | 105 | it("should format as 'never'", function() 106 | expect(gt.never():format()).toBe("never") 107 | end) 108 | 109 | it("should have a kind of 'never'", function() 110 | expect(gt.never().kind).toBe("never") 111 | end) 112 | end) 113 | 114 | local function basicTest(options: { 115 | kind: string, 116 | fn: () -> any, 117 | alternatives: { [string]: any }?, 118 | values: { any } | () -> (any, any), 119 | formatsAs: string?, 120 | describeName: string?, 121 | skipBasicTypeCheck: boolean?, 122 | }) 123 | assert(typeof(options.fn) == "function", "not fn\n" .. debug.traceback()) 124 | assert(typeof(options.fn()) == "table", "not table\n" .. debug.traceback()) 125 | 126 | describe(options.describeName or `gt.{options.kind}`, function() 127 | for _, value in options.values :: any do 128 | it(`should match {value}`, function() 129 | expect(options.fn():matches(value)).toBe(true) 130 | end) 131 | end 132 | it("should not match other types", function() 133 | for name, value in anyValues() do 134 | if typeof(value) ~= options.kind then 135 | expect(options.fn():matches(value)).toBe(false) 136 | end 137 | end 138 | end) 139 | 140 | for name, value in options.alternatives or {} :: typeof(assert(options.alternatives)) do 141 | it(`should be the same as {name}`, function() 142 | expect(options.fn).toBe(value) 143 | end) 144 | end 145 | 146 | it(`should format as '{options.formatsAs or options.kind}'`, function() 147 | expect(options.fn():format()).toBe(options.formatsAs or options.kind) 148 | end) 149 | 150 | if not options.skipBasicTypeCheck then 151 | it(`should have a kind of 'basic' and a .basic.typeof of '{options.kind}'`, function() 152 | local basic = options.fn() 153 | expect(basic.kind).toBe("basic") 154 | expect(basic.basic.typeof).toBe(options.kind) 155 | end) 156 | end 157 | end) 158 | end 159 | 160 | basicTest({ 161 | kind = "boolean", 162 | fn = gt.boolean, 163 | alternatives = { bool = gt.bool }, 164 | values = { true, false }, 165 | }) 166 | 167 | local function basicBasicTest(name, value) 168 | basicTest({ 169 | kind = name, 170 | fn = gt[name], 171 | values = { value }, 172 | }) 173 | 174 | basicTest({ 175 | kind = name, 176 | fn = function() 177 | return gt.isTypeof(name) 178 | end, 179 | values = { value }, 180 | describeName = `gt.basic({name})`, 181 | }) 182 | end 183 | 184 | basicBasicTest("Instance", Instance.new("Folder")) 185 | basicBasicTest("Vector2", Vector2.new()) 186 | basicBasicTest("Vector3", Vector3.new()) 187 | basicBasicTest("CFrame", CFrame.new()) 188 | basicBasicTest("Color3", Color3.new()) 189 | basicBasicTest("UDim", UDim.new()) 190 | basicBasicTest("UDim2", UDim2.new()) 191 | basicBasicTest("Ray", Ray.new(Vector3.new(0, 0, 0), Vector3.new(0, 0, 1))) 192 | basicBasicTest("Rect", Rect.new()) 193 | basicBasicTest("Region3", Region3.new(Vector3.new(0, 0, 0), Vector3.new(1, 1, 1))) 194 | basicBasicTest("BrickColor", BrickColor.new("Bright blue")) 195 | 196 | basicTest({ 197 | kind = "nil", 198 | fn = gt.none, 199 | values = oneNil(), 200 | describeName = "gt.none", 201 | }) 202 | 203 | local literalValues: { { value: any, formats: string } } = { 204 | { value = "hello", formats = '"hello"' }, 205 | { value = 5, formats = "literal<5>" }, 206 | { value = nil, formats = "literal" }, 207 | { value = true, formats = "literal" }, 208 | } 209 | 210 | describe("gt.literal", function() 211 | for _, value in literalValues do 212 | it(`should match {value.value} for {value.value}`, function() 213 | expect(gt.literal(value.value):matches(value.value)).toBe(true) 214 | expect(gt.literal(value.value):matches("anything else")).toBe(false) 215 | if value.value ~= nil then 216 | expect(gt.literal(value.value):matches(nil)).toBe(false) 217 | end 218 | end) 219 | it(`should format as '{value.formats}' for {value.value}`, function() 220 | expect(gt.literal(value.value):format()).toBe(value.formats) 221 | end) 222 | it(`should have a .literal.value of {value.value}`, function() 223 | expect(gt.literal(value.value).literal.value).toBe(value.value) 224 | end) 225 | end 226 | end) 227 | 228 | describe("gt.custom", function() 229 | it("should format as 'custom' if given a name", function() 230 | local namedCustom = gt.custom(function() end, "name here") 231 | expect(namedCustom:format()).toBe("custom") 232 | expect(namedCustom.custom.name).toBe("name here") 233 | end) 234 | it("should format as 'custom' if not given a name", function() 235 | local unnamedCustom = gt.custom(function() end) 236 | expect(unnamedCustom:format()).toMatch("custom<*.*%.spec:%d+>") 237 | expect(unnamedCustom.custom.name).never.toBe(nil) 238 | end) 239 | it("should format as 'custom' if not given a name and fn is named", function() 240 | local function fnName() end 241 | local unnamedCustom = gt.custom(fnName) 242 | expect(unnamedCustom:format()).toMatch("custom") 243 | expect(unnamedCustom.custom.name).never.toBe(nil) 244 | end) 245 | it("should call the custom typechecker with the value", function() 246 | local calls = 0 247 | local function typechecker(value) 248 | expect(value).toBe(5) 249 | calls += 1 250 | return true 251 | end 252 | 253 | local custom = gt.custom(typechecker) 254 | expect(custom:matches(5)).toBe(true) 255 | expect(calls).toBe(1) 256 | end) 257 | it("should fail if the typechecker fails (with message)", function() 258 | local custom = gt.custom(function(value) 259 | return false, "nope" 260 | end) 261 | local ok: boolean, cause: gt.Cause = custom:matches(5) 262 | expect(ok).toBe(false) 263 | expect(cause:formatErr()).toMatch("nope") 264 | end) 265 | 266 | it("should have a kind of 'custom'", function() 267 | local custom = gt.custom(function() end) 268 | expect(custom.kind).toBe("custom") 269 | end) 270 | end) 271 | 272 | describe("gt.isA indexed", function() 273 | it("should match the typename of the indexer and classes inherited from it", function() 274 | expect(gt.isA.Part():matches(Instance.new("Part"))).toBe(true) 275 | expect(gt.isA.BasePart():matches(Instance.new("Part"))).toBe(true) 276 | expect(gt.isA.Part():matches(Instance.new("Folder"))).toBe(false) 277 | expect(gt.isA.Part():matches("hi")).toBe(false) 278 | expect(gt.isA.Part():matches(nil)).toBe(false) 279 | end) 280 | 281 | it("should format as 'classname'", function() 282 | expect(gt.isA.Part():format()).toBe("Part") 283 | end) 284 | end) 285 | 286 | describe("gt.isA called", function() 287 | it("should match the typename of the indexer and classes inherited from it", function() 288 | expect(gt.isA("Part"):matches(Instance.new("Part"))).toBe(true) 289 | expect(gt.isA("BasePart"):matches(Instance.new("Part"))).toBe(true) 290 | expect(gt.isA("Part"):matches(Instance.new("Folder"))).toBe(false) 291 | expect(gt.isA("Part"):matches("hi")).toBe(false) 292 | expect(gt.isA("Part"):matches(nil)).toBe(false) 293 | end) 294 | 295 | it("should format as 'classname'", function() 296 | expect(gt.isA("Part"):format()).toBe("Part") 297 | end) 298 | end) 299 | 300 | basicTest({ 301 | kind = "number", 302 | fn = gt.number, 303 | values = { 304 | 5, 305 | 5.5, 306 | -5.5, 307 | 0, 308 | math.huge, 309 | -math.huge, 310 | }, 311 | skipBasicTypeCheck = true, 312 | }) 313 | 314 | describe("gt.number limits", function() 315 | it("should reject NaN by default", function() 316 | expect(gt.number():matches(0 / 0)).toBe(false) 317 | end) 318 | it("should allow NaN only if .nan = true", function() 319 | expect(gt.number():matches(0 / 0)).toBe(false) 320 | expect(gt.number({ nan = false }):matches(0 / 0)).toBe(false) 321 | expect(gt.number({ nan = true }):matches(0 / 0)).toBe(true) 322 | end) 323 | it("should reject non-integers when the integer flag is set", function() 324 | expect(gt.number():matches(5.5)).toBe(true) 325 | expect(gt.number({ integer = false }):matches(5.5)).toBe(true) 326 | expect(gt.number({ integer = true }):matches(5.5)).toBe(false) 327 | expect(gt.number({ integer = true }):matches(5)).toBe(true) 328 | end) 329 | it("should respect range when set", function() 330 | expect(gt.number():matches(5)).toBe(true) 331 | expect(gt.number({ range = "[0, 10]" }):matches(5)).toBe(true) 332 | expect(gt.number({ range = "[0, 10]" }):matches(10)).toBe(true) 333 | expect(gt.number({ range = "[0, 10]" }):matches(0)).toBe(true) 334 | expect(gt.number({ range = "[0, 10]" }):matches(-1)).toBe(false) 335 | expect(gt.number({ range = "[0, 10]" }):matches(11)).toBe(false) 336 | expect(gt.number({ range = "[0, 10]" }):matches(-math.huge)).toBe(false) 337 | expect(gt.number({ range = "[0, 10]" }):matches(math.huge)).toBe(false) 338 | 339 | expect(gt.number({ range = "(0, 10)" }):matches(5)).toBe(true) 340 | expect(gt.number({ range = "(0, 10)" }):matches(10)).toBe(false) 341 | expect(gt.number({ range = "(0, 10)" }):matches(0)).toBe(false) 342 | expect(gt.number({ range = "(0, 10)" }):matches(-1)).toBe(false) 343 | expect(gt.number({ range = "(0, 10)" }):matches(11)).toBe(false) 344 | expect(gt.number({ range = "(0, 10)" }):matches(-math.huge)).toBe(false) 345 | expect(gt.number({ range = "(0, 10)" }):matches(math.huge)).toBe(false) 346 | end) 347 | 348 | it("should format with limits if set", function() 349 | expect(gt.number():format()).toBe("number") 350 | 351 | expect(gt.number({ range = "[0, 10]" }):format()).toBe("number") 352 | expect(gt.number({ range = "(0, 10)" }):format()).toBe("number") 353 | expect(gt.number({ integer = true }):format()).toBe("number") 354 | expect(gt.number({ nan = true }):format()).toBe("number") 355 | 356 | expect(gt.number({ range = "(0, 10)", integer = true, nan = true }):format()).toBe( 357 | "number" 358 | ) 359 | end) 360 | end) 361 | 362 | basicTest({ 363 | kind = "string", 364 | fn = gt.string, 365 | values = { 366 | "hi", 367 | "hello world", 368 | "", 369 | }, 370 | skipBasicTypeCheck = true, 371 | }) 372 | 373 | local utf8Strings: { [string]: string } = { 374 | ["Valid ASCII"] = "a", 375 | ["Valid 2 Octet Sequence"] = "\xc3\xb1", 376 | ["Invalid 2 Octet Sequence"] = "\xc3\x28", 377 | ["Invalid Sequence Identifier"] = "\xa0\xa1", 378 | ["Valid 3 Octet Sequence"] = "\xe2\x82\xa1", 379 | ["Invalid 3 Octet Sequence (in 2nd Octet)"] = "\xe2\x28\xa1", 380 | ["Invalid 3 Octet Sequence (in 3rd Octet)"] = "\xe2\x82\x28", 381 | ["Valid 4 Octet Sequence"] = "\xf0\x90\x8c\xbc", 382 | ["Invalid 4 Octet Sequence (in 2nd Octet)"] = "\xf0\x28\x8c\xbc", 383 | ["Invalid 4 Octet Sequence (in 3rd Octet)"] = "\xf0\x90\x28\xbc", 384 | ["Invalid 4 Octet Sequence (in 4th Octet)"] = "\xf0\x28\x8c\x28", 385 | ["Invalid Unicode: Valid 5 Octet Sequence (but not Unicode!)"] = "\xf8\xa1\xa1\xa1\xa1", 386 | ["Invalid Unicode: Valid 6 Octet Sequence (but not Unicode!)"] = "\xfc\xa1\xa1\xa1\xa1\xa1", 387 | } 388 | 389 | describe("gt.string limits", function() 390 | describe("should respect unicode flag", function() 391 | for name, value in utf8Strings do 392 | if name:find("^Valid") then 393 | it(`should match {name}`, function() 394 | expect(gt.string():matches(value)).toBe(true) 395 | expect(gt.string({ unicode = true }):matches(value)).toBe(true) 396 | end) 397 | else 398 | it(`should not match {name} when unicode = true`, function() 399 | expect(gt.string():matches(value)).toBe(true) 400 | expect(gt.string({ unicode = true }):matches(value)).toBe(false) 401 | end) 402 | end 403 | end 404 | end) 405 | 406 | it("should respect bytes value", function() 407 | expect(gt.string({ bytes = "[0, 10]" }):matches("hello")).toBe(true) 408 | expect(gt.string({ bytes = "[0, 2]" }):matches("hello")).toBe(false) 409 | expect(gt.string({ bytes = "[0, 5]" }):matches("hello")).toBe(true) 410 | expect(gt.string({ bytes = "[0, 5)" }):matches("hello")).toBe(false) 411 | expect(gt.string({ bytes = "[0, 5]" }):matches("")).toBe(true) 412 | expect(gt.string({ bytes = "(0, 5]" }):matches("")).toBe(false) 413 | end) 414 | 415 | it("should respect graphemes value", function() 416 | expect(gt.string({ graphemes = "[0, 10]", bytes = "[0, 100]" }):matches("hello")).toBe(true) 417 | expect(gt.string({ graphemes = "[0, 2]", bytes = "[0, 100]" }):matches("hello")).toBe(false) 418 | expect(gt.string({ graphemes = "[0, 5]", bytes = "[0, 100]" }):matches("hello")).toBe(true) 419 | expect(gt.string({ graphemes = "[0, 5)", bytes = "[0, 100]" }):matches("hello")).toBe(false) 420 | expect(gt.string({ graphemes = "[0, 5]", bytes = "[0, 100]" }):matches("")).toBe(true) 421 | expect(gt.string({ graphemes = "(0, 5]", bytes = "[0, 100]" }):matches("")).toBe(false) 422 | 423 | -- One of the first examples from https://kermitproject.org/utf8.html#glass 424 | -- graphemes: 36 425 | -- bytes : 41 426 | local graphemeText = "kācaṃ śaknomyattum; nopahinasti mām." 427 | 428 | expect(gt.string({ graphemes = "[0, 36]", bytes = "[0, 41]" }):matches(graphemeText)).toBe(true) 429 | expect(gt.string({ graphemes = "[0, 36)", bytes = "[0, 41]" }):matches(graphemeText)).toBe(false) 430 | expect(gt.string({ graphemes = "[0, 36]", bytes = "[0, 41)" }):matches(graphemeText)).toBe(false) 431 | expect(gt.string({ graphemes = "[0, 30]", bytes = "[0, 40]" }):matches(graphemeText)).toBe(false) 432 | end) 433 | 434 | it("should error if graphemes is specified but bytes is not", function() 435 | expect(function() 436 | gt.string({ graphemes = "[0, 10]" }) 437 | end).toThrow() 438 | end) 439 | 440 | it("should respect pattern value", function() 441 | expect(gt.string({ pattern = "^[a-z]+$" }):matches("hello")).toBe(true) 442 | expect(gt.string({ pattern = "^[a-z]+$" }):matches("hello world")).toBe(false) 443 | expect(gt.string({ pattern = "^[a-z]*$" }):matches("")).toBe(true) 444 | expect(gt.string({ pattern = "^[a-z]+$" }):matches("123")).toBe(false) 445 | expect(gt.string({ pattern = "^hello" }):matches("hello world")).toBe(true) 446 | expect(gt.string({ pattern = "^hello" }):matches("Hello world")).toBe(false) 447 | end) 448 | 449 | it("should format with limits if set", function() 450 | expect(gt.string():format()).toBe("string") 451 | 452 | expect(gt.string({ unicode = true }):format()).toBe("string") 453 | expect(gt.string({ graphemes = "[0, 10]", bytes = "[0, 10]" }):format()).toBe( 454 | "string" 455 | ) 456 | expect(gt.string({ bytes = "[0, 10]" }):format()).toBe("string") 457 | expect(gt.string({ pattern = "[a-z]+" }):format()).toBe('string') 458 | expect(gt.string({ pattern = "[a-z]+\n" }):format()).toBe('string') 459 | 460 | expect(gt.string({ unicode = true, graphemes = "[5, 20]", bytes = "[0, 10]", pattern = "[a-z]+" }):format()).toBe( 461 | 'string' 462 | ) 463 | end) 464 | end) 465 | 466 | basicTest({ 467 | kind = "thread", 468 | fn = gt.thread, 469 | values = { 470 | coroutine.create(function() end), 471 | task.spawn(function() end), 472 | }, 473 | skipBasicTypeCheck = true, 474 | }) 475 | 476 | describe("gt.thread options", function() 477 | -- selene: allow(multiple_statements) 478 | -- stylua: ignore 479 | it("should handle statuses", function() 480 | local running = coroutine.running() 481 | local dead = coroutine.create(function() end); coroutine.resume(dead) 482 | local suspended = coroutine.create(function() coroutine.yield() end) 483 | 484 | expect(gt.thread({ status = "running" }):matches(running)).toBe(true) 485 | expect(gt.thread({ status = { "running" }}):matches(running)).toBe(true) 486 | expect(gt.thread({ status = "dead" }):matches(dead)).toBe(true) 487 | expect(gt.thread({ status = { "dead" }}):matches(dead)).toBe(true) 488 | expect(gt.thread({ status = "suspended" }):matches(suspended)).toBe(true) 489 | expect(gt.thread({ status = { "suspended" }}):matches(suspended)).toBe(true) 490 | 491 | expect(gt.thread({ status = "running" }):matches(suspended)).toBe(false) 492 | expect(gt.thread({ status = { "running" }}):matches(suspended)).toBe(false) 493 | 494 | expect(gt.thread({ status = { "running", "suspended" } }):matches(running)).toBe(true) 495 | expect(gt.thread({ status = { "running", "suspended" } }):matches(suspended)).toBe(true) 496 | expect(gt.thread({ status = { "running", "suspended" } }):matches(dead)).toBe(false) 497 | end) 498 | 499 | it("should format as 'thread' or 'thread'", function() 500 | expect(gt.thread():format()).toBe("thread") 501 | expect(gt.thread({ status = "running" }):format()).toBe("thread") 502 | expect(gt.thread({ status = { "running" } }):format()).toBe("thread") 503 | expect(gt.thread({ status = { "running", "suspended" } }):format()).toBe( 504 | "thread" 505 | ) 506 | 507 | -- should format alphabetically: 508 | expect(gt.thread({ status = { "suspended", "running" } }):format()).toBe( 509 | "thread" 510 | ) 511 | end) 512 | 513 | it("should not allow invalid statuses", function() 514 | expect(function() 515 | gt.thread({ status = "invalid" }) 516 | end).toThrow() 517 | end) 518 | end) 519 | 520 | describe("gt.vararg", function() 521 | it("should match zero or more of the given type only", function() 522 | expect(gt.vararg(gt.string()):matches()).toBe(true) 523 | expect(gt.vararg(gt.string()):matches("hello")).toBe(true) 524 | expect(gt.vararg(gt.string()):matches("hello", "hello2")).toBe(true) 525 | expect(gt.vararg(gt.string()):matches(5)).toBe(false) 526 | expect(gt.vararg(gt.string()):matches("hello", 5)).toBe(false) 527 | end) 528 | it("should ignore nil values on the end of the match", function() 529 | expect(gt.vararg(gt.string()):matches("hello", nil)).toBe(true) 530 | expect(gt.vararg(gt.string()):matches("hello", "hello2", nil)).toBe(true) 531 | expect(gt.vararg(gt.string()):matches("hello", nil, "hello3")).toBe(false) 532 | end) 533 | it("should format as '...type'", function() 534 | expect(gt.vararg(gt.string()):format()).toBe("...string") 535 | expect(gt.vararg(gt.number()):format()).toBe("...number") 536 | end) 537 | end) 538 | 539 | describe("gt.tuple", function() 540 | it("should match the given types", function() 541 | expect(gt.tuple(gt.string(), gt.number()):matches("hello", 5)).toBe(true) 542 | expect(gt.tuple(gt.string(), gt.number()):matches("hello", "hello2")).toBe(false) 543 | expect(gt.tuple(gt.string(), gt.number()):matches("hello", 5, "extra")).toBe(false) 544 | expect(gt.tuple(gt.string(), gt.number()):matches("hello")).toBe(false) 545 | expect(gt.tuple(gt.string(), gt.opt(gt.number())):matches("hello")).toBe(true) 546 | expect(gt.tuple():matches()).toBe(true) 547 | expect(gt.tuple():matches("hello")).toBe(false) 548 | 549 | expect(gt.tuple(gt.string(), gt.number()):matches("hello", 5, nil, nil, nil)).toBe(true) 550 | end) 551 | it("should match the given types with vararg", function() 552 | expect(gt.tuple(gt.string(), gt.vararg(gt.number())):matches("hello", 5)).toBe(true) 553 | expect(gt.tuple(gt.string(), gt.vararg(gt.number())):matches("hello", "hello2")).toBe(false) 554 | expect(gt.tuple(gt.string(), gt.vararg(gt.number())):matches("hello", 5, "extra")).toBe(false) 555 | expect(gt.tuple(gt.string(), gt.vararg(gt.number())):matches("hello")).toBe(true) 556 | expect(gt.tuple(gt.string(), gt.vararg(gt.number())):matches("hello", 5, 6, 7, 8)).toBe(true) 557 | 558 | expect(gt.tuple(gt.string(), gt.vararg(gt.number())):matches("hello", 5, 6, 7, 8, nil, nil, nil)).toBe(true) 559 | end) 560 | it("should simplify non-ending tuples or varargs", function() 561 | expect(gt.tuple(gt.vararg(gt.string()), gt.number()):matches("hello", 5)).toBe(true) 562 | expect(gt.tuple(gt.tuple(gt.string()), gt.number()):matches("hello", 5)).toBe(true) 563 | end) 564 | it("should expand ending tuples or varargs", function() 565 | expect(gt.tuple(gt.string(), gt.tuple(gt.number())):matches("hello", 5)).toBe(true) 566 | expect(gt.tuple(gt.string(), gt.tuple(gt.number(), gt.string())):matches("hello", 5, "hello2")).toBe(true) 567 | expect(gt.tuple(gt.string(), gt.vararg(gt.number())):matches("hello", 5)).toBe(true) 568 | expect(gt.tuple(gt.string(), gt.vararg(gt.number())):matches("hello", 5, 6)).toBe(true) 569 | end) 570 | 571 | it("should format correctly", function() 572 | expect(gt.tuple(gt.string(), gt.number()):format()).toBe("(string, number)") 573 | expect(gt.tuple(gt.string(), gt.vararg(gt.number())):format()).toBe("(string, ...number)") 574 | expect(gt.tuple(gt.string(), gt.tuple(gt.number())):format()).toBe("(string, number)") 575 | expect(gt.tuple(gt.string(), gt.tuple(gt.number(), gt.string())):format()).toBe("(string, number, string)") 576 | expect(gt.tuple(gt.tuple(gt.string(), gt.number())):format()).toBe("(string, number)") 577 | expect(gt.tuple(gt.tuple(gt.string())):format()).toBe("(string)") 578 | expect(gt.tuple(gt.tuple()):format()).toBe("()") 579 | end) 580 | end) 581 | 582 | describe("gt.fn", function() 583 | it("should match any function", function() 584 | -- It's not actually possible to inspect function args or values, so we just don't. 585 | expect(gt.fn(gt.args(), gt.returns()):matches(function() end)).toBe(true) 586 | expect(gt.fn(gt.args(), gt.returns()):matches(function(a, b, c) 587 | return 5, 6, 7 588 | end)).toBe(true) 589 | 590 | expect(gt.fn(gt.args(gt.string()), gt.returns(gt.number(), gt.string())):matches(function() end)).toBe(true) 591 | expect(gt.fn(gt.args(gt.string()), gt.returns(gt.number(), gt.string())):matches(function() end)).toBe(true) 592 | end) 593 | 594 | it("should format correctly", function() 595 | expect(gt.fn(gt.args(), gt.returns()):format()).toBe("() -> ()") 596 | expect(gt.fn(gt.args(gt.string()), gt.returns(gt.number())):format()).toBe("(string) -> number") 597 | expect(gt.fn(gt.args(gt.string()), gt.returns(gt.number(), gt.string())):format()).toBe( 598 | "(string) -> (number, string)" 599 | ) 600 | expect(gt.fn(gt.args(gt.vararg(gt.string())), gt.returns(gt.string(), gt.vararg(gt.number()))):format()).toBe( 601 | "(...string) -> (string, ...number)" 602 | ) 603 | 604 | expect(gt.anyfn():format()).toBe("(...any) -> ...any") 605 | end) 606 | end) 607 | 608 | describe("gt.table", function() 609 | it("should match basic tables", function() 610 | expect(gt.table({}):matches("not a table")).toBe(false) 611 | expect(gt.table({}):matches({})).toBe(true) 612 | expect(gt.table({ a = gt.number() }):matches({ a = 5 })).toBe(true) 613 | expect(gt.table({ a = gt.number() }):matches({ a = "hello" })).toBe(false) 614 | expect(gt.table({ a = gt.number(), b = gt.string() }):matches({ a = 5, b = "hello" })).toBe(true) 615 | expect(gt.table({ a = gt.number(), b = gt.string() }):matches({ a = 5 })).toBe(false) 616 | end) 617 | it("should match even if there are extra keys", function() 618 | expect(gt.table({ a = gt.number() }):matches({ a = 5, b = "hello" })).toBe(true) 619 | end) 620 | it("should match with an indexer for extra values", function() 621 | expect(gt.table({ a = gt.number(), [gt.string()] = gt.string() }):matches({ a = 5, b = "hello", c = "hi" })).toBe( 622 | true 623 | ) 624 | expect(gt.table({ a = gt.number(), [gt.string()] = gt.string() }):matches({ a = "hey", b = "hello", c = "hi" })).toBe( 625 | false 626 | ) 627 | end) 628 | it("should only allow one indexer", function() 629 | expect(function() 630 | gt.table({ 631 | [gt.string()] = gt.string(), 632 | [gt.string()] = gt.string(), 633 | }) 634 | end).toThrow() 635 | expect(function() 636 | gt.table({ 637 | [gt.string()] = gt.string(), 638 | [gt.number()] = gt.string(), 639 | }) 640 | end).toThrow() 641 | end) 642 | it("should only allow string keys (unless raw = true)", function() 643 | expect(function() 644 | gt.table({ 645 | [{}] = gt.string(), 646 | }) 647 | end).toThrow() 648 | 649 | expect(function() 650 | gt.table({ 651 | [{}] = gt.string(), 652 | }, { raw = true }) 653 | end).never.toThrow() 654 | end) 655 | it("should check for contiguous array when array = true", function() 656 | expect(gt.table({ gt.number() }, { array = true }):matches({ 1, 2, 3 })).toBe(true) 657 | expect(function() 658 | gt.table({}, { array = true }):matches({ 1, 2, 3, 4 }) 659 | end).toThrow() 660 | end) 661 | it("should limit indexer items when count is set", function() 662 | expect(gt.table({ gt.number() }, { count = "[1,2]" }):matches({ 5 })).toBe(true) 663 | expect(gt.table({ gt.number() }, { count = "[1,2]" }):matches({ 5, 6, 7 })).toBe(false) 664 | expect(gt.table({ gt.number() }, { count = "[1,2]" }):matches({})).toBe(false) 665 | expect( 666 | gt.table( 667 | { [gt.number()] = gt.number(), a = gt.number(), b = gt.number() }, 668 | { count = "[1,2]", a = 5, b = 5 } 669 | ) 670 | :matches({ [1] = 5, a = 5, b = 5 }) 671 | ).toBe(true) 672 | end) 673 | it("should respect __index and __iter", function() 674 | local iterT = { a = 1, b = 2, c = 3 } 675 | local t = setmetatable({}, { 676 | __iter = function() 677 | return pairs(iterT) 678 | end, 679 | __index = function(_, key) 680 | return iterT[key] 681 | end, 682 | }) 683 | 684 | expect(gt.table({ a = gt.number(), b = gt.number(), c = gt.number() }):matches(t)).toBe(true) 685 | expect(gt.table({ [gt.string()] = gt.number() }):matches(t)).toBe(true) 686 | expect(gt.table({ [gt.string()] = gt.string() }):matches(t)).toBe(false) 687 | end) 688 | it("should not care about metatable", function() 689 | local meta = {} 690 | local withMeta = setmetatable({}, meta) 691 | expect(gt.table(withMeta):matches(setmetatable({}, meta))).toBe(true) 692 | expect(gt.table(withMeta):matches(setmetatable({}, {}))).toBe(true) 693 | expect(gt.table(withMeta):matches({})).toBe(true) 694 | end) 695 | 696 | it("should format correctly", function() 697 | expect(gt.table({}):format()).toBe("{}") 698 | expect(gt.table({ a = gt.number() }):format()).toBe("{ a: number }") 699 | expect(gt.table({ a = gt.number(), b = gt.string() }):format()).toBe("{ a: number, b: string }") 700 | 701 | expect(gt.table({ [gt.string()] = gt.string() }):format()).toBe("{ [string]: string }") 702 | 703 | expect(gt.table({ [gt.string()] = gt.string(), a = gt.number() }):format()).toBe( 704 | "{ [string]: string, a: number }" 705 | ) 706 | 707 | local meta = {} 708 | local withMeta = setmetatable({}, meta) 709 | expect(gt.table(withMeta):format()).toBe("{}") 710 | end) 711 | end) 712 | 713 | describe("gt.union", function() 714 | it("should match only any of the given types", function() 715 | expect(gt.union(gt.number(), gt.string()):matches(5)).toBe(true) 716 | expect(gt.union(gt.number(), gt.string()):matches("hello")).toBe(true) 717 | expect(gt.union(gt.number(), gt.string()):matches(true)).toBe(false) 718 | end) 719 | it("should disallow implicit nil", function() 720 | expect(function() 721 | gt.union(gt.number(), nil, gt.string()) 722 | end).toThrow() 723 | end) 724 | 725 | it("should format correctly", function() 726 | expect(gt.union(gt.number(), gt.string()):format()).toBe("number | string") 727 | expect(gt.union(gt.number(), gt.string(), gt.none()):format()).toBe("number | string | nil") 728 | expect(gt.union(gt.number(), gt.none()):format()).toBe("number?") 729 | expect(gt.union(gt.number()):format()).toBe("number") 730 | expect(gt.union(gt.none()):format()).toBe("nil") 731 | expect(function() 732 | gt.union() 733 | end).toThrow() 734 | end) 735 | end) 736 | 737 | describe("gt.intersection", function() 738 | it("should match all of the given types", function() 739 | expect(gt.intersection(gt.number(), gt.string()):matches(5)).toBe(false) 740 | expect(gt.intersection(gt.number(), gt.string()):matches("hello")).toBe(false) 741 | expect(gt.intersection(gt.number(), gt.any()):matches(5)).toBe(true) 742 | expect( 743 | gt.intersection(gt.table({ a = gt.number() }, gt.table({ b = gt.string() }))) 744 | :matches({ a = 5, b = "hello" }) 745 | ).toBe(true) 746 | expect(gt.intersection(gt.table({ a = gt.number() }), gt.table({ b = gt.string() })):matches({ a = 5 })).toBe( 747 | false 748 | ) 749 | end) 750 | 751 | it("should disallow implicit nil", function() 752 | expect(function() 753 | gt.intersection(gt.number(), nil, gt.string()) 754 | end).toThrow() 755 | end) 756 | 757 | it("should format correctly", function() 758 | expect(gt.intersection(gt.number(), gt.string()):format()).toBe("number & string") 759 | expect(gt.intersection(gt.number(), gt.string(), gt.none()):format()).toBe("number & string & nil") 760 | expect(gt.intersection(gt.number()):format()).toBe("number") 761 | expect(gt.intersection(gt.none()):format()).toBe("nil") 762 | 763 | expect(function() 764 | gt.intersection() 765 | end).toThrow() 766 | end) 767 | end) 768 | 769 | describe("Type", function() 770 | describe("Type:assert", function() 771 | local RegExp = require(script.Parent.Parent.DevPackages.RegExp) 772 | 773 | it("should assert", function() 774 | expect(function() 775 | gt.number():assert(5) 776 | end).never.toThrow() 777 | expect(function() 778 | gt.number():assert("hello") 779 | end).toThrow(RegExp("expected number, got string")) 780 | end) 781 | 782 | it("should return the value", function() 783 | expect(gt.number():assert(5)).toBe(5) 784 | end) 785 | end) 786 | end) 787 | 788 | describe("failure to call detection", function() 789 | it("should throw if non-called function is passed in to typeof", function() 790 | for key, value in gt do 791 | expect(function() 792 | gt.typeof(value) 793 | print("failed to throw for:", typeof(key), key, value) 794 | end).toThrow() 795 | end 796 | for key, value in pairs(gt.isA) do 797 | expect(function() 798 | gt.typeof(value) 799 | print("failed to throw for:", typeof(key), key, value) 800 | end).toThrow() 801 | end 802 | for key, value in pairs(gt.t) do 803 | expect(function() 804 | gt.typeof(value) 805 | print("failed to throw for:", typeof(key), key, value) 806 | end).toThrow() 807 | end 808 | end) 809 | end) 810 | 811 | describe("gt.wrapFn (and variants)", function() 812 | local fnType = gt.fn(gt.args(gt.string()), gt.returns(gt.number())) 813 | local fnImpl = function(input: string) 814 | return 5 815 | end 816 | local fn = gt.wrapFn(fnType, fnImpl) 817 | local fnArgs = gt.wrapFnArgs(fnType, fnImpl) 818 | local fnReturns = gt.wrapFnReturns(fnType, fnImpl) 819 | 820 | local fnBadImpl = function(input: string) 821 | return "hello" 822 | end 823 | local fnBad = gt.wrapFn(fnType, fnBadImpl) 824 | local fnBadArgs = gt.wrapFnArgs(fnType, fnBadImpl) 825 | local fnBadReturns = gt.wrapFnReturns(fnType, fnBadImpl) 826 | 827 | it("should succeed if args and returns match", function() 828 | expect(fn("hello")).toBe(5) 829 | expect(fnArgs("hello")).toBe(5) 830 | expect(fnReturns("hello")).toBe(5) 831 | end) 832 | 833 | it("should fail if args don't match (except wrapFnReturns)", function() 834 | expect(function() 835 | fn(5) 836 | end).toThrow() 837 | expect(function() 838 | fnArgs(5) 839 | end).toThrow() 840 | expect(function() 841 | fnReturns(5) 842 | end).never.toThrow() 843 | 844 | expect(function() 845 | fn("hello", "hello2") 846 | end).toThrow() 847 | expect(function() 848 | fnArgs("hello", "hello2") 849 | end).toThrow() 850 | expect(function() 851 | fnReturns("hello", "hello2") 852 | end).never.toThrow() 853 | 854 | expect(function() 855 | fn() 856 | end).toThrow() 857 | expect(function() 858 | fnArgs() 859 | end).toThrow() 860 | expect(function() 861 | fnReturns() 862 | end).never.toThrow() 863 | end) 864 | 865 | it("should fail if returns don't match (except wrapFnArgs)", function() 866 | expect(function() 867 | fnBad("hello") 868 | end).toThrow() 869 | expect(function() 870 | fnBadArgs("hello") 871 | end).never.toThrow() 872 | expect(function() 873 | fnBadReturns("hello") 874 | end).toThrow() 875 | end) 876 | end) 877 | -------------------------------------------------------------------------------- /test/bench/lineLengthOf.bench.luau: -------------------------------------------------------------------------------- 1 | --[[ 2 | This file is for use by Benchmarker (https://boatbomber.itch.io/benchmarker) 3 | 4 | |WARNING| THIS RUNS IN YOUR REAL ENVIRONMENT. |WARNING| 5 | --]] 6 | 7 | return { 8 | ParameterGenerator = function() 9 | local lines = {} 10 | for line = 1, math.random(0, 1000) do 11 | local size = math.random(0, 1000) 12 | local line = {} 13 | for i = 1, size do 14 | table.insert(line, string.char(math.random(97, 122))) 15 | end 16 | table.insert(lines, table.concat(line)) 17 | end 18 | 19 | return table.concat(lines, "\n") 20 | end, 21 | 22 | Functions = { 23 | ["gmatch"] = function(Profiler, str) 24 | local maxLength = 0 25 | for line in string.gmatch(str, "[^\n]+") do 26 | if #line > maxLength then 27 | maxLength = #line 28 | end 29 | end 30 | return maxLength 31 | end, 32 | 33 | ["split"] = function(Profiler, str) 34 | local maxLength = 0 35 | for _, line in string.split(str, "\n") do 36 | if #line > maxLength then 37 | maxLength = #line 38 | end 39 | end 40 | return maxLength 41 | end, 42 | 43 | ["find"] = function(Profiler, str) 44 | local maxLength = 0 45 | local lastIndex = 1 46 | while true do 47 | local start, finish = string.find(str, "[^\n]+", lastIndex) 48 | if not (start and finish) then 49 | break 50 | end 51 | 52 | local length = finish - start + 1 53 | if length > maxLength then 54 | maxLength = length 55 | end 56 | 57 | lastIndex = finish + 1 58 | end 59 | return maxLength 60 | end, 61 | }, 62 | } 63 | -------------------------------------------------------------------------------- /test/bench/spaceLengthOf.bench.luau: -------------------------------------------------------------------------------- 1 | --[[ 2 | This file is for use by Benchmarker (https://boatbomber.itch.io/benchmarker) 3 | 4 | |WARNING| THIS RUNS IN YOUR REAL ENVIRONMENT. |WARNING| 5 | --]] 6 | 7 | return { 8 | ParameterGenerator = function() 9 | local lines = {} 10 | for line = 1, math.random(0, 1000) do 11 | local size = math.random(0, 1000) 12 | local line = {} 13 | table.insert(line, string.rep(" ", math.random(0, 100))) 14 | for i = 1, size do 15 | table.insert(line, string.char(math.random(97, 122))) 16 | end 17 | table.insert(lines, table.concat(line)) 18 | end 19 | 20 | return table.concat(lines, "\n") 21 | end, 22 | 23 | Functions = { 24 | ["gmatch"] = function(Profiler, str) 25 | local minSpaces 26 | for line in string.gmatch(str, "[^\n]+") do 27 | local spaces = string.match(line, "^ *") :: string 28 | if not minSpaces or #spaces < minSpaces then 29 | minSpaces = #spaces 30 | end 31 | end 32 | 33 | return minSpaces or 0 34 | end, 35 | 36 | ["split"] = function(Profiler, str) 37 | local minSpaces 38 | for _, line in string.split(str, "\n") do 39 | local spaces = string.match(line, "^ *") :: string 40 | if not minSpaces or #spaces < minSpaces then 41 | minSpaces = #spaces 42 | end 43 | end 44 | return minSpaces or 0 45 | end, 46 | 47 | ["find"] = function(Profiler, str) 48 | str = "\n" .. str 49 | local minLength 50 | local lastIndex = 1 51 | while true do 52 | local start, finish = string.find(str, "\n( *)", lastIndex) 53 | if not (start and finish) then 54 | break 55 | end 56 | 57 | local length = finish - start + 1 58 | if not minLength or length < minLength then 59 | minLength = length 60 | end 61 | 62 | lastIndex = finish + 1 63 | end 64 | return minLength or 0 65 | end, 66 | 67 | ["gmatch-find"] = function(Profiler, str) 68 | local minSpaces 69 | for line in string.gmatch(str, "[^\n]+") do 70 | local start, finish = string.find(line, "^ +") 71 | if start and finish then 72 | local length = finish - start + 1 73 | if not minSpaces or length < minSpaces then 74 | minSpaces = length 75 | end 76 | end 77 | end 78 | return minSpaces or 0 79 | end, 80 | 81 | ["split-find"] = function(Profiler, str) 82 | local minSpaces 83 | for _, line in string.split(str, "\n") do 84 | local start, finish = string.find(line, "^ +") 85 | if start and finish then 86 | local length = finish - start + 1 87 | if not minSpaces or length < minSpaces then 88 | minSpaces = length 89 | end 90 | end 91 | end 92 | return minSpaces or 0 93 | end, 94 | }, 95 | } 96 | -------------------------------------------------------------------------------- /test/init.server.luau: -------------------------------------------------------------------------------- 1 | local runCLI = require(script.Parent.DevPackages.Jest).runCLI 2 | 3 | local status, result = runCLI(script.Parent.GreenTea, { 4 | verbose = false, 5 | ci = false, 6 | }, { script }):awaitStatus() 7 | 8 | if status == "Rejected" then 9 | print(result) 10 | end 11 | 12 | -- local processServiceExists, ProcessService = pcall(function() 13 | -- return game:GetService("ProcessService") 14 | -- end) 15 | 16 | -- if status == "Resolved" and result.results.numFailedTestSuites == 0 and result.results.numFailedTests == 0 then 17 | -- if processServiceExists then 18 | -- ProcessService:ExitAsync(0) 19 | -- end 20 | -- end 21 | 22 | -- if processServiceExists then 23 | -- ProcessService:ExitAsync(1) 24 | -- end 25 | 26 | -- return nil 27 | -------------------------------------------------------------------------------- /test/jest.config.luau: -------------------------------------------------------------------------------- 1 | return { 2 | testMatch = { "**/*.spec" }, 3 | } 4 | -------------------------------------------------------------------------------- /test/tCompat.spec.luau: -------------------------------------------------------------------------------- 1 | --# selene: allow(deprecated) 2 | 3 | -- Used to check that tCompat is compatible with the t library. 4 | -- Taken from t's test suite. 5 | 6 | --[[ 7 | MIT License 8 | 9 | Copyright (c) 2018 Osyris 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | ]] 29 | 30 | local JestGlobals = require(script.Parent.Parent.DevPackages.JestGlobals) 31 | local describe = JestGlobals.describe 32 | local it = JestGlobals.it 33 | 34 | local t = require(script.Parent.Parent.GreenTea).t 35 | 36 | it("should support basic types", function() 37 | assert(t.any("")) 38 | assert(t.boolean(true)) 39 | assert(t.none(nil)) 40 | assert(t.number(1)) 41 | assert(t.string("foo")) 42 | assert(t.table({})) 43 | 44 | assert(not (t.any(nil))) 45 | assert(not (t.boolean("true"))) 46 | assert(not (t.none(1))) 47 | assert(not (t.number(true))) 48 | assert(not (t.string(true))) 49 | assert(not (t.table(82))) 50 | end) 51 | 52 | it("should support special number types", function() 53 | local maxTen = t.numberMax(10) 54 | local minTwo = t.numberMin(2) 55 | local maxTenEx = t.numberMaxExclusive(10) 56 | local minTwoEx = t.numberMinExclusive(2) 57 | local constrainedEightToEleven = t.numberConstrained(8, 11) 58 | local constrainedEightToElevenEx = t.numberConstrainedExclusive(8, 11) 59 | 60 | assert(maxTen(5)) 61 | assert(maxTen(10)) 62 | assert(not (maxTen(11))) 63 | assert(not (maxTen())) 64 | 65 | assert(minTwo(5)) 66 | assert(minTwo(2)) 67 | assert(not (minTwo(1))) 68 | assert(not (minTwo())) 69 | 70 | assert(maxTenEx(5)) 71 | assert(maxTenEx(9)) 72 | assert(not (maxTenEx(10))) 73 | assert(not (maxTenEx())) 74 | 75 | assert(minTwoEx(5)) 76 | assert(minTwoEx(3)) 77 | assert(not (minTwoEx(2))) 78 | assert(not (minTwoEx())) 79 | 80 | assert(not (constrainedEightToEleven(7))) 81 | assert(constrainedEightToEleven(8)) 82 | assert(constrainedEightToEleven(9)) 83 | assert(constrainedEightToEleven(11)) 84 | assert(not (constrainedEightToEleven(12))) 85 | assert(not (constrainedEightToEleven())) 86 | 87 | assert(not (constrainedEightToElevenEx(7))) 88 | assert(not (constrainedEightToElevenEx(8))) 89 | assert(constrainedEightToElevenEx(9)) 90 | assert(not (constrainedEightToElevenEx(11))) 91 | assert(not (constrainedEightToElevenEx(12))) 92 | assert(not (constrainedEightToElevenEx())) 93 | end) 94 | 95 | it("should support optional types", function() 96 | local check = t.optional(t.string) 97 | assert(check("")) 98 | assert(check()) 99 | assert(not (check(1))) 100 | end) 101 | 102 | it("should support tuple types", function() 103 | local myTupleCheck = t.tuple(t.number, t.string, t.optional(t.number)) 104 | assert(myTupleCheck(1, "2", 3)) 105 | assert(myTupleCheck(1, "2")) 106 | assert(not (myTupleCheck(1, "2", "3"))) 107 | end) 108 | 109 | it("should support union types", function() 110 | local numberOrString = t.union(t.number, t.string) 111 | assert(numberOrString(1)) 112 | assert(numberOrString("1")) 113 | assert(not (numberOrString(nil))) 114 | end) 115 | 116 | it("should support literal types", function() 117 | local checkSingle = t.literal("foo") 118 | local checkUnion = t.union(t.literal("foo"), t.literal("bar"), t.literal("oof")) 119 | 120 | assert(checkSingle("foo")) 121 | assert(checkUnion("foo")) 122 | assert(checkUnion("bar")) 123 | assert(checkUnion("oof")) 124 | 125 | assert(not (checkSingle("FOO"))) 126 | assert(not (checkUnion("FOO"))) 127 | assert(not (checkUnion("BAR"))) 128 | assert(not (checkUnion("OOF"))) 129 | end) 130 | 131 | it("should support multiple literal types", function() 132 | local checkSingle = t.literal("foo") 133 | local checkUnion = t.literal("foo", "bar", "oof") 134 | 135 | assert(checkSingle("foo")) 136 | assert(checkUnion("foo")) 137 | assert(checkUnion("bar")) 138 | assert(checkUnion("oof")) 139 | 140 | assert(not (checkSingle("FOO"))) 141 | assert(not (checkUnion("FOO"))) 142 | assert(not (checkUnion("BAR"))) 143 | assert(not (checkUnion("OOF"))) 144 | end) 145 | 146 | it("should support intersection types", function() 147 | local integerMax5000 = t.intersection(t.integer, t.numberMax(5000)) 148 | assert(integerMax5000(1)) 149 | assert(not (integerMax5000(5001))) 150 | assert(not (integerMax5000(1.1))) 151 | assert(not (integerMax5000("1"))) 152 | end) 153 | 154 | describe("array", function() 155 | it("should support array types", function() 156 | local stringArray = t.array(t.string) 157 | local anyArray = t.array(t.any) 158 | local stringValues = t.values(t.string) 159 | assert(not (anyArray("foo"))) 160 | assert(anyArray({ 1, "2", 3 } :: { any })) 161 | assert(not (stringArray({ 1, "2", 3 } :: { any }))) 162 | assert(not (stringArray())) 163 | assert(not (stringValues())) 164 | assert(anyArray({ "1", "2", "3" }, t.string)) 165 | assert(not (anyArray({ 166 | foo = "bar", 167 | }))) 168 | assert(not (anyArray({ 169 | [1] = "non", 170 | [5] = "sequential", 171 | }))) 172 | end) 173 | 174 | it("should not be fooled by sparse arrays", function() 175 | local anyArray = t.array(t.any) 176 | 177 | assert(not (anyArray({ 178 | [1] = 1, 179 | [2] = 2, 180 | [4] = 4, 181 | }))) 182 | end) 183 | end) 184 | 185 | it("should support map types", function() 186 | local stringNumberMap = t.map(t.string, t.number) 187 | assert(stringNumberMap({})) 188 | assert(stringNumberMap({ a = 1 })) 189 | assert(not (stringNumberMap({ [1] = "a" }))) 190 | assert(not (stringNumberMap({ a = "a" }))) 191 | assert(not (stringNumberMap())) 192 | end) 193 | 194 | it("should support set types", function() 195 | local stringSet = t.set(t.string) 196 | assert(stringSet({})) 197 | assert(stringSet({ a = true })) 198 | assert(not (stringSet({ [1] = "a" }))) 199 | assert(not (stringSet({ a = "a" }))) 200 | assert(not (stringSet({ a = false }))) 201 | assert(not (stringSet())) 202 | end) 203 | 204 | it("should support interface types", function() 205 | local IVector3 = t.interface({ 206 | x = t.number, 207 | y = t.number, 208 | z = t.number, 209 | }) 210 | 211 | assert(IVector3({ 212 | w = 0, 213 | x = 1, 214 | y = 2, 215 | z = 3, 216 | })) 217 | 218 | assert(not (IVector3({ 219 | w = 0, 220 | x = 1, 221 | y = 2, 222 | }))) 223 | end) 224 | 225 | it("should support strict interface types", function() 226 | local IVector3 = t.strictInterface({ 227 | x = t.number, 228 | y = t.number, 229 | z = t.number, 230 | }) 231 | 232 | assert(not (IVector3(0))) 233 | 234 | assert(not (IVector3({ 235 | w = 0, 236 | x = 1, 237 | y = 2, 238 | z = 3, 239 | }))) 240 | 241 | assert(not (IVector3({ 242 | w = 0, 243 | x = 1, 244 | y = 2, 245 | }))) 246 | 247 | assert(IVector3({ 248 | x = 1, 249 | y = 2, 250 | z = 3, 251 | })) 252 | end) 253 | 254 | it("should support deep interface types", function() 255 | local IPlayer = t.interface({ 256 | name = t.string, 257 | inventory = t.interface({ 258 | size = t.number, 259 | }), 260 | }) 261 | 262 | assert(IPlayer({ 263 | name = "TestPlayer", 264 | inventory = { 265 | size = 1, 266 | }, 267 | })) 268 | 269 | assert(not (IPlayer({ 270 | inventory = { 271 | size = 1, 272 | }, 273 | }))) 274 | 275 | assert(not (IPlayer({ 276 | name = "TestPlayer", 277 | inventory = {}, 278 | }))) 279 | 280 | assert(not (IPlayer({ 281 | name = "TestPlayer", 282 | }))) 283 | end) 284 | 285 | it("should support deep optional interface types", function() 286 | local IPlayer = t.interface({ 287 | name = t.string, 288 | inventory = t.optional(t.interface({ 289 | size = t.number, 290 | })), 291 | }) 292 | 293 | assert(IPlayer({ 294 | name = "TestPlayer", 295 | })) 296 | 297 | assert(not (IPlayer({ 298 | name = "TestPlayer", 299 | inventory = {}, 300 | }))) 301 | 302 | assert(IPlayer({ 303 | name = "TestPlayer", 304 | inventory = { 305 | size = 1, 306 | }, 307 | })) 308 | end) 309 | 310 | it("should support Roblox Instance types", function() 311 | local stringValueCheck = t.instanceOf("StringValue") 312 | local stringValue = Instance.new("StringValue") 313 | local boolValue = Instance.new("BoolValue") 314 | 315 | assert(stringValueCheck(stringValue)) 316 | assert(not (stringValueCheck(boolValue))) 317 | assert(not (stringValueCheck())) 318 | end) 319 | 320 | it("should support Roblox Instance types inheritance", function() 321 | local guiObjectCheck = t.instanceIsA("GuiObject") 322 | local frame = Instance.new("Frame") 323 | local textLabel = Instance.new("TextLabel") 324 | local stringValue = Instance.new("StringValue") 325 | 326 | assert(guiObjectCheck(frame)) 327 | assert(guiObjectCheck(textLabel)) 328 | assert(not (guiObjectCheck(stringValue))) 329 | assert(not (guiObjectCheck())) 330 | end) 331 | 332 | it("should support Roblox Enum types", function() 333 | local sortOrderEnumCheck = t.enum(Enum.SortOrder) 334 | assert(t.Enum(Enum.SortOrder)) 335 | assert(not (t.Enum("Enum.SortOrder"))) 336 | 337 | assert(t.EnumItem(Enum.SortOrder.Name)) 338 | assert(not (t.EnumItem("Enum.SortOrder.Name"))) 339 | 340 | assert(sortOrderEnumCheck(Enum.SortOrder.Name)) 341 | assert(sortOrderEnumCheck(Enum.SortOrder.Custom)) 342 | assert(not (sortOrderEnumCheck(Enum.EasingStyle.Linear))) 343 | assert(not (sortOrderEnumCheck())) 344 | end) 345 | 346 | it("should support Roblox RBXScriptSignal", function() 347 | assert(t.RBXScriptSignal(game.ChildAdded)) 348 | assert(not (t.RBXScriptSignal(nil))) 349 | assert(not (t.RBXScriptSignal(Vector3.new()))) 350 | end) 351 | 352 | -- TODO: Add this back when Lemur supports it 353 | -- it("should support Roblox RBXScriptConnection", function() 354 | -- local conn = game.ChildAdded:Connect(function() end) 355 | -- assert(t.RBXScriptConnection(conn)) 356 | -- assert(not (t.RBXScriptConnection(nil))) 357 | -- assert(not (t.RBXScriptConnection(Vector3.new()))) 358 | -- end) 359 | 360 | it("should support wrapping function types", function() 361 | local checkFoo = t.tuple(t.string, t.number, t.optional(t.string)) 362 | local foo: (...any) -> (boolean, string?) = t.wrap(function(a, b, c) 363 | local result = string.format("%s %d", a, b) 364 | if c then 365 | result = result .. " " .. c 366 | end 367 | return result 368 | end, checkFoo) :: any 369 | 370 | assert(not (pcall(foo))) 371 | assert(not (pcall(foo, "a"))) 372 | assert(not (pcall(foo, 2))) 373 | assert(pcall(foo, "a", 1)) 374 | assert(pcall(foo, "a", 1, "b")) 375 | end) 376 | 377 | it("should support strict types", function() 378 | local myType = t.strict(t.tuple(t.string, t.number)) 379 | assert(not (pcall(function() 380 | myType("a", "b") 381 | end))) 382 | assert(pcall(function() 383 | myType("a", 1) 384 | end)) 385 | end) 386 | 387 | it("should support common OOP types", function() 388 | local MyClass = {} 389 | MyClass.__index = MyClass 390 | 391 | function MyClass.new() 392 | local self = setmetatable({}, MyClass) 393 | return self 394 | end 395 | 396 | local function instanceOfClass(class) 397 | return function(value) 398 | local tableSuccess, tableErrMsg = t.table(value) 399 | if not tableSuccess then 400 | return false, tableErrMsg or "" 401 | end 402 | 403 | local mt = getmetatable(value) 404 | if not mt or mt.__index ~= class then 405 | return false, "bad member of class" 406 | end 407 | 408 | return true 409 | end 410 | end 411 | 412 | local instanceOfMyClass = instanceOfClass(MyClass) 413 | 414 | local myObject = MyClass.new() 415 | assert(instanceOfMyClass(myObject)) 416 | assert(not (instanceOfMyClass({}))) 417 | assert(not (instanceOfMyClass())) 418 | end) 419 | 420 | it("should not treat NaN as numbers", function() 421 | assert(t.number(1)) 422 | assert(not (t.number(0 / 0))) 423 | assert(not (t.number("1"))) 424 | end) 425 | 426 | it("should not treat numbers as NaN", function() 427 | assert(not (t.nan(1))) 428 | assert(t.nan(0 / 0)) 429 | assert(not (t.nan("1"))) 430 | end) 431 | 432 | it("should allow union of number and NaN", function() 433 | local numberOrNaN = t.union(t.number, t.nan) 434 | assert(numberOrNaN(1)) 435 | assert(numberOrNaN(0 / 0)) 436 | assert(not (numberOrNaN("1"))) 437 | end) 438 | 439 | it("should support non-string keys for interfaces", function() 440 | local key = {} 441 | local myInterface = t.interface({ [key] = t.number }) 442 | assert(myInterface({ [key] = 1 })) 443 | assert(not (myInterface({ [key] = "1" }))) 444 | end) 445 | 446 | it("should support failing on non-string keys for strict interfaces", function() 447 | local myInterface = t.strictInterface({ a = t.number }) 448 | assert(not (myInterface({ a = 1, [{}] = 2 }))) 449 | end) 450 | 451 | it("should support children", function() 452 | local myInterface = t.interface({ 453 | buttonInFrame = t.intersection( 454 | t.instanceOf("Frame"), 455 | t.children({ 456 | MyButton = t.instanceOf("ImageButton"), 457 | }) 458 | ), 459 | }) 460 | 461 | assert(not (t.children({})(5))) 462 | assert(not (myInterface({ buttonInFrame = Instance.new("Frame") }))) 463 | 464 | do 465 | local frame = Instance.new("Frame") 466 | local button = Instance.new("ImageButton", frame) 467 | button.Name = "MyButton" 468 | assert(myInterface({ buttonInFrame = frame })) 469 | end 470 | 471 | do 472 | local frame = Instance.new("Frame") 473 | local button = Instance.new("ImageButton", frame) 474 | button.Name = "NotMyButton" 475 | assert(not (myInterface({ buttonInFrame = frame }))) 476 | end 477 | 478 | do 479 | local frame = Instance.new("Frame") 480 | local button = Instance.new("TextButton", frame) 481 | button.Name = "MyButton" 482 | assert(not (myInterface({ buttonInFrame = frame }))) 483 | end 484 | 485 | do 486 | local frame = Instance.new("Frame") 487 | local button1 = Instance.new("ImageButton", frame) 488 | button1.Name = "MyButton" 489 | local button2 = Instance.new("ImageButton", frame) 490 | button2.Name = "MyButton" 491 | assert(not (myInterface({ buttonInFrame = frame }))) 492 | end 493 | end) 494 | 495 | it("should support t.instanceOf shorthand", function() 496 | local myInterface = t.interface({ 497 | buttonInFrame = t.instanceOf("Frame", { 498 | MyButton = t.instanceOf("ImageButton"), 499 | }), 500 | }) 501 | 502 | assert(not (t.children({})(5))) 503 | assert(not (myInterface({ buttonInFrame = Instance.new("Frame") }))) 504 | 505 | do 506 | local frame = Instance.new("Frame") 507 | local button = Instance.new("ImageButton", frame) 508 | button.Name = "MyButton" 509 | assert(myInterface({ buttonInFrame = frame })) 510 | end 511 | 512 | do 513 | local frame = Instance.new("Frame") 514 | local button = Instance.new("ImageButton", frame) 515 | button.Name = "NotMyButton" 516 | assert(not (myInterface({ buttonInFrame = frame }))) 517 | end 518 | 519 | do 520 | local frame = Instance.new("Frame") 521 | local button = Instance.new("TextButton", frame) 522 | button.Name = "MyButton" 523 | assert(not (myInterface({ buttonInFrame = frame }))) 524 | end 525 | 526 | do 527 | local frame = Instance.new("Frame") 528 | local button1 = Instance.new("ImageButton", frame) 529 | button1.Name = "MyButton" 530 | local button2 = Instance.new("ImageButton", frame) 531 | button2.Name = "MyButton" 532 | assert(not (myInterface({ buttonInFrame = frame }))) 533 | end 534 | end) 535 | 536 | it("should support t.instanceIsA shorthand", function() 537 | local myInterface = t.interface({ 538 | buttonInFrame = t.instanceIsA("Frame", { 539 | MyButton = t.instanceIsA("ImageButton"), 540 | }), 541 | }) 542 | 543 | assert(not (t.children({})(5))) 544 | assert(not (myInterface({ buttonInFrame = Instance.new("Frame") }))) 545 | 546 | do 547 | local frame = Instance.new("Frame") 548 | local button = Instance.new("ImageButton", frame) 549 | button.Name = "MyButton" 550 | assert(myInterface({ buttonInFrame = frame })) 551 | end 552 | 553 | do 554 | local frame = Instance.new("Frame") 555 | local button = Instance.new("ImageButton", frame) 556 | button.Name = "NotMyButton" 557 | assert(not (myInterface({ buttonInFrame = frame }))) 558 | end 559 | 560 | do 561 | local frame = Instance.new("Frame") 562 | local button = Instance.new("TextButton", frame) 563 | button.Name = "MyButton" 564 | assert(not (myInterface({ buttonInFrame = frame }))) 565 | end 566 | 567 | do 568 | local frame = Instance.new("Frame") 569 | local button1 = Instance.new("ImageButton", frame) 570 | button1.Name = "MyButton" 571 | local button2 = Instance.new("ImageButton", frame) 572 | button2.Name = "MyButton" 573 | assert(not (myInterface({ buttonInFrame = frame }))) 574 | end 575 | end) 576 | 577 | it("should support t.match", function() 578 | local check = t.match("%d+") 579 | assert(check("123")) 580 | assert(not (check("abc"))) 581 | assert(not (check())) 582 | end) 583 | 584 | it("should support t.keyOf", function() 585 | local myNewEnum = { 586 | OptionA = {}, 587 | OptionB = {}, 588 | } 589 | local check = t.keyOf(myNewEnum) 590 | assert(check("OptionA")) 591 | assert(not (check("OptionC"))) 592 | end) 593 | 594 | it("should support t.valueOf", function() 595 | local myNewEnum = { 596 | OptionA = {}, 597 | OptionB = {}, 598 | } 599 | local check = t.valueOf(myNewEnum) 600 | assert(check(myNewEnum.OptionA)) 601 | assert(not (check(1010))) 602 | end) 603 | 604 | it("should support t.strictArray", function() 605 | local fixedArrayCheck = t.strictArray(t.number, t.number) 606 | 607 | assert(fixedArrayCheck({ 1, 2 })) 608 | assert(not fixedArrayCheck({ 1, 2, 3 })) 609 | assert(not fixedArrayCheck({ 10 })) 610 | assert(not fixedArrayCheck({ "Hello", 10 } :: { any })) 611 | assert(not fixedArrayCheck({ Foo = "Bar" })) 612 | 613 | local fixedArrayCheck2 = t.strictArray(t.number, t.number, t.optional(t.string)) 614 | 615 | assert(fixedArrayCheck2({ 10, 20 })) 616 | assert(fixedArrayCheck2({ 10, 20, "Hello" } :: { any })) 617 | assert(not fixedArrayCheck2({ 10, 20, 30 })) 618 | end) 619 | -------------------------------------------------------------------------------- /wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "corecii/greentea" 3 | version = "0.4.11" 4 | registry = "https://github.com/UpliftGames/wally-index" 5 | realm = "shared" 6 | 7 | # Includes override excludes, and excludes is by default empty. 8 | # We exclude everything, then include only what we want. 9 | exclude = ["*"] 10 | include = [ 11 | "default.project.json", 12 | "src", 13 | "src/**", 14 | "wally.toml", 15 | "README.md", 16 | "CHANGELOG.md", 17 | "selene.toml", 18 | ".luaurc", 19 | ] 20 | 21 | [dev-dependencies] 22 | Jest = "jsdotlua/jest@3.6.1-rc.2" 23 | JestGlobals = "jsdotlua/jest-globals@3.6.1-rc.2" 24 | RegExp = "jsdotlua/luau-regexp@^0.2.1" 25 | 26 | _jest-roblox-shared = "jsdotlua/jest-roblox-shared@3.6.1-rc.2" 27 | --------------------------------------------------------------------------------