Skip to content

CFC-Servers/cfc_glua_style_guidelines

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

39 Commits
Β 
Β 
Β 
Β 

Repository files navigation

CFC Glua Style Guidelines

A set of guidelines to help keep your GLua code Clean and Cool 😎


πŸ‘· For CFC Members and Contributors

Please adhere to these rules in our repositories. Your PR will be automatically blocked if it does not meet the style guidelines.

🀠 For Non-CFC Members

Feel free to use these guidelines as you see fit! We believe they help maintain and share code effectively.

If you disagree with some guidelines, that's okay! The most important principle is consistency. If you choose to follow or ignore certain rules, ensure you do so consistently throughout your code. πŸ‘


Glossary


Tooling

GLuaLint

Use GLuaLint (GLuaFixer) as your linter. There are plugins available for most major editors.

GLuaFixer: https://github.com/FPtje/GLuaFixer

Use CFC's GLuaFixer config, found here: https://cfc.gg/configs/gluafixer/glualint.json


Spacing

Spaces around operators

Good

local x = a * b + c

Bad

local x = a* b+c

Spaces inside parentheses and curly braces if they contain content

Good

local x = ( 3 * myFunc() ) + 5
local data = { 5, {} }

Bad

local x = (3 * myFunc( )) + 5
local data = {5, { }}

Spaces after commas

Good

myFunc( 10, { 3, 5 } )

Bad

myFunc( 10,{ 3,5 } )

Indentation should be done with 4 spaces

Good

if cond then
    myFunc()
end

Bad

if cond then
  myFunc()
end

No spaces inside square brackets

Good

local val = tab[5] + tab[3]
local val2 = tab[5 * 3]

Bad

local val = tab[ 5 ] + tab[ 3 ]
local val2 = tab[ 5 * 3 ]

Single space after comment operators and before if not at start of line

Good

-- This is a good comment
local a = 3 -- This is also good

Bad

--This comment doesn't have a space before it
local a = 3-- This comment starts too close to the 3

Newlines

Never have more than 2 newlines

Good

local config = GM.Config


function GM:Think()
    -- do thing
end

Bad

local config = GM.Config



function GM:Think()
    -- do thing
end

Top level blocks should have either 1 or 2 newlines between them

Good

local config = GM.Config


function GM:Think()
    -- do thing
end

Bad

local config = GM.Config
function GM:Think()
    -- do thing
end

Non top level blocks/lines should never have more than 1 newline between them

Good

function test()
    local a = 3

    print( a )
end

Bad

function test()
    local a = 3


    print( a )
end

Returns should have one newline before them unless the codeblock is only one line

Good

function test()
    local a = 3

    return a
end

function test2()
    return 3
end

Bad

function test()
    local a = 3
    return a
end

Code should be split into managable chunks using a single new line

Good

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
end

Bad, 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
end

Gmod lua additions

Do not use GMod operators ( && || ! != )

Good

if a ~= b and not b then end

Bad, Garry's operators aren't recognized by standard Lua highlighting

if a != b && !b then end

Do not use GMod style comments ( /* */ and // )

Good

--[[
  This line does stuff
]]
do( stuff ) -- Stuff being done

Bad, gmod comments aren't recognized by standard Lua highlighting

/*
  This line does stuff
*/
do( stuff ) // Stuff being done

The use of continue should be avoided if possible

Good

for k, v in pairs( tab ) do
    if IsValid( v ) then
        v:Remove()
    end
end

Bad, garry's continue is a flawed implementation that is prone to errors when used in repeat-until loops

for k, v in pairs( tab ) do
    if not IsValid( v ) then continue end
    v:Remove()
end

If your condition is more complicated, consider deferring the logic to another function

local function processItem( item )
    if not IsValid( item ) then return end
    if not item:IsReady() then return end
    
    item:Process()
end

for _, item in ipairs( tab ) do
    processItem( item )
end

Naming conventions

Local variables and functions should always be written in camelCase

Good

local myVariable = 10

Bad

local MyVariable = 10
local my_variable = 20

Constants should be written in SCREAMING_SNAKE

Good

CATEGORY_NAME = "nothing"
local MAXIMUM_VALUE = 25

Bad

CategoryName = "nothing"
categoryName = "nothing"
category_name = "nothing"
local maximumValue = 25

Global variables should be written in PascalCase

Good

GlobalVariable = 10

Bad

globalVariable = 20
global_variable = 20

Methods for objects should be written in PascalCase

Good

function classTable:SetHealth( amount )
end

Bad

function classTable:setHealth( amount )
end

Table keys

Table keys should only contain a-z A-Z 0-9 and \_. they should not start with 0-9

Good

myTable.myValue = 4

Bad, accessing the table value requires brackets and quotes because of the hyphen

myTable["my-value"] = 4

Use _ as a variable to "throwaway" values that will not be used

Good

for _, ply in pairs( player.GetAll() ) do
    local _, shouldKill = ply:GetKillData()

    if shouldKill then
        ply:Kill()
    end
end

Bad, 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 naming:

  • Hook identifiers should be named as such Organization_AddonName_HookPurpose
  • The hook event name should not be included in the identifier. For example, you should not do ORG_MyAddon_OnDisconnect or even ORG_MyAddon_CleanupPropsOnDisconnect. ButORG_MyAddon_CleanupProps would 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

General

Quotations

You may use single or double quotes, but be consistent

Good, quote usage is consistent

myFunc( "hello ", "world!" )

Bad, different quotes are used interchangeably

myFunc( "hello ", 'world!' )

Do not use redundant parenthesis

Good

if x == y then

Bad, these parenthesis are not necessary

if ( x == y ) then

Redundant 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 ) + baseAmount

Unsavory, but fine

local amount = quantity * modifier + baseAmount

Multiline tables

Elements should begin on the next line and the last line should contain only a closing bracket. Elements inside should be indented once

Good

tbl = {
    v1 = c,
    v2 = d,
    v3 = k,
    v4 = j,
    v5 = y
}

Fine, if both the keys and values are short, it's acceptable to keep them in one line

tbl = { v1 = c, v2 = d, v3 = k, v4 = j, v5 = y }

Bad, the indentation is inconsistent

tbl = { v1 = v, v2 = d,
    v3 = k, v4 = j }

Multiline function calls

Multiline function calls should follow the same guidelines as Multiline tables

Good

myFunc(
    "First arg",
    secondArg,
    { third, arg }
)

Bad, this indentation is inconsistent (and objectively wrong)

myFunc( "First arg",
    secondArg, { third, arg } )

Return early from functions

Good

function test()
    if not myThing then return end

    -- Do a bunch of real complicated things
end

Bad, adds an extra level of indentation for no reason

function test()
    if myThing then
        -- Do a bunch of real complicated things
    end
end

"Magic numbers" should be pulled out into meaningful variables

Good

local maxX = 25

function checkX( x )
    return x > maxX
end

Bad, the significance of 25 is unknown without a meaningful variable name

function checkX( x )
    return x > 25
end

Complex expressions should be written on multiple lines with meaningful variable names

Good, each step of the equation is named and done individually. The math is easy to follow

local widthModifier = amount * damageMult
local age = 1 - lifetime / duration
local width = widthModifier * age

Bad, this math is difficult to figure out from a glance

local width = ( amount * 5 ) * ( 1 - lifetime / duration )

Never use semicolons

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 a

Bad, this exclusively makes the code harder to read

local a = 3; print( a );

return a;

Make use of existing constants where possible

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 )

Unnecessarily long conditions should be avoided

Conditions can be pulled out into meaningful variable names to avoid this

Good, each check is clear and it's easy to follow the reasoning

local entValid = IsValid( ent )
local entOwner = ent:CPPIGetOwner()
local ownerVehicleIsVisible = entOwner:GetVehicle():GetColor().a > 0
local ownerCapable = entOwner:IsAdmin() or entOwner.canDoThing

if entValid and ownerVehicleIsVisible and ownerCapable then
    -- doThing
end

Bad, the conditions are difficult to follow

if IsValid( ent ) and ent:GetCPPIOwner():GetVehicle():GetColor().a > 0
  and ( ent:CPPIGetOwner():IsAdmin() or ent:GetCPPIOwner().canDoThing ) then
    -- do thing
end

Lines should be around 110 characters long at the most

This isn't a hard limit, there are plenty of valid exceptions

Good

if IsValid( ent ) and ent:IsPlayer() and ent:IsAdmin() then
    local hue = ( CurTime() * 10 ) % 360
    local color = HSVToColor( hue, 1, 1 ) )

    ent:SetColor( color )
end

Bad, 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 ) ) ) end

Numbers

Prefer decimals with leading 0s

While 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.69

Bad, while the 0 is implied, it can make it harder to parse at a glance

local num = .69

Prefer whole numbers without leading 0s

Lua numbers can technically be prefixed with as many 0s as you'd like, but in most cases it's completely unnecessary

Good

local factor = 420

Bad, just why?

local factor = 00420

Prefer decimals with no trailing 0s

Except 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.2

Bad

local decimal = 0.200

Comments

Do not add useless comments

Good 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
end

Bad, 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

--

General Practices

Avoid unnecessary IsValid/:IsValid() checks

While these functions are relatively low-cost on their own, they still add overhead in places where it may not be necessary

Developers have been conditioned to pepper these checks anywhere-and-everywhere to avoid Attempt to index NULL errors. If you know an Entity can become NULL, first try to remedy the underlying issue that causes it. Only rely on IsValid if the situation is outside of your control (i.e. delayed timer actions).

Good, IsValid is only used in pieces of code that we can't avoid, and isn't used in places that add no value

net.Receive( "my_net_message", function( _, ply )
    -- No IsValid check is needed here, Players cannot become invalid at this point
    ply:ChatPrint( "Starting the process now..." )
    local steamID64 = ply:SteamID64()

    timer.Create( "delayed_action_" .. steamID64, 5, 1, function()
        -- IsValid check is required here because the Player may have disconnected before this timer triggers
        if not IsValid( ply ) then return end
        ply:DoAction()

        timer.Create( "delayed_action_step2_" .. steamID64, 5, 1, function()
            -- No IsValid check is needed here because we already saved the data we need from their Player object
            Database:UpdatePlayer( steamID64, true )
        end )
    end )
end )

Bad, IsValid is used in a hook where it's impossible for the Player to have become invalid

hook.Add( "PlayerCanSuicide", "check_suicide", function( ply )
    if not IsValid( ply ) then return end
    if ply:IsAdminFrozen() then return false end
end )

About

GLua Style guidelines to keep your code clean and cool 😎

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project