Files
dev/docs/00 INBOX/ai/nix-statix-deadnix-rules.md
alexion f98af6ad3f chore(nix): remove statix and deadnix from fixer
Drop the statix and deadnix packages from the fix app now that the repository has a local Nix style linter path. Remove the obsolete statix configuration, document the evaluated rule sets and architectures, and keep vendored third-party files out of treefmt.
2026-09-16 23:56:14 -04:00

12 KiB

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:

x == true
x == false

Use:

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:

let in value

Use:

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:

{
  package = package;
}

Use:

{
  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:

package = pkgs.package;

Use:

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:

let {
  body = x + y;
  x = 2;
  y = 3;
}

Use:

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:

let
  a = 1;
in
let
  b = 2;
in
  a + b

Use:

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:

map (x: double x) values

Use:

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:

let
  double = (x: x * 2);
in
  (value)

Use:

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:

{ ... }: value

Use:

_: 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:

args @ { ... }: args.nixpkgs

Use:

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:

url = github:owner/repository;

Use:

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:

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:

builtins.toPath "/path"

Use:

/. + "/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:

!(x == y)

Use:

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:

if x ? a then x.a else default

Use:

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:

{
  services.a = 1;
  services.b = 2;
  services.c = 3;
}

Use:

{
  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:

[] ++ values
values ++ []

Use:

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:

let
  unused = 1;
in
  value

Use:

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:

let
  inherit (builtins) unused;
in
  value

Use:

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:

arg: value

Use:

_: 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:

{ used, unused, ... }: used

Use:

{ 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:

args @ { used, ... }: used

Use:

{ 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:

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:

let
  _package = pkgs.hello;
in
  _package

Use:

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.