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