Skip to content

Style guide for core Lua modules

Jasper van Riet edited this page Jun 22, 2022 · 11 revisions

tl;dr

Keep code simple. Work within platform constraints to minimize user-facing errors. Use the power of lua while not abusing it. Keep in mind the Liquipedia contributor.

Table of Contents

Module basics

Module structure

Modules should strive to follow the structure in this section, to allow for easier understanding. This is needed because LUA is a very freeform language, which means there is a lot of freedom. In addition, by default, LUA does not have a real structure of its own. Common programming paradigms such as classes don't exist, instead everything is a table. On top of that, since our entry point is often from wikicode, there is additional complexity for our structure.

We divide desired structure for LUA into two types of modules: libraries and components:

  • Components are the building blocks of our wikis. These modules produce layouts, content, and more. These are almost always called from wikicode. They tend to need access to the frame object, which allows us to see them as "view" code.
  • Libraries are used by (various) Components, and provide general functionality that is to be re-used. These should generally not need access to the frame object, which means we can sort of see these modules as business logic or the data layer as they would commonly be referred to in more standardised software applications.

While proper separation of view code and business logic is not particularly feasible, we can use this separation as guiding principle. For example, we would generally not want a Math module to have access to the frame object.

Components

Components build the layouts we see on the wikis, which means they often utilize Templates. This means we rely on the frame object. Components should utilize libraries as much as possible to minimize business logic within the module, thus simplifying the module. This should allow less advanced users to make some (small) changes too.

Libraries

Libraries provide re-usable functionality to components (and in a rare case wikicode directly). This means their focus should be their usability from LUA, while still being operable from wikicode. The Module:Class module allows us to accomplish this. Example:

local SomeModule = {}                  <!--- This is what we call our "class"

local Class = require('Module:Class')

function SomeModule.doCoolStuff(a, b)  <!--- Callable from both LUA and wikicode!
  return a + b
end

return Class.export(SomeModule)

Please see the Class module documentation for more details. With this module, we can accomplish an object-oriented approach to these library modules.

Philosophy

The user should not see errors

The user should be protected from errors. If one does happen, it is preferably handled in the code. If one must happen, for example because of wrong input by contributors, minimize the impact and provide a clear error message so contributors can fix it.

Keep in mind the target audience

Many people on Liquipedia are not programmers or are still learning. Because of that, we want to keep our code relatively simple where possible. Avoid terms that are only recognizable to advanced programmers where possible. When building modules that are intended, or likely, to be changed by less proficient programmers, keep this in mind. For example, split a module into two modules, one of which acts like a config file and can thus be easily adjusted by anyone, and the other acting as the actual core logic.

Documentation

Every module that is in active use should have such documentation on the module page. The following structure for Module docs is convention:

  • A brief paragraph up top summarising the module
  • A Usage section highlighting any prerequisites needing for the module to work properly. This section can also show examples.
  • An API section, using Template:ApiDoc.
  • If wanted, a See Also section linking to related templates and modules.

Coding patterns

Avoid magical numbers

Numbers in code should be clearly understandable. Either extract the number to a constant, thus providing explanation via the variable name, or add a comment.

Naming

Use uppercase for global variables and constants

Use the uppercase format for global variables and constants, e.g. COOL_VARIABLE. Note that lua does not actually have constants, but we nonetheless use global variables as such.

Prefix private identifiers with _

To indicate a variable or function is not to be used outside a module, prefix it with _.

Prefer names recognizable to all audiences

When faced with a naming dilemma between a more generally used term in English and what is perhaps a more accurate term from software engineering, prefer the English term to facilitate understanding.

Formatting

Add a newline after a function

Functions should be followed by a newline.

Surround inner statements with brackets

In a larger boolean statement, inner statements should be surrounded by brackets.

if isSomething or x ~= nil and x.isTrue() then -- BAD

if isSomething or (x ~= nil and x.isTrue()) then -- GOOD

Limit nesting

The maximum nesting level is 5. If code exceeds it, extract inner logic into functions and use early returns where possible.

Files

Location of child components

Child components should indicate that in the file path. For example, if we have a module that creates the infobox for a company, the file path should be Module:Infobox/Company.