Files
gulie/README.md
2026-04-01 23:35:50 +02:00

216 lines
6.3 KiB
Markdown

# gulie
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]