Skip to content

Tibia-WindBot/lua-style-guide

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 

Repository files navigation

Lua Style Guide

This style guide contains a list of guidelines that we try to follow for our projects. It does not attempt to make arguments for the styles; its goal is to provide consistency across projects.

Feel free to fork this style guide and change to your own liking, and file issues / pull requests if you have questions, comments, or if you find any mistakes or typos.

  1. Types
  2. Tables
  3. Strings
  4. Functions
  5. Properties
  6. Variables
  7. Conditional Expressions & Equality
  8. Blocks
  9. Whitespace
  10. Commas
  11. Semicolons
  12. Type Casting & Coercion
  13. Naming Conventions
  14. Accessors
  15. Constructors
  16. Modules
  17. File Structure
  18. Contributors
  19. License
  • 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
    }
  • Consider nil properties when selecting lengths. A good idea is to store an n 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.firstName + ' ' + this.lastName
    	end
    }
    
    -- good
    local me = {
    	fullName = function(self)
    		return self.firstName + ' ' + self.lastName
    	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 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 isGoodName = function(name, options, arg)
    	local isGood = #name > 3
    	isGood = isGood and #name < 30
    
    	-- ...stuff...
    
    	return is_bad
    end
    
    -- good
    local isGoodName = 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 fullName(first, last)
    	local name
    
    	if first and last then
    		name = first .. ' ' .. last
    	else
    		name = 'John Smith'
    	end
    
    	return name
    end
    
    --good
    local function fullName(first, last)
    local name = 'John Smith'
    
    	if first and last then
    		name = first .. ' ' .. last
    	end
    
    	return name
    end
  • Short ternaries are okay.

    local function defaultName(name)
    	-- return the default 'Waldo' if name is nil
    	return name or 'Waldo'
    end
    
    local function brewCoffee(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 120 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 doComplicatedFunction(test) == false or seven == 8 and nine == 10 then doOtherComplicatedFunction() end
    
    -- good
    if
    	test < 1 and
    	doComplicatedFunction(test) == false or
    	seven == 8 and
    	nine == 10
    then
    
    	doOtherComplicatedFunction() 
    	return false 
    end

    [⬆]

  • Use hard tabs

    -- 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.

    -- bad
    local thing = {
    	once = 1
    ,	upon = 2
    ,	aTime = 3
    }
    
    -- good
    local thing = {
    	once = 1,
    	upon = 2,
    	aTime = 3
    }
    
    -- good
    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 camelCase when naming objects, local functions, and instances. Tend towards verbosity if unsure about naming.

    -- bad
    local OBJEcttsssss = {}
    local this_is_my_object = {}
    local this-is-my-object = {}
    
    local function dothathing()
    	-- ...stuff...
    end
    
    -- good
    local thisIsMyObject = {}
    
    local function doThatThing()
    	-- ...stuff...
    end
  • Use lowercase for global functions.

    -- bad
    function aLibraryFunction()
    	-- ...stuff...
    end
    
    -- good
    function alibraryfunction()
    	-- ...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 isEvil(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.)

[⬆]

  • Files should be named in all lowercase.

[⬆]

About

Tibis WindBot Lua Style Guide

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published