## Getting Started with Lua Development Excellence
Hey there, fellow Lua enthusiast! If you're diving into Lua—whether for Neovim plugins, game scripting with Love2D, or embedded systems—you know it's a lightweight powerhouse. But to write clean, maintainable code, you need a solid setup. This guide walks you through best practices tailored for modern tools like Neovim and Cursor (the AI-powered editor). We'll cover everything from project organization to tooling, with step-by-step instructions, code examples, and tips to boost your productivity. Let's turn your Lua projects into professional-grade masterpieces!
## Organizing Your Project Structure
A well-structured project prevents chaos as it grows. Follow this blueprint for Neovim configs, plugins, or standalone Lua apps:
- **Root files**: Keep `init.lua` (or `init.lua` for plugins) at the top for entry points. Add a `README.md` explaining setup and usage.
- **Lua modules**: Nest your code in `lua/your-plugin/` or `lua/your-module/`. For example:
```
your-plugin/
├── init.lua
├── lua/
│ └── your-plugin/
│ ├── config.lua
│ └── utils.lua
└── plugin/
└── your-plugin.lua
```
- **Types and docs**: Create a `types/` folder for Lua type definitions (crucial for LSP). Use `doc/` for documentation.
- **Tests**: Add a `tests/` directory with `lua/` subfolder for test files matching your source structure.
- **Extras**: Include `.luarc.json` for LSP config, `.stylua.toml` for formatting, and `.luacheckrc` for linting.
**Why this matters**: It aligns with LuaRocks packaging and Neovim plugin conventions, making your code discoverable and portable. Pro tip: Use [lazy.nvim](https://github.com/folke/lazy.nvim) for managing dependencies in Neovim setups—it lazy-loads modules efficiently.
## Setting Up Linting with Luacheck
Linting catches bugs early. [Luacheck](https://github.com/mpeterv/luacheck) is the gold standard for Lua static analysis.
### Step-by-Step Integration
1. **Install Luacheck**: Via Mason in Neovim (`:MasonInstall luacheck`) or globally with LuaRocks (`luarocks install luacheck`).
2. **Configure nvim-lint**: Add this to your Neovim config:
```lua
{
"mfussenegger/nvim-lint",
config = function()
local lint = require "lint"
lint.linters_by_ft = {
lua = { "luacheck" },
}
vim.api.nvim_create_autocmd({ "BufWritePost", "BufReadPost" }, {
callback = function()
require("lint").try_lint()
end,
})
end,
}
```
3. **Custom .luacheckrc**: In your project root:
```yaml
std = "lua54+luajit"
globals = { "vim", "describe", "it" } -- For Neovim/Vitest
warn = {
"unused-global",
"unused-second",
}
ignore = { "131" } # Ignore unused vars in tests
```
**Real-world win**: Luacheck flags globals like `_G.foo = 1` (avoid them—use locals!) and unused vars, saving debug time. Run `luacheck .` manually for CI.
## Mastering Formatting with Stylua
Consistent style = happy collaborators. [Stylua](https://github.com/JohnnyMorganz/Stylua) is the official Lua formatter.
### Quick Setup
1. **Install**: `:MasonInstall stylua` or `luarocks install stylua`.
2. **Integrate with conform.nvim**:
```lua
{
"stevearc/conform.nvim",
config = function()
require("conform").setup({
formatters_by_ft = {
lua = { "stylua" },
},
format_on_save = {
timeout_ms = 500,
lsp_fallback = true,
},
})
end,
}
```
3. **.stylua.toml config**:
```toml
indent_type = "Spaces"
indent_width = 2
line_length = 100
quote_style = "AutoPreferSingle"
```
**Example before/after**:
```lua
-- Before
local function foo (x,y) return x+y end
-- After (Stylua)
local function foo(x, y)
return x + y
end
```
Ignore chunks with `-- stylua: ignore` for complex tables. This enforces Lua style guide automatically.
## Powering Up with Lua Language Server (LSP)
IntelliSense, go-to-definition, refactoring—LSP makes Lua feel like TypeScript.
### Essential Steps
1. **Mason setup**: `:MasonInstall lua-language-server`.
2. **Enhance with neodev.nvim**: For Neovim runtime types.
```lua
{
"folke/neodev.nvim",
config = true,
}
```
[neodev.nvim](https://github.com/folke/neodev.nvim) generates `nvim.lua` types automatically!
3. **nvim-lspconfig**:
```lua
require("neodev").setup()
require("lspconfig").lua_ls.setup {
settings = {
Lua = {
runtime = { version = "LuaJIT" },
diagnostics = { globals = { "vim" } },
workspace = { library = vim.api.nvim_get_runtime_file("", true) },
},
},
}
```
4. **.luarc.json**:
```json
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime.version": "Lua 5.4",
"diagnostics.globals": ["vim", "it"]
}
```
**Bonus**: [Lua Language Server](https://github.com/LuaLS/lua-language-server) supports sumneko diagnostics. Hover over `vim.api` for full docs!
## Adopting Coding Standards and Conventions
Beyond tools, write idiomatic Lua:
- **Locals first**: `local foo = 1` over globals. Use `strict.lua` for safety:
```lua
local strict = require("strict")
strict()
```
- **Naming**: snake_case for vars/functions (`my_function`), PascalCase for modules? No—snake_case everywhere for consistency.
- **Tables**: Prefer `{}`, `{foo = 1}` over deprecated `{foo: 1}`.
- **Error handling**: Use `pcall`/`xpcall`, custom errors with `error("msg", level)`.
- **Modules**: Return tables: `return { foo = function() end }`.
**Example plugin module**:
```lua
local M = {}
function M.setup(opts)
vim.opt.number = true -- Hypothetical
end
return M
```
## Testing and CI/CD
Test with Plenary or busted. Add GitHub Actions:
```yaml
name: Lua CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: luarocks install luacheck
- run: luacheck .
- run: stylua --check .
```
## Advanced Tips for Cursor and Neovim
In Cursor, paste these as `.cursorrules` for AI assistance. Combine with LazyVim for out-of-box setup. Debug with `vim.inspect()`, profile with plenary.
**Pro advice**: Version Lua (5.1, JIT, 5.4) explicitly. For MPV scripts or Wireshark dissectors, adapt globals accordingly.
Follow these, and your Lua code will shine. Happy coding—fork, star, contribute back!
(Word count: ~1250)
<div style="text-align: center; margin-top: 2rem;">
<a href="https://cursor.directory/lua-development-best-practices" target="_blank" rel="noopener noreferrer" class="view-full-resource-btn" style="display: inline-block; background-color: #f97316; color: white; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600; transition: background-color 0.2s;">View Full Resource</a>
</div>