Olivine Labs Lua Style Guide
Switch branches/tags
Nothing to show
Clone or download
ajacksified Merge pull request #15 from dmarcoux/fix_busted_url
Fixed busted URL in the Testing section of the README
Latest commit 15726c7 Jan 11, 2016
Permalink
Failed to load latest commit information.
README.md Fixed busted URL in the Testing section Jan 10, 2016

README.md

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.

Table of Contents

  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. Testing
  19. Performance
  20. Resources
  21. In the Wild
  22. Contributors
  23. License

Types

  • 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		

    [⬆]

Tables

  • 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.first_name + ' ' + this.last_name
      end
    }
    
    -- good
    local me = {
      fullname = function(self)
        return self.first_name + ' ' + self.last_name
      end
    }

    [⬆]

Strings

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

    [⬆]

Functions

  • 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

[⬆]

Properties

  • 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')

    [⬆]

Variables

  • 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

    [⬆]

Conditional Expressions & Equality

  • 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

    [⬆]

Blocks

  • 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

    [⬆]

Whitespace

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

    [⬆]

Commas

  • 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,
    }

    [⬆]

Semicolons

  • Nope. Separate statements onto multiple lines.

    -- bad
    local whatever = 'sure';
    a = 1; b = 2
    
    -- good
    local whatever = 'sure'
    a = 1
    b = 2

    [⬆]

Type Casting & Coercion

  • 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)

    [⬆]

Naming Conventions

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

Modules

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

[⬆]

File Structure

  • Files should be named in all lowercase.

  • Lua files should be in a top-level src folder. The main library file should be called modulename.lua.

  • Rockspecs, license, readme, etc should be in the top level.

  • Tests should be in a top-level spec folder.

  • Executables should be in a top-level bin folder.

  • Example:

    ./my_module
      bin/
        script.sh
    
      spec/
        my_module_spec.lua
        some_file.lua
    
      src/
        my_module.lua
        some_file.lua
    
      README.md
      LICENSE.md
    

Testing

  • Use busted and write lots of tests in a /spec folder. Separate tests by module.

  • Use descriptive describe and it blocks so it's obvious to see what precisely is failing.

  • Test interfaces. Don't test private methods. If you need to test something that is private, it probably shouldn't be private in the first place.

  • Example:

    ./my_module
      bin/
        script.sh
    
      spec/
        my_module_spec.lua
    
        util/
          formatters_spec.lua
    
      src/
        my_module.lua
    
        util/
          formatters.lua
    
      README.md
      LICENSE.md
    

    [⬆]

Contributors

License

[⬆]