Files
dev/docs/00 INBOX/ai/nix-statix-deadnix-rules.md
alexion ddc7952399 feat(xatu): scaffold project
Add the Xatu Rust package as a minimal hello-world skeleton with Cargo workspace metadata and Bazel library, binary, and test targets.

Include the Statix and Deadnix evaluation notes so the new linter work starts with the prior rule and architecture research.
2026-09-17 00:26:12 -04:00

792 lines
12 KiB
Markdown

# Nix Statix and Deadnix Rules
## Purpose
This note rewrites the current `statix` and `deadnix` findings as possible `nix-style` rules.
Use the `Decision` line on each rule for `keep`, `discard`, `update`, or `need-more-info`.
## Source Baseline
- `statix` rule names come from `nix run --inputs-from . nixpkgs#statix -- list`.
- `deadnix` checks come from `nix run --inputs-from . nixpkgs#deadnix -- --help`.
- `deadnix` reports categories instead of named rules.
- At collection time, `src/dotfiles/apps.nix` ran `statix fix .` and `deadnix --edit` in `nix run .#fix`.
- At collection time, `statix.toml` disabled `repeated_keys`.
## Statix Rules
### `bool_comparison`
Decision: keep
Source: `statix` `W01`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Do not compare a boolean expression to `true` or `false`.
Use the expression directly for true checks.
Use `!` for false checks.
Avoid:
```nix
x == true
x == false
```
Use:
```nix
x
!x
```
#### Rationale
Boolean literal comparisons add noise without changing behavior.
Direct predicates make conditions easier to scan.
### `empty_let_in`
Decision: keep
Source: `statix` `W02`.
Status at collection time: enabled.
Autofix: yes when the `let` body has no comments.
#### Rule
Do not keep a `let` expression that declares no bindings.
Replace it with its body.
Avoid:
```nix
let in value
```
Use:
```nix
value
```
#### Rationale
An empty `let` adds syntax without naming a dependency.
It usually remains after editing or debugging.
### `manual_inherit`
Decision: Keep
Source: `statix` `W03`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Use `inherit` when an attribute assignment repeats the same name as its value.
Avoid:
```nix
{
package = package;
}
```
Use:
```nix
{
inherit package;
}
```
#### Rationale
`inherit` states that the attribute comes from the current scope.
It removes duplicated names.
### `manual_inherit_from`
Decision:
Source: `statix` `W04`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Use `inherit (source)` when an assignment selects an attribute with the same name.
Avoid:
```nix
package = pkgs.package;
```
Use:
```nix
inherit (pkgs) package;
```
#### Rationale
`inherit (source)` states that the value comes from an attribute set.
It avoids repeated selector names.
### `legacy_let_syntax`
Decision: keep
Source: `statix` `W05`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Do not use legacy `let { ... }` syntax.
Use documented Nix expressions.
Avoid:
```nix
let {
body = x + y;
x = 2;
y = 3;
}
```
Use:
```nix
rec {
body = x + y;
x = 2;
y = 3;
}.body
```
#### Rationale
Legacy `let` syntax is undocumented.
Documented forms are clearer to tools and readers.
### `collapsible_let_in`
Decision: keep
Source: `statix` `W06`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Collapse a `let` expression when its body is another `let` expression.
Put both binding groups in one `let` when scope permits it.
Avoid:
```nix
let
a = 1;
in
let
b = 2;
in
a + b
```
Use:
```nix
let
a = 1;
b = 2;
in
a + b
```
#### Rationale
Nested `let` expressions add structure when they do not add a scope boundary.
A single binding group shows the local dependencies together.
### `eta_reduction`
Decision: keep
Source: `statix` `W07`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Remove a lambda that only passes its argument to another function.
Keep the target function name when that preserves behavior.
Avoid:
```nix
map (x: double x) values
```
Use:
```nix
map double values
```
#### Rationale
The wrapper lambda adds no behavior.
The direct function reference exposes the operation.
### `useless_parens`
Decision: keep
Source: `statix` `W08`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Remove parentheses around expressions when they do not affect parsing.
Avoid:
```nix
let
double = (x: x * 2);
in
(value)
```
Use:
```nix
let
double = x: x * 2;
in
value
```
#### Rationale
Redundant parentheses make expressions harder to scan.
Required precedence parentheses remain valid.
### `empty_pattern`
Decision: keep
Source: `statix` `W10`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Use `_` instead of an empty variadic function pattern when the argument is ignored.
Do not apply this rule to NixOS-style modules that declare `imports`.
Avoid:
```nix
{ ... }: value
```
Use:
```nix
_: value
```
#### Rationale
`_` directly marks an ignored argument.
An empty variadic pattern looks like partial destructuring.
### `redundant_pattern_bind`
Decision: keep
Source: `statix` `W11`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Do not bind a variadic pattern when the pattern captures no fields.
Use a single argument name instead.
Avoid:
```nix
args @ { ... }: args.nixpkgs
```
Use:
```nix
args: args.nixpkgs
```
#### Rationale
The empty pattern adds no destructuring value.
A named argument shows that the whole argument is used.
### `unquoted_uri`
Decision: keep
Source: `statix` `W12`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Quote URI literals.
Do not use Nix's special unquoted URI syntax.
Avoid:
```nix
url = github:owner/repository;
```
Use:
```nix
url = "github:owner/repository";
```
#### Rationale
Quoted strings work for all URI forms.
They avoid ambiguity with punctuation after the URI.
### `empty_inherit`
Decision: keep
Source: `statix` `W14`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Remove `inherit` statements that inherit no names.
Avoid:
```nix
inherit;
```
Use no replacement.
#### Rationale
An empty `inherit` has no effect.
It usually remains after a refactor.
### `deprecated_to_path`
Decision: keep
Source: `statix` `W17`.
Status at collection time: enabled.
Autofix: no.
#### Rule
Do not use `builtins.toPath` or unqualified `toPath`.
Use explicit path construction instead.
Avoid:
```nix
builtins.toPath "/path"
```
Use:
```nix
/. + "/path"
./. + "/relative-path"
```
#### Rationale
`toPath` is deprecated.
Explicit path construction states whether the result is absolute or relative.
### `bool_simplification`
Decision: keep
Source: `statix` `W18`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Use `!=` instead of negating an equality comparison.
Avoid:
```nix
!(x == y)
```
Use:
```nix
x != y
```
#### Rationale
The direct operator expresses inequality without nested boolean syntax.
### `useless_has_attr`
Decision: keep
Source: `statix` `W19`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Use `or` when an `if` expression only checks for an attribute before selecting it.
Avoid:
```nix
if x ? a then x.a else default
```
Use:
```nix
x.a or default
```
#### Rationale
The `or` form is the Nix operator for attribute defaults.
It avoids repeating the attribute path.
### `repeated_keys`
Decision: update so that it doesn't conflict with the custom module convention.
Source: `statix` `W20`.
Status at collection time: disabled in `statix.toml`.
Autofix: no.
#### Rule
Group repeated attribute path prefixes in a nested attribute set.
Apply this rule only when at least three sibling assignments share the same first key.
Do not apply it inside `rec` attribute sets.
Avoid:
```nix
{
services.a = 1;
services.b = 2;
services.c = 3;
}
```
Use:
```nix
{
services = {
a = 1;
b = 2;
c = 3;
};
}
```
#### Rationale
Grouping repeated prefixes reduces visual repetition.
The local config disables this rule, so it needs an explicit decision before promotion.
### `empty_list_concat`
Decision: keep
Source: `statix` `W23`.
Status at collection time: enabled.
Autofix: yes.
#### Rule
Remove list concatenation with an empty list.
Avoid:
```nix
[] ++ values
values ++ []
```
Use:
```nix
values
```
#### Rationale
Concatenating `[]` changes no values.
It usually remains after conditional list construction changed.
## Deadnix Checks
### Unused `let` bindings
Decision: keep
Source: `deadnix` default check.
Status at collection time: active in `nix run .#fix`.
Autofix: yes.
#### Rule
Remove `let` bindings when no live expression uses their names.
Do not keep a binding only because another dead binding uses it.
Use `# deadnix: skip` only for intentional unused declarations.
Avoid:
```nix
let
unused = 1;
in
value
```
Use:
```nix
value
```
#### Rationale
Unused bindings hide the dependencies that matter.
They also make refactors look unfinished.
### Unused `inherit` names
Decision: keep
Source: `deadnix` default check.
Status at collection time: active in `nix run .#fix`.
Autofix: yes.
#### Rule
Remove names from `inherit` statements when no live expression uses those names.
Remove the whole `inherit` statement when it becomes empty.
Avoid:
```nix
let
inherit (builtins) unused;
in
value
```
Use:
```nix
value
```
#### Rationale
Unused inherited names create the same noise as unused `let` bindings.
An empty result also overlaps with `statix` `empty_inherit`.
### Unused lambda arguments
Decision: keep
Source: `deadnix` default check.
Status at collection time: active in `nix run .#fix`.
Autofix: yes.
#### Rule
Mark an unused single lambda argument with `_`.
Do not keep a meaningful name for an ignored argument.
Avoid:
```nix
arg: value
```
Use:
```nix
_: value
```
#### Rationale
`_` states that the argument is intentionally ignored.
A named unused argument implies missing logic.
### Unused lambda pattern names
Decision: keep
Source: `deadnix` default check.
Status at collection time: active in `nix run .#fix`.
Autofix: yes.
#### Rule
Remove unused names from lambda attribute patterns.
Keep only names used by the body or defaults.
Use `...` when callers can pass extra attributes.
Avoid:
```nix
{ used, unused, ... }: used
```
Use:
```nix
{ used, ... }: used
```
#### Rationale
Pattern names usually declare dependencies.
Unused names overstate the function's inputs.
### Unused lambda pattern aliases
Decision: keep
Source: `deadnix` default check.
Status at collection time: active in `nix run .#fix`.
Autofix: yes.
#### Rule
Remove a lambda pattern alias when the body does not use the alias.
Keep the alias only when code reads the whole argument set.
Avoid:
```nix
args @ { used, ... }: used
```
Use:
```nix
{ used, ... }: used
```
#### Rationale
The alias says the function needs the complete input set.
Keeping it unused makes the function contract look wider than it is.
### Ignored underscore bindings
Decision: keep
Source: `deadnix` `--no-underscore` option.
Status at collection time: inactive; current fixer does not pass `--no-underscore`.
Autofix: no separate rule.
#### Rule
Treat bindings whose names start with `_` as intentionally ignored.
Optionally skip unused diagnostics for those names.
Avoid, when the skip policy is active:
```nix
let
_unused = expensiveValue;
in
value
```
Use only for intentional placeholders.
#### Rationale
An underscore prefix is a common marker for intentionally unused names.
Skipping all underscore bindings can hide real dead code.
### Used underscore bindings
Decision: keep
Source: `deadnix` `--warn-used-underscore` option.
Status at collection time: inactive; current fixer does not pass `--warn-used-underscore`.
Autofix: no.
#### Rule
Do not use a binding whose name starts with `_`.
Rename it to a normal name when code references it.
Avoid:
```nix
let
_package = pkgs.hello;
in
_package
```
Use:
```nix
let
package = pkgs.hello;
in
package
```
#### Rationale
The underscore prefix communicates that the name is intentionally unused.
Using it contradicts that signal.
## Notes for Decisions
- `statix` rules are mostly syntax rewrites.
- `deadnix` rules need scope analysis and dead-code reachability.
- `deadnix` does not report `rec` attribute-set bindings as removable findings.
- `statix` `repeated_keys` is present in the installed rule list but disabled locally.
- Rules that change module argument patterns need extra care around NixOS modules and `callPackage` functions.