You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lua_style.md 14KB

Lua Style Guide

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

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. Testing
  18. 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
    }
    

    [⬆]

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

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

[⬆]

Testing

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

    [⬆]

License

[⬆]