# 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.