2026-04-01 23:35:50 +02:00
2026-04-01 23:35:50 +02:00
2026-04-04 16:11:46 +02:00
2026-04-01 23:35:50 +02:00
2026-04-01 23:55:07 +02:00
2026-04-01 23:35:50 +02:00
2026-04-01 23:35:50 +02:00
2026-04-01 23:35:50 +02:00
2026-04-02 00:26:05 +02:00
2026-04-02 00:26:05 +02:00

Gulie the GUile LInt Exorcist

A linter, static analyser, and formatter for Guile Scheme.

$ 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

Installation

Clone the repository and ensure bin/gulie is on your PATH, or run it directly:

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:

gulie mylib.scm

Check an entire project:

gulie src/

Auto-fix trailing whitespace and other fixable issues:

gulie --fix src/

Generate a config template:

gulie --init

Configuration

gulie looks for .gulie.sexp in the current directory and parent directories. Generate a template with gulie --init.

((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:

(define x    "messy") ; gulie:suppress trailing-whitespace

Suppress all rules on the next line:

;; gulie:suppress
(define intentionally-long-variable-name "value")

Region disable/enable:

;; 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

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]

Description
Linter, static analyser, and formatter for Guile Scheme
Readme BSD-2-Clause 203 KiB
Languages
Scheme 98.2%
Nix 1.8%