From: Vsevolod Stakhov Date: Sat, 22 Dec 2018 14:30:39 +0000 (+0000) Subject: [Minor] Doc: Add Lua style guide X-Git-Tag: 1.9.0~386 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=51b39f712d38cb69ede71d62e84c9079234cdb70;p=rspamd.git [Minor] Doc: Add Lua style guide --- diff --git a/lua_style.md b/lua_style.md new file mode 100644 index 000000000..aec6059dc --- /dev/null +++ b/lua_style.md @@ -0,0 +1,733 @@ +# 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](#types) + 1. [Tables](#tables) + 1. [Strings](#strings) + 1. [Functions](#functions) + 1. [Properties](#properties) + 1. [Variables](#variables) + 1. [Conditional Expressions & Equality](#conditionals) + 1. [Blocks](#blocks) + 1. [Whitespace](#whitespace) + 1. [Commas](#commas) + 1. [Semicolons](#semicolons) + 1. [Type Casting & Coercion](#type-coercion) + 1. [Naming Conventions](#naming-conventions) + 1. [Accessors](#accessors) + 1. [Constructors](#constructors) + 1. [Modules](#modules) + 1. [Testing](#testing) + 1. [License](#license) + +## Types + + - **Primitives**: When you access a primitive type you work directly on its value + + + `string` + + `number` + + `boolean` + + `nil` + + ```lua + 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` + + ```lua + 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 + ``` + + **[[⬆]](#TOC)** + +## Tables + + - Use the constructor syntax for table property creation where possible. + + ```lua + -- bad + local player = {} + player.name = 'Jack' + player.class = 'Rogue' + + -- good + local player = { + name = 'Jack', + class = 'Rogue' + } + ``` + + - Define functions externally to table definition. + + ```lua + -- bad + local player = { + attack = function() + -- ...stuff... + end + } + + -- good + local function attack() + end + + local player = { + attack = attack + } + ``` + + **[[⬆]](#TOC)** + +## Strings + + - Use single quotes `''` for strings. + + ```lua + -- 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. + + ```lua + -- 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.' + ``` + + **[[⬆]](#TOC)** + + +## Functions + - Prefer lots of small functions to large, complex functions. [Smalls Functions Are Good For The Universe](http://kikito.github.io/blog/2012/03/16/small-functions-are-good-for-the-universe/). + + - Prefer function syntax over variable syntax. This helps differentiate + between named and anonymous functions. + + ```lua + -- 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. + + ```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. + + ```lua + -- 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 + ``` + + **[[⬆]](#TOC)** + + +## Properties + + - Use dot notation when accessing known properties. + + ```lua + 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. + + ```lua + local luke = { + jedi = true, + age = 28 + } + + local function getProp(prop) + return luke[prop] + end + + local isJedi = getProp('jedi') + ``` + + **[[⬆]](#TOC)** + + +## Variables + + - Always use `local` to declare variables. Not doing so will result in + global variables to avoid polluting the global namespace. + + ```lua + -- 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. + + ```lua + -- 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 + ``` + + **[[⬆]](#TOC)** + + +## Conditional Expressions & Equality + + - False and nil are *falsy* in conditional expressions. All else is true. + + ```lua + local str = '' + + if str then + -- true + end + ``` + + - Use shortcuts when you can, unless you need to know the difference between + false and nil. + + ```lua + -- 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. + + ```lua + --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. + + ```lua + --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. + + ```lua + 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 + ``` + + + **[[⬆]](#TOC)** + + +## Blocks + + - Single line blocks are okay for *small* statements. Try to keep lines to 80 characters. + Indent lines if they overflow past the limit. + + ```lua + -- 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 + ``` + + **[[⬆]](#TOC)** + + +## Whitespace + + - Use soft tabs set to 2 spaces. + + ```lua + -- 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. + + ```lua + -- 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. + + ```lua + -- bad + (function(global) + -- ...stuff... + end)(self) + ``` + + ```lua + -- good + (function(global) + -- ...stuff... + end)(self) + + ``` + + - Surround operators with spaces. + + ```lua + -- 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. + + ```lua + --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. + + ```lua + --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. + + **[[⬆]](#TOC)** + +## Commas + + - Leading commas aren't okay. An ending comma on the last item is okay but discouraged. + + ```lua + -- 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, + } + ``` + + **[[⬆]](#TOC)** + + +## Semicolons + + - **Nope.** Separate statements onto multiple lines. + + ```lua + -- bad + local whatever = 'sure'; + a = 1; b = 2 + + -- good + local whatever = 'sure' + a = 1 + b = 2 + ``` + + **[[⬆]](#TOC)** + + +## 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. + + ```lua + -- bad + local totalScore = reviewScore .. '' + + -- good + local totalScore = tostring(reviewScore) + ``` + + - Use `tonumber` for Numbers. + + ```lua + local inputValue = '4' + + -- bad + local val = inputValue * 1 + + -- good + local val = tonumber(inputValue) + ``` + + **[[⬆]](#TOC)** + + +## 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. + + ```lua + -- bad + local function q() + -- ...stuff... + end + + -- good + local function query() + -- ..stuff.. + end + ``` + + - Use underscores for ignored variables in loops. + + ```lua + --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. + + ```lua + -- 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. + + ```lua + -- bad + local player = require('player') + + -- good + local Player = require('player') + local me = Player({ name = 'Jack' }) + ``` + + **[[⬆]](#TOC)** + + - Use `is` or `has` for boolean-returning functions that are part of tables. + + ```lua + --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. + + ```lua + -- 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](http://lua-users.org/wiki/TheEssenceOfLoadingCode) + and therefore should usually be factories (a function returning a new instance of a table) + unless static (like utility libraries.) + + **[[⬆]](#TOC)** + +## Testing + + - Use [telescope](https://github.com/norman/telescope) for unit tests and Robot framework for functional testing. + Unit tests can rely on LuaJIT ffi module if C function testing is required. + + **[[⬆]](#TOC)** + +## License + + - Released under CC0 (Public Domain). + Information can be found at [http://creativecommons.org/publicdomain/zero/1.0/](http://creativecommons.org/publicdomain/zero/1.0/). + +**[[⬆]](#TOC)** \ No newline at end of file