# Ameba: a static analysis linter for Crystal code

> Ameba is a static code analysis tool for the Crystal language that enforces the official code style conventions and flags code smells and wrong code constructs, with rule groups, per rule configuration and an autocorrect pass. It is a good fit for Crystal teams that want one command in CI, and a poor fit for anyone expecting type aware analysis or a pluggable rule API.

**crystal-ameba/ameba** — A static code analysis tool for Crystal

- Repository: https://github.com/crystal-ameba/ameba
- Website: https://crystal-ameba.org
- Stars: 571 · Forks: 44
- Language: Crystal
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/crystal-ameba-ameba

## What Ameba checks in a Crystal codebase

Ameba is a static code analysis tool for the Crystal language. It has two jobs, and the project documentation is explicit about both: it enforces a consistent Crystal code style, and it catches code smells and wrong code constructs. The style half is anchored to the published Crystal coding style conventions, so the rules are not arbitrary personal preferences but a shared reference that Crystal users already read. The smell half is where the interesting findings live, and the report the tool prints is the best evidence of what that means in practice.

A run prints a dot for every clean file, an F for every file with a finding, and then a per issue block with a path, a line and column, a severity marker and the rule name. The sample output in the documentation shows `Lint/UnusedArgument` flagging a named parameter that the method never uses, `Lint/UselessAssign` flagging an assignment whose value is overwritten, and `Style/RedundantReturn` flagging a `return` that a bare expression already provides. Those three names describe a class of problems that no formatter can fix, since a formatter only rearranges bytes and leaves the semantics of a useless assignment untouched.

The run also ends with a count of inspected files and failures, and with elapsed time in milliseconds. For a team, that summary line is the part that matters in a CI job, because it is a number you can fail the build on without parsing the report.

## Rule groups, --only and --except narrow the blast radius

Rules are addressed by a group and a name, written as `Group/RuleName`, and the documented examples show groups named `Style` and `Lint`. `Style/RedundantReturn` and `Style/RedundantBegin` sit in the style group, while `Lint/UselessAssign`, `Lint/UnusedArgument` and `Lint/Syntax` sit in the lint group. The command line takes either a single rule or a comma separated list of groups, and the same filter works in both directions.

```bash
$ ameba --only   Lint/Syntax # runs only Lint/Syntax rule
$ ameba --only   Style,Lint  # runs only rules from Style and Lint groups
$ ameba --except Lint/Syntax # runs all rules except Lint/Syntax
$ ameba --except Style,Lint  # runs all rules except rules in Style and Lint groups
```

This matters more than it sounds for adoption. A linter that lands with fifty findings on day one gets switched off, while a linter that lands with the one rule you agree with gets kept. The `--except` form is the migration tool: you can exclude the `Style` group for a month, then enable one style rule at a time once the team has seen what it actually catches.

The same selection is available from the configuration file, where a single rule can be switched off with an `Enabled` key, and the documentation notes that one or more rules or groups can be included or excluded either way. The rule names also carry a description, so a reviewer who does not understand a finding can ask the tool what the rule means rather than guessing.

## Four install paths, and the trade-off each one makes

Ameba can be added to a project as a development dependency, installed from Homebrew, run from a Docker image, or built from source. The project dependency path is the one most Crystal teams will use, and the documentation gives two ways to do it. Adding it to `shard.yml` and building through the shards tool gives you a compiled binary, while adding an explicit target prioritises runtime performance over compilation time.

```yaml
development_dependencies:
  ameba:
    github: crystal-ameba/ameba
```

```yaml
targets:
  ameba:
    main: lib/ameba/bin/ameba.cr
```

```bash
$ shards build ameba
```

```bash
$ crystal build -o bin/ameba lib/ameba/bin/ameba.cr
```

Both of those produce a binary under `bin/ameba`. You can also skip the target entirely and run the entry file directly, compiling it on the fly, which the documentation is candid about calling the slowest option. The Homebrew route is two commands and gives you a system wide `ameba`.

```bash
$ brew tap crystal-ameba/ameba
$ brew install ameba
```

The Docker route exists and the image is published on GitHub as a container package. You build it locally from the repository Dockerfile, then mount the directory you want inspected into `/src`, since the image entrypoint is the `ameba` binary itself and the working directory is set to `/src`.

```bash
$ docker build -t ghcr.io/crystal-ameba/ameba .
$ docker run -v $(pwd):/src ghcr.io/crystal-ameba/ameba
```

For CI, the container path is the most self contained, since the image is built on Alpine and the final stage adds the runtime libraries the compiled Crystal binary needs. Building from source is a clone plus `make install`, and the Makefile exposes the usual targets including `make test`, which runs the spec suite and then runs Ameba against its own code base.

## Autocorrection only touches the rules marked Correctable

Findings that the tool can fix itself are marked `[Correctable]` in the report, and the `--fix` flag applies them. The distinction is a good one, because it separates mechanical edits that carry no judgement, such as removing a redundant `return`, from findings that need a human to decide, such as an unused argument where the correct fix might be to actually use the argument rather than rename it to `_`.

```bash
$ ameba --fix
```

The practical risk is the same as with any rewrite pass: a tool that edits source files in place can change behaviour it did not intend to. The mitigation available here is that the set of files touched is limited to the rules that carry the `[Correctable]` marker, so a first run on a clean repository should be reviewed as a diff before it is committed. The documentation does not describe a dry run mode, a list of files that would be touched, or a way to undo a fix pass, so review the diff yourself.

One detail worth knowing: the `Lint/UnusedArgument` message itself suggests using `_` as an argument name to signal that the value is intentionally unused, which is a different remedy from the one the tool can apply automatically. It is a hint for the developer, and it is also a way to silence the rule deliberately rather than by suppression comment.

## .ameba.yml controls which files are inspected

The default configuration file is `.ameba.yml`, and it does three things: it configures rule properties, it disables specific rules, and it excludes sources from the rules. You can generate a starter file with `ameba --gen-config`, and the repository ships both `.ameba.yml` and a JSON schema for it, so editors can offer completion and validation for the keys.

The file selection is driven by two sections. `Globs` is an array of wildcards or paths to include, and the documented default is `%w[**/*.cr **/*.ecr]`, which picks up Crystal source and ECR template files. `Excluded` is an array of wildcards to subtract from that list, and its documented default is `%w[lib]`. A project that wants the opposite shape of default can write it out explicitly.

```yaml
Globs:
  - "**/*.cr"
  - "**/*.ecr"
```

Sources can also be excluded at the level of a single rule, which is the more precise tool when one rule misfires on a few files but is useful everywhere else. The documentation gives a per rule `Excluded` list as the form to use.

```yaml
Style/RedundantBegin:
  Excluded:
    - src/server/processor.cr
    - src/server/api.cr
```

Note the shape of the default exclusions, because it tells you something about expectations: the `lib` folder is skipped out of the box, so a linter run in a project whose real code lives under `lib` will report far less than a developer expects. Checking the `Globs` and `Excluded` values before the first CI run avoids that confusion.

## Inline directives silence a rule at the point it fires

Beyond configuration, a rule or group can be switched off for a single line by an inline comment. The syntax is a comment beginning with `ameba:disable`, followed by either a single rule, a comma separated list of rules, or a group name.

```crystal
# ameba:disable Style/LargeNumbers
time = Time.epoch(1483859302)

time = Time.epoch(1483859302) # ameba:disable Style/LargeNumbers, Lint/UselessAssign
time = Time.epoch(1483859302) # ameba:disable Style, Lint
```

The third form is the coarse one, disabling whole groups for that line, and it is the form most likely to hide something. The first two are narrow enough to be reviewable in a diff, since a reader can see exactly which rule was silenced and judge it at that point in the file.

The pattern used in the example is the interesting part. A literal epoch timestamp is a `Style/LargeNumbers` violation, and the fix a human would want is to name the constant rather than to suppress the rule. The directive is honest about being a suppression, but a codebase full of them is a codebase where the linter has stopped carrying information. Treating a growing count of inline disables as a signal to fix the underlying code, or to write a per rule exclusion for a whole file, keeps the suppressions from becoming the standard way to get a clean report.

## Where Ameba stops, and what it does not try to be

Ameba's rule set is fixed and built in. The documentation describes enabling, disabling and configuring rules, and there is no mention of a plugin interface, a rule DSL, or a way to write a custom check. If your team has a convention that Crystal itself does not have, Ameba has no documented mechanism to encode it, and you are left with inline disables or with writing your own separate check. That is the sharpest limitation of the tool, and it is worth knowing before you pick it up.

It is also not a type checker. Everything Ameba reports comes from the shape of the code, and the categories it names are code smells and wrong code constructs, which is a different thing from proving a type error. The sample rule names underline that: an unused argument, a useless assignment, a redundant return. None of them require type information, and none of them would catch a wrong argument type.

There is also a security dimension the tool does not address. The repository carries a `SECURITY.md`, which is a disclosure policy for the project itself, not a scanning feature. Ameba has no rule family for security findings, so a team that wants dependency vulnerability checking or pattern based detection of risky constructs has to run something else alongside it.

A last practical point: the report format is plain text with a path, a line and a column, which is friendly to editors. The documentation lists editor integrations including the Ameba Language Server `ameba-ls` and a Vim plugin, and explains how to ask for a single finding to be explained in depth by passing the `PATH:line:column` string from the report back to the tool.

```bash
$ ameba crystal/command/format.cr:26:83           # show explanation for the issue
$ ameba --explain crystal/command/format.cr:26:83 # same thing
```

The tool can also describe a rule outright, which is the quickest way to settle an argument about whether a finding is intentional.

```bash
$ ameba --describe Lint/UselessAssign
```

## Conclusion

Adopt Ameba if you maintain Crystal code and want a single command that reports style violations and code smells with an autocorrect pass you can run in CI. Do not adopt it as a type checker, as a security scanner, or as a platform for writing your own rules, since the documented surface stops at built in rule groups and configuration. Before rolling it out, run `ameba` on the existing codebase with the default `.ameba.yml` and check how many findings the `Excluded` list has to absorb, then confirm the rules marked `[Correctable]` produce a diff your team is willing to review.

## FAQ

### How do I install Ameba for a Crystal project?

The documentation offers four routes: add it to `shard.yml` as a development dependency and build it with `shards build ameba`, install it with `brew install ameba`, build the Docker image and mount your source directory into `/src`, or clone the repository and run `make install`.

### What does the --fix flag do in Ameba?

It automatically corrects the findings that are marked `[Correctable]` in the report, such as a `Style/RedundantReturn`. Findings that carry no such marker, including `Lint/UnusedArgument`, are left for a human to handle.

### How do I stop Ameba from reporting on a specific file or rule?

Use the `Excluded` section of `.ameba.yml` for whole files, a per rule `Excluded` list for a single rule, `Enabled: false` to switch a rule off, and the `--except` flag to skip groups from the command line. Inline `ameba:disable` comments silence a rule on one line.

### What does Ameba check that the Crystal formatter does not?

Ameba reports code smells and wrong code constructs that formatting does not touch, for example `Lint/UselessAssign` and `Lint/UnusedArgument`, in addition to enforcing the Crystal code style conventions. It groups these under `Style` and `Lint` rule names that can be selected with `--only` and `--except`.

### Can I write my own rules for Ameba?

The documentation covers configuring, enabling and disabling the built in rules, but it does not describe a plugin interface or a way to define custom rules. There is no documented extension point for a team specific check.

## Sources

- [crystal-ameba/ameba on GitHub](https://github.com/crystal-ameba/ameba)
- [License: MIT](https://github.com/crystal-ameba/ameba/blob/master/LICENSE)
- [Project website](https://crystal-ameba.org)
- [README](https://github.com/crystal-ameba/ameba/blob/master/README.md)
- [Releases](https://github.com/crystal-ameba/ameba/releases)

---

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