The official SEBRP Glua styling guidelines as used on most of our repositories and other work.
This is forked from CFC Servers (https://github.com/CFC-Servers) but edited because we want some things slightly different.
Use GLuaLint (GLuaFixer) as your linter. There are plugins available for most major editors.
GLuaFixer: https://github.com/FPtje/GLuaFixer
Use our GLuaFixer config, found here: https://github.com/SEBRP/glua_style_guidelines/blob/main/glualint.json
Good
local x = a * b + cBad
local x = a* b+cGood
myFunc(10, {3, 5})Bad
myFunc( 10,{ 3,5 })Good
if cond then
myFunc()
endBad
if cond then
myFunc()
endGood
local val = tab[5] + tab[3]
local val2 = tab[5 * 3]Bad
local val = tab[ 5 ] + tab[ 3 ]
local val2 = tab[ 5 * 3 ]Good
-- This is a good comment
local a = 3 -- This is also goodBad
--This comment doesn't have a space before it
local a = 3-- This comment starts too close to the 3Good
local config = GM.Config
function GM:Think()
-- do thing
endBad
local config = GM.Config
function GM:Think()
-- do thing
endGood
local config = GM.Config
function GM:Think()
-- do thing
endBad
local config = GM.Config
function GM:Think()
-- do thing
endGood
function test()
local a = 3
print( a )
endBad
function test()
local a = 3
print( a )
endGood
function test()
local a = 3
return a
end
function test2()
return 3
endBad
function test()
local a = 3
return a
endGood
function CFCNotifications.resolveFilter( filter )
if type( filter ) == "Player" then
filter = { filter }
end
if type( filter ) == "table" then
filter = fWrap( filter )
end
filter = filter or player.GetAll
local players = filter()
if type( players ) == "Player" then
players = { players }
end
if not players then players = player.GetAll() end
return players
endBad, far too dense and difficult to read at a glance
function CFCNotifications.resolveFilter( filter )
if type( filter ) == "Player" then
filter = { filter }
end
if type( filter ) == "table" then
filter = fWrap( filter )
end
filter = filter or player.GetAll
local players = filter()
if type( players ) == "Player" then
players = { players }
end
if not players then players = player.GetAll() end
return players
endGood
if a ~= b and not b then endBad, Garry's operators aren't recognized by standard Lua highlighting
if a != b && !b then endGood
--[[
This line does stuff
]]
do( stuff ) -- Stuff being doneBad, gmod comments aren't recognized by standard Lua highlighting
/*
This line does stuff
*/
do( stuff ) // Stuff being doneGood
local myVariable = 10Bad
local MyVariable = 10
local my_variable = 20Good
CATEGORY_NAME = "nothing"
local MAXIMUM_VALUE = 25Bad
CategoryName = "nothing"
categoryName = "nothing"
category_name = "nothing"
local maximumValue = 25Good
GlobalVariable = 10Bad
globalVariable = 20
global_variable = 20Good
function classTable:SetHealth( amount )
endBad
function classTable:setHealth( amount )
endGood
myTable.myValue = 4Bad, accessing the table value requires brackets and quotes because of the hyphen
myTable["my-value"] = 4Good
for _, ply in pairs( player.GetAll() ) do
local _, shouldKill = ply:GetKillData()
if shouldKill then
ply:Kill()
end
endBad, k isn't used
for k, ply in pairs( player.GetAll() ) do
local canKill, shouldKill = ply:GetKillData()
if shouldKill then
ply:Kill()
end
end- Hook identifiers should be named as such
AddonName_HookPurpose - The hook event name should not be included in the identifier.
For example, you should not do
ORG_MyAddon_OnDisconnector evenORG_MyAddon_CleanupPropsOnDisconnect. ButORG_MyAddon_CleanupPropswould be appropriate. The "HookPurpose" should state what a function does without restating information provided by the event name. - Hook event names should be named as such
Organization_EventName
Good, quote usage is consistent
myFunc("hello ", "world!")Bad, different quotes are used interchangeably
myFunc( "hello ", 'world!' )Good
if x == y thenBad, these parenthesis are not necessary
if ( x == y ) thenRedundant parenthesis may be used if it makes the order of operations clear. The author should use their best judgement
It's important for the author to remember who their target audience is. Not every reader will be good with maths, and it can be helpful to make equations easy to follow
Acceptable
local amount = ( quantity * modifier ) + baseAmountUnsavory, but fine
local amount = quantity * modifier + baseAmountElements should begin on the next line and the last line should contain only a closing bracket. Elements inside should be indented once
Good
tbl = {
key = x,
otherKey = y
}Bad, this indentation is inconsistent and can make it difficult for the reader to parse
tbl = { key = x,
otherKey = y}Good
myFunc("First arg",
secondArg,
{ third, arg }
)Bad, this indentation is inconsistent and objectively wrong
myFunc( "First arg",
secondArg, { third, arg } )Good
function test()
if not myThing then return end
-- Do a bunch of real complicated things
endBad, adds an extra level of indentation
function test()
if myThing then
-- Do a bunch of real complicated things
end
endGood
local maxX = 25
function checkX( x )
return x > maxX
endBad, the significance of 25 is unknown without a meaningful variable name
function checkX( x )
return x > 25
endGood, each step of the equation is named and done individually. The math is easy to follow This is not necessary if the equation is good commented
local widthModifier = amount * damageMult
local age = 1 - lifetime / duration
local width = widthModifier * ageBad, this math is difficult to figure out from a glance
local width = ( amount * 5 ) * ( 1 - lifetime / duration )Semicolons provide no functional value in Lua. While they can be used to delimit table items, a comma is preferred
Good
local a = 3
print( a )
return aBad, this exclusively makes the code harder to read
local a = 3; print( a );
return a;Good
x = y * math.pi
radians = math.rad( deg )Bad, the meaning of 3.142 may not be immediately clear. It also suffers a loss in precision compared to math.pi**
x = y * 3.142
radians = deg * ( 3.142 / 180 )Good, each check is clear and it's easy to follow the reasoning
if IsValid( ent )
and ent:GetCPPIOwner():GetVehicle():GetColor().a > 0
and ( ent:GetCPPIOwner():IsAdmin() or ent:GetCPPIOwner().canDoThing ) then
-- do thing
endBad, the conditions are difficult to follow and require some unpacking
if IsValid( ent ) and ent:GetCPPIOwner():GetVehicle():GetColor().a > 0 and ( ent:GetCPPIOwner():IsAdmin() or ent:GetCPPIOwner().canDoThing ) then
-- do thing
endGood
if IsValid(ent) and ent:IsPlayer() and ent:IsAdmin() then
local hue = ( CurTime() * 10 ) % 360
local color = HSVToColor(hue, 1, 1))
ent:SetColor(color)
endBad, this line is too long and could easily be split into multiple, easy-to-follow lines
if IsValid( ent ) and ent:IsPlayer() and ent:IsAdmin() then ent:SetColor( HSVToColor( ( CurTime() * 10 ) % 360, 1, 1 ) ) ) endWhile you can omit a zero in decimals in the range of 0-1, it's an antipattern and a one-off exception when compared to decimals outside of said range
Good
local num = 0.69Bad, while the 0 is implied, it can make it harder to parse at a glance
local num = .69Lua numbers can technically be prefixed with as many 0s as you'd like, but in most cases it's completely unnecessary
Good
local factor = 420Bad, just why?
local factor = 00420Except in cases where the trailing 0s make it easier to understand the values (e.g. when you have a column of configurable decimals with varying precision), there is no reason to include them and could confuse the reader
Good
local decimal = 0.2Bad
local decimal = 0.200Good variable and function names can make comments unecessary. Strive for self commenting code. Save comments for complicated code that may not be clear on its own
Good
for _, ply in pairs( players ) do
print( ply )
if ply:Alive() then ply:Kill() end
endBad, the code explains itself without comments
for _, v in pairs( stuff ) do -- loop through players
print( v ) -- print the player
if v:Alive() then v:Kill() end -- kill player if player is alive
end