This style guide contains a list of guidelines that we try to follow for Rspamd.
This guide is forked from https://github.com/Olivine-Labs/lua-style-guide
Primitives: When you access a primitive type you work directly on its value
string
number
boolean
nil
local foo = 1
local bar = foo
bar = 9
print(foo, bar) -- => 1 9
Complex: When you access a complex type you work on a reference to its value
table
function
userdata
local foo = { 1, 2 }
local bar = foo
bar[0] = 9
foo[1] = 3
print(foo[0], bar[0]) -- => 9 9
print(foo[1], bar[1]) -- => 3 3
print(foo[2], bar[2]) -- => 2 2
Use the constructor syntax for table property creation where possible.
-- bad
local player = {}
player.name = 'Jack'
player.class = 'Rogue'
-- good
local player = {
name = 'Jack',
class = 'Rogue'
}
Define functions externally to table definition.
-- bad
local player = {
attack = function()
-- ...stuff...
end
}
-- good
local function attack()
end
local player = {
attack = attack
}
Use single quotes ''
for strings.
-- bad
local name = "Bob Parr"
-- good
local name = 'Bob Parr'
-- bad
local fullName = "Bob " .. self.lastName
-- good
local fullName = 'Bob ' .. self.lastName
Strings longer than 80 characters should be written across multiple lines using concatenation. This allows you to indent nicely.
-- bad
local errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'
-- bad
local errorMessage = 'This is a super long error that \
was thrown because of Batman. \
When you stop to think about \
how Batman had anything to do \
with this, you would get nowhere \
fast.'
-- bad
local errorMessage = [[This is a super long error that
was thrown because of Batman.
When you stop to think about
how Batman had anything to do
with this, you would get nowhere
fast.]]
-- good
local errorMessage = 'This is a super long error that ' ..
'was thrown because of Batman. ' ..
'When you stop to think about ' ..
'how Batman had anything to do ' ..
'with this, you would get nowhere ' ..
'fast.'
Prefer lots of small functions to large, complex functions. Smalls Functions Are Good For The Universe.
Prefer function syntax over variable syntax. This helps differentiate between named and anonymous functions.
-- bad
local nope = function(name, options)
-- ...stuff...
end
-- good
local function yup(name, options)
-- ...stuff...
end
Never name a parameter arg
, this will take precendence over the arg
object that is given to every function scope in older versions of Lua.
-- bad
local function nope(name, options, arg)
-- ...stuff...
end
-- good
local function yup(name, options, ...)
-- ...stuff...
end
Perform validation early and return as early as possible.
-- bad
local is_good_name = function(name, options, arg)
local is_good = #name > 3
is_good = is_good and #name < 30
-- ...stuff...
return is_bad
end
-- good
local is_good_name = function(name, options, args)
if #name < 3 or #name > 30 then return false end
-- ...stuff...
return true
end
Use dot notation when accessing known properties.
local luke = {
jedi = true,
age = 28
}
-- bad
local isJedi = luke['jedi']
-- good
local isJedi = luke.jedi
Use subscript notation []
when accessing properties with a variable
or if using a table as a list.
local luke = {
jedi = true,
age = 28
}
local function getProp(prop)
return luke[prop]
end
local isJedi = getProp('jedi')
Always use local
to declare variables. Not doing so will result in
global variables to avoid polluting the global namespace.
-- bad
superPower = SuperPower()
-- good
local superPower = SuperPower()
Assign variables at the top of their scope where possible. This makes it easier to check for existing variables.
-- bad
local bad = function()
test()
print('doing stuff..')
//..other stuff..
local name = getName()
if name == 'test' then
return false
end
return name
end
-- good
local function good()
local name = getName()
test()
print('doing stuff..')
//..other stuff..
if name == 'test' then
return false
end
return name
end
False and nil are falsy in conditional expressions. All else is true.
local str = ''
if str then
-- true
end
Use shortcuts when you can, unless you need to know the difference between false and nil.
-- bad
if name ~= nil then
-- ...stuff...
end
-- good
if name then
-- ...stuff...
end
Prefer true statements over false statements where it makes sense. Prioritize truthy conditions when writing multiple conditions.
--bad
if not thing then
-- ...stuff...
else
-- ...stuff...
end
--good
if thing then
-- ...stuff...
else
-- ...stuff...
end
Prefer defaults to else
statements where it makes sense. This results in
less complex and safer code at the expense of variable reassignment, so
situations may differ.
--bad
local function full_name(first, last)
local name
if first and last then
name = first .. ' ' .. last
else
name = 'John Smith'
end
return name
end
--good
local function full_name(first, last)
local name = 'John Smith'
if first and last then
name = first .. ' ' .. last
end
return name
end
Short ternaries are okay.
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
Single line blocks are okay for small statements. Try to keep lines to 80 characters. Indent lines if they overflow past the limit.
-- good
if test then return false 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
Use soft tabs set to 2 spaces.
-- bad
function()
∙∙∙∙local name
end
-- bad
function()
∙local name
end
-- good
function()
∙∙local name
end
Place 1 space before opening and closing braces. Place no spaces around parens.
-- bad
local test = {one=1}
-- good
local test = { one = 1 }
-- bad
dog.set('attr',{
age = '1 year',
breed = 'Bernese Mountain Dog'
})
-- good
dog.set('attr', {
age = '1 year',
breed = 'Bernese Mountain Dog'
})
Place an empty newline at the end of the file.
-- bad
(function(global)
-- ...stuff...
end)(self)
-- good
(function(global)
-- ...stuff...
end)(self)
Surround operators with spaces.
-- bad
local thing=1
thing = thing-1
thing = thing*1
thing = 'string'..'s'
-- good
local thing = 1
thing = thing - 1
thing = thing * 1
thing = 'string' .. 's'
Use one space after commas.
--bad
local thing = {1,2,3}
thing = {1 , 2 , 3}
thing = {1 ,2 ,3}
--good
local thing = {1, 2, 3}
Add a line break after multiline blocks.
--bad
if thing then
-- ...stuff...
end
function derp()
-- ...stuff...
end
local wat = 7
--good
if thing then
-- ...stuff...
end
function derp()
-- ...stuff...
end
local wat = 7
Delete unnecessary whitespace at the end of lines.
Leading commas aren’t okay. An ending comma on the last item is okay but discouraged.
-- bad
local thing = {
once = 1
, upon = 2
, aTime = 3
}
-- good
local thing = {
once = 1,
upon = 2,
aTime = 3
}
-- okay
local thing = {
once = 1,
upon = 2,
aTime = 3,
}
Nope. Separate statements onto multiple lines.
-- bad
local whatever = 'sure';
a = 1; b = 2
-- good
local whatever = 'sure'
a = 1
b = 2
Perform type coercion at the beginning of the statement. Use the built-in functions. (tostring
, tonumber
, etc.)
Use tostring
for strings if you need to cast without string concatenation.
-- bad
local totalScore = reviewScore .. ''
-- good
local totalScore = tostring(reviewScore)
Use tonumber
for Numbers.
local inputValue = '4'
-- bad
local val = inputValue * 1
-- good
local val = tonumber(inputValue)
Avoid single letter names. Be descriptive with your naming. You can get away with single-letter names when they are variables in loops.
-- bad
local function q()
-- ...stuff...
end
-- good
local function query()
-- ..stuff..
end
Use underscores for ignored variables in loops.
--good
for _, name in pairs(names) do
-- ...stuff...
end
Use snake_case when naming objects, functions, and instances. Tend towards verbosity if unsure about naming.
-- bad
local OBJEcttsssss = {}
local thisIsMyObject = {}
local c = function()
-- ...stuff...
end
-- good
local this_is_my_object = {}
local function do_that_thing()
-- ...stuff...
end
Use PascalCase for factories.
-- bad
local player = require('player')
-- good
local Player = require('player')
local me = Player({ name = 'Jack' })
Use is
or has
for boolean-returning functions that are part of tables.
--bad
local function evil(alignment)
return alignment < 100
end
--good
local function is_evil(alignment)
return alignment < 100
end
The file should be named like the module.
-- thing.lua
local thing = { }
local meta = {
__call = function(self, key, vars)
print key
end
}
return setmetatable(thing, meta)
Note that modules are loaded as singletons and therefore should usually be factories (a function returning a new instance of a table) unless static (like utility libraries.)
Use telescope for unit tests and Robot framework for functional testing. Unit tests can rely on LuaJIT ffi module if C function testing is required.