# Stan: a static analyser for Haskell that reads GHC's HIE files

> Stan is a Haskell command line analyser that works from the HIE files GHC already produces, matching AST and type patterns to surface anti-patterns, performance issues and ecosystem best practices that the type system does not cover. It is beta software with an API that may still change.

**kowainik/stan** — 🕵️ Haskell STatic ANalyser

- Repository: https://github.com/kowainik/stan
- Website: https://kowainik.github.io/projects/stan
- Stars: 596 · Forks: 55
- Language: Haskell
- License: MPL-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/kowainik-stan

## It starts from what GHC already knows

Stan is a command line tool for analysing Haskell projects, and the core of its design is that it does not re-implement a compiler. The analysis is based on the HIE files, which are compile-time information about Haskell source code gathered and recorded by GHC, and they contain the Haskell AST, detailed information about each identifier, and the types of all expressions and sub-expressions. The README is explicit about the reasoning: GHC does a huge amount of work when compiling a Haskell project, and Stan takes advantage of that to avoid duplicating the work and to focus on the unique features. That is a strong architectural argument, because a separate type checker or linter for Haskell would either be shallow or would need the same compiler front end. The cost is the constraint described later in this article, that Stan and the project being analysed have to be built with the same GHC version. The tool describes itself as a Haskell STatic ANalysis tool, and it also analyses information from the .cabal files, not only the source code.

## Inspections, observations and stable IDs

The vocabulary is precise, and understanding it is what makes the reports readable. Each analysis check is represented by an inspection with a unique ID, and each inspection has a name, a description, a severity, a list of categories, a pattern for matching relevant parts of source code, and possible solutions to the problem. When an inspection is applied to a project it produces zero or more observations, and an observation is a pair of an inspection and a piece of source code where that inspection was triggered. Each observation is assigned a unique stable ID that depends on the source location, so you can refer to an observation later or ignore it, and that matters for a tool whose output you intend to act on over time rather than read once. The goals behind the design are listed separately and they are worth reading as a specification of intent, since they explain what a finding means. Stan aims to catch common errors, anti-patterns and performance issues, to provide meaningful insights on the project generally, to point out potential bugs and weak points in the program flow so users can evaluate each problem against the code, to help beginners learn best practices, to generate a report that can be used as a proof of code quality, and to provide a flexible interface including opt-in and opt-out inspections. That last goal is the one that distinguishes it from a linter with a fixed rule set.

## Checks, filters and scope decide what runs where

You control which inspections run through checks, which are rules for controlling which inspections to run and where. Each check has a type, which is include or exclude, a filter, which can select by inspection id, category or severity, and a scope, which can be a file, a directory or everything. Checks can be specified through either the TOML or the command line interface, and by default Stan analyses all source files using all implemented inspections. This is the mechanism that makes the tool usable on a real codebase, because a large Haskell project with a house style will not want every inspection applied everywhere, and the scope dimension lets you say that a rule applies to your library but not to your generated code, or that a rule applies only in a particular directory. The severity field is what feeds the reporting, and the categories are what let you group findings in the HTML report by kind of problem rather than by inspection name. The README also mentions a separate page for converting between TOML and CLI configurations, so a team can keep a configuration file in version control and still override it per run on the command line.

## The pattern matching is where the domain knowledge lives

To analyse the HIE files, the developers built an eDSL for defining AST and Type patterns, based on the final tagless approach. The algorithm traverses the HIE AST for each HIE file in the project and matches every AST node against the given pattern to find potential improvement areas in the code. This is the part that distinguishes Stan from a general linter framework. A linter usually ships a fixed catalogue of rules written in the language of the host tool, while Stan's rules are patterns over both the AST and the types, which is what allows a check to say something like this expression has a type that could be specialised, or this function matches a shape that is known to be slow in a particular library. The README also explains the position Stan takes against the compiler itself, and it is a deliberate one. Haskell is statically typed, but not all properties can be encoded in types, and although GHC is a powerful compiler it tries to be library-agnostic and to provide only language-specific suggestions, while Stan uses knowledge about the current state of the ecosystem and commonly used libraries. So the tool is not trying to out-GHC GHC, it is trying to add the layer that a type system and a language-agnostic compiler cannot give you.

## The GHC version has to match, and that dictates installation

The installation section explains the constraint that shapes everything. Stan takes advantage of the GHC API to provide its analysis, and because of that, Stan and the analysed project need to be built with the same GHC version. The README links issue 178 for more details and states that this is why the way the README recommends is to build Stan from sources on your own machine. Compatibility is with GHC versions at or above 8.8. The README lists several installation routes, among them Cabal and Stack, Hackage, Homebrew, an Ubuntu PPA, and downloading a binary. The repository contents show how the multi-version problem is handled in practice, with a stack.yaml and a family of stack-ghc files pinned to specific compiler versions, including 9.4.8, 9.6.3, 9.6.4, 9.6.5, 9.6.6, 9.6.7, 9.8.4 and 9.10.2, one per supported compiler line, so a user of any of those versions has a resolvable dependency set. The example configuration files at the top level, .stan.toml and .stan-windows.toml, are the same configuration format described in the check rules section, and the presence of a Windows variant shows the tool is expected to run on both platforms.

## Reports as HTML or in the terminal

The output side is what most people will judge the tool on, and the README lists the features that make it distinct. You get pretty analysis results in both HTML and terminal reports, with an example of the terminal output included to show what sorts of analysis to expect, and a hosted example of an HTML report so you can see the full presentation rather than infer it. The reports carry suggestions and possible solutions for fixing the existing problems, not just the location of a smell, which is the difference between a linter that tells you what is wrong and one that helps you decide what to do. The goal of generating a report that can be used as a proof of code quality points at a use case that is not obvious from the name, and it is a legitimate one. For a team that wants to demonstrate to an auditor or a reviewer that a Haskell codebase follows a set of internal standards, a report generated by a configurable analyser is a defensible artefact, and the stable observation IDs are what make it a stable artefact rather than a snapshot that changes every run. The tool also has a wiki, and the README points to a glossary page for the terminology.

## Beta status, an open API and where Stan sits against other Haskell tooling

The project carries a warning at the top of the README that it is in the beta phase and that the API is subject to change if required by the maintainers' needs. The release history backs that reading. The most recent tags are v0.1.0.1 from 2023-10-15 and v0.1.0.0 from 2023-10-13, with v0.0.1.0 from 2020-07-09 described as covering binaries, an improved CLI and fixes, so the tool has been in a pre-1.0 state for the whole of its tagged life. The last push to the repository was on 2026-10-16 and the repository is not archived, so maintenance continues even though the tags are old, which is a reminder that for a Haskell library the source of truth is often the branch rather than the release list. The alternative to Stan is the toolchain you already have, and it is not a weak one. GHC's own warnings cover a good deal, HLint covers a different and large set of stylistic and partial patterns, and a type-driven linter such as a Liquid or Refinement type checker catches a category of error that neither Stan nor HLint claims. Stan's differentiator is the ecosystem knowledge and the HIE-based AST and type pattern matching, and its weakness is the same one it warns about, an API that can move under you. It is licensed MPL-2.0, the file-level copyleft licence, which is a reasonable choice for a tool that people extend with their own inspections.

## Conclusion

Adopt Stan if you maintain a Haskell codebase and want an ecosystem-aware lint that goes past what GHC reports, especially for library-agnostic advice about commonly used packages. Skip it if you need a stable tool with a frozen API, since the README calls Stan a beta whose API may change, and skip it if you cannot build Stan with the same GHC version as the project you want to analyse. Verify first that your GHC version is at or above 8.8, that you can build Stan against that exact compiler, and that the inspections you intend to enable are the ones matching your codebase's style rather than a generic list.

## FAQ

### How does Stan analyse a Haskell project?

Stan works from the HIE files that GHC produces, which contain the Haskell AST, identifier information and the types of all expressions and sub-expressions. It traverses the HIE AST for each file and matches every node against AST and type patterns defined in an eDSL, so it reuses the compiler's work instead of re-analyzing the source.

### Which GHC versions does Stan support?

The README states compatibility with GHC versions at or above 8.8, and that Stan and the analysed project must be built with the same GHC version because Stan uses the GHC API. For that reason the README recommends building Stan from sources, and the repository ships a stack file for each supported compiler line.

### How do I turn individual Stan inspections off?

You use checks, which have a type of include or exclude, a filter that can select by inspection id, category or severity, and a scope covering a file, a directory or everything. Checks can be written in TOML or given on the command line, and by default Stan analyses all source files with all implemented inspections.

## Sources

- [kowainik/stan on GitHub](https://github.com/kowainik/stan)
- [License: MPL-2.0](https://github.com/kowainik/stan/blob/main/LICENSE)
- [Project website](https://kowainik.github.io/projects/stan)
- [README](https://github.com/kowainik/stan/blob/main/README.md)
- [Releases](https://github.com/kowainik/stan/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kowainik-stan
