Linting
Built-in static analysis to catch style issues and code smells without executing your code.
Usage
# Lint all .sl files in current directory (recursive)
soli lint
# Lint a specific directory
soli lint src/
# Lint a single file
soli lint app/main.sl
Exit codes
0 — no issues found. 1 — one or more issues found.
Output Format
Each issue is reported on a single line with the file path, line, column, rule, and message:
app/main.sl:12:5 - [naming/snake-case] variable 'myVar' should use snake_case
app/main.sl:30:9 - [smell/unreachable-code] unreachable code after return statement
2 issue(s)
found in 1 file(s)
Rules
Naming
naming/snake-case
Variables, functions, methods, and parameters should use snake_case.
# Bad
let myVar = 10
def processData end
# Good
let my_var = 10
def process_data end
naming/pascal-case
Classes and interfaces should use PascalCase.
# Bad
class my_class end
interface data_store end
# Good
class MyClass end
interface DataStore end
Style
style/empty-block
Blocks should not be empty. Add a comment or remove the block.
style/line-length
Lines should not exceed 120 characters.
Code Smells
smell/unreachable-code
Code after a return statement is unreachable and will never execute.
def example
return 42
print("never reached"); # Warning: unreachable code
end
smell/empty-catch
Catch blocks should not be empty. Silently swallowing errors hides bugs.
# Bad - error silently ignored
try
risky()catch e
end
# Good - at least log the error
try
risky()catch e
print("Error: " + str(e))
end
smell/deep-nesting
Nesting depth should not exceed 4 levels. Consider extracting logic into separate functions.
smell/duplicate-methods
A class should not have two methods with the same name.
smell/dangerous-server-builtin
Calls to db_query_raw, Trusted.*, System.shell / System.shell_sync, or backtick command substitution from app/controllers/, app/middleware/, or app/views/. These primitives are powerful but become injection / traversal sinks when fed request-controlled data. The diagnostic spells out the safe alternative for each:
db_query_raw→ parameterised@sdbql{ ... #{value} ... }block, orModel.where("x = #{v}", { "v": v }).Trusted.*→ jailedFile.*(read/write/exists), which keeps every operation under the app root.System.shell/ backticks →System.run(["prog", "arg1", ...])with an argv array, which never invokes a shell.
Models, migrations, and tests are out of scope — those layers legitimately use these APIs against operator-controlled data.
Suppressing Warnings
When a warning is a known false-positive or an intentional exception, suppress it inline with a directive comment.
Single-line forms
disable-next-line covers the line below; disable-line covers the same line.
# soli-lint-disable-next-line smell/dangerous-server-builtin
if Trusted.is_dir(wt_path)
...
end
Trusted.read(p) # soli-lint-disable-line smell/dangerous-server-builtin
Block forms
disable / enable toggle a rule for a region. Useful when several adjacent lines are intentional exceptions.
# soli-lint-disable smell/dangerous-server-builtin
exists = Trusted.is_dir(path)
data = Trusted.read(path)
# soli-lint-enable smell/dangerous-server-builtin
- Omit the rule name to suppress every rule (e.g.
# soli-lint-disable). Pass a comma-separated list to scope to multiple rules. - An
enablefor a specific rule re-enables only that rule, even if the priordisablewas a blanket one. - A block
disablewith no matchingenableruns to the end of the file. - Prefer naming the exact rule so unrelated warnings still surface.
Editor Integration
The VS Code / Cursor extension provides full Language Server Protocol (LSP) support with real-time linting, hover documentation, autocomplete, and more.
Features
- Real-time linting — warnings and errors displayed inline
- Hover information — documentation for functions, classes, and builtins
- Autocomplete — suggestions for keywords, types, and symbols
- Go to definition — jump to symbol definitions
- Find references — locate all uses of a symbol
Installation
cd editors/vscode
vsce package
# Install the generated .vsix file in Cursor or VS Code
Settings
soli.lsp.enable— Enable/disable LSP server (default:true)soli.lsp.executablePath— Path to thesolibinary (default:"soli")soli.lint.enable— Enable/disable linting (default:true)soli.lint.onSave— Run linter on file save (default:true)
Manual LSP Setup
For editors that support custom LSP servers directly (Neovim, Emacs, etc.):
require('lspconfig').soli.setup({
cmd = {"soli", "lsp"},
filetypes = {"soli"},
root_dir = lspconfig.util.root_pattern("soli.toml", ".git"),
})
Learn More
For full editor setup instructions and all available LSP features, see the Editor Integration guide.