mirror of
https://github.com/bjc/prosody.git
synced 2025-04-01 20:27:39 +03:00
doc/coding_style.{txt,md}: Update coding style guide
This commit is contained in:
parent
80e119abd6
commit
0666d291f6
2 changed files with 806 additions and 1 deletions
2
HACKERS
2
HACKERS
|
@ -5,7 +5,7 @@ involved you can join us on our mailing list and discussion rooms. More
|
||||||
information on these at https://prosody.im/discuss
|
information on these at https://prosody.im/discuss
|
||||||
|
|
||||||
Patches are welcome, though before sending we would appreciate if you read
|
Patches are welcome, though before sending we would appreciate if you read
|
||||||
docs/coding_style.txt for guidelines on how to format your code, and other tips.
|
docs/coding_style.md for guidelines on how to format your code, and other tips.
|
||||||
|
|
||||||
Documentation for developers can be found at https://prosody.im/doc/developers
|
Documentation for developers can be found at https://prosody.im/doc/developers
|
||||||
|
|
||||||
|
|
805
doc/coding_style.md
Normal file
805
doc/coding_style.md
Normal file
|
@ -0,0 +1,805 @@
|
||||||
|
|
||||||
|
# Prosody Coding Style Guide
|
||||||
|
|
||||||
|
This style guides lists the coding conventions used in the
|
||||||
|
[Prosody](https://prosody.im/) project. It is based heavily on the [style guide used by the LuaRocks project](https://github.com/luarocks/lua-style-guide).
|
||||||
|
|
||||||
|
## Indentation and formatting
|
||||||
|
|
||||||
|
* Prosody code is indented with tabs at the start of the line, a single
|
||||||
|
tab per logical indent level:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
for i, pkg in ipairs(packages) do
|
||||||
|
for name, version in pairs(pkg) do
|
||||||
|
if name == searched then
|
||||||
|
print(version)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Tab width is configurable in editors, so never assume a particular width.
|
||||||
|
Specically this means you should not mix tabs and spaces, or use tabs for
|
||||||
|
alignment of items at different indentation levels.
|
||||||
|
|
||||||
|
* Use LF (Unix) line endings.
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
|
||||||
|
* Comments are encouraged where necessary to explain non-obvious code.
|
||||||
|
|
||||||
|
* In general comments should be used to explain 'why', not 'how'
|
||||||
|
|
||||||
|
### Comment tags
|
||||||
|
|
||||||
|
A comment may be prefixed with one of the following tags:
|
||||||
|
|
||||||
|
* **FIXME**: Indicates a serious problem with the code that should be addressed
|
||||||
|
* **TODO**: Indicates an open task, feature request or code restructuring that
|
||||||
|
is primarily of interest to developers (otherwise it should be in the
|
||||||
|
issue tracker).
|
||||||
|
* **COMPAT**: Must be used on all code that is present only for backwards-compatibility,
|
||||||
|
and may be removed one day. For example code that is added to support old
|
||||||
|
or buggy third-party software or dependencies.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- TODO: implement method
|
||||||
|
local function something()
|
||||||
|
-- FIXME: check conditions
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variable names
|
||||||
|
|
||||||
|
* Variable names with larger scope should be more descriptive than those with
|
||||||
|
smaller scope. One-letter variable names should be avoided except for very
|
||||||
|
small scopes (less than ten lines) or for iterators.
|
||||||
|
|
||||||
|
* `i` should be used only as a counter variable in for loops (either numeric for
|
||||||
|
or `ipairs`).
|
||||||
|
|
||||||
|
* Prefer more descriptive names than `k` and `v` when iterating with `pairs`,
|
||||||
|
unless you are writing a function that operates on generic tables.
|
||||||
|
|
||||||
|
* Use `_` for ignored variables (e.g. in for loops:)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
do_something_with_item(item)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
* Generally all identifiers (variables and function names) should use `snake_case`,
|
||||||
|
i.e. lowercase words joined by `_`.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local OBJEcttsssss = {}
|
||||||
|
local thisIsMyObject = {}
|
||||||
|
local c = function()
|
||||||
|
-- ...stuff...
|
||||||
|
end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local this_is_my_object = {}
|
||||||
|
|
||||||
|
local function do_that_thing()
|
||||||
|
-- ...stuff...
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** The standard library uses lowercase APIs, with `joinedlowercase`
|
||||||
|
names, but this does not scale too well for more complex APIs. `snake_case`
|
||||||
|
tends to look good enough and not too out-of-place along side the standard
|
||||||
|
APIs.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
for _, name in pairs(names) do
|
||||||
|
-- ...stuff...
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
* Prefer using `is_` when naming boolean functions:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local function evil(alignment)
|
||||||
|
return alignment < 100
|
||||||
|
end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local function is_evil(alignment)
|
||||||
|
return alignment < 100
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
* `UPPER_CASE` is to be used sparingly, with "constants" only.
|
||||||
|
|
||||||
|
> **Rationale:** "Sparingly", since Lua does not have real constants. This
|
||||||
|
notation is most useful in libraries that bind C libraries, when bringing over
|
||||||
|
constants from C.
|
||||||
|
|
||||||
|
* Do not use uppercase names starting with `_`, they are reserved by Lua.
|
||||||
|
|
||||||
|
## Tables
|
||||||
|
|
||||||
|
* When creating a table, prefer populating its fields all at once, if possible:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local player = { name = "Jack", class = "Rogue" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* Items should be separated by commas. If there are many items, put each
|
||||||
|
key/value on a separate line and use a semi-colon after each item (including
|
||||||
|
the last one):
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local player = {
|
||||||
|
name = "Jack";
|
||||||
|
class = "Rogue";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** This makes the structure of your tables more evident at a glance.
|
||||||
|
Trailing commas make it quicker to add new fields and produces shorter diffs.
|
||||||
|
|
||||||
|
* Use plain `key` syntax whenever possible, use `["key"]` syntax when using names
|
||||||
|
that can't be represented as identifiers and avoid mixing representations in
|
||||||
|
a declaration:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local mytable = {
|
||||||
|
["1394-E"] = val1,
|
||||||
|
["UTF-8"] = val2,
|
||||||
|
["and"] = val2,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Strings
|
||||||
|
|
||||||
|
* Use `"double quotes"` for strings; use `'single quotes'` when writing strings
|
||||||
|
that contain double quotes.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local name = "Prosody"
|
||||||
|
local sentence = 'The name of the program is "Prosody"'
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** Double quotes are used as string delimiters in a larger number of
|
||||||
|
programming languages. Single quotes are useful for avoiding escaping when
|
||||||
|
using double quotes in literals.
|
||||||
|
|
||||||
|
## Line lengths
|
||||||
|
|
||||||
|
* There are no hard or soft limits on line lengths. Line lengths are naturally
|
||||||
|
limited by using one statement per line. If that still produces lines that are
|
||||||
|
too long (e.g. an expression that produces a line over 256-characters long,
|
||||||
|
for example), this means the expression is too complex and would do better
|
||||||
|
split into subexpressions with reasonable names.
|
||||||
|
|
||||||
|
> **Rationale:** No one works on VT100 terminals anymore. If line lengths are a proxy
|
||||||
|
for code complexity, we should address code complexity instead of using line
|
||||||
|
breaks to fit mind-bending statements over multiple lines.
|
||||||
|
|
||||||
|
## Function declaration syntax
|
||||||
|
|
||||||
|
* Prefer function syntax over variable syntax. This helps differentiate between
|
||||||
|
named and anonymous functions.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local nope = function(name, options)
|
||||||
|
-- ...stuff...
|
||||||
|
end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local function yup(name, options)
|
||||||
|
-- ...stuff...
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
* Perform validation early and return as early as possible.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local function is_good_name(name, options, arg)
|
||||||
|
local is_good = #name > 3
|
||||||
|
is_good = is_good and #name < 30
|
||||||
|
|
||||||
|
-- ...stuff...
|
||||||
|
|
||||||
|
return is_good
|
||||||
|
end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local function is_good_name(name, options, args)
|
||||||
|
if #name < 3 or #name > 30 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ...stuff...
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Function calls
|
||||||
|
|
||||||
|
* Even though Lua allows it, generally you should not omit parentheses
|
||||||
|
for functions that take a unique string literal argument.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local data = get_data"KRP"..tostring(area_number)
|
||||||
|
-- good
|
||||||
|
local data = get_data("KRP"..tostring(area_number))
|
||||||
|
local data = get_data("KRP")..tostring(area_number)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** It is not obvious at a glace what the precedence rules are
|
||||||
|
when omitting the parentheses in a function call. Can you quickly tell which
|
||||||
|
of the two "good" examples in equivalent to the "bad" one? (It's the second
|
||||||
|
one).
|
||||||
|
|
||||||
|
* You should not omit parenthesis for functions that take a unique table
|
||||||
|
argument on a single line. You may do so for table arguments that span several
|
||||||
|
lines.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local an_instance = a_module.new {
|
||||||
|
a_parameter = 42,
|
||||||
|
another_parameter = "yay",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** The use as in `a_module.new` above occurs alone in a statement,
|
||||||
|
so there are no precedence issues.
|
||||||
|
|
||||||
|
## Table attributes
|
||||||
|
|
||||||
|
* Use dot notation when accessing known properties.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local luke = {
|
||||||
|
jedi = true,
|
||||||
|
age = 28,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- bad
|
||||||
|
local is_jedi = luke["jedi"]
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local is_jedi = luke.jedi
|
||||||
|
```
|
||||||
|
|
||||||
|
* Use subscript notation `[]` when accessing properties with a variable or if using a table as a list.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local vehicles = load_vehicles_from_disk("vehicles.dat")
|
||||||
|
|
||||||
|
if vehicles["Porsche"] then
|
||||||
|
porsche_handler(vehicles["Porsche"])
|
||||||
|
vehicles["Porsche"] = nil
|
||||||
|
end
|
||||||
|
for name, cars in pairs(vehicles) do
|
||||||
|
regular_handler(cars)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** Using dot notation makes it clearer that the given key is meant
|
||||||
|
to be used as a record/object field.
|
||||||
|
|
||||||
|
## Functions in tables
|
||||||
|
|
||||||
|
* When declaring modules and classes, declare functions external to the table definition:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local my_module = {}
|
||||||
|
|
||||||
|
function my_module.a_function(x)
|
||||||
|
-- code
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
* When declaring metatables, declare function internal to the table definition.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local version_mt = {
|
||||||
|
__eq = function(a, b)
|
||||||
|
-- code
|
||||||
|
end;
|
||||||
|
__lt = function(a, b)
|
||||||
|
-- code
|
||||||
|
end;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** Metatables contain special behavior that affect the tables
|
||||||
|
they're assigned (and are used implicitly at the call site), so it's good to
|
||||||
|
be able to get a view of the complete behavior of the metatable at a glance.
|
||||||
|
|
||||||
|
This is not as important for objects and modules, which usually have way more
|
||||||
|
code, and which don't fit in a single screen anyway, so nesting them inside
|
||||||
|
the table does not gain much: when scrolling a longer file, it is more evident
|
||||||
|
that `check_version` is a method of `Api` if it says `function Api:check_version()`
|
||||||
|
than if it says `check_version = function()` under some indentation level.
|
||||||
|
|
||||||
|
## Variable declaration
|
||||||
|
|
||||||
|
* Always use `local` to declare variables.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
superpower = get_superpower()
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local superpower = get_superpower()
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** Not doing so will result in global variables to avoid polluting
|
||||||
|
the global namespace.
|
||||||
|
|
||||||
|
## Variable scope
|
||||||
|
|
||||||
|
* Assign variables with the smallest possible scope.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local function good()
|
||||||
|
local name = get_name()
|
||||||
|
|
||||||
|
test()
|
||||||
|
print("doing stuff..")
|
||||||
|
|
||||||
|
--...other stuff...
|
||||||
|
|
||||||
|
if name == "test" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local bad = function()
|
||||||
|
test()
|
||||||
|
print("doing stuff..")
|
||||||
|
|
||||||
|
--...other stuff...
|
||||||
|
|
||||||
|
local name = get_name()
|
||||||
|
|
||||||
|
if name == "test" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** Lua has proper lexical scoping. Declaring the function later means that its
|
||||||
|
scope is smaller, so this makes it easier to check for the effects of a variable.
|
||||||
|
|
||||||
|
## Conditional expressions
|
||||||
|
|
||||||
|
* False and nil are falsy in conditional expressions. Use shortcuts when you
|
||||||
|
can, unless you need to know the difference between false and nil.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
if name ~= nil then
|
||||||
|
-- ...stuff...
|
||||||
|
end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
if name then
|
||||||
|
-- ...stuff...
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
* Avoid designing APIs which depend on the difference between `nil` and `false`.
|
||||||
|
|
||||||
|
* Use the `and`/`or` idiom for the pseudo-ternary operator when it results in
|
||||||
|
more straightforward code. When nesting expressions, use parentheses to make it
|
||||||
|
easier to scan visually:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local function default_name(name)
|
||||||
|
-- return the default "Waldo" if name is nil
|
||||||
|
return name or "Waldo"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function brew_coffee(machine)
|
||||||
|
return (machine and machine.is_loaded) and "coffee brewing" or "fill your water"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the `x and y or z` as a substitute for `x ? y : z` does not work if
|
||||||
|
`y` may be `nil` or `false` so avoid it altogether for returning booleans or
|
||||||
|
values which may be nil.
|
||||||
|
|
||||||
|
## Blocks
|
||||||
|
|
||||||
|
* Use single-line blocks only for `then return`, `then break` and `function return` (a.k.a "lambda") constructs:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- good
|
||||||
|
if test then break end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
if not ok then return nil, "this failed for this reason: " .. reason end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
use_callback(x, function(k) return k.last end)
|
||||||
|
|
||||||
|
-- good
|
||||||
|
if test then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- bad
|
||||||
|
if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == 10 then do_other_complicated_function() end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == 10 then
|
||||||
|
do_other_complicated_function()
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
* Separate statements onto multiple lines. Do not use semicolons as statement terminators.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local whatever = "sure";
|
||||||
|
a = 1; b = 2
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local whatever = "sure"
|
||||||
|
a = 1
|
||||||
|
b = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Spacing
|
||||||
|
|
||||||
|
* Use a space after `--`.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
--bad
|
||||||
|
-- good
|
||||||
|
```
|
||||||
|
|
||||||
|
* Always put a space after commas and between operators and assignment signs:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local x = y*9
|
||||||
|
local numbers={1,2,3}
|
||||||
|
numbers={1 , 2 , 3}
|
||||||
|
numbers={1 ,2 ,3}
|
||||||
|
local strings = { "hello"
|
||||||
|
, "Lua"
|
||||||
|
, "world"
|
||||||
|
}
|
||||||
|
dog.set( "attr",{
|
||||||
|
age="1 year",
|
||||||
|
breed="Bernese Mountain Dog"
|
||||||
|
})
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local x = y * 9
|
||||||
|
local numbers = {1, 2, 3}
|
||||||
|
local strings = {
|
||||||
|
"hello";
|
||||||
|
"Lua";
|
||||||
|
"world";
|
||||||
|
}
|
||||||
|
dog.set("attr", {
|
||||||
|
age = "1 year",
|
||||||
|
breed = "Bernese Mountain Dog",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
* Indent tables and functions according to the start of the line, not the construct:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local my_table = {
|
||||||
|
"hello",
|
||||||
|
"world",
|
||||||
|
}
|
||||||
|
using_a_callback(x, function(...)
|
||||||
|
print("hello")
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local my_table = {
|
||||||
|
"hello";
|
||||||
|
"world";
|
||||||
|
}
|
||||||
|
using_a_callback(x, function(...)
|
||||||
|
print("hello")
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** This keep indentation levels aligned at predictable places. You don't
|
||||||
|
need to realign the entire block if something in the first line changes (such as
|
||||||
|
replacing `x` with `xy` in the `using_a_callback` example above).
|
||||||
|
|
||||||
|
* The concatenation operator gets a pass for avoiding spaces:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- okay
|
||||||
|
local message = "Hello, "..user.."! This is your day # "..day.." in our platform!"
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** Being at the baseline, the dots already provide some visual spacing.
|
||||||
|
|
||||||
|
* No spaces after the name of a function in a declaration or in its arguments:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local function hello ( name, language )
|
||||||
|
-- code
|
||||||
|
end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local function hello(name, language)
|
||||||
|
-- code
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
* Add blank lines between functions:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local function foo()
|
||||||
|
-- code
|
||||||
|
end
|
||||||
|
local function bar()
|
||||||
|
-- code
|
||||||
|
end
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local function foo()
|
||||||
|
-- code
|
||||||
|
end
|
||||||
|
|
||||||
|
local function bar()
|
||||||
|
-- code
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
* Avoid aligning variable declarations:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local a = 1
|
||||||
|
local long_identifier = 2
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local a = 1
|
||||||
|
local long_identifier = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** This produces extra diffs which add noise to `git blame`.
|
||||||
|
|
||||||
|
* Alignment is occasionally useful when logical correspondence is to be highlighted:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- okay
|
||||||
|
sys_command(form, UI_FORM_UPDATE_NODE, "a", FORM_NODE_HIDDEN, false)
|
||||||
|
sys_command(form, UI_FORM_UPDATE_NODE, "sample", FORM_NODE_VISIBLE, false)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Typing
|
||||||
|
|
||||||
|
* In non-performance critical code, it can be useful to add type-checking assertions
|
||||||
|
for function arguments:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function manif.load_manifest(repo_url, lua_version)
|
||||||
|
assert(type(repo_url) == "string")
|
||||||
|
assert(type(lua_version) == "string" or not lua_version)
|
||||||
|
|
||||||
|
-- ...
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
* Use the standard functions for type conversion, avoid relying on coercion:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local total_score = review_score .. ""
|
||||||
|
|
||||||
|
-- good
|
||||||
|
local total_score = tostring(review_score)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
* Functions that can fail for reasons that are expected (e.g. I/O) should
|
||||||
|
return `nil` and a (string) error message on error, possibly followed by other
|
||||||
|
return values such as an error code.
|
||||||
|
|
||||||
|
* On errors such as API misuse, an error should be thrown, either with `error()`
|
||||||
|
or `assert()`.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
Follow [these guidelines](http://hisham.hm/2014/01/02/how-to-write-lua-modules-in-a-post-module-world/) for writing modules. In short:
|
||||||
|
|
||||||
|
* Always require a module into a local variable named after the last component of the module’s full name.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local bar = require("foo.bar") -- requiring the module
|
||||||
|
|
||||||
|
bar.say("hello") -- using the module
|
||||||
|
```
|
||||||
|
|
||||||
|
* Don’t rename modules arbitrarily:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local skt = require("socket")
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** Code is much harder to read if we have to keep going back to the top
|
||||||
|
to check how you chose to call a module.
|
||||||
|
|
||||||
|
* Start a module by declaring its table using the same all-lowercase local
|
||||||
|
name that will be used to require it. You may use an LDoc comment to identify
|
||||||
|
the whole module path.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
--- @module foo.bar
|
||||||
|
local bar = {}
|
||||||
|
```
|
||||||
|
|
||||||
|
* Try to use names that won't clash with your local variables. For instance, don't
|
||||||
|
name your module something like “size”.
|
||||||
|
|
||||||
|
* Use `local function` to declare _local_ functions only: that is, functions
|
||||||
|
that won’t be accessible from outside the module.
|
||||||
|
|
||||||
|
That is, `local function helper_foo()` means that `helper_foo` is really local.
|
||||||
|
|
||||||
|
* Public functions are declared in the module table, with dot syntax:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function bar.say(greeting)
|
||||||
|
print(greeting)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** Visibility rules are made explicit through syntax.
|
||||||
|
|
||||||
|
* Do not set any globals in your module and always return a table in the end.
|
||||||
|
|
||||||
|
* If you would like your module to be used as a function, you may set the
|
||||||
|
`__call` metamethod on the module table instead.
|
||||||
|
|
||||||
|
> **Rationale:** Modules should return tables in order to be amenable to have their
|
||||||
|
contents inspected via the Lua interactive interpreter or other tools.
|
||||||
|
|
||||||
|
* Requiring a module should cause no side-effect other than loading other
|
||||||
|
modules and returning the module table.
|
||||||
|
|
||||||
|
* A module should not have state. If a module needs configuration, turn
|
||||||
|
it into a factory. For example, do not make something like this:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- bad
|
||||||
|
local mp = require "MessagePack"
|
||||||
|
mp.set_integer("unsigned")
|
||||||
|
```
|
||||||
|
|
||||||
|
and do something like this instead:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- good
|
||||||
|
local messagepack = require("messagepack")
|
||||||
|
local mpack = messagepack.new({integer = "unsigned"})
|
||||||
|
```
|
||||||
|
|
||||||
|
* The invocation of require may omit parentheses around the module name:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local bla = require "bla"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Metatables, classes and objects
|
||||||
|
|
||||||
|
If creating a new type of object that has a metatable and methods, the
|
||||||
|
metatable and methods table should be separate, and the metatable name
|
||||||
|
should end with `_mt`.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local mytype_methods = {};
|
||||||
|
local mytype_mt = { __index = mytype_methods };
|
||||||
|
|
||||||
|
function mytype_methods:add_new_thing(thing)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function new()
|
||||||
|
return setmetatable({}, mytype_mt);
|
||||||
|
end
|
||||||
|
|
||||||
|
return { new = new };
|
||||||
|
```
|
||||||
|
|
||||||
|
* Use the method notation when invoking methods:
|
||||||
|
|
||||||
|
```
|
||||||
|
-- bad
|
||||||
|
my_object.my_method(my_object)
|
||||||
|
|
||||||
|
-- good
|
||||||
|
my_object:my_method()
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** This makes it explicit that the intent is to use the function as a method.
|
||||||
|
|
||||||
|
* Do not rely on the `__gc` metamethod to release resources other than memory.
|
||||||
|
If your object manage resources such as files, add a `close` method to their
|
||||||
|
APIs and do not auto-close via `__gc`. Auto-closing via `__gc` would entice
|
||||||
|
users of your module to not close resources as soon as possible. (Note that
|
||||||
|
the standard `io` library does not follow this recommendation, and users often
|
||||||
|
forget that not closing files immediately can lead to "too many open files"
|
||||||
|
errors when the program runs for a while.)
|
||||||
|
|
||||||
|
> **Rationale:** The garbage collector performs automatic *memory* management,
|
||||||
|
dealing with memory only. There is no guarantees as to when the garbage
|
||||||
|
collector will be invoked, and memory pressure does not correlate to pressure
|
||||||
|
on other resources.
|
||||||
|
|
||||||
|
## File structure
|
||||||
|
|
||||||
|
* Lua files should be named in all lowercase.
|
||||||
|
|
||||||
|
* Tests should be in a top-level `spec` directory. Prosody uses
|
||||||
|
[Busted](http://olivinelabs.com/busted/) for testing.
|
||||||
|
|
||||||
|
## Static checking
|
||||||
|
|
||||||
|
All code should pass [luacheck](https://github.com/mpeterv/luacheck) using
|
||||||
|
the `.luacheckrc` provided in the Prosody repository, and using miminal
|
||||||
|
inline exceptions.
|
||||||
|
|
||||||
|
* luacheck warnings of class 211, 212, 213 (unused variable, argument or loop
|
||||||
|
variable) may be ignored, if the unused variable was added explicitly: for
|
||||||
|
example, sometimes it is useful, for code understandability, to spell out what
|
||||||
|
the keys and values in a table are, even if you're only using one of them.
|
||||||
|
Another example is a function that needs to follow a given signature for API
|
||||||
|
reasons (e.g. a callback that follows a given format) but doesn't use some of
|
||||||
|
its arguments; it's better to spell out in the argument what the API the
|
||||||
|
function implements is, instead of adding `_` variables.
|
||||||
|
|
||||||
|
```
|
||||||
|
local foo, bar = some_function() --luacheck: ignore 212/foo
|
||||||
|
print(bar)
|
||||||
|
```
|
||||||
|
|
||||||
|
* luacheck warning 542 (empty if branch) can also be ignored, when a sequence
|
||||||
|
of `if`/`elseif`/`else` blocks implements a "switch/case"-style list of cases,
|
||||||
|
and one of the cases is meant to mean "pass". For example:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
if warning >= 600 and warning <= 699 then
|
||||||
|
print("no whitespace warnings")
|
||||||
|
elseif warning == 542 then --luacheck: ignore 542
|
||||||
|
-- pass
|
||||||
|
else
|
||||||
|
print("got a warning: "..warning)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rationale:** This avoids writing negated conditions in the final fallback
|
||||||
|
case, and it's easy to add another case to the construct without having to
|
||||||
|
edit the fallback.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue