Files
dev/docs/00 INBOX/ai/nix-statix-deadnix-rules.md

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.