lints and suggestions for the nix programming language

Overview

statix

Lints and suggestions for the Nix programming language.

statix highlights antipatterns in Nix code. statix --fix can fix several such occurrences.

For the time-being, statix works only with ASTs produced by the rnix-parser crate and does not evaluate any nix code (imports, attr sets etc.).

Examples

$ statix tests/c.nix
[W04] Warning: Assignment instead of inherit from
   ╭─[tests/c.nix:2:3]
   │
 2 │   mtl = pkgs.haskellPackages.mtl;
   ·   ───────────────┬───────────────
   ·                  ╰───────────────── This assignment is better written with inherit
───╯

$ statix --fix --dry-run tests/c.nix
--- tests/c.nix
+++ tests/c.nix [fixed]
@@ -1,6 +1,6 @@
 let
-  mtl = pkgs.haskellPackages.mtl;
+  inherit (pkgs.haskellPackages) mtl;
 in
 null

Installation

statix is available via a nix flake:

# build from source
nix build git+https://git.peppe.rs/languages/statix
./result/bin/statix --help

# statix also provides a flake app
nix run git+https://git.peppe.rs/languages/statix -- --help

# save time on builds using cachix
cachix use statix

Usage

Basic usage is as simple as:

# recursively finds nix files and raises lints
statix /path/to/dir

# ignore generated files, such as Cargo.nix
statix /path/to/dir -i '*Cargo.nix'

# see `statix -h` for a full list of options

Certain lints have suggestions. Apply suggestions back to the source with:

statix --fix /path/to/file

# show diff, do not write to file
statix --fix --dry-run /path/to/file

statix supports a variety of output formats; standard, json and errfmt:

statix /path/to/dir -o json
statix /path/to/dir -o errfmt # singleline, easy to integrate with vim

Architecture

statix has the following components:

  • bin: the CLI/entrypoint
  • lib: library of lints and utilities to define these lints
  • vfs: virtual filesystem
  • macros: procedural macros to help define a lint

bin

This is the main point of interaction between statix and the end user. It's output is human-readable and should also support JSON/errorfmt outputs for external tools to use.

lib

A library of AST-based lints and utilities to help write those lints. It should be easy for newcomers to write lints without being familiar with the rest of the codebase.

vfs

VFS is an in-memory filesystem. It provides cheap-to-copy handles (FileIds) to access paths and file contents.

macros

This crate intends to be a helper layer to declare lints and their metadata.

TODO

  • Offline documentation for each lint
  • Test suite for lints and suggestions
  • Vim plugin (qf list population, apply suggestions)
  • Resolve imports and scopes for better lints
  • Add silent flag that exits with status
Comments
  • Support brew and/or cargo

    Support brew and/or cargo

    Hello, is it possible to get this tool to crates.io and/or homebrew ? That would streamline the installation process greatly (because if I don't use nix on a mac, I have to build it from source). Thanks in advance!

    enhancement help wanted 
    opened by delneg 11
  • Non-zero exit code on found problems

    Non-zero exit code on found problems

    I checked statix today and except for the unquoted splices (#8) and empty pattern I agree with its suggestions on my projects and I'd like to add it to CI.

    Though to make any use of it in CI, it is required that we get a non-zero exit code on found suggestions.

    bug 
    opened by NobbZ 7
  • Support macOS

    Support macOS

    I tried statix on my mac, but couldn't because the flake only enables x86_64-linux. I imagine this might be useful to others, so here's a little diff.

    Note that the libiconv addition is necessary so that the rust crate compiles on Darwin; I'm not sure why this is different from Linux, but a c compile step fails without it.

    opened by antifuchs 7
  • [Bug] Empty Pattern in `nixosModule`

    [Bug] Empty Pattern in `nixosModule`

    Original File:

    { ... }: rec { imports = [ ./console.nix ./firmware.nix ]; }
    

    Lint:

    $ statix check
    [W10] Warning: Found empty pattern in function argument
       ╭─[./modules/base/default.nix:1:1]
       │
     1 │ { ... }: rec { imports = [ ./console.nix ./firmware.nix ]; }
       · ───┬───  
       ·    ╰───── This pattern is empty, use _ instead
    ───╯
    

    File after using statix fix:

    _: rec { imports = [ ./console.nix ./firmware.nix ]; }
    

    Error:

    $ nix flake check
    error: module must match an open attribute set ('{ config, ... }')
     … while checking the NixOS module 'nixosModules.base'
    

    Using statix 0.4.0 on NixOS 21.11 with Nix 2.5

    opened by maydayv7 5
  • Json range output

    Json range output

    Nice tool! I was trying to integrate with null-ls using the json output, however the location output is a bit hard to parse it seems to be character position, an additional row/col output would be nice.

    opened by meck 5
  • Feature request: allow disabling certain checks

    Feature request: allow disabling certain checks

    I want to start applying the tool to nixpkgs. For that I would like to disable certain checks or only enable one to keep the changes small and for example only remove extra ().

    opened by SuperSandro2000 4
  • Deletes comments within empty let in

    Deletes comments within empty let in

    While debugging a package in nixpkgs I noticed the following oddity: when you format a file which has only comments in the let in, all comments get deleted. Since the comments can be actual code I don't think that is intended.

    Before:

    _:
    
    let
    # those = {};
    # are  = {};
    # comments = {};
    in {}
    

    after:

    _:
    
    {}
    

    statix 0.5.4

    opened by SuperSandro2000 3
  • statix also checks git ref files of branches with

    statix also checks git ref files of branches with ".nix" suffix

    i happen to have files like this in my project:

    • .git/logs/refs/heads/foobar.nix
    • .git/logs/refs/remotes/remote-name/foobar.nix

    statix will scan them unless explicitly ignored and error-out because these files have nothing to do with nix, it's just branch names with a .nix suffix.

    i can tell statix to ignore them using --ignore .git but it appears useful to teach statix to generally avoid .git subfolders, does this make sense?

    opened by tfc 3
  • Provide an output that has JSON enabled

    Provide an output that has JSON enabled

    Currently one has to override the statix package to be able to use the json feature, which again makes it impossible to use through nix run without wrapping the output in an own flake.

    This also results in not beeing able to use the cachix.

    It therefore would be nice to have an output that has the JSON feature enabled.

    Also --format json should not appear in the help if the JSON feature is not available.

    enhancement 
    opened by NobbZ 3
  • Invalid eta-reduction suggestions

    Invalid eta-reduction suggestions

    [W07] Warning: This function expression is eta reducible
        ╭─[./modules/syncthing.nix:86:13]
        │
     86 │       (map (folder: nameValuePair folder.path folder) foldersOnThisDevice);
        ·             ────────────────────┬───────────────────  
        ·                                 ╰───────────────────── Found eta-reduction: nameValuePair folder.path
    ────╯
    

    This warning is a false-positive because folder would now be undefined.

    Statix should ensure that any removed function arguments do not occur in the remaining expression before it suggests eta-reduction.

    opened by danth 3
  • Make include/exclude patterns available in `config.toml`

    Make include/exclude patterns available in `config.toml`

    Currently it seems as if enabling/disabling individual checks/lints is the only thing supported in the config.toml, this means that I still have to manually maintain a list of ignored files when running statix.

    I'd like if this was configurable through the config.toml.

    opened by NobbZ 2
  • Disabling checks locally via comments

    Disabling checks locally via comments

    for example:

    {
      # statix-ignore
      overlays.default = final: prev: import ./overlay.nix final prev;
    }
    
    {
      # statix-ignore: eta_reduction bool_simplification
      overlays.default = final: prev: import ./overlay.nix final prev;
    }
    
    opened by figsoda 0
  • [bin/src/config.rs:79] [self.ignore.as_slice(), extra_ignores].concat() = []

    [bin/src/config.rs:79] [self.ignore.as_slice(), extra_ignores].concat() = []

    If I run statix check on a file or directory, it prints the output:

    [bin/src/config.rs:79] [self.ignore.as_slice(), extra_ignores].concat() = []

    and nothing else. Looking at that line of code, I can see it is using the dbg! macro to print some debug output that doesn't seem relevant to the end user.

    It looks like this might be the expected output if there are no problems with a particular nix file, but it would be nice if there was some explicit acknowledgement that there was nothing wrong with the linted files, rather than just this debug print and exiting.

    opened by neunenak 1
  • `check` command fails on valid path where `fix` succeeds

    `check` command fails on valid path where `fix` succeeds

    I started using statix in CI and we ran into a very strange error, I reduced it down to this minimal reproduction:

    ./${f}.f
    

    This is a valid path, and if f is set to say "foo", then nix would eval it successfully to $PWD/foo.f. To make matters a little stranger, if you run statix fix on this same path it exits with a 0 exit status, no error reported. There must be some divergence between the check logic in the check command, and that in the fix command.

    opened by nrdxp 0
  • Warn when inheriting attributes from a function call

    Warn when inheriting attributes from a function call

    It is possible to inherit attributes from another set[^1]. For example:

    let
      x = { a = 1; b = 2; }
      y = { inherit (x) a b; }
    in
    y # evaluates to `{ a = 1; b = 2; }`
    

    But since Nix does not have maximal laziness, inheriting from the result of a function call will evaluate the function for each inherited attribute. That is, evaluating the expression below:

    let
      f = x:
        builtins.trace "evaluated" {
          a = x + 1;
          b = x + 2;
        };
      inherit (f 0) a b;
    in
    [ a b ]
    

    will evaluate f 0 2 times (which can be checked with trace). This can be avoided with:

    let
      f = x:
        builtins.trace "evaluated" {
          a = x + 1;
          b = x + 2;
        };
      set = f 0;
      inherit (set) a b;
    in
    [ a b ]
    

    In this case f 0 will be evaluated only once because the result is saved in a variable.

    [^1]: https://nixos.org/manual/nix/unstable/language/constructs.html#inheriting-attributes

    An exception to this is import. While the manual notes that import is a regular function, result of import <path> seems to be cached internally, so:

    let
      inherit (import <path>) f g;            # this is OK
      inherit (import <path> <some-args>) f g # this is not OK 
    in
    
    opened by ilkecan 0
Releases(v0.5.6)
  • v0.5.6(Jun 4, 2022)

    rework behavior of empty_let_in, thanks to a report from @SuperSandro2000:

    • if the let-in expression consists of only comments, statix check raises a warning, but statix fix leaves it as it is, because the comments may be commented out code
    • if the let-in expression is completely empty (no comments, no bindings, no inherits), statix check will raise a warning, statix fix will apply the fix
    Source code(tar.gz)
    Source code(zip)
  • v0.5.5(May 1, 2022)

  • v0.5.4(Feb 20, 2022)

    • statix runs in parallel over all files thanks to rayon
    • new lints: useless_has_attr, bool_simplification
    • new subcommand: statix-list
    • show only available out-formats when conditionally compiled
    Source code(tar.gz)
    Source code(zip)
  • v0.5.3(Feb 13, 2022)

Owner
Akshay
Akshay
A repository for showcasing my knowledge of the Rust programming language, and continuing to learn the language.

Learning Rust I started learning the Rust programming language before using GitHub, but increased its usage afterwards. I have found it to be a fast a

Sean P. Myrick V19.1.7.2 2 Nov 8, 2022
Nyah is a programming language runtime built for high performance and comes with a scripting language.

?? Nyah ( Unfinished ) Nyah is a programming language runtime built for high performance and comes with a scripting language. ??️ Status Nyah is not c

Stacker 3 Mar 6, 2022
An alternative broken buggy Nix implementation in Rust + Java (for evaluation)

An alternative broken buggy Nix implementation in Rust + Java (for evaluation)

Moritz Hedtke 1 Feb 12, 2022
Tvix - A Rust implementation of Nix

Tvix Tvix is a new implementation of the Nix language and package manager. See the announcement post for information about the background of this proj

The Virus Lounge 49 Feb 17, 2023
lelang programming language is a toy language based on LLVM.

lelang leang是一门使用Rust编写,基于LLVM(inkwell llvm safe binding library)实现的编程语言,起初作为课程实验项目,现在为个人长期维护项目。 Target Features 支持8至64位的整形类型和32/64位浮点 基本的函数定义,调用,声明外部

Aya0wind 5 Sep 4, 2022
Orion lang is a lispy programming language that is strongly and statically typed.

Orion Orion is a lisp inspired statically typed programming language written in Rust Install To install orion you can either: Download binary from the

Wafelack 226 Dec 17, 2022
Mote is a systems-programming language designed to be practical, performant, and simple.

Mote NOTE: this following lists the goals for what Mote is supposed to be. It does not promise that any of the features here will be accomplished or a

The Mote Programming Language 14 Jul 28, 2021
A turing-complete programming language using only zero-width unicode characters, inspired by brainfuck and whitespace.

Zero-Width A turing-complete programming language using only zero-width unicode characters, inspired by brainfuck and whitespace. Currently a (possibl

Gavin M 2 Jan 14, 2022
A simple programming language for something between C and Rust.

inuc inuc is a systems programming language that is something between C and Rust. Features : [] Strong , static typing (type inference not a priority

Sagnik Chatterjee 1 Feb 7, 2022
🐱 A high-speed JIT programming language and its runtime, meow~

?? A high-speed JIT programming language and its runtime, meow~

EnabledFish 30 Dec 22, 2022
A statically-typed, interpreted programming language, with generics and type inference

Glide A programming language. Currently, this includes: Static typing Generics, with monomorphization Type inference on function calls func identity<T

Patrick Gu 1 Apr 10, 2022
Stack-based programming language which emulates the look and feel of the 60s

Cauchemar Cauchemar is a stack-based programming language inspired by FORTH but more arcane. Emulates the look and feel of a programming language from

Yuki Langley 4 Dec 29, 2022
The compiler for Gera, a statically typed and garbage collected programming language.

Gera The compiler for Gera, a satically typed and garbage collected programming language. Currently WIP (Work in progress). Progress This is a rough o

null 4 Oct 26, 2023
Nixt is an interpreted programming language written in Rust

Nixt Nixt is an interpreted lisp inspired programming language written in Rust Index About Examples Installation Build About Nixt goal is to provide a

Wafelack 17 Jul 18, 2022
A programming language somewhat resembling cellular processes.

cytosol An embeddable programming language somewhat resembling cellular processes. State of the implementation tokenising parsing semantic analysis an

null 33 Sep 14, 2022
a function programming language for real world applications made in rust

a function programming language for real world applications made in rust

Tanay Pingalkar 6 Jun 12, 2022
Rust implementation of µKanren, a featherweight relational programming language.

µKanren-rs This is a Rust implementation of µKanren, a featherweight relational programming language. See the original Scheme implementation here for

Eric Zhang 99 Dec 8, 2022
A stack based interpreted programming language.

Nightmare Nightmare is a dynamically-typed, procedural programming language that aims to be fast & simple. let user = input() as Int; print("You were

&potato 4 Nov 12, 2021
Gecko is a high-level, general-purpose programming language built on top of the LLVM project.

Gecko is a high-level, general-purpose programming language built on top of the LLVM project. Gecko Technology & principles Gecko is a general-purpose

Gecko 19 Oct 3, 2022