type_check_inside_call
What it does
-Checks for type(foo == "type")
, instead of type(foo) == "type"
.
Checks for type(foo == "type")
, instead of type(foo) == "type"
.
Why this is bad
-This will always return "boolean"
, and is undoubtedly not what you intended to write.
This will always return "boolean"
, and is undoubtedly not what you intended to write.
Example
-return type(foo == "number")
+return type(foo == "number")
...should be written as...
-return type(foo) == "number"
+return type(foo) == "number"
Remarks
When using the Roblox standard library, this checks typeof
as well.
diff --git a/lints/undefined_variable.html b/lints/undefined_variable.html
index 14968f74..91638219 100644
--- a/lints/undefined_variable.html
+++ b/lints/undefined_variable.html
@@ -180,7 +180,7 @@ Why this is b
This is most likely a typo.
Example
-- vv oops!
-prinnt("hello, world!")
+prinnt("hello, world!")
Remarks
If you are using a different standard library where a global variable is defined that selene isn't picking up on, create a standard library that specifies it.
diff --git a/lints/unscoped_variables.html b/lints/unscoped_variables.html
index 7e6236ed..5e169fab 100644
--- a/lints/unscoped_variables.html
+++ b/lints/unscoped_variables.html
@@ -179,7 +179,7 @@ What it doesWhy this is bad
Unscoped variables make code harder to read and debug, as well as making it harder for selene to analyze.
Configuration
-ignore_pattern
(default: "^_"
) - A regular expression for variables that are allowed to be unscoped. The default allows for variables like _
to be unscoped, as they shouldn't be used anyway.
+ignore_pattern
(default: "^_"
) - A regular expression for variables that are allowed to be unscoped. The default allows for variables like _
to be unscoped, as they shouldn't be used anyway.
Example
baz = 3
diff --git a/lints/unused_variable.html b/lints/unused_variable.html
index 6b53a8a1..1ef6a9cb 100644
--- a/lints/unused_variable.html
+++ b/lints/unused_variable.html
@@ -180,7 +180,7 @@ Why this is b
The existence of unused variables could indicate buggy code.
Configuration
allow_unused_self
(default: true
) - A bool that determines whether not using self
in a method function (function Player:SwapWeapons()
) is allowed.
-ignore_pattern
(default: "^_"
) - A regular expression for variables that are allowed to be unused. The default allows for variables like _
to be unused, as they shouldn't be used anyway.
+ignore_pattern
(default: "^_"
) - A regular expression for variables that are allowed to be unused. The default allows for variables like _
to be unused, as they shouldn't be used anyway.
Example
local foo = 1
diff --git a/luacheck.html b/luacheck.html
index 569c27d6..782ff55d 100644
--- a/luacheck.html
+++ b/luacheck.html
@@ -207,8 +207,8 @@ selene
...while luacheck does not.
-- selene has English names for lints instead of arbitrary numbers. In luacheck, you ignore "
211
", while in selene you ignore "unbalanced_assignments
".
-- selene has distinctions for "deny" and "warn", while every luacheck lint is the same.
+- selene has English names for lints instead of arbitrary numbers. In luacheck, you ignore "
211
", while in selene you ignore "unbalanced_assignments
".
+- selene has distinctions for "deny" and "warn", while every luacheck lint is the same.
- selene has a much simpler codebase, and is much easier to add your own lints to.
- selene has optional support and large focus specifically for Roblox development.
- selene will only show you files that lint, luacheck only does this with the
-q
option (quiet).
diff --git a/motivation.html b/motivation.html
index eec4695e..8404683e 100644
--- a/motivation.html
+++ b/motivation.html
@@ -209,7 +209,7 @@ Because bugsOther bugs arise because of Lua's lack of typing. While it can feel freeing to developers to not have to specify types everywhere, it makes it easier to mess up and write broken code. For example, take the following code:
for _, shop in pairs(GoldShop, ItemShop, MedicineShop) do
-
This code is yet again technically correct, but not what we wanted to do. pairs
will take the first argument, GoldShop
, and ignore the rest. Worse, the shop
variable will now be the values of the contents of GoldShop
, not the shop itself. This can cause massive headaches, since although you're likely to get an error later down the line, it's more likely it'll be in the vein of "attempt to index a nil value items
" than something more helpful. If you used ipairs
instead of pairs
, your code inside might just not run, and won't produce an error at all.
+This code is yet again technically correct, but not what we wanted to do. pairs
will take the first argument, GoldShop
, and ignore the rest. Worse, the shop
variable will now be the values of the contents of GoldShop
, not the shop itself. This can cause massive headaches, since although you're likely to get an error later down the line, it's more likely it'll be in the vein of "attempt to index a nil value items
" than something more helpful. If you used ipairs
instead of pairs
, your code inside might just not run, and won't produce an error at all.
Yet again, selene saves us.
error[incorrect_standard_library_use]: standard library function `pairs` requires 1 parameters, 3 passed
@@ -238,7 +238,7 @@ B
│
Furthermore, selene is meant to be easy for developers to add their own lints to. You could create your own lints for your team to prevent behavior that is non-idiomatic to the codebase. For example, let's say you're working on a Roblox codebase, and you don't want your developers using the data storage methods directly. You could create your own lint so that this code:
-local DataStoreService = game:GetService("DataStoreService")
+local DataStoreService = game:GetService("DataStoreService")
...creates a warning, discouraging its use. For more information on how to create your own lints, check out the contribution guide.
diff --git a/print.html b/print.html
index 279c7ed9..22c9c245 100644
--- a/print.html
+++ b/print.html
@@ -222,7 +222,7 @@ Because bugsOther bugs arise because of Lua's lack of typing. While it can feel freeing to developers to not have to specify types everywhere, it makes it easier to mess up and write broken code. For example, take the following code:
for _, shop in pairs(GoldShop, ItemShop, MedicineShop) do
-
This code is yet again technically correct, but not what we wanted to do. pairs
will take the first argument, GoldShop
, and ignore the rest. Worse, the shop
variable will now be the values of the contents of GoldShop
, not the shop itself. This can cause massive headaches, since although you're likely to get an error later down the line, it's more likely it'll be in the vein of "attempt to index a nil value items
" than something more helpful. If you used ipairs
instead of pairs
, your code inside might just not run, and won't produce an error at all.
+This code is yet again technically correct, but not what we wanted to do. pairs
will take the first argument, GoldShop
, and ignore the rest. Worse, the shop
variable will now be the values of the contents of GoldShop
, not the shop itself. This can cause massive headaches, since although you're likely to get an error later down the line, it's more likely it'll be in the vein of "attempt to index a nil value items
" than something more helpful. If you used ipairs
instead of pairs
, your code inside might just not run, and won't produce an error at all.
Yet again, selene saves us.
error[incorrect_standard_library_use]: standard library function `pairs` requires 1 parameters, 3 passed
@@ -251,7 +251,7 @@ B
│
Furthermore, selene is meant to be easy for developers to add their own lints to. You could create your own lints for your team to prevent behavior that is non-idiomatic to the codebase. For example, let's say you're working on a Roblox codebase, and you don't want your developers using the data storage methods directly. You could create your own lint so that this code:
-local DataStoreService = game:GetService("DataStoreService")
+local DataStoreService = game:GetService("DataStoreService")
...creates a warning, discouraging its use. For more information on how to create your own lints, check out the contribution guide.
Luacheck Comparison
@@ -288,8 +288,8 @@ selene
...while luacheck does not.
-- selene has English names for lints instead of arbitrary numbers. In luacheck, you ignore "
211
", while in selene you ignore "unbalanced_assignments
".
-- selene has distinctions for "deny" and "warn", while every luacheck lint is the same.
+- selene has English names for lints instead of arbitrary numbers. In luacheck, you ignore "
211
", while in selene you ignore "unbalanced_assignments
".
+- selene has distinctions for "deny" and "warn", while every luacheck lint is the same.
- selene has a much simpler codebase, and is much easier to add your own lints to.
- selene has optional support and large focus specifically for Roblox development.
- selene will only show you files that lint, luacheck only does this with the
-q
option (quiet).
@@ -345,7 +345,7 @@ Advanced op
--num-threads num-threads
Specifies the number of threads for selene to use. Defaults to however many cores your CPU has. If you type selene --help
, you can see this number because it will show as the default for you.
--pattern pattern
-A glob to match what files selene should check for. For example, if you only wanted to check files that end with .spec.lua
, you would input --pattern **/*.spec.lua
. Defaults to **/*.lua
, meaning "any lua file", or **/*.lua
and **/*.luau
with the roblox feature flag, meaning "any lua/luau file".
+A glob to match what files selene should check for. For example, if you only wanted to check files that end with .spec.lua
, you would input --pattern **/*.spec.lua
. Defaults to **/*.lua
, meaning "any lua file", or **/*.lua
and **/*.luau
with the roblox feature flag, meaning "any lua/luau file".
Usage
In this section, you will learn how to interact with selene from your code and how to fit it to your liking.
Configuration
@@ -409,17 +409,17 @@ Advanced op
Changing the severity of lints
You can change the severity of lints by entering the following into selene.toml:
[lints]
-lint_1 = "severity"
-lint_2 = "severity"
+lint_1 = "severity"
+lint_2 = "severity"
...
-Where "severity" is one of the following:
+Where "severity" is one of the following:
-"allow"
- Don't check for this lint
-"warn"
- Warn for this lint
-"deny"
- Error for this lint
+"allow"
- Don't check for this lint
+"warn"
- Warn for this lint
+"deny"
- Error for this lint
-Note that "deny" and "warn" are effectively the same, only warn will give orange text while error gives red text, and they both have different counters.
+Note that "deny" and "warn" are effectively the same, only warn will give orange text while error gives red text, and they both have different counters.
Configuring specific lints
You can configure specific lints by entering the following into selene.toml:
[config]
@@ -434,19 +434,19 @@ Setting the standard library
Many lints use standard libraries for either verifying their correct usage or for knowing that variables exist where they otherwise wouldn't.
By default, selene uses Lua 5.1, though if we wanted to use the Lua 5.2 standard library, we would write:
-std = "lua52"
+std = "lua52"
...at the top of selene.toml. You can learn more about the standard library format on the standard library guide. The standard library given can either be one of the builtin ones (currently only lua51
and lua52
) or the filename of a standard library file in this format. For example, if we had a file named special.toml
, we would write:
-std = "special"
+std = "special"
Chaining the standard library
We can chain together multiple standard libraries by simply using a plus sign (+
) in between the names.
For example, if we had game.toml
and engine.toml
standard libraries, we could chain them together like so:
-std = "game+engine"
+std = "game+engine"
Excluding files from being linted
It is possible to exclude files from being linted using the exclude option:
-exclude = ["external/*", "*.spec.lua"]
+exclude = ["external/*", "*.spec.lua"]
Filtering
Lints can be toggled on and off in the middle of code when necessary through the use of special comments.
@@ -469,7 +469,7 @@ configuration:
[lints]
-unused_variable = "allow" # I'm fine with unused variables in code
+unused_variable = "allow" # I'm fine with unused variables in code
...and have this in a separate file:
-- I'm usually okay with unused variables, but not this one
@@ -553,22 +553,22 @@ Functions
If method
is specified as true
and the function is inside a table, then it will require the function be called in the form of Table:FunctionName()
, instead of Table.FunctionName()
.
args
is an array of arguments, in order of how they're used in the function. An argument is in the form of:
required?: false | true | string;
-type: "any" | "bool" | "function" | "nil"
- | "number" | "string" | "table" | "..."
- | string[] | { "display": string }
+type: "any" | "bool" | "function" | "nil"
+ | "number" | "string" | "table" | "..."
+ | string[] | { "display": string }
-"required"
+"required"
true
- The default, this argument is required.
false
- This argument is optional.
- A string - This argument is required, and not using it will give this as the reason why.
-"observes"
+"observes"
This field is used for allowing smarter introspection of how the argument given is used.
-- "read-write" - The default. This argument is potentially both written to and read from.
-- "read" - This argument is only read from. Currently unused.
-- "write" - This argument is only written to. Used by
unused_variable
to assist in detecting a variable only being written to, even if passed into a function.
+- "read-write" - The default. This argument is potentially both written to and read from.
+- "read" - This argument is only read from. Currently unused.
+- "write" - This argument is only written to. Used by
unused_variable
to assist in detecting a variable only being written to, even if passed into a function.
Example:
table.insert:
@@ -579,7 +579,7 @@ "observes"
-"must_use"
+"must_use"
This field is used for checking if the return value of a function is used.
false
- The default. The return value of this function does not need to be used.
@@ -593,11 +593,11 @@ "must_use"
Argument types
-"any"
- Allows any value.
-"bool"
, "function"
, "nil"
, "number"
, "string"
, "table"
- Expects a value of the respective type.
-"..."
- Allows any number of variables after this one. If required
is true (it is by default), then this will lint if no additional arguments are given. It is incorrect to have this in the middle.
-- Constant list of strings - Will check if the value provided is one of the strings in the list. For example,
collectgarbage
only takes one of a few exact string arguments--doing collectgarbage("count")
will work, but collectgarbage("whoops")
won't.
-{ "display": string }
- Used when no constant could possibly be correct. If a constant is used, selene will tell the user that an argument of the type (display) is required. For an example, the Roblox method Color3.toHSV
expects a Color3
object--no constant inside it could be correct, so this is defined as:
+"any"
- Allows any value.
+"bool"
, "function"
, "nil"
, "number"
, "string"
, "table"
- Expects a value of the respective type.
+"..."
- Allows any number of variables after this one. If required
is true (it is by default), then this will lint if no additional arguments are given. It is incorrect to have this in the middle.
+- Constant list of strings - Will check if the value provided is one of the strings in the list. For example,
collectgarbage
only takes one of a few exact string arguments--doing collectgarbage("count")
will work, but collectgarbage("whoops")
won't.
+{ "display": string }
- Used when no constant could possibly be correct. If a constant is used, selene will tell the user that an argument of the type (display) is required. For an example, the Roblox method Color3.toHSV
expects a Color3
object--no constant inside it could be correct, so this is defined as:
---
globals:
@@ -619,10 +619,10 @@ Properties
The value of property tells selene how it can be mutated and used:
-"read-only"
- New fields cannot be added or set, and the variable itself cannot be redefined.
-"new-fields"
- New fields can be added and set, but variable itself cannot be redefined. In the case of _G, it means that _G = "foo"
is linted against.
-"override-fields"
- New fields can't be added, but entire variable can be overridden. In the case of Roblox's Instance.Name
, it means we can do Instance.Name = "Hello"
, but not Instance.Name.Call()
.
-"full-write"
- New fields can be added and entire variable can be overridden.
+"read-only"
- New fields cannot be added or set, and the variable itself cannot be redefined.
+"new-fields"
- New fields can be added and set, but variable itself cannot be redefined. In the case of _G, it means that _G = "foo"
is linted against.
+"override-fields"
- New fields can't be added, but entire variable can be overridden. In the case of Roblox's Instance.Name
, it means we can do Instance.Name = "Hello"
, but not Instance.Name.Call()
.
+"full-write"
- New fields can be added and entire variable can be overridden.
Struct
---
@@ -649,9 +649,9 @@ Deprecated
- type: table
- type: number
deprecated:
- message: "`table.getn` has been superseded by #."
+ message: "`table.getn` has been superseded by #."
replace:
- - "#%1"
+ - "#%1"
The deprecated field consists of two subfields.
message
is required, and is a human readable explanation of what the deprecation is, and potentially why.
@@ -667,11 +667,11 @@ Deprecated
globals:
call:
deprecated:
- message: "call will be removed in the next version"
+ message: "call will be removed in the next version"
replace:
- - "newcall(%...)"
+ - "newcall(%...)"
args:
- - type: "..."
+ - type: "..."
required: false
...will suggest newcall(1, 2, 3)
for call(1, 2, 3)
, and newcall()
for call()
.
@@ -702,11 +702,11 @@ Structs
...and selene will know that workspace.Changed:Connect(callback)
is valid, but workspace.Changed:RandomNameHere()
is not.
Wildcards
Fields can specify requirements if a field is referenced that is not explicitly named. For example, in Roblox, instances can have arbitrary fields of other instances (workspace.Baseplate
indexes an instance named Baseplate inside workspace
, but Baseplate
is nowhere in the Roblox API).
-We can specify this behavior by using the special "*"
field.
+We can specify this behavior by using the special "*"
field.
workspace.*:
struct: Instance
-This will tell selene "any field accessed from workspace
that doesn't exist must be an Instance struct".
+This will tell selene "any field accessed from workspace
that doesn't exist must be an Instance struct".
Wildcards can even be used in succession. For example, consider the following:
script.Name:
property: override-fields
@@ -716,7 +716,7 @@ Wildcards
Ignoring the wildcard, so far this means:
-script.Name = "Hello"
will work.
+script.Name = "Hello"
will work.
script = nil
will not work, because the writability of script
is not specified.
script.Name.UhOh
will not work, because script.Name
does not have fields.
@@ -729,7 +729,7 @@ Wildcards
Internal properties
There are some properties that exist in standard library YAMLs that exist specifically for internal purposes. This is merely a reference, but these are not guaranteed to be stable.
name
-This specifies the name of the standard library. This is used internally for cases such as only giving Roblox lints if the standard library is named "roblox"
.
+This specifies the name of the standard library. This is used internally for cases such as only giving Roblox lints if the standard library is named "roblox"
.
last_updated
A timestamp of when the standard library was last updated. This is used by the Roblox standard library generator to update when it gets too old.
last_selene_version
@@ -738,24 +738,24 @@ roblox_classes<
A map of every Roblox class and their properties, for roblox_incorrect_roact_usage.
Roblox Guide
selene is built with Roblox development in mind, and has special features for Roblox developers.
-If you try to run selene on a Roblox codebase, you'll get a bunch of errors saying things such as "game
is not defined". This is because these are Roblox specific globals that selene does not know about. You'll need to install the Roblox standard library in order to fix these issues, as well as get Roblox specific lints.
+If you try to run selene on a Roblox codebase, you'll get a bunch of errors saying things such as "game
is not defined". This is because these are Roblox specific globals that selene does not know about. You'll need to install the Roblox standard library in order to fix these issues, as well as get Roblox specific lints.
Installation
Thankfully, this process is very simple. All you need to do is edit your selene.toml
(or create one) and add the following:
-std = "roblox"
+std = "roblox"
-The next time you run selene, or if you use the Visual Studio Code extension and start typing Lua code, a Roblox standard library will be automatically generated and used. This is an automatic process that occurs whenever you don't have a cached standard library file and your selene.toml
has std = "roblox"
.
+The next time you run selene, or if you use the Visual Studio Code extension and start typing Lua code, a Roblox standard library will be automatically generated and used. This is an automatic process that occurs whenever you don't have a cached standard library file and your selene.toml
has std = "roblox"
.
Updating definitions
The Roblox standard library file is updated automatically every 6 hours. If you need an update faster than that, you can run selene update-roblox-std
manually.
TestEZ Support
Roblox has provided an open source testing utility called TestEZ, which allows you to write unit tests for your code. Writing unit tests is good practice, but selene will get angry at you if you don't include a testez.yml
file and set the standard library to the following:
-std = "roblox+testez"
+std = "roblox+testez"
But first you'll need to create a testez.yml
file, which you can do so with this template.
Pinned standard library
There may be cases where you would rather not have selene automatically update the Roblox standard library, such as if speed is critically important and you want to limit potential internet access (generating the standard library requires an active internet connection).
-selene supports "pinning" the standard library to a specific version.
+selene supports "pinning" the standard library to a specific version.
Add the following to your selene.toml
configuration:
# `floating` by default, meaning it is stored in a cache folder on your system
-roblox-std-source = "pinned"
+roblox-std-source = "pinned"
This will generate the standard library file into roblox.yml
where it is run.
You can also create a roblox.yml
file manually with selene generate-roblox-std
.
@@ -843,17 +843,17 @@ Writing tests
fn test_cool_lint() {
test_lint(
CoolLint::new(()).unwrap(),
- "cool_lint",
- "cool_lint",
+ "cool_lint",
+ "cool_lint",
);
}
}
Let's discuss what this code means, assuming you're familiar with the way tests are written and performed in Rust.
The test_lint
function is the easiest way to test that a lint works. It'll search the source code we made before, run selene on it (only your lint), and check its results with the existing [filename].stderr
file, or create one if it does not yet exist.
-The first argument is the lint object to use. CoolLint::new(())
just means "create CoolLint
with a configuration of ()
". If your lint specifies a configuration, this will instead be something such as CoolLintConfig::default()
or whatever you're specifically testing.
+The first argument is the lint object to use. CoolLint::new(())
just means "create CoolLint
with a configuration of ()
". If your lint specifies a configuration, this will instead be something such as CoolLintConfig::default()
or whatever you're specifically testing.
The .unwrap()
is just because CoolLint::new
returns a Result
. If you want to test configuration errors, you can avoid test_lint
altogether and just test CoolLint::new(...).is_err()
directly.
-The first "cool_lint"
is the name of the folder we created. The second "cool_lint"
is the name of the Lua file we created.
+The first "cool_lint"
is the name of the folder we created. The second "cool_lint"
is the name of the Lua file we created.
Now, just run cargo test
, and a .stderr
file will be automatically generated! You can manipulate it however you see fit as well as modifying your lint, and so long as the file is there, selene will make sure that its accurate.
Optionally, you can add a .std.toml
with the same name as the test next to the lua file, where you can specify a custom standard library to use. If you do not, the Lua 5.1 standard library will be used.
Documenting it
@@ -884,7 +884,7 @@ Documenting it<
## Remarks
If there's anything else a user should know when using this lint, write it here.
-This isn't a strict format, and you can mess with it as appropriate. For example, standard_library
does not have a "Why this is bad" section as not only is it a very encompassing lint, but it should be fairly obvious. Many lints don't specify a "...should be written as..." as it is either something with various potential fixes (such as global_usage
) or because the "good code" is just removing parts entirely (such as unbalanced_assignments
).
+This isn't a strict format, and you can mess with it as appropriate. For example, standard_library
does not have a "Why this is bad" section as not only is it a very encompassing lint, but it should be fairly obvious. Many lints don't specify a "...should be written as..." as it is either something with various potential fixes (such as global_usage
) or because the "good code" is just removing parts entirely (such as unbalanced_assignments
).
Lints
The following is the list of lints that selene will check for in your code.
almost_swapped
@@ -905,7 +905,7 @@ What it doesWhy this is bad
This will always fail.
Example
-if x == { "a", "b", "c" } then
+if x == { "a", "b", "c" } then
...will never pass.
if x == {} then
@@ -919,7 +919,7 @@ What it doesWhy this is bad
Deprecated fields may not be getting any support, or even face the possibility of being removed.
Configuration
-allow
- A list of patterns where the deprecated lint will not throw. For instance, ["table.getn"]
will allow you to use table.getn
, even though it is deprecated. This supports wildcards, so table.*
will allow both table.getn
and table.foreach
.
+allow
- A list of patterns where the deprecated lint will not throw. For instance, ["table.getn"]
will allow you to use table.getn
, even though it is deprecated. This supports wildcards, so table.*
will allow both table.getn
and table.foreach
.
Example
local count = table.getn(x)
@@ -948,23 +948,23 @@ Example
local foo = {
a = 1,
b = 5,
- ["a"] = 3, -- duplicate definition
+ ["a"] = 3, -- duplicate definition
c = 3,
b = 1, -- duplicate definition
}
local bar = {
- "foo",
- "bar",
- [1524] = "hello",
- "baz",
- "foobar",
- [2] = "goodbye", -- duplicate to `bar` which has key `2`
+ "foo",
+ "bar",
+ [1524] = "hello",
+ "baz",
+ "foobar",
+ [2] = "goodbye", -- duplicate to `bar` which has key `2`
}
Remarks
Only handles keys which constant string/number literals or named (such as { a = true }
).
-Array-like values are also handled, where {"foo"}
is implicitly handled as { [1] = "foo" }
.
+Array-like values are also handled, where {"foo"}
is implicitly handled as { [1] = "foo" }
.
empty_if
What it does
Checks for empty if blocks.
@@ -1020,17 +1020,17 @@ Configuration
maximum_complexity
(default: 40
) - A number that determines the maximum threshold for cyclomatic complexity, beyond which the lint will report.
Example
function MyComponent(props)
- if props.option1 == "enum_value1" then -- 1st path
- return React.createElement("Instance")
- elseif props.option1 == "enum_value2" -- 2nd path
+ if props.option1 == "enum_value1" then -- 1st path
+ return React.createElement("Instance")
+ elseif props.option1 == "enum_value2" -- 2nd path
or props.option2 == nil then -- 3rd path
return React.createElement(
- "TextLabel",
- { Text = if _G.__DEV__ then "X" else "Y" }-- 4th path
+ "TextLabel",
+ { Text = if _G.__DEV__ then "X" else "Y" }-- 4th path
)
else
return if props.option2 == true -- 5th path
- then React.createElement("Frame")
+ then React.createElement("Frame")
else nil
end
end
@@ -1038,7 +1038,7 @@ Example
Remarks
This lint is off by default. In order to enable it, add this to your selene.toml:
[lints]
-high_cyclomatic_complexity = "warn" # Or "deny"
+high_cyclomatic_complexity = "warn" # Or "deny"
if_same_then_else
What it does
@@ -1108,7 +1108,7 @@ Remarks
The detected looping patterns are pairs(t)
, ipairs(t)
, next, t
, and t
(Luau generalized iteration). If ipairs
is used, table.clone
is not an exact match if the table is not exclusively an array. For example:
local mixedTable = { 1, 2, 3 }
-mixedTable.key = "value"
+mixedTable.key = "value"
local clone = {}
@@ -1134,18 +1134,18 @@ Remarks
foo(1)
could be used when meaning foo(1, nil)
.
If a defined function is reassigned anywhere in the program, it will try to match the best possible overlap. Take this example:
local function foo(a, b, c)
- print("a")
+ print("a")
end
function updateFoo()
foo = function(a, b, c, d)
- print("b")
+ print("b")
end
end
-foo(1, 2, 3, 4) --> "a" [mismatched args, but selene doesn't know]
+foo(1, 2, 3, 4) --> "a" [mismatched args, but selene doesn't know]
updateFoo()
-foo(1, 2, 3, 4) --> "b" [no more mismatched args]
+foo(1, 2, 3, 4) --> "b" [no more mismatched args]
selene can not tell that foo
corresponds to a new definition because updateFoo()
was called in the current context, without actually running the program.
However, this would still lint properly:
@@ -1160,7 +1160,7 @@ Remarks
end
-- No definition of `log` takes more than 1 argument, so this will lint.
-log("LOG MESSAGE", "Something happened!")
+log("LOG MESSAGE", "Something happened!")
mixed_table
What it does
@@ -1169,8 +1169,8 @@ Why thi
Mixed tables harms readability and are prone to bugs. There is almost always a better alternative.
Example
local foo = {
- "array field",
- bar = "dictionary field",
+ "array field",
+ bar = "dictionary field",
}
multiple_statements
@@ -1179,11 +1179,11 @@ What it does<
Why this is bad
This can make your code difficult to read.
Configuration
-one_line_if
(default: "break-return-only"
) - Defines whether or not one line if statements should be allowed. One of three options:
+one_line_if
(default: "break-return-only"
) - Defines whether or not one line if statements should be allowed. One of three options:
-- "break-return-only" (default) -
if x then return end
or if x then break end
is ok, but if x then call() end
is not.
-- "allow" - All one line if statements are allowed.
-- "deny" - No one line if statements are allowed.
+- "break-return-only" (default) -
if x then return end
or if x then break end
is ok, but if x then call() end
is not.
+- "allow" - All one line if statements are allowed.
+- "deny" - No one line if statements are allowed.
Example
foo() bar() baz()
@@ -1206,7 +1206,7 @@ Example
...as bit32.bor
only produces a new value, it does not mutate anything.
Remarks
-The output is deemed "unused" if the function call is its own statement.
+The output is deemed "unused" if the function call is its own statement.
parenthese_conditions
What it does
Checks for conditions in the form of (expression)
.
@@ -1239,20 +1239,20 @@ Why thi
This is guaranteed to fail once it is rendered. Furthermore, the createElement itself will not error--only once it's mounted will it error.
Example
-- Using Roact17
-React.createElement("Frame", {
- key = "Valid property for React",
+React.createElement("Frame", {
+ key = "Valid property for React",
})
-- Using legacy Roact
-Roact.createElement("Frame", {
- key = "Invalid property for Roact",
+Roact.createElement("Frame", {
+ key = "Invalid property for Roact",
ThisPropertyDoesntExist = true,
- Name = "This property should not be passed in",
+ Name = "This property should not be passed in",
[Roact.Event.ThisEventDoesntExist] = function() end,
})
-Roact.createElement("BadClass", {})
+Roact.createElement("BadClass", {})
Remarks
This lint is naive and makes several assumptions about the way you write your code. The assumptions are based on idiomatic Roact.
@@ -1284,7 +1284,7 @@ What it does<
Why this is bad
This can cause confusion when reading the code when trying to understand which variable is being used, and if you want to use the original variable you either have to redefine it under a temporary name or refactor the code that shadowed it.
Configuration
-ignore_pattern
(default: "^_"
) - A regular expression that is used to specify names that are allowed to be shadowed. The default allows for variables like _
to be shadowed, as they shouldn't be used anyway.
+ignore_pattern
(default: "^_"
) - A regular expression that is used to specify names that are allowed to be shadowed. The default allows for variables like _
to be shadowed, as they shouldn't be used anyway.
Example
local x = 1
@@ -1305,14 +1305,14 @@ Example
type_check_inside_call
What it does
-Checks for type(foo == "type")
, instead of type(foo) == "type"
.
+Checks for type(foo == "type")
, instead of type(foo) == "type"
.
Why this is bad
-This will always return "boolean"
, and is undoubtedly not what you intended to write.
+This will always return "boolean"
, and is undoubtedly not what you intended to write.
Example
-return type(foo == "number")
+return type(foo == "number")
...should be written as...
-return type(foo) == "number"
+return type(foo) == "number"
Remarks
When using the Roblox standard library, this checks typeof
as well.
@@ -1354,7 +1354,7 @@ Why thi
This is most likely a typo.
Example
-- vv oops!
-prinnt("hello, world!")
+prinnt("hello, world!")
Remarks
If you are using a different standard library where a global variable is defined that selene isn't picking up on, create a standard library that specifies it.
@@ -1364,7 +1364,7 @@ What it does<
Why this is bad
Unscoped variables make code harder to read and debug, as well as making it harder for selene to analyze.
Configuration
-ignore_pattern
(default: "^_"
) - A regular expression for variables that are allowed to be unscoped. The default allows for variables like _
to be unscoped, as they shouldn't be used anyway.
+ignore_pattern
(default: "^_"
) - A regular expression for variables that are allowed to be unscoped. The default allows for variables like _
to be unscoped, as they shouldn't be used anyway.
Example
baz = 3
@@ -1375,7 +1375,7 @@ Why thi
The existence of unused variables could indicate buggy code.
Configuration
allow_unused_self
(default: true
) - A bool that determines whether not using self
in a method function (function Player:SwapWeapons()
) is allowed.
-ignore_pattern
(default: "^_"
) - A regular expression for variables that are allowed to be unused. The default allows for variables like _
to be unused, as they shouldn't be used anyway.
+ignore_pattern
(default: "^_"
) - A regular expression for variables that are allowed to be unused. The default allows for variables like _
to be unused, as they shouldn't be used anyway.
Example
local foo = 1
@@ -1431,7 +1431,7 @@ Examples
[selene]
Anything under the key [selene]
is used for meta information. The following paths are accepted:
[selene.base]
- Used for specifying what standard library to be based off of. Currently only accepts built in standard libraries, meaning lua51
or lua52
.
-[selene.name]
- Used for specifying the name of the standard library. Used internally for cases such as only giving Roblox lints if the standard library is named "roblox"
.
+[selene.name]
- Used for specifying the name of the standard library. Used internally for cases such as only giving Roblox lints if the standard library is named "roblox"
.
[selene.structs]
- Used for declaring structs.
[globals]
This is where the magic happens. The globals
field is a dictionary where the keys are the globals you want to define. The value you give tells selene what the value can be, do, and provide.
@@ -1445,21 +1445,21 @@ Any
Functions
Example:
[[tonumber.args]]
-type = "any"
+type = "any"
[[tonumber.args]]
-type = "number"
+type = "number"
required = false
A field is a function if it contains an args
and/or method
field.
If method
is specified as true
and the function is inside a table, then it will require the function be called in the form of Table:FunctionName()
, instead of Table.FunctionName()
.
args
is an array of arguments, in order of how they're used in the function. An argument is in the form of:
required?: false | true | string;
-type: "any" | "bool" | "function" | "nil"
- | "number" | "string" | "table" | "..."
- | string[] | { "display": string }
+type: "any" | "bool" | "function" | "nil"
+ | "number" | "string" | "table" | "..."
+ | string[] | { "display": string }
-"required"
+"required"
true
- The default, this argument is required.
false
- This argument is optional.
@@ -1467,14 +1467,14 @@ "required"
Argument types
-"any"
- Allows any value.
-"bool"
, "function"
, "nil"
, "number"
, "string"
, "table"
- Expects a value of the respective type.
-"..."
- Allows any number of variables after this one. If required
is true (it is by default), then this will lint if no additional arguments are given. It is incorrect to have this in the middle.
-- Constant list of strings - Will check if the value provided is one of the strings in the list. For example,
collectgarbage
only takes one of a few exact string arguments--doing collectgarbage("count")
will work, but collectgarbage("whoops")
won't.
-{ "display": string }
- Used when no constant could possibly be correct. If a constant is used, selene will tell the user that an argument of the type (display) is required. For an example, the Roblox method Color3.toHSV
expects a Color3
object--no constant inside it could be correct, so this is defined as:
+"any"
- Allows any value.
+"bool"
, "function"
, "nil"
, "number"
, "string"
, "table"
- Expects a value of the respective type.
+"..."
- Allows any number of variables after this one. If required
is true (it is by default), then this will lint if no additional arguments are given. It is incorrect to have this in the middle.
+- Constant list of strings - Will check if the value provided is one of the strings in the list. For example,
collectgarbage
only takes one of a few exact string arguments--doing collectgarbage("count")
will work, but collectgarbage("whoops")
won't.
+{ "display": string }
- Used when no constant could possibly be correct. If a constant is used, selene will tell the user that an argument of the type (display) is required. For an example, the Roblox method Color3.toHSV
expects a Color3
object--no constant inside it could be correct, so this is defined as:
[[Color3.toHSV.args]]
-type = { display = "Color3" }
+type = { display = "Color3" }
Properties
Example:
@@ -1485,19 +1485,19 @@ Properties
The same goes for _G
, which is defined as:
[_G]
property = true
-writable = "new-fields"
+writable = "new-fields"
writable
is an optional field that tells selene how the property can be mutated and used:
-"new-fields"
- New fields can be added and set, but variable itself cannot be redefined. In the case of _G, it means that _G = "foo"
is linted against.
-"overridden"
- New fields can't be added, but entire variable can be overridden. In the case of Roblox's Instance.Name
, it means we can do Instance.Name = "Hello"
, but not Instance.Name.Call()
.
-"full"
- New fields can be added and entire variable can be overridden.
+"new-fields"
- New fields can be added and set, but variable itself cannot be redefined. In the case of _G, it means that _G = "foo"
is linted against.
+"overridden"
- New fields can't be added, but entire variable can be overridden. In the case of Roblox's Instance.Name
, it means we can do Instance.Name = "Hello"
, but not Instance.Name.Call()
.
+"full"
- New fields can be added and entire variable can be overridden.
If writable
is not specified, selene will assume it can neither have new fields associated with it nor can be overridden.
Struct
Example:
[game]
-struct = "DataModel"
+struct = "DataModel"
Specifies that the field is an instance of a struct. The value is the name of the struct.
Table
@@ -1522,32 +1522,32 @@ Structs
method = true
[[selene.structs.Event.Connect.args]]
-type = "function"
+type = "function"
From there, it can define:
[workspace.Changed]
-struct = "Event"
+struct = "Event"
...and selene will know that workspace.Changed:Connect(callback)
is valid, but workspace.Changed:RandomNameHere()
is not.
Wildcards
Fields can specify requirements if a field is referenced that is not explicitly named. For example, in Roblox, instances can have arbitrary fields of other instances (workspace.Baseplate
indexes an instance named Baseplate inside workspace
, but Baseplate
is nowhere in the Roblox API).
-We can specify this behavior by using the special "*"
field.
-[workspace."*"]
-struct = "Instance"
+We can specify this behavior by using the special "*"
field.
+[workspace."*"]
+struct = "Instance"
-This will tell selene "any field accessed from workspace
that doesn't exist must be an Instance struct".
+This will tell selene "any field accessed from workspace
that doesn't exist must be an Instance struct".
Wildcards can even be used in succession. For example, consider the following:
[script.Name]
property = true
-writable = "overridden"
+writable = "overridden"
-[script."*"."*"]
+[script."*"."*"]
property = true
-writable = "full"
+writable = "full"
Ignoring the wildcard, so far this means:
-script.Name = "Hello"
will work.
+script.Name = "Hello"
will work.
script = nil
will not work, because the writability of script
is not specified.
script.Name.UhOh
will not work, because script.Name
does not have fields.
diff --git a/roblox.html b/roblox.html
index ea6af51d..6e04fb20 100644
--- a/roblox.html
+++ b/roblox.html
@@ -175,24 +175,24 @@ selene Documentation
Roblox Guide
selene is built with Roblox development in mind, and has special features for Roblox developers.
-If you try to run selene on a Roblox codebase, you'll get a bunch of errors saying things such as "game
is not defined". This is because these are Roblox specific globals that selene does not know about. You'll need to install the Roblox standard library in order to fix these issues, as well as get Roblox specific lints.
+If you try to run selene on a Roblox codebase, you'll get a bunch of errors saying things such as "game
is not defined". This is because these are Roblox specific globals that selene does not know about. You'll need to install the Roblox standard library in order to fix these issues, as well as get Roblox specific lints.
Installation
Thankfully, this process is very simple. All you need to do is edit your selene.toml
(or create one) and add the following:
-std = "roblox"
+std = "roblox"
-The next time you run selene, or if you use the Visual Studio Code extension and start typing Lua code, a Roblox standard library will be automatically generated and used. This is an automatic process that occurs whenever you don't have a cached standard library file and your selene.toml
has std = "roblox"
.
+The next time you run selene, or if you use the Visual Studio Code extension and start typing Lua code, a Roblox standard library will be automatically generated and used. This is an automatic process that occurs whenever you don't have a cached standard library file and your selene.toml
has std = "roblox"
.
Updating definitions
The Roblox standard library file is updated automatically every 6 hours. If you need an update faster than that, you can run selene update-roblox-std
manually.
TestEZ Support
Roblox has provided an open source testing utility called TestEZ, which allows you to write unit tests for your code. Writing unit tests is good practice, but selene will get angry at you if you don't include a testez.yml
file and set the standard library to the following:
-std = "roblox+testez"
+std = "roblox+testez"
But first you'll need to create a testez.yml
file, which you can do so with this template.
Pinned standard library
There may be cases where you would rather not have selene automatically update the Roblox standard library, such as if speed is critically important and you want to limit potential internet access (generating the standard library requires an active internet connection).
-selene supports "pinning" the standard library to a specific version.
+selene supports "pinning" the standard library to a specific version.
Add the following to your selene.toml
configuration:
# `floating` by default, meaning it is stored in a cache folder on your system
-roblox-std-source = "pinned"
+roblox-std-source = "pinned"
This will generate the standard library file into roblox.yml
where it is run.
You can also create a roblox.yml
file manually with selene generate-roblox-std
.
diff --git a/searcher.js b/searcher.js
index d2b0aeed..dc03e0a0 100644
--- a/searcher.js
+++ b/searcher.js
@@ -316,7 +316,7 @@ window.search = window.search || {};
// Eventhandler for keyevents on `document`
function globalKeyHandler(e) {
- if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text') { return; }
+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text' || !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)) { return; }
if (e.keyCode === ESCAPE_KEYCODE) {
e.preventDefault();
diff --git a/usage/configuration.html b/usage/configuration.html
index d008875f..2f261972 100644
--- a/usage/configuration.html
+++ b/usage/configuration.html
@@ -179,17 +179,17 @@ Configuration
Changing the severity of lints
You can change the severity of lints by entering the following into selene.toml:
[lints]
-lint_1 = "severity"
-lint_2 = "severity"
+lint_1 = "severity"
+lint_2 = "severity"
...
-Where "severity" is one of the following:
+Where "severity" is one of the following:
-"allow"
- Don't check for this lint
-"warn"
- Warn for this lint
-"deny"
- Error for this lint
+"allow"
- Don't check for this lint
+"warn"
- Warn for this lint
+"deny"
- Error for this lint
-Note that "deny" and "warn" are effectively the same, only warn will give orange text while error gives red text, and they both have different counters.
+Note that "deny" and "warn" are effectively the same, only warn will give orange text while error gives red text, and they both have different counters.
Configuring specific lints
You can configure specific lints by entering the following into selene.toml:
[config]
@@ -204,19 +204,19 @@ Setting the standard library
Many lints use standard libraries for either verifying their correct usage or for knowing that variables exist where they otherwise wouldn't.
By default, selene uses Lua 5.1, though if we wanted to use the Lua 5.2 standard library, we would write:
-std = "lua52"
+std = "lua52"
...at the top of selene.toml. You can learn more about the standard library format on the standard library guide. The standard library given can either be one of the builtin ones (currently only lua51
and lua52
) or the filename of a standard library file in this format. For example, if we had a file named special.toml
, we would write:
-std = "special"
+std = "special"
Chaining the standard library
We can chain together multiple standard libraries by simply using a plus sign (+
) in between the names.
For example, if we had game.toml
and engine.toml
standard libraries, we could chain them together like so:
-std = "game+engine"
+std = "game+engine"
Excluding files from being linted
It is possible to exclude files from being linted using the exclude option:
-exclude = ["external/*", "*.spec.lua"]
+exclude = ["external/*", "*.spec.lua"]
diff --git a/usage/filtering.html b/usage/filtering.html
index 13f52a59..ba1a4d88 100644
--- a/usage/filtering.html
+++ b/usage/filtering.html
@@ -194,7 +194,7 @@ configuration:
[lints]
-unused_variable = "allow" # I'm fine with unused variables in code
+unused_variable = "allow" # I'm fine with unused variables in code
...and have this in a separate file:
-- I'm usually okay with unused variables, but not this one
diff --git a/usage/std.html b/usage/std.html
index acf8b98c..3180d435 100644
--- a/usage/std.html
+++ b/usage/std.html
@@ -210,22 +210,22 @@ Functions
If method
is specified as true
and the function is inside a table, then it will require the function be called in the form of Table:FunctionName()
, instead of Table.FunctionName()
.
args
is an array of arguments, in order of how they're used in the function. An argument is in the form of:
required?: false | true | string;
-type: "any" | "bool" | "function" | "nil"
- | "number" | "string" | "table" | "..."
- | string[] | { "display": string }
+type: "any" | "bool" | "function" | "nil"
+ | "number" | "string" | "table" | "..."
+ | string[] | { "display": string }
-"required"
+"required"
true
- The default, this argument is required.
false
- This argument is optional.
- A string - This argument is required, and not using it will give this as the reason why.
-"observes"
+"observes"
This field is used for allowing smarter introspection of how the argument given is used.
-- "read-write" - The default. This argument is potentially both written to and read from.
-- "read" - This argument is only read from. Currently unused.
-- "write" - This argument is only written to. Used by
unused_variable
to assist in detecting a variable only being written to, even if passed into a function.
+- "read-write" - The default. This argument is potentially both written to and read from.
+- "read" - This argument is only read from. Currently unused.
+- "write" - This argument is only written to. Used by
unused_variable
to assist in detecting a variable only being written to, even if passed into a function.
Example:
table.insert:
@@ -236,7 +236,7 @@ "observes"
-"must_use"
+"must_use"
This field is used for checking if the return value of a function is used.
false
- The default. The return value of this function does not need to be used.
@@ -250,11 +250,11 @@ "must_use"
Argument types
-"any"
- Allows any value.
-"bool"
, "function"
, "nil"
, "number"
, "string"
, "table"
- Expects a value of the respective type.
-"..."
- Allows any number of variables after this one. If required
is true (it is by default), then this will lint if no additional arguments are given. It is incorrect to have this in the middle.
-- Constant list of strings - Will check if the value provided is one of the strings in the list. For example,
collectgarbage
only takes one of a few exact string arguments--doing collectgarbage("count")
will work, but collectgarbage("whoops")
won't.
-{ "display": string }
- Used when no constant could possibly be correct. If a constant is used, selene will tell the user that an argument of the type (display) is required. For an example, the Roblox method Color3.toHSV
expects a Color3
object--no constant inside it could be correct, so this is defined as:
+"any"
- Allows any value.
+"bool"
, "function"
, "nil"
, "number"
, "string"
, "table"
- Expects a value of the respective type.
+"..."
- Allows any number of variables after this one. If required
is true (it is by default), then this will lint if no additional arguments are given. It is incorrect to have this in the middle.
+- Constant list of strings - Will check if the value provided is one of the strings in the list. For example,
collectgarbage
only takes one of a few exact string arguments--doing collectgarbage("count")
will work, but collectgarbage("whoops")
won't.
+{ "display": string }
- Used when no constant could possibly be correct. If a constant is used, selene will tell the user that an argument of the type (display) is required. For an example, the Roblox method Color3.toHSV
expects a Color3
object--no constant inside it could be correct, so this is defined as:
---
globals:
@@ -276,10 +276,10 @@ Properties
The value of property tells selene how it can be mutated and used:
-"read-only"
- New fields cannot be added or set, and the variable itself cannot be redefined.
-"new-fields"
- New fields can be added and set, but variable itself cannot be redefined. In the case of _G, it means that _G = "foo"
is linted against.
-"override-fields"
- New fields can't be added, but entire variable can be overridden. In the case of Roblox's Instance.Name
, it means we can do Instance.Name = "Hello"
, but not Instance.Name.Call()
.
-"full-write"
- New fields can be added and entire variable can be overridden.
+"read-only"
- New fields cannot be added or set, and the variable itself cannot be redefined.
+"new-fields"
- New fields can be added and set, but variable itself cannot be redefined. In the case of _G, it means that _G = "foo"
is linted against.
+"override-fields"
- New fields can't be added, but entire variable can be overridden. In the case of Roblox's Instance.Name
, it means we can do Instance.Name = "Hello"
, but not Instance.Name.Call()
.
+"full-write"
- New fields can be added and entire variable can be overridden.
Struct
---
@@ -306,9 +306,9 @@ Deprecated
- type: table
- type: number
deprecated:
- message: "`table.getn` has been superseded by #."
+ message: "`table.getn` has been superseded by #."
replace:
- - "#%1"
+ - "#%1"
The deprecated field consists of two subfields.
message
is required, and is a human readable explanation of what the deprecation is, and potentially why.
@@ -324,11 +324,11 @@ Deprecated
globals:
call:
deprecated:
- message: "call will be removed in the next version"
+ message: "call will be removed in the next version"
replace:
- - "newcall(%...)"
+ - "newcall(%...)"
args:
- - type: "..."
+ - type: "..."
required: false
...will suggest newcall(1, 2, 3)
for call(1, 2, 3)
, and newcall()
for call()
.
@@ -359,11 +359,11 @@ Structs
...and selene will know that workspace.Changed:Connect(callback)
is valid, but workspace.Changed:RandomNameHere()
is not.
Wildcards
Fields can specify requirements if a field is referenced that is not explicitly named. For example, in Roblox, instances can have arbitrary fields of other instances (workspace.Baseplate
indexes an instance named Baseplate inside workspace
, but Baseplate
is nowhere in the Roblox API).
-We can specify this behavior by using the special "*"
field.
+We can specify this behavior by using the special "*"
field.
workspace.*:
struct: Instance
-This will tell selene "any field accessed from workspace
that doesn't exist must be an Instance struct".
+This will tell selene "any field accessed from workspace
that doesn't exist must be an Instance struct".
Wildcards can even be used in succession. For example, consider the following:
script.Name:
property: override-fields
@@ -373,7 +373,7 @@ Wildcards
Ignoring the wildcard, so far this means:
-script.Name = "Hello"
will work.
+script.Name = "Hello"
will work.
script = nil
will not work, because the writability of script
is not specified.
script.Name.UhOh
will not work, because script.Name
does not have fields.
@@ -386,7 +386,7 @@ Wildcards
Internal properties
There are some properties that exist in standard library YAMLs that exist specifically for internal purposes. This is merely a reference, but these are not guaranteed to be stable.
name
-This specifies the name of the standard library. This is used internally for cases such as only giving Roblox lints if the standard library is named "roblox"
.
+This specifies the name of the standard library. This is used internally for cases such as only giving Roblox lints if the standard library is named "roblox"
.
last_updated
A timestamp of when the standard library was last updated. This is used by the Roblox standard library generator to update when it gets too old.
last_selene_version