Tools
The midnight command has subcommands for working w/ Midnight projects:
| Command | Does |
|---|---|
midnight compile | compiles Midnight files to Lua |
midnight clean | removes Lua files generated by midnight compile |
midnight lint | checks Midnight files for likely mistakes |
midnight fmt | formats Midnight files |
All of them take files and / or directories (directories are searched for *.mn files recursively, and default to .), accept flags before or after paths. compile and lint also take -markup-factory name (see markup). -h shows a command's usage.
compile
midnight compile [-o dir] [-f] [-p] [-markup-factory name] [paths]
Compiles foo.mn to foo.lua (next to it, or under -o dir, keeping the relative path). Same as the reference compiler's midnight compile:
- Compiled files end w/ a
-- __MIDNIGHT_COMPILED__footer. - Existing
.luafiles without the footer are never overwritten, unless-f. -pprints the compiled code instead of writing files.- Stops at the first file that fails to compile (exit 1).
The output is Lua 5.1 + goto (continue compiles to goto), w/ bit operators compiled to bit32 calls, so it runs on LuaJIT and Lua 5.2+ (or 5.1 w/ a bit32 polyfill). Statements are kept on their Midnight lines, so errors report Midnight line numbers.
clean
midnight clean [paths]
Removes .lua files that end w/ the -- __MIDNIGHT_COMPILED__ footer (from either midnight or the reference compiler). Other Lua files are never removed.
lint
midnight lint [-g list] [-markup-factory name] [paths | -]
Checks for likely mistakes. Explicitly named .lua files are checked as Lua, and - reads stdin.
| Rule | Reports |
|---|---|
undefined-global | reading a global that is never set (in the file) |
set-global | setting an undeclared global (ex. x = 1 or function f() without global / local) |
unused-local | a local (or local function, or destructured name) that is never read |
shadow | a local (or parameter, or loop variable) that shadows another local |
redefined | a local declared twice in the same scope |
unreachable | code after break, continue, or goto (code after return is already a syntax error) |
fat-arrow-component | a => function that builds markup elements but doesn't use self: probably a component, whose props would end up in self (use ->) |
and-or | a && b || c where b can be nil or false: it's then c even when a is true (use an if expression: if a { b } else { c }). Not reported when b can't be falsy (a literal, table, function, element, .., arithmetic, math.*, tostring, ...), when c is nil / false, or for safe navigation (x && x.y || d, x && x:m() || d, x && f(x) || d) |
syntax | the file doesn't parse |
Not reported:
- Names starting w/
_(ex._,_unused) when unused. - Unused parameters and loop variables.
global x/module xnames (they compile to_G.x/_MODULE.x).- The implicit
selfof=>functions and:methods, when shadowed. - Names made up by the Midnight parser (ex. for
continue,match, destructuring). Standard globals (Lua 5.1 - 5.5, LuaJIT, and midnight's
bit32/channel). Add others (ex. ones the host program provides) w/-g name,name, or w/ a.luarc.json(below).
Globals are also read from the nearest .luarc.json (lua-language-server's config, looked up from each file's directory), so editor and linter agree:
diagnostics.globals: names listed directly.workspace.library:*.luafiles (or directories of them, relative to the.luarc.json) whose top-level globals count as defined, ex. a---@metadefinitions file w/harmony = {}orfunction fetch(url) end.
{
"workspace.library": ["../src/bots/lua_definitions"],
"diagnostics.globals": ["provided_by_host"]
}
A comment containing midnight-lint: ignore silences its line:
print(provided_by_host) -- midnight-lint: ignore
Limitations: diagnostics have line numbers only (no columns), and destructured names w/ defaults always count as used (the default compiles to a nil check, which reads them).
The linter runs on the AST (the lint package), after Midnight is lowered to Lua.
Output
One line per problem, sorted by line:
path:line: message (rule)
Stdin is reported as <stdin>. Exits 1 if there are any problems, 0 otherwise.
fmt
midnight fmt [-check] [paths | -]
Formats files in place (printing the paths it changed), or stdin to stdout w/ -. -check doesn't write anything: it prints the paths of files that need formatting, and exits 1 if there are any.
The formatter works on tokens, not a syntax tree, so it keeps your line breaks (one line blocks stay on one line, and long lines aren't wrapped). It normalizes:
Indentation. 2 spaces per open bracket. Brackets opened on one line only indent once (ex.
f({), and a line starting w/ a closing bracket lines up w/ the line that opened it. Lines continuing an expression (ex. after a line ending in../&&/=, or starting w/../:method()) are indented once more, except directly inside parentheses.Spacing. One space around binary operators,
=,->, and=>, after,, and inside{ }. None inside( )/[ ], around., after unary operators, or before call / index brackets (ex.f(x),t[1]). Compound assignments are kept together (ex.+=).Blank lines. At most one in a row, and none at the start or end of a block / table. Leading and trailing blank lines are removed.
Multiline braces. A
{ ... }(block or table) that spans lines gets its contents on their own lines (ex.if x { a()\n}becomesif x {\n a()\n}). A comment directly after{stays on its line. Parentheses are left alone.Trailing commas. Added to multiline tables (and destructures), removed from single line ones.
Quotes.
"abc"becomes'abc', unless the string has interpolation or would need escapes.Comments.
--xbecomes-- x(not---,--[[, etc.), and trailing whitespace is removed. Trailing comments get one space after the code, but comments on consecutive lines that were aligned (started at the same column) stay aligned: at their original column (shifted w/ any change in the line's indentation), or one space after the longest line in the run if the code no longer fits.
Strings (including interpolations and block strings) are never changed, and \r\n line endings become \n.
Every result is checked to parse to the same program as the input. If it doesn't (a formatter bug), the file is left unchanged and the error is reported. Files that don't parse are reported and left unchanged.
Output
midnight fmt -: the formatted source on stdout, exit 0. On errors, a message on stderr and exit 1 (nothing on stdout).midnight fmt paths: the paths of changed files on stdout. Exits 1 if any file failed.midnight fmt -check paths: the paths of unformatted files on stdout. Exits 1 if there are any (or any file failed).