Sift offers a unified approach to Lisp development by providing an idempotent formatter, a rule-based linter, and a language server (LSP) for Scheme, Emacs Lisp, and Common Lisp. By treating Lisp source as a tree of forms rather than a sequence of text lines, the tool ensures that formatting remains a 'fixed point' where repeated applications do not change the output.

Animated demonstration of the Sift linting and formatting tool in action.
A demonstration of Sift's linting and formatting capabilities on Lisp source code.github.com

This animation illustrates the core functionality of Sift as a unified toolchain for formatting and linting.

Dialect-Specific Logic

A primary challenge in Lisp tooling is that different dialects use the same characters for different purposes. For example, a square bracket might represent a vector in one dialect but a clause in another. Sift avoids compromising between these rules by using specific reports for each dialect.

This approach ensures that students and developers working on specific projects—such as Guile and Guix codebases or Emacs package development—receive formatting that aligns with their specific language conventions rather than a generic standard.

“A Lisp program is a datum with a read syntax, and a formatter that treats it as such can be exact and cheap at the same time.”

— GitHub - jjba23/sift: Lisp, Scheme and Emacs Lisp formatter, linter and LSP (language server)

Mechanism of Lossless Rewriting

Sift operates on the premise that a Lisp program is a datum with a read syntax. By reading the file into a tree of forms that carries source positions, the tool can perform rewrites that are 'byte-exact' regarding comments and special forms.

Screenshot of a Lisp code listing from the Sift project documentation.
Example of Lisp source code as processed by the Sift toolchain.github.com

This code listing provides a concrete example of the Lisp syntax that Sift handles across different dialects.

Because the reader and the printer are inverse operations, Sift can move whitespace while ensuring that the underlying structure remains unchanged. This allows the tool to provide a diff whose hunks are the specific changes, making it suitable for both human review and automated pipelines.

Tooling and Integration

The project ships as a single executable and an Emacs package, providing a consistent experience across different editors. It supports various output formats, including JSON for structured data, SARIF 2.1.0 for code scanning, and GitHub annotations for pull requests.

The language server (sift lsp) provides diagnostics, quickfixes, and formatting for all three dialects. It also offers features like 'go to definition,' inlay hints, and file renames, using the dialect's own compiler as a 'second opinion' when available.

Limitations and Safety

Sift prioritizes correctness over aggressive automation. The 'fix' command only applies changes that are stated as equivalences and will discard any fix that would introduce a parse error. It also avoids renaming identifiers or inventing new delimiters.

While the tool provides 92 rules with clear rationales, the specific performance benchmarks for large-scale repositories are not provided in the current documentation. Users can query the reasoning for any specific rule using the 'sift explain' command.