Contributing to Documentation
There is a unified system for both the generation of the wiki and the generation of the typechecking.
You will find this in the documentation/documentation.yaml
file.
As this file needs to be able to represent all possible Luau types in a vaguely readable tabular form, it is rather complex.
First, it is a good idea to ignore the variables
section, as you'll see the &
symbol, these are not actually used in the typechecking, they are just shorthands for common types that use YAML's anchors and aliases.
Once you're finished reading, if you want to see all the mechanisms in action, a good set of types to check out are EventConnection
, EventConnectionMetatable
, and Event
.
Only values with a key that refers to a type are included in the generated documentation, this is how folders are produced on the wiki, for example:
foo: # foo is not a type, as it doesn't have a type specified
bar: # bar is a type
type: any
baz: # baz is a type
type: any
These will show under the foo
directory.
Type Key Prefixes
Note that type
is not the only type key, there are others that will be covered later, but these prefixes can also be applied to them.
Nullables
All types can be prefixed with nullable
(i.e., nullable-type
, nullable-union
) to mean 'this value can be nil' (exactly like T?
in Luau types).
Arrays
For arrays, you would do.
- You could also transform the type into
{T}
by doingarray-type
- If you wanted to do
{T?}
you would needsparse-array-type
, - If you were even bolder, and wanted
{T?}?
, you would furthermore donullable-sparse-array-type
. - By extension, if you hadn't figured it out already
nullable-array-type
is still possible, this meaning{T}?
.
Strings
It is often annoying to write out a string that Luau would consider valid (i.e., having to escape your quotes and whatnot).
For this there is a shorthand, you are able to write things such as:
foobar:
string-type: Planet
And this will accurately be interpreted as "Planet"
. This will handle all the requisite escapes for you, as internally it just passes it through string.format("%q", ...)
Standard Types
The type
key both represents types of the form T
and of the form { key: T, [string]: V }
.
This is just done by either doing:
foobar:
type: T
Or, for the dictionary type:
foobar:
type:
key:
type: T
[string]: # this is an abuse of the YAML syntax (the key is treated as an array), it may change in future
type: V
Unions & Intersections
You may be familiar with these from Luau typechecking, or even set theory.
A union of a type basically means it is of type a
or b
.
Whereas an intersection is both a
and b
.
Here, you would use the key union
or intersection
(all the other prefixes still apply)
Do note that you would not do union-type
or intersection-type
. These are not prefixes, but standalone root keys that define composite types.
These keys do not directly go into their type, as usual, but rather, are an array of many other descendant types, for example:
foobar:
union:
- type: string
- type: number
foobaz:
intersection:
- type: string
- type: number
foobar
represents the type string | number
, and foobaz
represents the type string & number
(this second one is rather nonsensical, but will make more sense when we introduce dictionaries).
For the union
types, you are also able to prefix it with string
and, only in combination with string
, open
:
a:
string-union:
- foo
- bar
- baz
b:
open-string-union:
- foo
- bar
- baz
In these cases:
a
is the type"foo" | "bar" | "baz"
b
is the type"foo" | "bar" | "baz" | string
You might wonder why the open
prefix exists, why bother specifying possible strings if you're not going to strictly enforce them? This is because it provides autofill, along with some concrete examples for what the value could look like.
Methods & Functions
These are rather simple, the definitions of methods and functions are identical syntactically (save for function
being swapped for method
and vice versa), so we'll just focus on function
for now.
All you need is to specify an array of parameters
and returns
, note that none of the prefixes work with these two. Something to note with these is that you're allowed to specify a name at the same scope level as the type key.
Variadic returns/parameters are done through setting the name to ...
.
You are allowed to leave either parameters
or returns
blank, but if you leave both blank, YAML will attempt to discard the function
or method
key, so you need to explicitly specify it as {}
.
Remember that you can still prefix these with things like nullable-function
or array-function
.
a:
function:
parameters:
- name: parameter # that's a new one! a name!
type: string
returns:
- name: ... # variadic return
type: any
b:
function:
returns:
- name: namedReturn # Luau does not support named returns; names are used here solely for wiki documentation
type: any
c:
function: {} # YAML discards empty keys, we have to be explicit about this having no members
In these examples:
a
is(parameter: string) -> ...any
b
is() -> any
, see how the name is discarded in the type. This doesn't mean you shouldn't write them, though.c
is() -> ()
Generics
To specify generic types, there are two keys (which are siblings of the type key), them being, generics
and generic-definitions
.
The reason these are separate keys as it needs to be possible to support types such as the following:
type Type<K = string, V = number> = OtherType<V, K>
As an example, I will show how this type is represented in the YAML format:
Type:
generic-definitions:
- name: K
type: string # this specifies the default
- name: V
type: number
type: OtherType
generics:
- type: V
- type: K
As of writing this document, there is no way to specify generic functions, if you are in need of generic functions, please make an issue about it or contact me on discord, and I will implement it, or, if you feel capable, you can implement it yourself.
Self
Sometimes you need to manually specify what should be considered self
for a type's methods (i.e., in the case of metatables, where the methods are defined separate from the data).
To do this, as a sibling to the type key, you construct another type key (meaning it could technically be self-union
or some other such construct), but prefixed with self
.
For example:
foobar:
self-type: HelloWorld
type:
Method:
method: {}
Would represent the type:
type foobar = {
Method: (self: HelloWorld) -> ()
}
Rather than (if you left self
unspecified):
type foobar = {
Method: (self: foobar) -> ()
}
You'll see a better example in the next section, which is on metatables.
Metatables
Metatables are a big part of Luau, I couldn't really find a neat way to specify metatables nicely, so I opted for:
FoobarMetatable:
export: false # you've not seen this yet, but this just prevents this type from being exposed as a global type
self-type: Foobar
type:
Method:
method: {}
__index:
type: FoobarMetatable
Foobar:
type:
foo:
type: string
bar:
type: number
metatable-type: FoobarMetatable
Wow, that is a huge type. Let's see what it actually represents:
type FoobarMetatable = {
__index: FoobarMetatable,
Method: (self: Foobar) -> (), -- 'Foobar', not 'FoobarMetatable', we specified the `self-type`.
}
type Foobar = typeof(setmetatable(
{} :: {
foo: string,
bar: number,
},
{} :: FoobarMetatable
))
Hypothetically, the following would also work, which does avoid the use of two types, but is often not actually representative of the true type (given the standard style of faux-oop in Roblox).
Foobar:
type:
foo:
type: string
bar:
type: number
metatable-type:
__index:
type:
Method:
method: {}
Self & Metatable Addendum
Do note that it is also possible to separate the self-type
into self
and type
separately (the same goes for metatable-type
), like so:
EventConnectionMetatable:
self:
type: EventConnection
generics:
- type: Name
- type: Callback
This is done, as you may have noticed, to group type
and generics
together and avoid self-type
and self-generics
being sibling keys.
Object Classes
The object classes do not directly represent a Luau type, there is a step in-between to translate them into a (less-readable, as it is more verbose) lower level form (found in /source/modules/Hydrators.luau
).
'Hydrators' is a poor name for it, the idea is that you take a useless form of data and 'hydrate' it into a usuable form.
PilotObject:
hydrator:
name: wos-object # this is a wos object
abstract: true # this object doesn't really exist, it is just a tool for reusing definitions
events: # events that are present
EventName:
parameters: # like a function, but with no returns
- name: userId
type: number
methods: # methods that are present
MethodName: # here it diverges, this is not a `method` type, it is translated to that later
parameters:
- name: parameter
type: string # this is all the same though
returns:
- name: ...
type: any
properties: # read only configurables, only PilotObject has these
PropertyName:
type: string # standard type key
configurables: # configurables that are present
ConfigurableName:
type: string # standard type key
All you have is a hydrator
key, with the name
set to wos-object
(if this object is incredibly simple, such as grass, you can directly set hydrator
to the string wos-object
rather than make it an object).
This hydrator key has the keys events
, methods
, configurables
and properties
(you rarely need properties, they are only used for the PilotObject
class to specify things like Position
).
It also has an abstract
key (i.e., if you made a ContainerObject
class, it doesn't really exist in the game, but many objects act like containers) to specify whether it is a real object or just a class of object (like Roblox BasePart
s).
For objects like 100k
, which are not valid Luau identifiers (as they start with numbers). You should specify them with a class-name
and a different type name, like so:
Part100k:
hydrator:
name: wos-object
class-name: 100k
There is also an extends
key for inheritence, it defaults to PilotObject
for classes who have abstract
as undefined or false
.
The only divergence between how the types are specified once categoried is that events
are their own custom format (and are later translated to normal Luau types), and methods
directly specify parameters
and returns
without specifying the method
key.
Documentation
So far, we've only covered types, but, of course, you can also attach descriptions to types or their members using the documentation key.
This is done through a documentation
key, which is a sibling of any type keys (it doesn't just have to be the root type, you can also document subkeys, for example RegionInfo.SubType
could have a description, and so could RegionInfo
itself).
Basic Documentation
The documentation
key can just directly be a string, for example:
PortLike:
documentation: A value that will be accepted as a port by the <code>GetPart(s)(FromPort)</code> functions.
union:
- type: Port
- type: number
Range & Increment
This is good for simple types, which just need a description, but if you wish to specify that a value is deprecated, or maybe a range/increment, you would do something like (with a description
key as a within the documentation
key):
Transparency:
type: number
documentation:
description: The transparency of the AR display.
range: [0, 1]
increment: 0.1
Note that, for ranges that only have a bound in one direction, you can just specify min
and max
instead of range
(do not specify min
and max
both at once).
Deprecation
If specifying something as deprecated, you can either set it to the boolean value of true
, or set it to a string
(if it is a string, it will be treated as a note about the deprecated, like an alternative to use, why the deprecation happened, what behaviour the now deprecated function exhibits).
CraftItems:
documentation:
description: Will craft the specified set of items.
deprecated: true
parameters: ...
GetInventory:
documentation:
deprecated: Returns an empty, frozen table.
returns: ...
Categories
You can specify an array of categories
within the documentation
key, such as:
Grass:
documentation:
description: ...
categories: [Natural, Resources, Spawnable]
hydrator: wos-object
Code Examples
Another available documentation is code-sample
or code-samples
. code-sample
is a string of just a single code example, whereas code-samples
is an array of multiple code examples.
Each of the specified code examples (for either the plural or singular keys) can be specified as a file path to a file in /documentation/programs
.
Information Sources
If you have a source for where you got the information from, you can specify a source
or sources
key with a link to the information source. These sources could be developer messages or people talking about the feature working in the way you've documented.