Difference between revisions of "Lua code style guidelines"

From Minetest Developer Wiki
Jump to navigation Jump to search
m (namespace tables might be phased out, but probably not *soon* :-))
 
(15 intermediate revisions by 6 users not shown)
Line 1: Line 1:
These are only guidelines for more readable code, in some (rare) cases they may result in less readable code and should not be followed.
 
  
hh#############################################################
+
This is largely based on [https://www.python.org/dev/peps/pep-0008 the Python style guide] except for some indentation and language syntax differences.  When in doubt, consult that guide.
hh#############################################################
 
  
DRAFT TO BE DISCUSSED!)
+
Note that these are only ''guidelines'' for more readable code.  In some (rare) cases they may result in ''less'' readable code.  Use your best judgement.
  
hh#############################################################
+
== Comments ==
hh#############################################################
 
  
== Comments ==
+
* Incorrect or outdated comments are worse than no comments.
  
 +
* Avoid inline comments, unless they're very short.
  
''Unspecific definition potentially resulting in shitload of discussions''
+
* Write comments to clarify anything that may be confusing. Don't write comments that describe things that are obvious from the code.
v------------------------------------------------------------------------------------------------
+
<span style="color: #282;">Good:</span> <source lang="Lua">width = width - 2  -- Adjust for 1px border on each side</source>
:::* Write comments to describe why something is done, or why it is done in a particular way. Don't write comments that describe things that are obvious from the code.
+
<span style="color: #b44;">Bad:</span> <source lang="Lua">width = width - 2  -- Decrement width by two</source>
::: Good: <source lang="lua">width = width - 2  -- Adjust for 1px border on each side</source>
 
::: Bad: <source lang="lua">width = width - 2  -- Decrement width by two</source>
 
^------------------------------------------------------------------------------------------------
 
  
* Comments should follow English grammar rules, this means starting with a capital letter, using commas and apostrophes where appropriate, and ending with a period. The period may be omitted in single-sentence comments. If the first word of a comment is an identifier it's casing should not be changed.
+
* Comments should follow English grammar rules, this means '''starting with a capital letter''', using commas and apostrophes where appropriate, and '''ending with a period'''. The period may be omitted in single-sentence comments. If the first word of a comment is an identifier, its casing should not be changed. '''Check the spelling.'''
<source lang="lua">
+
<source lang="Lua">
 
-- This is a properly formatted comment.
 
-- This is a properly formatted comment.
-- This isnt.              (Missing apostrophe)
+
-- This isnt.              (missing apostrophe)
-- neither is this.        (Lowercase first letter)
+
-- neither is this.        (lowercase first letter)
 
</source>
 
</source>
  
 
* Comments should have a space between the comment characters and the first word.
 
* Comments should have a space between the comment characters and the first word.
<source lang="lua">
+
<source lang="Lua">
 
--This is wrong.
 
--This is wrong.
 +
-- This is right.
 
</source>
 
</source>
  
 
* Inline comments should be separated from the code by two spaces.
 
* Inline comments should be separated from the code by two spaces.
<source lang="lua">
+
<source lang="Lua">
foo()  -- A proper comment
+
foo()  -- A proper comment.
 
</source>
 
</source>
  
 +
* If you write comments for a documentation generation tool, write the comments in LuaDoc format.
  
 +
* Short multi-line comments should use the regular single-line comment style.
  
''Missing multiline comment definition''
+
* Long multi-line comments should use Lua's multi-line comment format with no leading characters except a <code>--</code> before the closer.
 
+
<source lang="Lua">
v------------------------------------------------------------------------------------------------
+
--[[
^------------------------------------------------------------------------------------------------
+
Very long
 
+
multi-line comment.
 
+
--]]
 
+
</source>
''Why not doxygen?''
 
  
v------------------------------------------------------------------------------------------------
 
:::* If you write comments for a documentation generation tool, write the comments in LuaDoc format.
 
^------------------------------------------------------------------------------------------------
 
  
 
== Lines, spaces, and indentation ==
 
== Lines, spaces, and indentation ==
  
* Indentation is done with one tab per indentation level.  Tabs are considered to have eight space width.
+
* Indentation is done with one tab per indentation level.
  
* Lines are wrapped at 80 characters where possible, with an upper limit of 90.
+
* '''Lines are wrapped at 80 characters''' where possible, with a hard limit of 90.  If you need more you're probably doing something wrong.
  
  
 
=== Continuation lines ===
 
=== Continuation lines ===
  
* Conditionals have following lines indented by two tabs:
+
* Conditional expressions have continuation lines indented by two tabs.
<source lang="lua">
+
<source lang="Lua">
 
if long_function_call(with, many, arguments) and
 
if long_function_call(with, many, arguments) and
 
another_function_call() then
 
another_function_call() then
Line 67: Line 61:
 
end
 
end
 
</source>
 
</source>
 
+
<br/>
* Function arguments are indented by two tabs:
+
* Function arguments are indented by two tabs if multiple arguments are in a line, same for definition continuations.
<source lang="lua">
+
<source lang="Lua">
 
foo(bar, biz, "This is a long string..."
 
foo(bar, biz, "This is a long string..."
 
baz, qux, "Lua")
 
baz, qux, "Lua")
</source>
 
  
* Function definition continuations are also indented by two tabs.
 
<source lang="lua">
 
 
function foo(a, b, c, d,
 
function foo(a, b, c, d,
 
e, f, g, h)
 
e, f, g, h)
...
+
[…]
 
end
 
end
 
</source>
 
</source>
 +
<br/>
 +
* If the function arguments contain a table, it's indented by one tab and if the arguments get own lines, it's indented like a table.
 +
<source lang="Lua">
 +
register_node("name", {
 +
"This is a long string...",
 +
0.3,
 +
})
 +
 +
list = filterlist.create(
 +
preparemodlist,
 +
comparemod,
 +
function()
 +
return "no comma at the end"
 +
end
 +
)
 +
</source>
 +
<br/>
 +
* When strings don't fit into the line, you should add the string (changes) to the next line(s) indented by one tab.
 +
<source lang="Lua">
 +
longvarname = longvarname ..
 +
"Thanks for reading this example!"
  
 +
local retval =
 +
"size[11.5,7.5,true]" ..
 +
"label[0.5,0;" .. fgettext("World:") .. "]" ..
 +
"label[1.75,0;" .. data.worldspec.name .. "]"
 +
</source>
 +
<br/>
 
* When breaking around a binary operator you should break after the operator.
 
* When breaking around a binary operator you should break after the operator.
<source lang="lua">
+
<source lang="Lua">
foo["bar"]["biz"]["baz"] =
 
"Example"
 
 
if a or b or c or d or
 
if a or b or c or d or
 
e or f then
 
e or f then
Line 91: Line 107:
 
end
 
end
 
</source>
 
</source>
 
+
<br/>
  
 
=== Empty lines ===
 
=== Empty lines ===
Line 97: Line 113:
 
* Use a single empty line to separate sections of long functions.
 
* Use a single empty line to separate sections of long functions.
  
''Why not use something more visible as a line of ---?''
+
* Use two empty lines to separate top-level functions and large tables.
 
 
v------------------------------------------------------------------------------------------------
 
:::* Use two empty lines to separate top-level functions and large tables.
 
^------------------------------------------------------------------------------------------------
 
 
 
  
* Do not use a empty line after conditional or looping opening statements.
+
* Do not use a empty line after conditional, looping, or function opening statements.
Good:
+
<span style="color: #282;">Good:</span>
<source lang="lua">
+
<source lang="Lua">
 
function foo()
 
function foo()
 
if x then
 
if x then
Line 113: Line 124:
 
end
 
end
 
</source>
 
</source>
Bad:
+
<span style="color: #b44;">Bad:</span>
<source lang="lua">
+
<source lang="Lua">
 
function foo()
 
function foo()
  
Line 129: Line 140:
  
 
* Spaces are not used around parenthesis, brackets, or curly braces.
 
* Spaces are not used around parenthesis, brackets, or curly braces.
Good:
+
<span style="color: #282;">Good:</span>
<source lang="lua">
+
<source lang="Lua">
 
foo({baz=true})
 
foo({baz=true})
 
bar[1] = "Hello world"
 
bar[1] = "Hello world"
 
</source>
 
</source>
 
+
<span style="color: #b44;">Bad:</span>
''Inconsistent example at beginning there's shown to use spaces around binary operators (as told some lines below!!!)''
+
<source lang="Lua">
 
 
v------------------------------------------------------------------------------------------------
 
^------------------------------------------------------------------------------------------------
 
 
 
 
 
Bad:
 
<source lang="lua">
 
 
foo ( { baz=true } )
 
foo ( { baz=true } )
 
bar [ 1 ]
 
bar [ 1 ]
Line 148: Line 152:
  
 
* Spaces are used after, but not before, commas and semicolons.
 
* Spaces are used after, but not before, commas and semicolons.
Good: <source lang="lua">foo(a, b, {c, d})</source>
+
<span style="color: #282;">Good:</span> <source lang="Lua">foo(a, b, {c, d})</source>
Bad: <source lang="lua">foo(a,b,{c , d})</source>
+
<span style="color: #b44;">Bad:</span> <source lang="Lua">foo(a,b,{c , d})</source>
 
 
''Why can they be omitted? It's hard to read without them any other code uses them for what reason allow it here?''
 
 
 
v------------------------------------------------------------------------------------------------
 
:::* Spaces are used around binary operators.  With the exception of the member access operator :::(".") where there shouldn't be spaces, and the concatenation operator ("..") where spaces are optional.  In short one-line table definitions the spaces around the equals sign can be omitted.
 
^------------------------------------------------------------------------------------------------
 
  
Good:
+
* Spaces are used around binary operators with following exceptions:
<source lang="lua">
+
** There mustn't be spaces around the member access operator (".")
 +
** Spaces around the concatenation operator ("..") are optional.
 +
** In short one-line table definitions the spaces around the equals sign can be omitted.
 +
** When in-/decrementing a variable by 1, the spaces around the + and - operator can be omitted if you want to "get the neighbour" of something, e.g. when increasing some counter variable.
 +
<span style="color: #282;">Good:</span>
 +
<source lang="Lua">
 
local num = 2 * (3 / 4)
 
local num = 2 * (3 / 4)
 
foo({bar=true})
 
foo({bar=true})
Line 166: Line 169:
 
bar = false,
 
bar = false,
 
}
 
}
 +
i = i+1
 +
sometable[#sometable+1] = v
 
</source>
 
</source>
Bad:
+
<span style="color: #b44;">Bad:</span>
<source lang="lua">
+
<source lang="Lua">
 
local num=2*(3/4)
 
local num=2*(3/4)
 
local def={
 
local def={
Line 174: Line 179:
 
bar=false,
 
bar=false,
 
}
 
}
 +
playerpos.y = playerpos.y+1  -- playerpos.y is not an integer
 
</source>
 
</source>
  
''Way to unspecific and potential source of lots of discussions ... what's ok 5 spaces 10 15?''
+
* Use spaces to align related things, but don't go overboard:
 
+
<span style="color: #282;">Good:</span>
v------------------------------------------------------------------------------------------------
+
<source lang="Lua">
::: * Use spaces to align related things, but don't go overboard:
 
::: Good:
 
<source lang="lua">
 
 
local node_up  = minetest.get_node(pos_up)
 
local node_up  = minetest.get_node(pos_up)
 
local node_down = minetest.get_node(pos_down)
 
local node_down = minetest.get_node(pos_down)
 +
-- Too long relative to the other lines, don't align with it
 +
local node_up_1_east_2_north_3 = minetest.get_node(pos_up_1_east_2_north_3)
 
</source>
 
</source>
::: Bad:
+
<span style="color: #b44;">Bad:</span>
<source lang="lua">
+
<source lang="Lua">
 
local x                      = true
 
local x                      = true
 
local very_long_variable_name = false
 
local very_long_variable_name = false
Line 193: Line 198:
 
local unrelated = {}
 
local unrelated = {}
 
</source>
 
</source>
^------------------------------------------------------------------------------------------------
 
  
 
=== Tables ===
 
=== Tables ===
  
* Small tables may be placed on one line.  Large tables have one entry per line, with the opening and closing braces on lines without items.  In large tables the final element has a trailing comma.
+
* '''Small tables may be placed on one line.''' Large tables have one entry per line, with the opening and closing braces on lines without items; and with a comma after the last item.
Good:
+
<span style="color: #282;">Good:</span>
<source lang="lua">
+
<source lang="Lua">
 
local foo = {bar=true}
 
local foo = {bar=true}
 
foo = {
 
foo = {
Line 207: Line 211:
 
}
 
}
 
</source>
 
</source>
Bad:
+
<span style="color: #b44;">Bad:</span>
<source lang="lua">
+
<source lang="Lua">
 
foo = {bar = 0,
 
foo = {bar = 0,
 
biz = 1,
 
biz = 1,
Line 219: Line 223:
  
 
* In list-style tables where each element is short multiple elements may be placed on each line.
 
* In list-style tables where each element is short multiple elements may be placed on each line.
<source lang="lua">
+
<source lang="Lua">
 
local first_eight_letters = {
 
local first_eight_letters = {
 
"a", "b", "c", "d",
 
"a", "b", "c", "d",
Line 225: Line 229:
 
}
 
}
 
</source>
 
</source>
 
  
 
== Naming ==
 
== Naming ==
  
* Functions and variables should be named in <code>lowercase_underscore_style</code>. The exception is class-like functions, eg <code>PseudoRandom()</code>, which use UpperCamelCase.
+
* '''Functions and variables should be named in <code>lowercase_underscore_style</code>''', with the exception of constructor-like functions such as <code>PseudoRandom()</code>, which should use UpperCamelCase.
  
* Avoid inventing compound words; filename is O.K., but getbox and collisiondetection are not.
+
* Don't invent compound words.  Common words like <code>filename</code> are okay, but mashes like <code>getbox</code> and <code>collisiondetection</code> aren't.
  
 
* Avoid leading and/or trailing underscores.  They're ugly and can be hard to see.
 
* Avoid leading and/or trailing underscores.  They're ugly and can be hard to see.
Line 238: Line 241:
 
== Misc ==
 
== Misc ==
  
* Multiple statements on the same line are discouraged.
+
* Don't put multiple statements on the same line.
Bad: <source lang="lua">foo(); bar()</source>
 
  
* You can put conditionals/loops with small conditions and bodies on one line, although this is discouraged for all but the smallest ones.
+
* You can put conditionals / loops with small conditions and bodies on one line.  This is discouraged for all but the smallest ones though.
Good:
+
<span style="color: #282;">Good:</span>
<source lang="lua">
+
<source lang="Lua">
 
local f, err = io.open(filename, "r")
 
local f, err = io.open(filename, "r")
 
if not f then return err end
 
if not f then return err end
  
if    a then return a
+
if    foo then return foo
elseif b then return b
+
elseif bar then return bar
elseif c then return c
+
elseif qux then return qux
 
end
 
end
 
</source>
 
</source>
Bad:
+
<span style="color: #b44;">Bad:</span>
<source lang="lua">
+
<source lang="Lua">
 
if not f and use_error then error(err) elseif not f then return err end
 
if not f and use_error then error(err) elseif not f then return err end
 
</source>
 
</source>
  
* Don't compare values explicitly to true, false, or nil, unless it's really needed.
+
* Don't compare values explicitly to <code>true</code>, <code>false</code>, or <code>nil</code>, unless it's really needed.
Good:
+
<span style="color: #282;">Good:</span>
<source lang="lua">
+
<source lang="Lua">
 
local f, err = io.open(filename, "r")
 
local f, err = io.open(filename, "r")
 
if not f then return err end
 
if not f then return err end
Line 265: Line 267:
 
local t = {"a", true, false}
 
local t = {"a", true, false}
 
for i = 1, 5 do
 
for i = 1, 5 do
if t[i] == nil then  -- Needs an explicit nil check to avoid triggering on false.
+
-- Needs an explicit nil check to avoid triggering
 +
-- on false, which is a valid value.
 +
if t[i] == nil then
 
t[i] = "Default"
 
t[i] = "Default"
 
end
 
end
 
end
 
end
 
</source>
 
</source>
 
+
<span style="color: #b44;">Bad:</span>
''Comment: nil evaluating to false on boolean comparison doesn't mean they're equal.''
+
<source lang="Lua">
 
 
Bad:
 
<source lang="lua">
 
 
if f == nil then return err end
 
if f == nil then return err end
 
</source>
 
</source>
  
 
* Don't use unnecessary parenthesis unless they improve readability a lot.
 
* Don't use unnecessary parenthesis unless they improve readability a lot.
 
+
<source lang="Lua">
v------------------------------------------------------------------------------------------------
 
 
 
''Paranthesis always improve readability a lot''
 
 
 
^------------------------------------------------------------------------------------------------
 
 
 
<source lang="lua">
 
 
if y then bar() end -- Good
 
if y then bar() end -- Good
 
if (not x) then foo() end -- Bad
 
if (not x) then foo() end -- Bad
 
</source>
 
</source>
  
* Write function definitions of the form <code>function foo()</code> instead of the lambda form <code>foo = function()</code>, except when putting functions in tables, where the second should be used.
+
* Write function definitions of the form <code>function foo()</code> instead of the lambda form <code>foo = function()</code>, except when inserting functions in tables inline, where only the second form will work.
  
* Avoid globals.  The only globals that you should create are namespace tables.
+
* '''Avoid globals like the plague.''' The only globals that you should create are namespace tables&mdash;and even those might eventually be phased out.
  
 
* Don't let functions get too large.  Maximum length depends on complexity; simple functions can be longer than complex functions.
 
* Don't let functions get too large.  Maximum length depends on complexity; simple functions can be longer than complex functions.
  
* Minetest core is Lua API 5.1 don't even think about using LuaJIT or Lua 5.2 functions in core!
+
[[Category:Rules and Guidelines]]

Latest revision as of 01:52, 21 March 2021

This is largely based on the Python style guide except for some indentation and language syntax differences. When in doubt, consult that guide.

Note that these are only guidelines for more readable code. In some (rare) cases they may result in less readable code. Use your best judgement.

Comments

  • Incorrect or outdated comments are worse than no comments.
  • Avoid inline comments, unless they're very short.
  • Write comments to clarify anything that may be confusing. Don't write comments that describe things that are obvious from the code.

Good:

width = width - 2  -- Adjust for 1px border on each side

Bad:

width = width - 2  -- Decrement width by two
  • Comments should follow English grammar rules, this means starting with a capital letter, using commas and apostrophes where appropriate, and ending with a period. The period may be omitted in single-sentence comments. If the first word of a comment is an identifier, its casing should not be changed. Check the spelling.
-- This is a properly formatted comment.
-- This isnt.              (missing apostrophe)
-- neither is this.        (lowercase first letter)
  • Comments should have a space between the comment characters and the first word.
--This is wrong.
-- This is right.
  • Inline comments should be separated from the code by two spaces.
foo()  -- A proper comment.
  • If you write comments for a documentation generation tool, write the comments in LuaDoc format.
  • Short multi-line comments should use the regular single-line comment style.
  • Long multi-line comments should use Lua's multi-line comment format with no leading characters except a -- before the closer.
--[[
Very long
multi-line comment.
--]]


Lines, spaces, and indentation

  • Indentation is done with one tab per indentation level.
  • Lines are wrapped at 80 characters where possible, with a hard limit of 90. If you need more you're probably doing something wrong.


Continuation lines

  • Conditional expressions have continuation lines indented by two tabs.
if long_function_call(with, many, arguments) and
		another_function_call() then
	do_something()
end


  • Function arguments are indented by two tabs if multiple arguments are in a line, same for definition continuations.
foo(bar, biz, "This is a long string..."
		baz, qux, "Lua")

function foo(a, b, c, d,
		e, f, g, h)
	[…]
end


  • If the function arguments contain a table, it's indented by one tab and if the arguments get own lines, it's indented like a table.
register_node("name", {
	"This is a long string...",
	0.3,
})

list = filterlist.create(
	preparemodlist,
	comparemod,
	function()
		return "no comma at the end"
	end
)


  • When strings don't fit into the line, you should add the string (changes) to the next line(s) indented by one tab.
longvarname = longvarname ..
	"Thanks for reading this example!"

local retval =
	"size[11.5,7.5,true]" ..
	"label[0.5,0;" .. fgettext("World:") .. "]" ..
	"label[1.75,0;" .. data.worldspec.name .. "]"


  • When breaking around a binary operator you should break after the operator.
if a or b or c or d or
		e or f then
	foo()
end


Empty lines

  • Use a single empty line to separate sections of long functions.
  • Use two empty lines to separate top-level functions and large tables.
  • Do not use a empty line after conditional, looping, or function opening statements.

Good:

function foo()
	if x then
		bar()
	end
end

Bad:

function foo()

	if x then

		bar()
	end
end
  • Don't leave white-space at the end of lines.

Spaces

  • Spaces are not used around parenthesis, brackets, or curly braces.

Good:

foo({baz=true})
bar[1] = "Hello world"

Bad:

foo ( { baz=true } )
bar [ 1 ]
  • Spaces are used after, but not before, commas and semicolons.

Good:

foo(a, b, {c, d})

Bad:

foo(a,b,{c , d})
  • Spaces are used around binary operators with following exceptions:
    • There mustn't be spaces around the member access operator (".")
    • Spaces around the concatenation operator ("..") are optional.
    • In short one-line table definitions the spaces around the equals sign can be omitted.
    • When in-/decrementing a variable by 1, the spaces around the + and - operator can be omitted if you want to "get the neighbour" of something, e.g. when increasing some counter variable.

Good:

local num = 2 * (3 / 4)
foo({bar=true})
foo({bar = true})
local def = {
	foo = true,
	bar = false,
}
i = i+1
sometable[#sometable+1] = v

Bad:

local num=2*(3/4)
local def={
	foo=true,
	bar=false,
}
playerpos.y = playerpos.y+1  -- playerpos.y is not an integer
  • Use spaces to align related things, but don't go overboard:

Good:

local node_up   = minetest.get_node(pos_up)
local node_down = minetest.get_node(pos_down)
-- Too long relative to the other lines, don't align with it
local node_up_1_east_2_north_3 = minetest.get_node(pos_up_1_east_2_north_3)

Bad:

local x                       = true
local very_long_variable_name = false

local foobar    = true
local unrelated = {}

Tables

  • Small tables may be placed on one line. Large tables have one entry per line, with the opening and closing braces on lines without items; and with a comma after the last item.

Good:

local foo = {bar=true}
foo = {
	bar = 0,
	biz = 1,
	baz = 2,
}

Bad:

foo = {bar = 0,
	biz = 1,
	baz = 2}
foo = {
	bar = 0, biz = 1,
	baz = 2
}
  • In list-style tables where each element is short multiple elements may be placed on each line.
local first_eight_letters = {
	"a", "b", "c", "d",
	"e", "f", "g", "h",
}

Naming

  • Functions and variables should be named in lowercase_underscore_style, with the exception of constructor-like functions such as PseudoRandom(), which should use UpperCamelCase.
  • Don't invent compound words. Common words like filename are okay, but mashes like getbox and collisiondetection aren't.
  • Avoid leading and/or trailing underscores. They're ugly and can be hard to see.


Misc

  • Don't put multiple statements on the same line.
  • You can put conditionals / loops with small conditions and bodies on one line. This is discouraged for all but the smallest ones though.

Good:

local f, err = io.open(filename, "r")
if not f then return err end

if     foo then return foo
elseif bar then return bar
elseif qux then return qux
end

Bad:

if not f and use_error then error(err) elseif not f then return err end
  • Don't compare values explicitly to true, false, or nil, unless it's really needed.

Good:

local f, err = io.open(filename, "r")
if not f then return err end

local t = {"a", true, false}
for i = 1, 5 do
	-- Needs an explicit nil check to avoid triggering
	-- on false, which is a valid value.
	if t[i] == nil then
		t[i] = "Default"
	end
end

Bad:

if f == nil then return err end
  • Don't use unnecessary parenthesis unless they improve readability a lot.
if y then bar() end -- Good
if (not x) then foo() end -- Bad
  • Write function definitions of the form function foo() instead of the lambda form foo = function(), except when inserting functions in tables inline, where only the second form will work.
  • Avoid globals like the plague. The only globals that you should create are namespace tables—and even those might eventually be phased out.
  • Don't let functions get too large. Maximum length depends on complexity; simple functions can be longer than complex functions.