-
Notifications
You must be signed in to change notification settings - Fork 27
Lua Style Guide (DEPRECATED)
This style guide contains a list of guidelines to follow for code changes to this project. It does not attempt to make arguments for the styles; its goal is to provide consistency across projects.
- Documentation
- Tables
- Strings
- Functions
- Properties
- Variables
- Variable Scope
- Conditional Expressions & Equality
- Blocks
- Whitespace
- Commas
- Semicolons
- Type Casting & Coercion
- Naming Conventions
- Accessors
- Constructors
- Modules
- Document function signatures using LDoc. Specifying typing information after each parameter or return value is a nice plus.
--- Load a local or remote manifest describing a repository.
-- All functions that use manifest tables assume they were obtained
-- through either this function or load_local_manifest.
-- @param repo_url string: URL or pathname for the repository.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @return table or (nil, string, [string]): A table representing the manifest,
-- or nil followed by an error message and an optional error code.
function manif.load_manifest(repo_url, lua_version)
-- code
end
- Use TODO and FIXME tags in comments. TODO indicates a missing feature to be implemented later. FIXME indicates a problem in the existing code (inefficient implementation, bug, unnecessary code, etc).
-- TODO: implement method
local function something()
-- FIXME: check conditions
end
-
Prefer LDoc comments over the function that explain what the function does than inline comments inside the function that explain how it does it. Ideally, the implementation should be simple enough so that comments aren't needed. If the function grows complex, split it into multiple functions so that their names explain what each part does.
-
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 }
-
Consider
nil
properties when selecting lengths. A good idea is to store ann
property on lists that contain the length (as noted in Storing Nils in Tables)-- nils don't count local list = {} list[0] = nil list[1] = 'item' print(#list) -- 0 print(select('#', list)) -- 1
-
When tables have functions, use
self
when referring to itself.-- bad local me = { fullname = function(this) return this.first_name + ' ' + this.last_name end } -- good local me = { fullname = function(self) return self.first_name + ' ' + self.last_name end }
-
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 thearg
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
- Assign variables with the smallest possible scope.
-- 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
-
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. Tab characters and 4-space tabs result in public flogging.
-- 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 this-is-my-object = {} 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
orhas
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 module should return a table or function.
-
The module should not use the global namespace for anything ever. The module should be a closure.
-
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.)
SWLegion.Dev • A Community Run Organization • Our Philosophy • Wiki