216 lines
6.3 KiB
Markdown
216 lines
6.3 KiB
Markdown
# Gulie – the GUile LInt Exorcist
|
||
|
||
A linter, static analyser, and formatter for [Guile Scheme](https://www.gnu.org/software/guile/).
|
||
|
||
```
|
||
$ gulie gulie/
|
||
gulie/engine.scm:97:80: warning: line-length: line exceeds 80 characters (82)
|
||
gulie/tokenizer.scm:131:0: warning: trailing-whitespace: trailing whitespace
|
||
```
|
||
|
||
## Why
|
||
|
||
No linter, formatter, or static analysis tool exists for Guile Scheme. gulie
|
||
fills that gap with a two-pass architecture that catches both surface-level
|
||
formatting issues and deep semantic problems.
|
||
|
||
## Features
|
||
|
||
- **Surface rules** (no parsing needed): trailing whitespace, line length, tabs,
|
||
excessive blank lines, comment style conventions
|
||
- **Semantic rules** (via Guile's compiler): unused variables, unbound
|
||
variables, arity mismatches, format string errors, shadowed top-levels,
|
||
unused modules
|
||
- **Inline suppression**: `; gulie:suppress rule-name` on a line, or region
|
||
disable/enable blocks
|
||
- **Auto-fix mode**: `--fix` applies automatic corrections where available
|
||
- **Configuration**: `.gulie.sexp` in your project root, overridable via CLI
|
||
- **CI friendly**: exit code 0 for clean, 1 for findings
|
||
|
||
## Requirements
|
||
|
||
- [Guile](https://www.gnu.org/software/guile/) 3.0 or later
|
||
|
||
## Installation
|
||
|
||
Clone the repository and ensure `bin/gulie` is on your `PATH`, or run it
|
||
directly:
|
||
|
||
```sh
|
||
bin/gulie --check .
|
||
```
|
||
|
||
## Usage
|
||
|
||
```
|
||
gulie [OPTIONS] [FILE|DIR...]
|
||
|
||
Options:
|
||
-h, --help Show help message
|
||
-v, --version Print version
|
||
--check Check mode (default): report issues, exit non-zero on findings
|
||
--fix Fix mode: auto-fix what's possible, report the rest
|
||
--init Generate .gulie.sexp template in current directory
|
||
--pass PASS Run only: surface, semantic, all (default: all)
|
||
--config FILE Config file path (default: auto-discover .gulie.sexp)
|
||
--rule RULE Enable only this rule
|
||
--disable RULE Disable this rule
|
||
--severity SEV Minimum severity: error, warning, info
|
||
--output FORMAT Output format: standard (default), json, compact
|
||
--list-rules List all available rules
|
||
```
|
||
|
||
### Examples
|
||
|
||
Check a single file:
|
||
|
||
```sh
|
||
gulie mylib.scm
|
||
```
|
||
|
||
Check an entire project:
|
||
|
||
```sh
|
||
gulie src/
|
||
```
|
||
|
||
Auto-fix trailing whitespace and other fixable issues:
|
||
|
||
```sh
|
||
gulie --fix src/
|
||
```
|
||
|
||
Generate a config template:
|
||
|
||
```sh
|
||
gulie --init
|
||
```
|
||
|
||
## Configuration
|
||
|
||
gulie looks for `.gulie.sexp` in the current directory and parent directories.
|
||
Generate a template with `gulie --init`.
|
||
|
||
```scheme
|
||
((line-length . 80)
|
||
(indent . 2)
|
||
(max-blank-lines . 2)
|
||
(enable trailing-whitespace line-length no-tabs blank-lines
|
||
comment-semicolons unused-variable unbound-variable
|
||
arity-mismatch)
|
||
(disable)
|
||
(rules
|
||
(line-length (max . 100)))
|
||
(indent-rules
|
||
(define . 1) (let . 1) (lambda . 1)
|
||
(with-syntax . 1) (match . 1))
|
||
(ignore "build/**" ".direnv/**"))
|
||
```
|
||
|
||
## Inline Suppression
|
||
|
||
Suppress a rule on the current line:
|
||
|
||
```scheme
|
||
(define x "messy") ; gulie:suppress trailing-whitespace
|
||
```
|
||
|
||
Suppress all rules on the next line:
|
||
|
||
```scheme
|
||
;; gulie:suppress
|
||
(define intentionally-long-variable-name "value")
|
||
```
|
||
|
||
Region disable/enable:
|
||
|
||
```scheme
|
||
;; gulie:disable line-length
|
||
(define long-line ...............................................)
|
||
(define another .................................................)
|
||
;; gulie:enable line-length
|
||
```
|
||
|
||
## Architecture
|
||
|
||
gulie uses a two-pass design:
|
||
|
||
```
|
||
.gulie.sexp
|
||
|
|
||
file.scm --+--> [Tokenizer] --> tokens --> [CST parser] --> CST
|
||
| |
|
||
| [Pass 1: Surface] line rules + CST rules
|
||
| |
|
||
| diagnostics-1
|
||
|
|
||
+--> [Guile compiler] --> Tree-IL --> CPS
|
||
|
|
||
[Pass 2: Semantic] Guile's built-in analyses
|
||
|
|
||
diagnostics-2
|
||
|
|
||
[merge + suppress + sort + report]
|
||
```
|
||
|
||
**Pass 1** uses a hand-written tokenizer that preserves all whitespace, comments,
|
||
and exact source text. The critical invariant:
|
||
`(string-concatenate (map token-text (tokenize input)))` reproduces the input
|
||
exactly. This feeds a lightweight concrete syntax tree for formatting checks.
|
||
|
||
**Pass 2** delegates to Guile's own compiler and analysis infrastructure:
|
||
`unused-variable-analysis`, `arity-analysis`, `format-analysis`, and others.
|
||
These are battle-tested and handle macroexpansion correctly.
|
||
|
||
The two passes are independent because Guile's reader irrecoverably strips
|
||
comments and whitespace — there is no way to get formatting info and semantic
|
||
info from a single parse.
|
||
|
||
## Rules
|
||
|
||
| Rule | Type | Category | Description |
|
||
|------|------|----------|-------------|
|
||
| `trailing-whitespace` | line | format | Trailing spaces or tabs |
|
||
| `line-length` | line | format | Line exceeds maximum width |
|
||
| `no-tabs` | line | format | Tab characters in source |
|
||
| `blank-lines` | line | format | Excessive consecutive blank lines |
|
||
| `comment-semicolons` | cst | style | Comment style conventions (`;`/`;;`/`;;;`) |
|
||
| `unused-variable` | semantic | correctness | Unused local variable |
|
||
| `unused-toplevel` | semantic | correctness | Unused top-level definition |
|
||
| `unused-module` | semantic | correctness | Unused module import |
|
||
| `unbound-variable` | semantic | correctness | Reference to undefined variable |
|
||
| `arity-mismatch` | semantic | correctness | Wrong number of arguments |
|
||
| `shadowed-toplevel` | semantic | correctness | Top-level binding shadows import |
|
||
| `format-string` | semantic | correctness | Format string validation |
|
||
|
||
## Module Structure
|
||
|
||
```
|
||
gulie/
|
||
cli.scm Command-line interface
|
||
config.scm Configuration loading and merging
|
||
diagnostic.scm Diagnostic record type and formatting
|
||
tokenizer.scm Hand-written lexer preserving all tokens
|
||
cst.scm Token stream to concrete syntax tree
|
||
compiler.scm Guile compiler wrapper for semantic analysis
|
||
rule.scm Rule record type and registry
|
||
engine.scm Orchestrator: file discovery, pass sequencing
|
||
suppression.scm Inline suppression parsing and filtering
|
||
rules/
|
||
surface.scm Line-based formatting rules
|
||
comments.scm Comment style rules
|
||
```
|
||
|
||
## Testing
|
||
|
||
```sh
|
||
guile --no-auto-compile -L . -s test/run-tests.scm
|
||
```
|
||
|
||
84 tests covering tokenizer roundtrip, CST parsing, surface rules, suppression,
|
||
and semantic analysis.
|
||
|
||
## Licence
|
||
|
||
[TODO: add licence]
|