diff --git a/modules/hooks.nix b/modules/hooks.nix index 5c71a3e2..4d112640 100644 --- a/modules/hooks.nix +++ b/modules/hooks.nix @@ -19,80 +19,9 @@ let in { + # PLEASE keep this sorted alphabetically. options.settings = { - ansible-lint = - { - configPath = mkOption { - type = types.str; - description = lib.mdDoc "Path to the YAML configuration file."; - # an empty string translates to use default configuration of the - # underlying ansible-lint binary - default = ""; - }; - subdir = mkOption { - type = types.str; - description = lib.mdDoc "Path to the Ansible subdirectory."; - default = ""; - }; - }; - hpack = - { - silent = - mkOption { - type = types.bool; - description = lib.mdDoc "Whether generation should be silent."; - default = false; - }; - }; - hlint = - { - hintFile = - mkOption { - type = types.nullOr types.path; - description = lib.mdDoc "Path to hlint.yaml. By default, hlint searches for .hlint.yaml in the project root."; - default = null; - }; - }; - isort = - { - profile = - mkOption { - type = types.enum [ "" "black" "django" "pycharm" "google" "open_stack" "plone" "attrs" "hug" "wemake" "appnexus" ]; - description = lib.mdDoc "Built-in profiles to allow easy interoperability with common projects and code styles."; - default = ""; - }; - flags = - mkOption { - type = types.str; - description = lib.mdDoc "Flags passed to isort. See all available [here](https://pycqa.github.io/isort/docs/configuration/options.html)."; - default = ""; - }; - }; - latexindent = - { - flags = - mkOption { - type = types.str; - description = lib.mdDoc "Flags passed to latexindent. See available flags [here](https://latexindentpl.readthedocs.io/en/latest/sec-how-to-use.html#from-the-command-line)"; - default = "--local --silent --overwriteIfDifferent"; - }; - }; - ormolu = - { - defaultExtensions = - mkOption { - type = types.listOf types.str; - description = lib.mdDoc "Haskell language extensions to enable."; - default = [ ]; - }; - cabalDefaultExtensions = - mkOption { - type = types.bool; - description = lib.mdDoc "Use `default-extensions` from `.cabal` files."; - default = false; - }; - }; alejandra = { check = @@ -132,6 +61,66 @@ in example = "quiet"; }; }; + ansible-lint = + { + configPath = mkOption { + type = types.str; + description = lib.mdDoc "Path to the YAML configuration file."; + # an empty string translates to use default configuration of the + # underlying ansible-lint binary + default = ""; + }; + subdir = mkOption { + type = types.str; + description = lib.mdDoc "Path to the Ansible subdirectory."; + default = ""; + }; + }; + autoflake = + { + binPath = + mkOption { + type = types.str; + description = lib.mdDoc "Path to autoflake binary."; + default = "${pkgs.autoflake}/bin/autoflake"; + defaultText = lib.literalExpression '' + "''${pkgs.autoflake}/bin/autoflake" + ''; + }; + + flags = + mkOption { + type = types.str; + description = lib.mdDoc "Flags passed to autoflake."; + default = "--in-place --expand-star-imports --remove-duplicate-keys --remove-unused-variables"; + }; + }; + clippy = + { + denyWarnings = mkOption { + type = types.bool; + description = lib.mdDoc "Fail when warnings are present"; + default = false; + }; + offline = mkOption { + type = types.bool; + description = lib.mdDoc "Run clippy offline"; + default = true; + }; + allFeatures = mkOption { + type = types.bool; + description = lib.mdDoc "Run clippy with --all-features"; + default = false; + }; + }; + credo = { + strict = + mkOption { + type = types.bool; + description = lib.mdDoc "Whether to auto-promote the changes."; + default = true; + }; + }; deadnix = { edit = @@ -183,114 +172,23 @@ in default = false; }; }; - statix = + denofmt = { - format = + write = mkOption { - type = types.enum [ "stderr" "errfmt" "json" ]; - description = lib.mdDoc "Error Output format."; - default = "errfmt"; + type = types.bool; + description = lib.mdDoc "Whether to edit files inplace."; + default = true; }; - - ignore = + configPath = mkOption { - type = types.listOf types.str; - description = lib.mdDoc "Globs of file patterns to skip."; - default = [ ]; - example = [ "flake.nix" "_*" ]; + type = types.str; + description = lib.mdDoc "Path to the configuration JSON file"; + # an empty string translates to use default configuration of the + # underlying deno binary (i.e deno.json or deno.jsonc) + default = ""; }; }; - markdownlint = { - config = - mkOption { - type = types.attrs; - description = lib.mdDoc - "See https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.jsonc"; - default = { }; - }; - }; - mdl = { - package = - mkOption { - type = types.package; - description = lib.mdDoc "The `mdl` package to use."; - default = "${tools.mdl}"; - defaultText = "\${tools.mdl}"; - example = "\${pkgs.mdl}"; - }; - configPath = - mkOption { - type = types.str; - description = lib.mdDoc "The configuration file to use."; - default = ""; - }; - git-recurse = - mkOption { - type = types.bool; - description = lib.mdDoc "Only process files known to git when given a directory."; - default = false; - }; - ignore-front-matter = - mkOption { - type = types.bool; - description = lib.mdDoc "Ignore YAML front matter."; - default = false; - }; - json = - mkOption { - type = types.bool; - description = lib.mdDoc "Format output as JSON."; - default = false; - }; - rules = - mkOption { - type = types.listOf types.str; - description = lib.mdDoc "Markdown rules to use for linting. Per default all rules are processed."; - default = [ ]; - }; - rulesets = - mkOption { - type = types.listOf types.str; - description = lib.mdDoc "Specify additional ruleset files to load."; - default = [ ]; - }; - show-aliases = - mkOption { - type = types.bool; - description = lib.mdDoc "Show rule alias instead of rule ID when viewing rules."; - default = false; - }; - warnings = - mkOption { - type = types.bool; - description = lib.mdDoc "Show Kramdown warnings."; - default = false; - }; - skip-default-ruleset = - mkOption { - type = types.bool; - description = lib.mdDoc "Do not load the default markdownlint ruleset. Use this option if you only want to load custom rulesets."; - default = false; - }; - style = - mkOption { - type = types.str; - description = lib.mdDoc "Select which style mdl uses."; - default = "default"; - }; - tags = - mkOption { - type = types.listOf types.str; - description = lib.mdDoc "Markdown rules to use for linting containing the given tags. Per default all rules are processed."; - default = [ ]; - }; - verbose = - mkOption { - type = types.bool; - description = lib.mdDoc "Increase verbosity."; - default = false; - }; - }; denolint = { format = @@ -309,88 +207,20 @@ in default = ""; }; }; - denofmt = + dune-fmt = { - write = + auto-promote = mkOption { type = types.bool; - description = lib.mdDoc "Whether to edit files inplace."; + description = lib.mdDoc "Whether to auto-promote the changes."; default = true; }; - configPath = + + extraRuntimeInputs = mkOption { - type = types.str; - description = lib.mdDoc "Path to the configuration JSON file"; - # an empty string translates to use default configuration of the - # underlying deno binary (i.e deno.json or deno.jsonc) - default = ""; - }; - }; - mypy = - { - binPath = - mkOption { - type = types.str; - description = lib.mdDoc "Mypy binary path. Should be used to specify the mypy executable in an environment containing your typing stubs."; - default = "${pkgs.mypy}/bin/mypy"; - defaultText = lib.literalExpression '' - "''${pkgs.mypy}/bin/mypy" - ''; - }; - }; - nixfmt = - { - width = - mkOption { - type = types.nullOr types.int; - description = lib.mdDoc "Line width."; - default = null; - }; - }; - prettier = - { - binPath = - mkOption { - type = types.path; - description = lib.mdDoc - "`prettier` binary path. E.g. if you want to use the `prettier` in `node_modules`, use `./node_modules/.bin/prettier`."; - default = "${tools.prettier}/bin/prettier"; - defaultText = lib.literalExpression '' - "''${tools.prettier}/bin/prettier" - ''; - }; - - write = - mkOption { - type = types.bool; - description = lib.mdDoc "Whether to edit files inplace."; - default = true; - }; - - output = - mkOption { - description = lib.mdDoc "Output format."; - type = types.nullOr (types.enum [ "check" "list-different" ]); - default = "list-different"; - }; - }; - eslint = - { - binPath = - mkOption { - type = types.path; - description = lib.mdDoc - "`eslint` binary path. E.g. if you want to use the `eslint` in `node_modules`, use `./node_modules/.bin/eslint`."; - default = "${tools.eslint}/bin/eslint"; - defaultText = lib.literalExpression "\${tools.eslint}/bin/eslint"; - }; - - extensions = - mkOption { - type = types.str; - description = lib.mdDoc - "The pattern of files to run on, see [https://pre-commit.com/#hooks-files](https://pre-commit.com/#hooks-files)."; - default = "\\.js$"; + type = types.listOf types.package; + description = lib.mdDoc "Extra runtimeInputs to add to the environment, eg. `ocamlformat`."; + default = [ ]; }; }; eclint = @@ -434,122 +264,44 @@ in default = 0; }; }; - rome = + eslint = { binPath = mkOption { type = types.path; - description = lib.mdDoc "`rome` binary path. E.g. if you want to use the `rome` in `node_modules`, use `./node_modules/.bin/rome`."; - default = "${pkgs.rome}/bin/rome"; - defaultText = "\${pkgs.rome}/bin/rome"; + description = lib.mdDoc + "`eslint` binary path. E.g. if you want to use the `eslint` in `node_modules`, use `./node_modules/.bin/eslint`."; + default = "${tools.eslint}/bin/eslint"; + defaultText = lib.literalExpression "\${tools.eslint}/bin/eslint"; }; - write = + extensions = mkOption { - type = types.bool; - description = lib.mdDoc "Whether to edit files inplace."; - default = true; + type = types.str; + description = lib.mdDoc + "The pattern of files to run on, see [https://pre-commit.com/#hooks-files](https://pre-commit.com/#hooks-files)."; + default = "\\.js$"; }; - - configPath = mkOption { - type = types.str; - description = lib.mdDoc "Path to the configuration JSON file"; - # an empty string translates to use default configuration of the - # underlying rome binary (i.e rome.json if exists) - default = ""; - }; }; - - typos = + flake8 = { - color = - mkOption { - type = types.enum [ "auto" "always" "never" ]; - description = lib.mdDoc "When to use generate output."; - default = "auto"; - }; - - config = + binPath = mkOption { type = types.str; - description = lib.mdDoc "Multiline-string configuration passed as config file."; - default = ""; - example = '' - [files] - ignore-dot = true - - [default] - binary = false - - [type.py] - extend-glob = [] + description = lib.mdDoc "flake8 binary path. Should be used to specify flake8 binary from your Nix-managed Python environment."; + default = "${pkgs.python39Packages.flake8}/bin/flake8"; + defaultText = lib.literalExpression '' + "''${pkgs.python39Packages.flake8}/bin/flake8" ''; }; - configPath = - mkOption { - type = types.str; - description = lib.mdDoc "Path to a custom config file."; - default = ""; - }; - - diff = - mkOption { - type = types.bool; - description = lib.mdDoc "Whether to print a diff of what would change."; - default = false; - }; - - exclude = - mkOption { - type = types.str; - description = lib.mdDoc "Which files & directories to exclude matching the glob."; - default = ""; - example = "*.nix"; - }; - format = - mkOption { - type = types.enum [ "silent" "brief" "long" "json" ]; - description = lib.mdDoc "Which output format to use."; - default = "long"; - }; - - hidden = - mkOption { - type = types.bool; - description = lib.mdDoc "Whether to search hidden files and directories."; - default = false; - }; - - locale = - mkOption { - type = types.enum [ "en" "en-us" "en-gb" "en-ca" "en-au" ]; - description = lib.mdDoc "Which language to use for spell checking."; - default = "en"; - }; - - write = - mkOption { - type = types.bool; - description = lib.mdDoc "Whether to fix spelling in files by writing them. Cannot be used with `typos.settings.diff`."; - default = false; - }; - }; - - revive = - { - configPath = mkOption { type = types.str; - description = lib.mdDoc "Path to the configuration TOML file."; - # an empty string translates to use default configuration of the - # underlying revive binary - default = ""; + description = lib.mdDoc "Output format."; + default = "default"; }; - }; - flynt = { aggressive = @@ -628,210 +380,453 @@ in default = false; }; }; - - phpcs = + headache = { - binPath = + header-file = mkOption { + type = types.str; + description = lib.mdDoc "Path to the header file."; + default = ".header"; + }; + }; + hlint = + { + hintFile = mkOption { - type = types.str; - description = lib.mdDoc "PHP_CodeSniffer binary path."; - default = "${pkgs.php82Packages.phpcs}/bin/phpcs"; - defaultText = lib.literalExpression '' - "''${pkgs.php80Packages.phpcs}/bin/phpcs" - ''; + type = types.nullOr types.path; + description = lib.mdDoc "Path to hlint.yaml. By default, hlint searches for .hlint.yaml in the project root."; + default = null; }; }; - - phpcbf = + hpack = { - binPath = + silent = mkOption { - type = types.str; - description = lib.mdDoc "PHP_CodeSniffer binary path."; - default = "${pkgs.php82Packages.phpcbf}/bin/phpcbf"; - defaultText = lib.literalExpression '' - "''${pkgs.php80Packages.phpcbf}/bin/phpcbf" - ''; + type = types.bool; + description = lib.mdDoc "Whether generation should be silent."; + default = false; }; }; - - phpstan = + isort = { - binPath = + profile = + mkOption { + type = types.enum [ "" "black" "django" "pycharm" "google" "open_stack" "plone" "attrs" "hug" "wemake" "appnexus" ]; + description = lib.mdDoc "Built-in profiles to allow easy interoperability with common projects and code styles."; + default = ""; + }; + flags = mkOption { type = types.str; - description = lib.mdDoc "PHPStan binary path."; - default = "${pkgs.php82Packages.phpstan}/bin/phpstan"; - defaultText = lib.literalExpression '' - "''${pkgs.php81Packages.phpstan}/bin/phpstan" - ''; + description = lib.mdDoc "Flags passed to isort. See all available [here](https://pycqa.github.io/isort/docs/configuration/options.html)."; + default = ""; }; }; - - php-cs-fixer = + latexindent = { - binPath = + flags = mkOption { type = types.str; - description = lib.mdDoc "PHP-CS-Fixer binary path."; - default = "${pkgs.php82Packages.php-cs-fixer}/bin/php-cs-fixer"; - defaultText = lib.literalExpression '' - "''${pkgs.php81Packages.php-cs-fixer}/bin/php-cs-fixer" - ''; + description = lib.mdDoc "Flags passed to latexindent. See available flags [here](https://latexindentpl.readthedocs.io/en/latest/sec-how-to-use.html#from-the-command-line)"; + default = "--local --silent --overwriteIfDifferent"; }; }; - - psalm = + lua-ls = + { + checklevel = mkOption { + type = types.enum [ "Error" "Warning" "Information" "Hint" ]; + description = lib.mdDoc + "The diagnostic check level"; + default = "Warning"; + }; + config = mkOption { + type = types.attrs; + description = lib.mdDoc + "See https://github.com/LuaLS/lua-language-server/wiki/Configuration-File#luarcjson"; + default = { }; + }; + }; + lychee = { + configPath = + mkOption { + type = types.str; + description = lib.mdDoc "Path to the config file."; + default = ""; + }; + flags = + mkOption { + type = types.str; + description = lib.mdDoc "Flags passed to lychee. See all available [here](https://lychee.cli.rs/#/usage/cli)."; + default = ""; + }; + }; + markdownlint = { + config = + mkOption { + type = types.attrs; + description = lib.mdDoc + "See https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.jsonc"; + default = { }; + }; + }; + mdl = { + package = + mkOption { + type = types.package; + description = lib.mdDoc "The `mdl` package to use."; + default = "${tools.mdl}"; + defaultText = "\${tools.mdl}"; + example = "\${pkgs.mdl}"; + }; + configPath = + mkOption { + type = types.str; + description = lib.mdDoc "The configuration file to use."; + default = ""; + }; + git-recurse = + mkOption { + type = types.bool; + description = lib.mdDoc "Only process files known to git when given a directory."; + default = false; + }; + ignore-front-matter = + mkOption { + type = types.bool; + description = lib.mdDoc "Ignore YAML front matter."; + default = false; + }; + json = + mkOption { + type = types.bool; + description = lib.mdDoc "Format output as JSON."; + default = false; + }; + rules = + mkOption { + type = types.listOf types.str; + description = lib.mdDoc "Markdown rules to use for linting. Per default all rules are processed."; + default = [ ]; + }; + rulesets = + mkOption { + type = types.listOf types.str; + description = lib.mdDoc "Specify additional ruleset files to load."; + default = [ ]; + }; + show-aliases = + mkOption { + type = types.bool; + description = lib.mdDoc "Show rule alias instead of rule ID when viewing rules."; + default = false; + }; + warnings = + mkOption { + type = types.bool; + description = lib.mdDoc "Show Kramdown warnings."; + default = false; + }; + skip-default-ruleset = + mkOption { + type = types.bool; + description = lib.mdDoc "Do not load the default markdownlint ruleset. Use this option if you only want to load custom rulesets."; + default = false; + }; + style = + mkOption { + type = types.str; + description = lib.mdDoc "Select which style mdl uses."; + default = "default"; + }; + tags = + mkOption { + type = types.listOf types.str; + description = lib.mdDoc "Markdown rules to use for linting containing the given tags. Per default all rules are processed."; + default = [ ]; + }; + verbose = + mkOption { + type = types.bool; + description = lib.mdDoc "Increase verbosity."; + default = false; + }; + }; + mkdocs-linkcheck = { binPath = mkOption { - type = types.str; - description = lib.mdDoc "Psalm binary path."; - default = "${pkgs.php82Packages.psalm}/bin/psalm"; + type = types.path; + description = lib.mdDoc "mkdocs-linkcheck binary path. Should be used to specify the mkdocs-linkcheck binary from your Nix-managed Python environment."; + default = "${pkgs.python311Packages.mkdocs-linkcheck}/bin/mkdocs-linkcheck"; defaultText = lib.literalExpression '' - "''${pkgs.php81Packages.phpstan}/bin/psalm" + "''${pkgs.python311Packages.mkdocs-linkcheck}/bin/mkdocs-linkcheck" ''; }; - }; - pylint = - { - binPath = + path = mkOption { type = types.str; - description = lib.mdDoc "Pylint binary path. Should be used to specify Pylint binary from your Nix-managed Python environment."; - default = "${pkgs.python39Packages.pylint}/bin/pylint"; - defaultText = lib.literalExpression '' - "''${pkgs.python39Packages.pylint}/bin/pylint" - ''; + description = lib.mdDoc "Path to check"; + default = ""; }; - reports = + local-only = mkOption { type = types.bool; - description = lib.mdDoc "Whether to display a full report."; + description = lib.mdDoc "Whether to only check local links."; default = false; }; - score = + recurse = mkOption { type = types.bool; - description = lib.mdDoc "Whether to activate the evaluation score."; - default = true; + description = lib.mdDoc "Whether to recurse directories under path."; + default = false; }; - }; - pyupgrade = + extension = + mkOption { + type = types.str; + description = lib.mdDoc "File extension to scan for."; + default = ""; + }; + + method = + mkOption { + type = types.enum [ "get" "head" ]; + description = lib.mdDoc "HTTP method to use when checking external links."; + default = "get"; + }; + }; + mypy = { binPath = mkOption { type = types.str; - description = lib.mdDoc "pyupgrade binary path. Should be used to specify the pyupgrade binary from your Nix-managed Python environment."; - default = "${pkgs.pyupgrade}/bin/pyupgrade"; + description = lib.mdDoc "Mypy binary path. Should be used to specify the mypy executable in an environment containing your typing stubs."; + default = "${pkgs.mypy}/bin/mypy"; defaultText = lib.literalExpression '' - "''${pkgs.pyupgrade}/bin/pyupgrade" + "''${pkgs.mypy}/bin/mypy" ''; }; }; - - pyright = + nixfmt = + { + width = + mkOption { + type = types.nullOr types.int; + description = lib.mdDoc "Line width."; + default = null; + }; + }; + ormolu = + { + defaultExtensions = + mkOption { + type = types.listOf types.str; + description = lib.mdDoc "Haskell language extensions to enable."; + default = [ ]; + }; + cabalDefaultExtensions = + mkOption { + type = types.bool; + description = lib.mdDoc "Use `default-extensions` from `.cabal` files."; + default = false; + }; + }; + php-cs-fixer = { binPath = mkOption { type = types.str; - description = lib.mdDoc "Pyright binary path. Should be used to specify the pyright executable in an environment containing your typing stubs."; - default = "${pkgs.pyright}/bin/pyright"; + description = lib.mdDoc "PHP-CS-Fixer binary path."; + default = "${pkgs.php82Packages.php-cs-fixer}/bin/php-cs-fixer"; defaultText = lib.literalExpression '' - "''${pkgs.pyright}/bin/pyright" + "''${pkgs.php81Packages.php-cs-fixer}/bin/php-cs-fixer" ''; }; }; - - flake8 = + phpcbf = { binPath = mkOption { type = types.str; - description = lib.mdDoc "flake8 binary path. Should be used to specify flake8 binary from your Nix-managed Python environment."; - default = "${pkgs.python39Packages.flake8}/bin/flake8"; + description = lib.mdDoc "PHP_CodeSniffer binary path."; + default = "${pkgs.php82Packages.phpcbf}/bin/phpcbf"; defaultText = lib.literalExpression '' - "''${pkgs.python39Packages.flake8}/bin/flake8" + "''${pkgs.php80Packages.phpcbf}/bin/phpcbf" ''; }; - - format = - mkOption { - type = types.str; - description = lib.mdDoc "Output format."; - default = "default"; - }; }; - - autoflake = + phpcs = { binPath = mkOption { type = types.str; - description = lib.mdDoc "Path to autoflake binary."; - default = "${pkgs.autoflake}/bin/autoflake"; + description = lib.mdDoc "PHP_CodeSniffer binary path."; + default = "${pkgs.php82Packages.phpcs}/bin/phpcs"; defaultText = lib.literalExpression '' - "''${pkgs.autoflake}/bin/autoflake" + "''${pkgs.php80Packages.phpcs}/bin/phpcs" ''; }; - - flags = + }; + phpstan = + { + binPath = mkOption { type = types.str; - description = lib.mdDoc "Flags passed to autoflake."; - default = "--in-place --expand-star-imports --remove-duplicate-keys --remove-unused-variables"; + description = lib.mdDoc "PHPStan binary path."; + default = "${pkgs.php82Packages.phpstan}/bin/phpstan"; + defaultText = lib.literalExpression '' + "''${pkgs.php81Packages.phpstan}/bin/phpstan" + ''; }; }; - - rust = - { - cargoManifestPath = mkOption { - type = types.nullOr types.str; - description = lib.mdDoc "Path to Cargo.toml"; - default = null; - }; - }; - - yamllint = + prettier = { - relaxed = mkOption { - type = types.bool; - description = lib.mdDoc "Whether to use the relaxed configuration."; - default = false; - }; + binPath = + mkOption { + type = types.path; + description = lib.mdDoc + "`prettier` binary path. E.g. if you want to use the `prettier` in `node_modules`, use `./node_modules/.bin/prettier`."; + default = "${tools.prettier}/bin/prettier"; + defaultText = lib.literalExpression '' + "''${tools.prettier}/bin/prettier" + ''; + }; + write = + mkOption { + type = types.bool; + description = lib.mdDoc "Whether to edit files inplace."; + default = true; + }; + output = + mkOption { + description = lib.mdDoc "Output format."; + type = types.nullOr (types.enum [ "check" "list-different" ]); + default = "list-different"; + }; + }; + psalm = + { + binPath = + mkOption { + type = types.str; + description = lib.mdDoc "Psalm binary path."; + default = "${pkgs.php82Packages.psalm}/bin/psalm"; + defaultText = lib.literalExpression '' + "''${pkgs.php81Packages.phpstan}/bin/psalm" + ''; + }; + }; + pylint = + { + binPath = + mkOption { + type = types.str; + description = lib.mdDoc "Pylint binary path. Should be used to specify Pylint binary from your Nix-managed Python environment."; + default = "${pkgs.python39Packages.pylint}/bin/pylint"; + defaultText = lib.literalExpression '' + "''${pkgs.python39Packages.pylint}/bin/pylint" + ''; + }; + reports = + mkOption { + type = types.bool; + description = lib.mdDoc "Whether to display a full report."; + default = false; + }; + score = + mkOption { + type = types.bool; + description = lib.mdDoc "Whether to activate the evaluation score."; + default = true; + }; + }; + pyright = + { + binPath = + mkOption { + type = types.str; + description = lib.mdDoc "Pyright binary path. Should be used to specify the pyright executable in an environment containing your typing stubs."; + default = "${pkgs.pyright}/bin/pyright"; + defaultText = lib.literalExpression '' + "''${pkgs.pyright}/bin/pyright" + ''; + }; + }; + pyupgrade = + { + binPath = + mkOption { + type = types.str; + description = lib.mdDoc "pyupgrade binary path. Should be used to specify the pyupgrade binary from your Nix-managed Python environment."; + default = "${pkgs.pyupgrade}/bin/pyupgrade"; + defaultText = lib.literalExpression '' + "''${pkgs.pyupgrade}/bin/pyupgrade" + ''; + }; + }; + revive = + { + configPath = + mkOption { + type = types.str; + description = lib.mdDoc "Path to the configuration TOML file."; + # an empty string translates to use default configuration of the + # underlying revive binary + default = ""; + }; + }; + rome = + { + binPath = + mkOption { + type = types.path; + description = lib.mdDoc "`rome` binary path. E.g. if you want to use the `rome` in `node_modules`, use `./node_modules/.bin/rome`."; + default = "${pkgs.rome}/bin/rome"; + defaultText = "\${pkgs.rome}/bin/rome"; + }; + + write = + mkOption { + type = types.bool; + description = lib.mdDoc "Whether to edit files inplace."; + default = true; + }; configPath = mkOption { type = types.str; - description = lib.mdDoc "Path to the YAML configuration file."; + description = lib.mdDoc "Path to the configuration JSON file"; # an empty string translates to use default configuration of the - # underlying yamllint binary + # underlying rome binary (i.e rome.json if exists) default = ""; }; }; - - clippy = + rust = { - denyWarnings = mkOption { - type = types.bool; - description = lib.mdDoc "Fail when warnings are present"; - default = false; - }; - offline = mkOption { - type = types.bool; - description = lib.mdDoc "Run clippy offline"; - default = true; - }; - allFeatures = mkOption { - type = types.bool; - description = lib.mdDoc "Run clippy with --all-features"; - default = false; + cargoManifestPath = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Path to Cargo.toml"; + default = null; }; }; + statix = + { + format = + mkOption { + type = types.enum [ "stderr" "errfmt" "json" ]; + description = lib.mdDoc "Error Output format."; + default = "errfmt"; + }; + ignore = + mkOption { + type = types.listOf types.str; + description = lib.mdDoc "Globs of file patterns to skip."; + default = [ ]; + example = [ "flake.nix" "_*" ]; + }; + }; treefmt = { package = mkOption { @@ -860,126 +855,100 @@ in ''; }; }; - - mkdocs-linkcheck = + typos = { - binPath = + color = mkOption { - type = types.path; - description = lib.mdDoc "mkdocs-linkcheck binary path. Should be used to specify the mkdocs-linkcheck binary from your Nix-managed Python environment."; - default = "${pkgs.python311Packages.mkdocs-linkcheck}/bin/mkdocs-linkcheck"; - defaultText = lib.literalExpression '' - "''${pkgs.python311Packages.mkdocs-linkcheck}/bin/mkdocs-linkcheck" - ''; + type = types.enum [ "auto" "always" "never" ]; + description = lib.mdDoc "When to use generate output."; + default = "auto"; }; - path = + config = mkOption { type = types.str; - description = lib.mdDoc "Path to check"; + description = lib.mdDoc "Multiline-string configuration passed as config file."; default = ""; + example = '' + [files] + ignore-dot = true + + [default] + binary = false + + [type.py] + extend-glob = [] + ''; }; - local-only = + configPath = mkOption { - type = types.bool; - description = lib.mdDoc "Whether to only check local links."; - default = false; + type = types.str; + description = lib.mdDoc "Path to a custom config file."; + default = ""; }; - recurse = + diff = mkOption { type = types.bool; - description = lib.mdDoc "Whether to recurse directories under path."; + description = lib.mdDoc "Whether to print a diff of what would change."; default = false; }; - extension = + exclude = mkOption { type = types.str; - description = lib.mdDoc "File extension to scan for."; + description = lib.mdDoc "Which files & directories to exclude matching the glob."; default = ""; + example = "*.nix"; }; - method = + format = mkOption { - type = types.enum [ "get" "head" ]; - description = lib.mdDoc "HTTP method to use when checking external links."; - default = "get"; + type = types.enum [ "silent" "brief" "long" "json" ]; + description = lib.mdDoc "Which output format to use."; + default = "long"; }; - }; - dune-fmt = - { - auto-promote = + hidden = mkOption { type = types.bool; - description = lib.mdDoc "Whether to auto-promote the changes."; - default = true; + description = lib.mdDoc "Whether to search hidden files and directories."; + default = false; }; - extraRuntimeInputs = + locale = mkOption { - type = types.listOf types.package; - description = lib.mdDoc "Extra runtimeInputs to add to the environment, eg. `ocamlformat`."; - default = [ ]; + type = types.enum [ "en" "en-us" "en-gb" "en-ca" "en-au" ]; + description = lib.mdDoc "Which language to use for spell checking."; + default = "en"; }; - }; - headache = - { - header-file = mkOption { + write = + mkOption { + type = types.bool; + description = lib.mdDoc "Whether to fix spelling in files by writing them. Cannot be used with `typos.settings.diff`."; + default = false; + }; + }; + vale = { + config = + mkOption { type = types.str; - description = lib.mdDoc "Path to the header file."; - default = ".header"; + description = lib.mdDoc "Multiline-string configuration passed as config file."; + default = ""; + example = '' + MinAlertLevel = suggestion + [*] + BasedOnStyles = Vale + ''; }; - }; - - lua-ls = - { - checklevel = mkOption { - type = types.enum [ "Error" "Warning" "Information" "Hint" ]; - description = lib.mdDoc - "The diagnostic check level"; - default = "Warning"; - }; - config = mkOption { - type = types.attrs; - description = lib.mdDoc - "See https://github.com/LuaLS/lua-language-server/wiki/Configuration-File#luarcjson"; - default = { }; - }; - }; - - credo = { - strict = - mkOption { - type = types.bool; - description = lib.mdDoc "Whether to auto-promote the changes."; - default = true; - }; - }; - - vale = { - config = - mkOption { - type = types.str; - description = lib.mdDoc "Multiline-string configuration passed as config file."; - default = ""; - example = '' - MinAlertLevel = suggestion - [*] - BasedOnStyles = Vale - ''; - }; - configPath = mkOption { type = types.str; description = lib.mdDoc "Path to the config file."; default = ""; }; - flags = mkOption { type = types.str; @@ -987,24 +956,25 @@ in default = ""; }; }; - - lychee = { - configPath = - mkOption { - type = types.str; - description = lib.mdDoc "Path to the config file."; - default = ""; + yamllint = + { + relaxed = mkOption { + type = types.bool; + description = lib.mdDoc "Whether to use the relaxed configuration."; + default = false; }; - flags = - mkOption { + configPath = mkOption { type = types.str; - description = lib.mdDoc "Flags passed to lychee. See all available [here](https://lychee.cli.rs/#/usage/cli)."; + description = lib.mdDoc "Path to the YAML configuration file."; + # an empty string translates to use default configuration of the + # underlying yamllint binary default = ""; }; - }; + }; }; + # PLEASE keep this sorted alphabetically. config.hooks = { actionlint = @@ -1015,6 +985,30 @@ in types = [ "yaml" ]; entry = "${tools.actionlint}/bin/actionlint"; }; + alejandra = + { + name = "alejandra"; + description = "The Uncompromising Nix Code Formatter."; + entry = + let + cmdArgs = + mkCmdArgs (with settings.alejandra; [ + [ check "--check" ] + [ (exclude != [ ]) "--exclude ${lib.escapeShellArgs (lib.unique exclude)}" ] + [ (verbosity == "quiet") "-q" ] + [ (verbosity == "silent") "-qq" ] + [ (threads != null) "--threads ${toString threads}" ] + ]); + in + "${settings.alejandra.package}/bin/alejandra ${cmdArgs}"; + files = "\\.nix$"; + }; + annex = + { + name = "annex"; + description = "Runs the git-annex hook for large file support"; + entry = "${tools.git-annex}/bin/git-annex pre-commit"; + }; ansible-lint = { name = "ansible-lint"; @@ -1030,6 +1024,28 @@ in "${tools.ansible-lint}/bin/ansible-lint ${cmdArgs}"; files = if settings.ansible-lint.subdir != "" then "${settings.ansible-lint.subdir}/" else ""; }; + autoflake = + { + name = "autoflake"; + description = "Remove unused imports and variables from Python code."; + entry = "${settings.autoflake.binPath} ${settings.autoflake.flags}"; + types = [ "python" ]; + }; + bats = + { + name = "bats"; + description = "Run bash unit tests."; + types = [ "shell" ]; + types_or = [ "bats" "bash" ]; + entry = "${tools.bats}/bin/bats -p"; + }; + beautysh = + { + name = "beautysh"; + description = "Format shell files."; + types = [ "shell" ]; + entry = "${tools.beautysh}/bin/beautysh"; + }; black = { name = "black"; @@ -1037,12 +1053,12 @@ in entry = "${pkgs.python3Packages.black}/bin/black"; types = [ "file" "python" ]; }; - ruff = + cabal-fmt = { - name = "ruff"; - description = " An extremely fast Python linter, written in Rust."; - entry = "${pkgs.ruff}/bin/ruff --fix"; - types = [ "python" ]; + name = "cabal-fmt"; + description = "Format Cabal files"; + entry = "${tools.cabal-fmt}/bin/cabal-fmt --inplace"; + files = "\\.cabal$"; }; cabal2nix = { @@ -1051,6 +1067,33 @@ in files = "\\.cabal$"; entry = "${tools.cabal2nix-dir}/bin/cabal2nix-dir"; }; + cargo-check = + { + name = "cargo-check"; + description = "Check the cargo package for errors."; + entry = "${tools.cargo}/bin/cargo check ${cargoManifestPathArg}"; + files = "\\.rs$"; + pass_filenames = false; + }; + checkmake = { + name = "checkmake"; + description = "Experimental linter/analyzer for Makefiles."; + types = [ "makefile" ]; + entry = + ## NOTE: `checkmake` 0.2.2 landed in nixpkgs on 12 April 2023. Once + ## this gets into a NixOS release, the following code will be useless. + lib.throwIf + (tools.checkmake == null) + "The version of nixpkgs used by pre-commit-hooks.nix must have `checkmake` in version at least 0.2.2 for it to work on non-Linux systems." + "${tools.checkmake}/bin/checkmake"; + }; + chktex = + { + name = "chktex"; + description = "LaTeX semantic checker"; + types = [ "file" "tex" ]; + entry = "${tools.chktex}/bin/chktex"; + }; clang-format = { name = "clang-format"; @@ -1076,157 +1119,256 @@ in entry = "${tools.clang-tools}/bin/clang-tidy --fix"; types = [ "c" "c++" "c#" "objective-c" ]; }; - dhall-format = { - name = "dhall-format"; - description = "Dhall code formatter."; - entry = "${tools.dhall}/bin/dhall format"; - files = "\\.dhall$"; - }; - dune-opam-sync = { - name = "dune/opam sync"; - description = "Check that Dune-generated OPAM files are in sync."; - entry = "${tools.dune-build-opam-files}/bin/dune-build-opam-files"; - files = "(\\.opam$)|(\\.opam.template$)|((^|/)dune-project$)"; - ## We don't pass filenames because they can only be misleading. Indeed, - ## we need to re-run `dune build` for every `*.opam` file, but also when - ## the `dune-project` file has changed. - pass_filenames = false; + clippy = + let + wrapper = pkgs.symlinkJoin { + name = "clippy-wrapped"; + paths = [ tools.clippy ]; + nativeBuildInputs = [ pkgs.makeWrapper ]; + postBuild = '' + wrapProgram $out/bin/cargo-clippy \ + --prefix PATH : ${lib.makeBinPath [ tools.cargo ]} + ''; + }; + in + { + name = "clippy"; + description = "Lint Rust code."; + entry = "${wrapper}/bin/cargo-clippy clippy ${cargoManifestPathArg} ${lib.optionalString settings.clippy.offline "--offline"} ${lib.optionalString settings.clippy.allFeatures "--all-features"} -- ${lib.optionalString settings.clippy.denyWarnings "-D warnings"}"; + files = "\\.rs$"; + pass_filenames = false; + }; + cljfmt = + { + name = "cljfmt"; + description = "A tool for formatting Clojure code."; + entry = "${pkgs.cljfmt}/bin/cljfmt fix"; + types_or = [ "clojure" "clojurescript" "edn" ]; + }; + commitizen = + { + name = "commitizen check"; + description = '' + Check whether the current commit message follows committing rules. + ''; + entry = "${tools.commitizen}/bin/cz check --allow-abort --commit-msg-file"; + stages = [ "commit-msg" ]; + }; + conform = { + name = "conform enforce"; + description = "Policy enforcement for commits."; + entry = "${tools.conform}/bin/conform enforce --commit-msg-file"; + stages = [ "commit-msg" ]; }; - gptcommit = { - name = "gptcommit"; - description = "Generate a commit message using GPT3."; + convco = { + name = "convco"; entry = let - script = pkgs.writeShellScript "precommit-gptcomit" '' - ${tools.gptcommit}/bin/gptcommit prepare-commit-msg --commit-source \ - "$PRE_COMMIT_COMMIT_MSG_SOURCE" --commit-msg-file "$1" + script = pkgs.writeShellScript "precommit-convco" '' + cat $1 | ${pkgs.convco}/bin/convco check --from-stdin ''; + # need version >= 0.4.0 for the --from-stdin flag + toolVersionCheck = lib.versionAtLeast tools.convco.version "0.4.0"; in - lib.throwIf (tools.gptcommit == null) "The version of Nixpkgs used by pre-commit-hooks.nix does not have the `gptcommit` package. Please use a more recent version of Nixpkgs." - toString + lib.throwIf (tools.convco == null || !toolVersionCheck) "The version of Nixpkgs used by pre-commit-hooks.nix does not have the `convco` package (>=0.4.0). Please use a more recent version of Nixpkgs." + builtins.toString script; - stages = [ "prepare-commit-msg" ]; + stages = [ "commit-msg" ]; }; - hlint = - { - name = "hlint"; - description = - "HLint gives suggestions on how to improve your source code."; - entry = "${tools.hlint}/bin/hlint${if settings.hlint.hintFile == null then "" else " --hint=${settings.hlint.hintFile}"}"; - files = "\\.l?hs(-boot)?$"; - }; - hpack = + credo = { + name = "credo"; + description = "Runs a static code analysis using Credo"; + entry = + let strict = if settings.credo.strict then "--strict" else ""; + in "${pkgs.elixir}/bin/mix credo"; + files = "\\.exs?$"; + }; + crystal = { + name = "crystal"; + description = "A tool that automatically formats Crystal source code"; + entry = "${tools.crystal}/bin/crystal tool format"; + files = "\\.cr$"; + }; + cspell = { - name = "hpack"; - description = - "`hpack` converts package definitions in the hpack format (`package.yaml`) to Cabal files."; - entry = "${tools.hpack-dir}/bin/hpack-dir --${if settings.hpack.silent then "silent" else "verbose"}"; - files = "(\\.l?hs(-boot)?$)|(\\.cabal$)|((^|/)package\\.yaml$)"; - # We don't pass filenames because they can only be misleading. - # Indeed, we need to rerun `hpack` in every directory: - # 1. In which there is a *.cabal file, or - # 2. Below which there are haskell files, or - # 3. In which there is a package.yaml that references haskell files - # that have been changed at arbitrary locations specified in that - # file. - # In other words: We have no choice but to always run `hpack` on every `package.yaml` directory. - pass_filenames = false; + name = "cspell"; + description = "A Spell Checker for Code"; + entry = "${tools.cspell}/bin/cspell"; }; - isort = + deadnix = { - name = "isort"; - description = "A Python utility / library to sort imports."; - types = [ "file" "python" ]; + name = "deadnix"; + description = "Scan Nix files for dead code (unused variable bindings)."; + entry = + let + cmdArgs = + mkCmdArgs (with settings.deadnix; [ + [ noLambdaArg "--no-lambda-arg" ] + [ noLambdaPatternNames "--no-lambda-pattern-names" ] + [ noUnderscore "--no-underscore" ] + [ quiet "--quiet" ] + [ hidden "--hidden" ] + [ edit "--edit" ] + [ (exclude != [ ]) "--exclude ${lib.escapeShellArgs exclude}" ] + ]); + in + "${tools.deadnix}/bin/deadnix ${cmdArgs} --fail"; + files = "\\.nix$"; + }; + denofmt = + { + name = "denofmt"; + description = "Auto-format JavaScript, TypeScript, Markdown, and JSON files."; + types_or = [ "javascript" "jsx" "ts" "tsx" "markdown" "json" ]; + entry = + let + cmdArgs = + mkCmdArgs [ + [ (!settings.denofmt.write) "--check" ] + [ (settings.denofmt.configPath != "") "-c ${settings.denofmt.configPath}" ] + ]; + in + "${tools.deno}/bin/deno fmt ${cmdArgs}"; + }; + denolint = + { + name = "denolint"; + description = "Lint JavaScript/TypeScript source code."; + types_or = [ "javascript" "jsx" "ts" "tsx" ]; + entry = + let + cmdArgs = + mkCmdArgs [ + [ (settings.denolint.format == "compact") "--compact" ] + [ (settings.denolint.format == "json") "--json" ] + [ (settings.denolint.configPath != "") "-c ${settings.denolint.configPath}" ] + ]; + in + "${tools.deno}/bin/deno lint ${cmdArgs}"; + }; + dhall-format = { + name = "dhall-format"; + description = "Dhall code formatter."; + entry = "${tools.dhall}/bin/dhall format"; + files = "\\.dhall$"; + }; + dialyzer = { + name = "dialyzer"; + description = "Runs a static code analysis using Dialyzer"; + entry = "${pkgs.elixir}/bin/mix dialyzer"; + files = "\\.exs?$"; + }; + dune-fmt = { + name = "dune-fmt"; + description = "Runs Dune's formatters on the code tree."; + entry = + let + auto-promote = if settings.dune-fmt.auto-promote then "--auto-promote" else ""; + run-dune-fmt = pkgs.writeShellApplication { + name = "run-dune-fmt"; + runtimeInputs = settings.dune-fmt.extraRuntimeInputs; + text = "${tools.dune-fmt}/bin/dune-fmt ${auto-promote}"; + }; + in + "${run-dune-fmt}/bin/run-dune-fmt"; + pass_filenames = false; + }; + dune-opam-sync = { + name = "dune/opam sync"; + description = "Check that Dune-generated OPAM files are in sync."; + entry = "${tools.dune-build-opam-files}/bin/dune-build-opam-files"; + files = "(\\.opam$)|(\\.opam.template$)|((^|/)dune-project$)"; + ## We don't pass filenames because they can only be misleading. Indeed, + ## we need to re-run `dune build` for every `*.opam` file, but also when + ## the `dune-project` file has changed. + pass_filenames = false; + }; + eclint = + { + name = "eclint"; + description = "EditorConfig linter written in Go."; + types = [ "file" ]; entry = let cmdArgs = mkCmdArgs - (with settings.isort; [ - [ (profile != "") " --profile ${profile}" ] + (with settings.eclint; [ + [ fix "-fix" ] + [ summary "-summary" ] + [ (color != "auto") "-color ${color}" ] + [ (exclude != [ ]) "-exclude ${lib.escapeShellArgs exclude}" ] + [ (verbosity != 0) "-verbosity ${toString verbosity}" ] ]); in - "${pkgs.python3Packages.isort}/bin/isort${cmdArgs} ${settings.isort.flags}"; + "${settings.eclint.package}/bin/eclint ${cmdArgs}"; }; - latexindent = + editorconfig-checker = { - name = "latexindent"; - description = "Perl script to add indentation to LaTeX files."; - types = [ "file" "tex" ]; - entry = "${tools.latexindent}/bin/latexindent ${settings.latexindent.flags}"; + name = "editorconfig-checker"; + description = "Verify that the files are in harmony with the `.editorconfig`."; + entry = "${tools.editorconfig-checker}/bin/editorconfig-checker"; + types = [ "file" ]; }; - luacheck = + elm-format = { - name = "luacheck"; - description = "A tool for linting and static analysis of Lua code."; - types = [ "file" "lua" ]; - entry = "${tools.luacheck}/bin/luacheck"; + name = "elm-format"; + description = "Format Elm files."; + entry = + "${tools.elm-format}/bin/elm-format --yes --elm-version=0.19"; + files = "\\.elm$"; }; - lua-ls = - let - # .luarc.json has to be in a directory, - # or lua-language-server will hang forever. - luarc = pkgs.writeText ".luarc.json" (builtins.toJSON settings.lua-ls.config); - luarc-dir = pkgs.stdenv.mkDerivation { - name = "luarc"; - unpackPhase = "true"; - installPhase = '' - mkdir $out - cp ${luarc} $out/.luarc.json - ''; - }; - script = pkgs.writeShellApplication { - name = "lua-ls-lint"; - runtimeInputs = [ tools.lua-language-server ]; - checkPhase = ""; # The default checkPhase depends on GHC - text = '' - set -e - export logpath="$(mktemp -d)" - lua-language-server --check $(realpath .) \ - --checklevel="${settings.lua-ls.checklevel}" \ - --configpath="${luarc-dir}/.luarc.json" \ - --logpath="$logpath" - if [[ -f $logpath/check.json ]]; then - echo "+++++++++++++++ lua-language-server diagnostics +++++++++++++++" - cat $logpath/check.json - exit 1 - fi - ''; - }; - in + elm-review = { - name = "lua-ls"; - description = "Uses the lua-language-server CLI to statically type-check and lint Lua code."; - entry = "${script}/bin/lua-ls-lint"; - files = "\\.lua$"; + name = "elm-review"; + description = "Analyzes Elm projects, to help find mistakes before your users find them."; + entry = "${tools.elm-review}/bin/elm-review"; + files = "\\.elm$"; pass_filenames = false; }; - ocp-indent = + elm-test = { - name = "ocp-indent"; - description = "A tool to indent OCaml code."; - entry = "${tools.ocp-indent}/bin/ocp-indent --inplace"; - files = "\\.mli?$"; + name = "elm-test"; + description = "Run unit tests and fuzz tests for Elm code."; + entry = "${tools.elm-test}/bin/elm-test"; + files = "\\.elm$"; + pass_filenames = false; }; - opam-lint = + eslint = { - name = "opam lint"; - description = "OCaml package manager configuration checker."; - entry = "${tools.opam}/bin/opam lint"; - files = "\\.opam$"; + name = "eslint"; + description = "Find and fix problems in your JavaScript code."; + entry = "${settings.eslint.binPath} --fix"; + files = "${settings.eslint.extensions}"; }; - ormolu = + flake8 = { - name = "ormolu"; - description = "Haskell code prettifier."; + name = "flake8"; + description = "Check the style and quality of Python files."; + entry = "${settings.flake8.binPath} --format ${settings.flake8.format}"; + types = [ "python" ]; + }; + flynt = + { + name = "flynt"; + description = "CLI tool to convert a python project's %-formatted strings to f-strings."; entry = let - extensions = - lib.escapeShellArgs (lib.concatMap (ext: [ "--ghc-opt" "-X${ext}" ]) settings.ormolu.defaultExtensions); - cabalExtensions = - if settings.ormolu.cabalDefaultExtensions then "--cabal-default-extensions" else ""; + cmdArgs = + mkCmdArgs (with settings.flynt; [ + [ aggressive "--aggressive" ] + [ dry-run "--dry-run" ] + [ (exclude != [ ]) "--exclude ${lib.escapeShellArgs exclude}" ] + [ fail-on-change "--fail-on-change" ] + [ (line-length != null) "--line-length ${toString line-length}" ] + [ no-multiline "--no-multiline" ] + [ quiet "--quiet" ] + [ string "--string" ] + [ transform-concats "--transform-concats" ] + [ verbose "--verbose" ] + ]); in - "${tools.ormolu}/bin/ormolu --mode inplace ${extensions} ${cabalExtensions}"; - files = "\\.l?hs(-boot)?$"; + "${settings.flynt.binPath} ${cmdArgs}"; + types = [ "python" ]; }; fourmolu = { @@ -1238,402 +1380,183 @@ in }"; files = "\\.l?hs(-boot)?$"; }; - hindent = - { - name = "hindent"; - description = "Haskell code prettifier."; - entry = "${tools.hindent}/bin/hindent"; - files = "\\.l?hs(-boot)?$"; - }; - cabal-fmt = - { - name = "cabal-fmt"; - description = "Format Cabal files"; - entry = "${tools.cabal-fmt}/bin/cabal-fmt --inplace"; - files = "\\.cabal$"; - }; - chktex = - { - name = "chktex"; - description = "LaTeX semantic checker"; - types = [ "file" "tex" ]; - entry = "${tools.chktex}/bin/chktex"; - }; - stylish-haskell = - { - name = "stylish-haskell"; - description = "A simple Haskell code prettifier"; - entry = "${tools.stylish-haskell}/bin/stylish-haskell --inplace"; - files = "\\.l?hs(-boot)?$"; - }; - alejandra = + fprettify = { + name = "fprettify"; + description = "Auto-formatter for modern Fortran code."; + types = [ "fortran " ]; + entry = "${tools.fprettify}/bin/fprettify"; + }; + gofmt = { - name = "alejandra"; - description = "The Uncompromising Nix Code Formatter."; + name = "gofmt"; + description = "A tool that automatically formats Go source code"; entry = let - cmdArgs = - mkCmdArgs (with settings.alejandra; [ - [ check "--check" ] - [ (exclude != [ ]) "--exclude ${lib.escapeShellArgs (lib.unique exclude)}" ] - [ (verbosity == "quiet") "-q" ] - [ (verbosity == "silent") "-qq" ] - [ (threads != null) "--threads ${toString threads}" ] - ]); - in - "${settings.alejandra.package}/bin/alejandra ${cmdArgs}"; - files = "\\.nix$"; - }; - deadnix = - { - name = "deadnix"; - description = "Scan Nix files for dead code (unused variable bindings)."; - entry = - let - cmdArgs = - mkCmdArgs (with settings.deadnix; [ - [ noLambdaArg "--no-lambda-arg" ] - [ noLambdaPatternNames "--no-lambda-pattern-names" ] - [ noUnderscore "--no-underscore" ] - [ quiet "--quiet" ] - [ hidden "--hidden" ] - [ edit "--edit" ] - [ (exclude != [ ]) "--exclude ${lib.escapeShellArgs exclude}" ] - ]); - in - "${tools.deadnix}/bin/deadnix ${cmdArgs} --fail"; - files = "\\.nix$"; - }; - flynt = - { - name = "flynt"; - description = "CLI tool to convert a python project's %-formatted strings to f-strings."; - entry = - let - cmdArgs = - mkCmdArgs (with settings.flynt; [ - [ aggressive "--aggressive" ] - [ dry-run "--dry-run" ] - [ (exclude != [ ]) "--exclude ${lib.escapeShellArgs exclude}" ] - [ fail-on-change "--fail-on-change" ] - [ (line-length != null) "--line-length ${toString line-length}" ] - [ no-multiline "--no-multiline" ] - [ quiet "--quiet" ] - [ string "--string" ] - [ transform-concats "--transform-concats" ] - [ verbose "--verbose" ] - ]); - in - "${settings.flynt.binPath} ${cmdArgs}"; - types = [ "python" ]; - }; - mdsh = - let - script = pkgs.writeShellScript "precommit-mdsh" '' - for file in $(echo "$@"); do - ${tools.mdsh}/bin/mdsh -i "$file" - done - ''; - in - { - name = "mdsh"; - description = "Markdown shell pre-processor."; - entry = toString script; - files = "\\.md$"; - }; - mypy = - { - name = "mypy"; - description = "Static type checker for Python"; - entry = settings.mypy.binPath; - files = "\\.py$"; - }; - nil = - { - name = "nil"; - description = "Incremental analysis assistant for writing in Nix."; - entry = - let - script = pkgs.writeShellScript "precommit-nil" '' - errors=false - echo Checking: $@ - for file in $(echo "$@"); do - ${tools.nil}/bin/nil diagnostics "$file" - exit_code=$? - - if [[ $exit_code -ne 0 ]]; then - echo \"$file\" failed with exit code: $exit_code - errors=true - fi + script = pkgs.writeShellScript "precommit-gofmt" '' + set -e + failed=false + for file in "$@"; do + # redirect stderr so that violations and summaries are properly interleaved. + if ! ${tools.go}/bin/gofmt -l -w "$file" 2>&1 + then + failed=true + fi done - if [[ $errors == true ]]; then - exit 1 + if [[ $failed == "true" ]]; then + exit 1 fi ''; in builtins.toString script; - files = "\\.nix$"; - }; - nixfmt = - { - name = "nixfmt"; - description = "Nix code prettifier."; - entry = "${tools.nixfmt}/bin/nixfmt ${lib.optionalString (settings.nixfmt.width != null) "--width=${toString settings.nixfmt.width}"}"; - files = "\\.nix$"; - }; - nixpkgs-fmt = - { - name = "nixpkgs-fmt"; - description = "Nix code prettifier."; - entry = "${tools.nixpkgs-fmt}/bin/nixpkgs-fmt"; - files = "\\.nix$"; - }; - statix = - { - name = "statix"; - description = "Lints and suggestions for the Nix programming language."; - entry = with settings.statix; - "${tools.statix}/bin/statix check -o ${format} ${if (ignore != [ ]) then "-i ${lib.escapeShellArgs (lib.unique ignore)}" else ""}"; - files = "\\.nix$"; - pass_filenames = false; - }; - elm-format = - { - name = "elm-format"; - description = "Format Elm files."; - entry = - "${tools.elm-format}/bin/elm-format --yes --elm-version=0.19"; - files = "\\.elm$"; - }; - elm-review = - { - name = "elm-review"; - description = "Analyzes Elm projects, to help find mistakes before your users find them."; - entry = "${tools.elm-review}/bin/elm-review"; - files = "\\.elm$"; - pass_filenames = false; - }; - elm-test = - { - name = "elm-test"; - description = "Run unit tests and fuzz tests for Elm code."; - entry = "${tools.elm-test}/bin/elm-test"; - files = "\\.elm$"; - pass_filenames = false; - }; - shellcheck = - { - name = "shellcheck"; - description = "Format shell files."; - types = [ "shell" ]; - entry = "${tools.shellcheck}/bin/shellcheck"; - }; - bats = - { - name = "bats"; - description = "Run bash unit tests."; - types = [ "shell" ]; - types_or = [ "bats" "bash" ]; - entry = "${tools.bats}/bin/bats -p"; - }; - stylua = - { - name = "stylua"; - description = "An Opinionated Lua Code Formatter."; - types = [ "file" "lua" ]; - entry = "${tools.stylua}/bin/stylua"; - }; - shfmt = - { - name = "shfmt"; - description = "Format shell files."; - types = [ "shell" ]; - entry = "${tools.shfmt}/bin/shfmt -w -s -l"; - }; - beautysh = - { - name = "beautysh"; - description = "Format shell files."; - types = [ "shell" ]; - entry = "${tools.beautysh}/bin/beautysh"; - }; - terraform-format = - { - name = "terraform-format"; - description = "Format terraform (`.tf`) files."; - entry = "${tools.terraform-fmt}/bin/terraform-fmt"; - files = "\\.tf$"; - }; - tflint = - { - name = "tflint"; - description = "A Pluggable Terraform Linter."; - entry = "${tools.tflint}/bin/tflint"; - files = "\\.tf$"; - }; - yamllint = - { - name = "yamllint"; - description = "Yaml linter."; - types = [ "file" "yaml" ]; - entry = - let - cmdArgs = - mkCmdArgs [ - [ (settings.yamllint.relaxed) "-d relaxed" ] - [ (settings.yamllint.configPath != "") "-c ${settings.yamllint.configPath}" ] - ]; - in - "${tools.yamllint}/bin/yamllint ${cmdArgs}"; - }; - rustfmt = - let - wrapper = pkgs.symlinkJoin { - name = "rustfmt-wrapped"; - paths = [ tools.rustfmt ]; - nativeBuildInputs = [ pkgs.makeWrapper ]; - postBuild = '' - wrapProgram $out/bin/cargo-fmt \ - --prefix PATH : ${lib.makeBinPath [ tools.cargo tools.rustfmt ]} - ''; - }; - in - { - name = "rustfmt"; - description = "Format Rust code."; - entry = "${wrapper}/bin/cargo-fmt fmt ${cargoManifestPathArg} -- --color always"; - files = "\\.rs$"; - pass_filenames = false; + files = "\\.go$"; }; - clippy = - let - wrapper = pkgs.symlinkJoin { - name = "clippy-wrapped"; - paths = [ tools.clippy ]; - nativeBuildInputs = [ pkgs.makeWrapper ]; - postBuild = '' - wrapProgram $out/bin/cargo-clippy \ - --prefix PATH : ${lib.makeBinPath [ tools.cargo ]} + golangci-lint = { + name = "golangci-lint"; + description = "Fast linters runner for Go."; + entry = + let + script = pkgs.writeShellScript "precommit-golangci-lint" '' + set -e + for dir in $(echo "$@" | xargs -n1 dirname | sort -u); do + ${tools.golangci-lint}/bin/golangci-lint run ./"$dir" + done ''; - }; - in - { - name = "clippy"; - description = "Lint Rust code."; - entry = "${wrapper}/bin/cargo-clippy clippy ${cargoManifestPathArg} ${lib.optionalString settings.clippy.offline "--offline"} ${lib.optionalString settings.clippy.allFeatures "--all-features"} -- ${lib.optionalString settings.clippy.denyWarnings "-D warnings"}"; - files = "\\.rs$"; - pass_filenames = false; - }; - cargo-check = - { - name = "cargo-check"; - description = "Check the cargo package for errors."; - entry = "${tools.cargo}/bin/cargo check ${cargoManifestPathArg}"; - files = "\\.rs$"; - pass_filenames = false; - }; - purty = - { - name = "purty"; - description = "Format purescript files."; - entry = "${tools.purty}/bin/purty"; - files = "\\.purs$"; - }; - purs-tidy = - { - name = "purs-tidy"; - description = "Format purescript files."; - entry = "${tools.purs-tidy}/bin/purs-tidy format-in-place"; - files = "\\.purs$"; - }; + in + builtins.toString script; + files = "\\.go$"; + # to avoid multiple invocations of the same directory input, provide + # all file names in a single run. + require_serial = true; + }; + gotest = { + name = "gotest"; + description = "Run go tests"; + entry = + let + script = pkgs.writeShellScript "precommit-gotest" '' + set -e + # find all directories that contain tests + dirs=() + for file in "$@"; do + # either the file is a test + if [[ "$file" = *_test.go ]]; then + dirs+=("$(dirname "$file")") + continue + fi - prettier = + # or the file has an associated test + filename="''${file%.go}" + test_file="''${filename}_test.go" + if [[ -f "$test_file" ]]; then + dirs+=("$(dirname "$test_file")") + continue + fi + done + + # ensure we are not duplicating dir entries + IFS=$'\n' sorted_dirs=($(sort -u <<<"''${dirs[*]}")); unset IFS + + # test each directory one by one + for dir in "''${sorted_dirs[@]}"; do + ${tools.go}/bin/go test "./$dir" + done + ''; + in + builtins.toString script; + files = "\\.go$"; + # to avoid multiple invocations of the same directory input, provide + # all file names in a single run. + require_serial = true; + }; + govet = { - name = "prettier"; - description = "Opinionated multi-language code formatter."; - entry = with settings.prettier; - "${binPath} ${lib.optionalString write "--write"} ${lib.optionalString (output != null) "--${output}"} --ignore-unknown"; - types = [ "text" ]; + name = "govet"; + description = "Checks correctness of Go programs."; + entry = + let + # go vet requires package (directory) names as inputs. + script = pkgs.writeShellScript "precommit-govet" '' + set -e + for dir in $(echo "$@" | xargs -n1 dirname | sort -u); do + ${tools.go}/bin/go vet ./"$dir" + done + ''; + in + builtins.toString script; + # to avoid multiple invocations of the same directory input, provide + # all file names in a single run. + require_serial = true; + files = "\\.go$"; }; - pre-commit-hook-ensure-sops = { - name = "pre-commit-hook-ensure-sops"; + gptcommit = { + name = "gptcommit"; + description = "Generate a commit message using GPT3."; entry = - ## NOTE: pre-commit-hook-ensure-sops landed in nixpkgs on 8 July 2022. Once it reaches a - ## release of NixOS, the `throwIf` piece of code below will become - ## useless. - lib.throwIf - (tools.pre-commit-hook-ensure-sops == null) - "The version of nixpkgs used by pre-commit-hooks.nix does not have the `pre-commit-hook-ensure-sops` package. Please use a more recent version of nixpkgs." - '' - ${tools.pre-commit-hook-ensure-sops}/bin/pre-commit-hook-ensure-sops + let + script = pkgs.writeShellScript "precommit-gptcomit" '' + ${tools.gptcommit}/bin/gptcommit prepare-commit-msg --commit-source \ + "$PRE_COMMIT_COMMIT_MSG_SOURCE" --commit-msg-file "$1" ''; - files = lib.mkDefault "^secrets"; + in + lib.throwIf (tools.gptcommit == null) "The version of Nixpkgs used by pre-commit-hooks.nix does not have the `gptcommit` package. Please use a more recent version of Nixpkgs." + toString + script; + stages = [ "prepare-commit-msg" ]; }; - hunspell = + hadolint = { - name = "hunspell"; - description = "Spell checker and morphological analyzer."; - entry = "${tools.hunspell}/bin/hunspell -l"; - files = "\\.((txt)|(html)|(xml)|(md)|(rst)|(tex)|(odf)|\\d)$"; + name = "hadolint"; + description = "Dockerfile linter, validate inline bash."; + entry = "${tools.hadolint}/bin/hadolint"; + files = "Dockerfile$"; }; - - topiary = + headache = { - name = "topiary"; - description = "A universal formatter engine within the Tree-sitter ecosystem, with support for many languages."; + name = "headache"; + description = "Lightweight tool for managing headers in source code files."; + ## NOTE: Supported `files` are taken from + ## https://github.com/Frama-C/headache/blob/master/config_builtin.txt + files = "(\\.ml[ily]?$)|(\\.fmli?$)|(\\.[chy]$)|(\\.tex$)|(Makefile)|(README)|(LICENSE)"; entry = - ## NOTE: Topiary landed in nixpkgs on 2 Dec 2022. Once it reaches a - ## release of NixOS, the `throwIf` piece of code below will become - ## useless. + ## NOTE: `headache` made into in nixpkgs on 12 April 2023. At the + ## next NixOS release, the following code will become irrelevant. lib.throwIf - (tools.topiary == null) - "The version of nixpkgs used by pre-commit-hooks.nix does not have the `topiary` package. Please use a more recent version of nixpkgs." - ( - let - topiary-inplace = pkgs.writeShellApplication { - name = "topiary-inplace"; - text = '' - for file; do - ${tools.topiary}/bin/topiary --in-place --input-file "$file" - done - ''; - }; - in - "${topiary-inplace}/bin/topiary-inplace" - ); - files = "(\\.json$)|(\\.toml$)|(\\.mli?$)"; + (tools.headache == null) + "The version of nixpkgs used by pre-commit-hooks.nix does not have `ocamlPackages.headache`. Please use a more recent version of nixpkgs." + "${tools.headache}/bin/headache -h ${settings.headache.header-file}"; }; - - typos = + hindent = { - name = "typos"; - description = "Source code spell checker"; - entry = - let - configFile = builtins.toFile "config.toml" "${settings.typos.config}"; - cmdArgs = - mkCmdArgs - (with settings.typos; [ - [ (color != "") "--color ${color}" ] - [ (configPath != "") "--config ${configPath}" ] - [ (config != "" && configPath == "") "--config ${configFile}" ] - [ (exclude != "") "--exclude ${exclude} --force-exclude" ] - [ (format != "") "--format ${format}" ] - [ (locale != "") "--locale ${locale}" ] - [ (write && !diff) "--write-changes" ] - ]); - in - "${tools.typos}/bin/typos ${cmdArgs}${lib.optionalString settings.typos.diff " --diff"}${lib.optionalString settings.typos.hidden " --hidden"}"; - types = [ "text" ]; + name = "hindent"; + description = "Haskell code prettifier."; + entry = "${tools.hindent}/bin/hindent"; + files = "\\.l?hs(-boot)?$"; }; - - cspell = + hlint = { - name = "cspell"; - description = "A Spell Checker for Code"; - entry = "${tools.cspell}/bin/cspell"; + name = "hlint"; + description = + "HLint gives suggestions on how to improve your source code."; + entry = "${tools.hlint}/bin/hlint${if settings.hlint.hintFile == null then "" else " --hint=${settings.hlint.hintFile}"}"; + files = "\\.l?hs(-boot)?$"; + }; + hpack = + { + name = "hpack"; + description = + "`hpack` converts package definitions in the hpack format (`package.yaml`) to Cabal files."; + entry = "${tools.hpack-dir}/bin/hpack-dir --${if settings.hpack.silent then "silent" else "verbose"}"; + files = "(\\.l?hs(-boot)?$)|(\\.cabal$)|((^|/)package\\.yaml$)"; + # We don't pass filenames because they can only be misleading. + # Indeed, we need to rerun `hpack` in every directory: + # 1. In which there is a *.cabal file, or + # 2. Below which there are haskell files, or + # 3. In which there is a package.yaml that references haskell files + # that have been changed at arbitrary locations specified in that + # file. + # In other words: We have no choice but to always run `hpack` on every `package.yaml` directory. + pass_filenames = false; }; - html-tidy = { name = "html-tidy"; @@ -1641,59 +1564,115 @@ in entry = "${tools.html-tidy}/bin/tidy -quiet -errors"; files = "\\.html$"; }; - - eslint = + hunspell = { - name = "eslint"; - description = "Find and fix problems in your JavaScript code."; - entry = "${settings.eslint.binPath} --fix"; - files = "${settings.eslint.extensions}"; + name = "hunspell"; + description = "Spell checker and morphological analyzer."; + entry = "${tools.hunspell}/bin/hunspell -l"; + files = "\\.((txt)|(html)|(xml)|(md)|(rst)|(tex)|(odf)|\\d)$"; }; - - eclint = + isort = { - name = "eclint"; - description = "EditorConfig linter written in Go."; - types = [ "file" ]; + name = "isort"; + description = "A Python utility / library to sort imports."; + types = [ "file" "python" ]; entry = let cmdArgs = mkCmdArgs - (with settings.eclint; [ - [ fix "-fix" ] - [ summary "-summary" ] - [ (color != "auto") "-color ${color}" ] - [ (exclude != [ ]) "-exclude ${lib.escapeShellArgs exclude}" ] - [ (verbosity != 0) "-verbosity ${toString verbosity}" ] + (with settings.isort; [ + [ (profile != "") " --profile ${profile}" ] ]); in - "${settings.eclint.package}/bin/eclint ${cmdArgs}"; + "${pkgs.python3Packages.isort}/bin/isort${cmdArgs} ${settings.isort.flags}"; }; - - rome = + juliaformatter = + { + description = "Run JuliaFormatter.jl against Julia source files"; + files = "\\.jl$"; + entry = '' + ${tools.julia-bin}/bin/julia -e ' + using Pkg + Pkg.activate(".") + using JuliaFormatter + format(ARGS) + out = Cmd(`git diff --name-only`) |> read |> String + if out == "" + exit(0) + else + @error "Some files have been formatted !!!" + write(stdout, out) + exit(1) + end' + ''; + }; + latexindent = { - name = "rome"; - description = "Unified developer tools for JavaScript, TypeScript, and the web"; - types_or = [ "javascript" "jsx" "ts" "tsx" "json" ]; - entry = - let - cmdArgs = - mkCmdArgs [ - [ (settings.rome.write) "--apply" ] - [ (settings.rome.configPath != "") "--config-path ${settings.rome.configPath}" ] - ]; - in - "${settings.rome.binPath} check ${cmdArgs}"; + name = "latexindent"; + description = "Perl script to add indentation to LaTeX files."; + types = [ "file" "tex" ]; + entry = "${tools.latexindent}/bin/latexindent ${settings.latexindent.flags}"; }; - - hadolint = + lua-ls = + let + # .luarc.json has to be in a directory, + # or lua-language-server will hang forever. + luarc = pkgs.writeText ".luarc.json" (builtins.toJSON settings.lua-ls.config); + luarc-dir = pkgs.stdenv.mkDerivation { + name = "luarc"; + unpackPhase = "true"; + installPhase = '' + mkdir $out + cp ${luarc} $out/.luarc.json + ''; + }; + script = pkgs.writeShellApplication { + name = "lua-ls-lint"; + runtimeInputs = [ tools.lua-language-server ]; + checkPhase = ""; # The default checkPhase depends on GHC + text = '' + set -e + export logpath="$(mktemp -d)" + lua-language-server --check $(realpath .) \ + --checklevel="${settings.lua-ls.checklevel}" \ + --configpath="${luarc-dir}/.luarc.json" \ + --logpath="$logpath" + if [[ -f $logpath/check.json ]]; then + echo "+++++++++++++++ lua-language-server diagnostics +++++++++++++++" + cat $logpath/check.json + exit 1 + fi + ''; + }; + in { - name = "hadolint"; - description = "Dockerfile linter, validate inline bash."; - entry = "${tools.hadolint}/bin/hadolint"; - files = "Dockerfile$"; + name = "lua-ls"; + description = "Uses the lua-language-server CLI to statically type-check and lint Lua code."; + entry = "${script}/bin/lua-ls-lint"; + files = "\\.lua$"; + pass_filenames = false; }; - + luacheck = + { + name = "luacheck"; + description = "A tool for linting and static analysis of Lua code."; + types = [ "file" "lua" ]; + entry = "${tools.luacheck}/bin/luacheck"; + }; + lychee = { + name = "lychee"; + description = "A fast, async, stream-based link checker that finds broken hyperlinks and mail adresses inside Markdown, HTML, reStructuredText, or any other text file or website."; + entry = + let + cmdArgs = + mkCmdArgs + (with settings.lychee; [ + [ (configPath != "") " --config ${configPath}" ] + ]); + in + "${pkgs.lychee}/bin/lychee${cmdArgs} ${settings.lychee.flags}"; + types = [ "text" ]; + }; markdownlint = { name = "markdownlint"; @@ -1701,7 +1680,6 @@ in entry = "${tools.markdownlint-cli}/bin/markdownlint -c ${pkgs.writeText "markdownlint.json" (builtins.toJSON settings.markdownlint.config)}"; files = "\\.md$"; }; - mdl = { name = "mdl"; @@ -1728,127 +1706,224 @@ in "${settings.mdl.package}/bin/mdl ${cmdArgs}"; files = "\\.md$"; }; - - denolint = + mdsh = + let + script = pkgs.writeShellScript "precommit-mdsh" '' + for file in $(echo "$@"); do + ${tools.mdsh}/bin/mdsh -i "$file" + done + ''; + in { - name = "denolint"; - description = "Lint JavaScript/TypeScript source code."; - types_or = [ "javascript" "jsx" "ts" "tsx" ]; - entry = - let - cmdArgs = - mkCmdArgs [ - [ (settings.denolint.format == "compact") "--compact" ] - [ (settings.denolint.format == "json") "--json" ] - [ (settings.denolint.configPath != "") "-c ${settings.denolint.configPath}" ] - ]; - in - "${tools.deno}/bin/deno lint ${cmdArgs}"; + name = "mdsh"; + description = "Markdown shell pre-processor."; + entry = toString script; + files = "\\.md$"; }; - - denofmt = + mix-format = { + name = "mix-format"; + description = "Runs the built-in Elixir syntax formatter"; + entry = "${pkgs.elixir}/bin/mix format"; + files = "\\.exs?$"; + }; + mix-test = { + name = "mix-test"; + description = "Runs the built-in Elixir test framework"; + entry = "${pkgs.elixir}/bin/mix test"; + files = "\\.exs?$"; + }; + mkdocs-linkcheck = { + name = "mkdocs-linkcheck"; + description = "Validate links associated with markdown-based, statically generated websites."; + entry = + let + cmdArgs = + mkCmdArgs + (with settings.mkdocs-linkcheck; [ + [ local-only " --local" ] + [ recurse " --recurse" ] + [ (extension != "") " --ext ${extension}" ] + [ (method != "") " --method ${method}" ] + [ (path != "") " ${path}" ] + ]); + in + "${settings.mkdocs-linkcheck.binPath}${cmdArgs}"; + types = [ "text" "markdown" ]; + }; + mypy = { - name = "denofmt"; - description = "Auto-format JavaScript, TypeScript, Markdown, and JSON files."; - types_or = [ "javascript" "jsx" "ts" "tsx" "markdown" "json" ]; - entry = - let - cmdArgs = - mkCmdArgs [ - [ (!settings.denofmt.write) "--check" ] - [ (settings.denofmt.configPath != "") "-c ${settings.denofmt.configPath}" ] - ]; - in - "${tools.deno}/bin/deno fmt ${cmdArgs}"; + name = "mypy"; + description = "Static type checker for Python"; + entry = settings.mypy.binPath; + files = "\\.py$"; }; - - govet = + nil = { - name = "govet"; - description = "Checks correctness of Go programs."; + name = "nil"; + description = "Incremental analysis assistant for writing in Nix."; entry = let - # go vet requires package (directory) names as inputs. - script = pkgs.writeShellScript "precommit-govet" '' - set -e - for dir in $(echo "$@" | xargs -n1 dirname | sort -u); do - ${tools.go}/bin/go vet ./"$dir" + script = pkgs.writeShellScript "precommit-nil" '' + errors=false + echo Checking: $@ + for file in $(echo "$@"); do + ${tools.nil}/bin/nil diagnostics "$file" + exit_code=$? + + if [[ $exit_code -ne 0 ]]; then + echo \"$file\" failed with exit code: $exit_code + errors=true + fi done + if [[ $errors == true ]]; then + exit 1 + fi ''; in builtins.toString script; - # to avoid multiple invocations of the same directory input, provide - # all file names in a single run. - require_serial = true; - files = "\\.go$"; + files = "\\.nix$"; }; - - gotest = { - name = "gotest"; - description = "Run go tests"; + nixfmt = + { + name = "nixfmt"; + description = "Nix code prettifier."; + entry = "${tools.nixfmt}/bin/nixfmt ${lib.optionalString (settings.nixfmt.width != null) "--width=${toString settings.nixfmt.width}"}"; + files = "\\.nix$"; + }; + nixpkgs-fmt = + { + name = "nixpkgs-fmt"; + description = "Nix code prettifier."; + entry = "${tools.nixpkgs-fmt}/bin/nixpkgs-fmt"; + files = "\\.nix$"; + }; + ocp-indent = + { + name = "ocp-indent"; + description = "A tool to indent OCaml code."; + entry = "${tools.ocp-indent}/bin/ocp-indent --inplace"; + files = "\\.mli?$"; + }; + opam-lint = + { + name = "opam lint"; + description = "OCaml package manager configuration checker."; + entry = "${tools.opam}/bin/opam lint"; + files = "\\.opam$"; + }; + ormolu = + { + name = "ormolu"; + description = "Haskell code prettifier."; + entry = + let + extensions = + lib.escapeShellArgs (lib.concatMap (ext: [ "--ghc-opt" "-X${ext}" ]) settings.ormolu.defaultExtensions); + cabalExtensions = + if settings.ormolu.cabalDefaultExtensions then "--cabal-default-extensions" else ""; + in + "${tools.ormolu}/bin/ormolu --mode inplace ${extensions} ${cabalExtensions}"; + files = "\\.l?hs(-boot)?$"; + }; + php-cs-fixer = + { + name = "php-cs-fixer"; + description = "Lint PHP files."; + entry = with settings.php-cs-fixer; + "${binPath} fix"; + types = [ "php" ]; + }; + phpcbf = + { + name = "phpcbf"; + description = "Lint PHP files."; + entry = with settings.phpcbf; + "${binPath}"; + types = [ "php" ]; + }; + phpcs = + { + name = "phpcs"; + description = "Lint PHP files."; + entry = with settings.phpcs; + "${binPath}"; + types = [ "php" ]; + }; + phpstan = + { + name = "phpstan"; + description = "Static Analysis of PHP files."; + entry = with settings.phpstan; + "${binPath} analyse"; + types = [ "php" ]; + }; + pre-commit-hook-ensure-sops = { + name = "pre-commit-hook-ensure-sops"; entry = - let - script = pkgs.writeShellScript "precommit-gotest" '' - set -e - # find all directories that contain tests - dirs=() - for file in "$@"; do - # either the file is a test - if [[ "$file" = *_test.go ]]; then - dirs+=("$(dirname "$file")") - continue - fi - - # or the file has an associated test - filename="''${file%.go}" - test_file="''${filename}_test.go" - if [[ -f "$test_file" ]]; then - dirs+=("$(dirname "$test_file")") - continue - fi - done - - # ensure we are not duplicating dir entries - IFS=$'\n' sorted_dirs=($(sort -u <<<"''${dirs[*]}")); unset IFS - - # test each directory one by one - for dir in "''${sorted_dirs[@]}"; do - ${tools.go}/bin/go test "./$dir" - done + ## NOTE: pre-commit-hook-ensure-sops landed in nixpkgs on 8 July 2022. Once it reaches a + ## release of NixOS, the `throwIf` piece of code below will become + ## useless. + lib.throwIf + (tools.pre-commit-hook-ensure-sops == null) + "The version of nixpkgs used by pre-commit-hooks.nix does not have the `pre-commit-hook-ensure-sops` package. Please use a more recent version of nixpkgs." + '' + ${tools.pre-commit-hook-ensure-sops}/bin/pre-commit-hook-ensure-sops ''; - in - builtins.toString script; - files = "\\.go$"; - # to avoid multiple invocations of the same directory input, provide - # all file names in a single run. - require_serial = true; + files = lib.mkDefault "^secrets"; }; - - gofmt = + prettier = + { + name = "prettier"; + description = "Opinionated multi-language code formatter."; + entry = with settings.prettier; + "${binPath} ${lib.optionalString write "--write"} ${lib.optionalString (output != null) "--${output}"} --ignore-unknown"; + types = [ "text" ]; + }; + psalm = + { + name = "psalm"; + description = "Static Analysis of PHP files."; + entry = with settings.psalm; + "${binPath}"; + types = [ "php" ]; + }; + purs-tidy = { - name = "gofmt"; - description = "A tool that automatically formats Go source code"; - entry = - let - script = pkgs.writeShellScript "precommit-gofmt" '' - set -e - failed=false - for file in "$@"; do - # redirect stderr so that violations and summaries are properly interleaved. - if ! ${tools.go}/bin/gofmt -l -w "$file" 2>&1 - then - failed=true - fi - done - if [[ $failed == "true" ]]; then - exit 1 - fi - ''; - in - builtins.toString script; - files = "\\.go$"; + name = "purs-tidy"; + description = "Format purescript files."; + entry = "${tools.purs-tidy}/bin/purs-tidy format-in-place"; + files = "\\.purs$"; + }; + purty = + { + name = "purty"; + description = "Format purescript files."; + entry = "${tools.purty}/bin/purty"; + files = "\\.purs$"; + }; + pylint = + { + name = "pylint"; + description = "Lint Python files."; + entry = with settings.pylint; + "${binPath} ${lib.optionalString reports "-ry"} ${lib.optionalString (! score) "-sn"}"; + types = [ "python" ]; + }; + pyright = + { + name = "pyright"; + description = "Static type checker for Python"; + entry = settings.pyright.binPath; + files = "\\.py$"; + }; + pyupgrade = + { + name = "pyupgrade"; + description = "Automatically upgrade syntax for newer versions."; + entry = with settings.pyupgrade; + "${binPath}"; + types = [ "python" ]; }; - revive = { name = "revive"; @@ -1877,7 +1952,61 @@ in # all file names in a single run. require_serial = true; }; - + rome = + { + name = "rome"; + description = "Unified developer tools for JavaScript, TypeScript, and the web"; + types_or = [ "javascript" "jsx" "ts" "tsx" "json" ]; + entry = + let + cmdArgs = + mkCmdArgs [ + [ (settings.rome.write) "--apply" ] + [ (settings.rome.configPath != "") "--config-path ${settings.rome.configPath}" ] + ]; + in + "${settings.rome.binPath} check ${cmdArgs}"; + }; + ruff = + { + name = "ruff"; + description = " An extremely fast Python linter, written in Rust."; + entry = "${pkgs.ruff}/bin/ruff --fix"; + types = [ "python" ]; + }; + rustfmt = + let + wrapper = pkgs.symlinkJoin { + name = "rustfmt-wrapped"; + paths = [ tools.rustfmt ]; + nativeBuildInputs = [ pkgs.makeWrapper ]; + postBuild = '' + wrapProgram $out/bin/cargo-fmt \ + --prefix PATH : ${lib.makeBinPath [ tools.cargo tools.rustfmt ]} + ''; + }; + in + { + name = "rustfmt"; + description = "Format Rust code."; + entry = "${wrapper}/bin/cargo-fmt fmt ${cargoManifestPathArg} -- --color always"; + files = "\\.rs$"; + pass_filenames = false; + }; + shellcheck = + { + name = "shellcheck"; + description = "Format shell files."; + types = [ "shell" ]; + entry = "${tools.shellcheck}/bin/shellcheck"; + }; + shfmt = + { + name = "shfmt"; + description = "Format shell files."; + types = [ "shell" ]; + entry = "${tools.shfmt}/bin/shfmt -w -s -l"; + }; staticcheck = { name = "staticcheck"; @@ -1903,104 +2032,39 @@ in # all file names in a single run. require_serial = true; }; - - editorconfig-checker = - { - name = "editorconfig-checker"; - description = "Verify that the files are in harmony with the `.editorconfig`."; - entry = "${tools.editorconfig-checker}/bin/editorconfig-checker"; - types = [ "file" ]; - }; - - - phpcs = - { - name = "phpcs"; - description = "Lint PHP files."; - entry = with settings.phpcs; - "${binPath}"; - types = [ "php" ]; - }; - - phpcbf = - { - name = "phpcbf"; - description = "Lint PHP files."; - entry = with settings.phpcbf; - "${binPath}"; - types = [ "php" ]; - }; - - phpstan = - { - name = "phpstan"; - description = "Static Analysis of PHP files."; - entry = with settings.phpstan; - "${binPath} analyse"; - types = [ "php" ]; - }; - - php-cs-fixer = - { - name = "php-cs-fixer"; - description = "Lint PHP files."; - entry = with settings.php-cs-fixer; - "${binPath} fix"; - types = [ "php" ]; - }; - - psalm = - { - name = "psalm"; - description = "Static Analysis of PHP files."; - entry = with settings.psalm; - "${binPath}"; - types = [ "php" ]; - }; - - - pylint = - { - name = "pylint"; - description = "Lint Python files."; - entry = with settings.pylint; - "${binPath} ${lib.optionalString reports "-ry"} ${lib.optionalString (! score) "-sn"}"; - types = [ "python" ]; - }; - - pyupgrade = + statix = { - name = "pyupgrade"; - description = "Automatically upgrade syntax for newer versions."; - entry = with settings.pyupgrade; - "${binPath}"; - types = [ "python" ]; + name = "statix"; + description = "Lints and suggestions for the Nix programming language."; + entry = with settings.statix; + "${tools.statix}/bin/statix check -o ${format} ${if (ignore != [ ]) then "-i ${lib.escapeShellArgs (lib.unique ignore)}" else ""}"; + files = "\\.nix$"; + pass_filenames = false; }; - - pyright = + stylish-haskell = { - name = "pyright"; - description = "Static type checker for Python"; - entry = settings.pyright.binPath; - files = "\\.py$"; + name = "stylish-haskell"; + description = "A simple Haskell code prettifier"; + entry = "${tools.stylish-haskell}/bin/stylish-haskell --inplace"; + files = "\\.l?hs(-boot)?$"; }; - - flake8 = + stylua = { - name = "flake8"; - description = "Check the style and quality of Python files."; - entry = "${settings.flake8.binPath} --format ${settings.flake8.format}"; - types = [ "python" ]; + name = "stylua"; + description = "An Opinionated Lua Code Formatter."; + types = [ "file" "lua" ]; + entry = "${tools.stylua}/bin/stylua"; }; - - autoflake = + tagref = { - name = "autoflake"; - description = "Remove unused imports and variables from Python code."; - entry = "${settings.autoflake.binPath} ${settings.autoflake.flags}"; - types = [ "python" ]; + name = "tagref"; + description = '' + Have tagref check all references and tags. + ''; + entry = "${tools.tagref}/bin/tagref"; + types = [ "text" ]; + pass_filenames = false; }; - taplo = { name = "taplo"; @@ -2008,44 +2072,46 @@ in entry = "${pkgs.taplo}/bin/taplo fmt"; types = [ "toml" ]; }; - - cljfmt = - { - name = "cljfmt"; - description = "A tool for formatting Clojure code."; - entry = "${pkgs.cljfmt}/bin/cljfmt fix"; - types_or = [ "clojure" "clojurescript" "edn" ]; - }; - - zprint = + terraform-format = { - name = "zprint"; - description = "Beautifully format Clojure and Clojurescript source code and s-expressions."; - entry = "${pkgs.zprint}/bin/zprint '{:search-config? true}' -w"; - types_or = [ "clojure" "clojurescript" "edn" ]; + name = "terraform-format"; + description = "Format terraform (`.tf`) files."; + entry = "${tools.terraform-fmt}/bin/terraform-fmt"; + files = "\\.tf$"; }; - - commitizen = + tflint = { - name = "commitizen check"; - description = '' - Check whether the current commit message follows committing rules. - ''; - entry = "${tools.commitizen}/bin/cz check --allow-abort --commit-msg-file"; - stages = [ "commit-msg" ]; + name = "tflint"; + description = "A Pluggable Terraform Linter."; + entry = "${tools.tflint}/bin/tflint"; + files = "\\.tf$"; }; - - tagref = + topiary = { - name = "tagref"; - description = '' - Have tagref check all references and tags. - ''; - entry = "${tools.tagref}/bin/tagref"; - types = [ "text" ]; - pass_filenames = false; + name = "topiary"; + description = "A universal formatter engine within the Tree-sitter ecosystem, with support for many languages."; + entry = + ## NOTE: Topiary landed in nixpkgs on 2 Dec 2022. Once it reaches a + ## release of NixOS, the `throwIf` piece of code below will become + ## useless. + lib.throwIf + (tools.topiary == null) + "The version of nixpkgs used by pre-commit-hooks.nix does not have the `topiary` package. Please use a more recent version of nixpkgs." + ( + let + topiary-inplace = pkgs.writeShellApplication { + name = "topiary-inplace"; + text = '' + for file; do + ${tools.topiary}/bin/topiary --in-place --input-file "$file" + done + ''; + }; + in + "${topiary-inplace}/bin/topiary-inplace" + ); + files = "(\\.json$)|(\\.toml$)|(\\.mli?$)"; }; - treefmt = { name = "treefmt"; @@ -2054,117 +2120,34 @@ in pass_filenames = true; entry = "${settings.treefmt.package}/bin/treefmt --fail-on-change"; }; - - mkdocs-linkcheck = { - name = "mkdocs-linkcheck"; - description = "Validate links associated with markdown-based, statically generated websites."; - entry = - let - cmdArgs = - mkCmdArgs - (with settings.mkdocs-linkcheck; [ - [ local-only " --local" ] - [ recurse " --recurse" ] - [ (extension != "") " --ext ${extension}" ] - [ (method != "") " --method ${method}" ] - [ (path != "") " ${path}" ] - ]); - in - "${settings.mkdocs-linkcheck.binPath}${cmdArgs}"; - types = [ "text" "markdown" ]; - }; - - checkmake = { - name = "checkmake"; - description = "Experimental linter/analyzer for Makefiles."; - types = [ "makefile" ]; - entry = - ## NOTE: `checkmake` 0.2.2 landed in nixpkgs on 12 April 2023. Once - ## this gets into a NixOS release, the following code will be useless. - lib.throwIf - (tools.checkmake == null) - "The version of nixpkgs used by pre-commit-hooks.nix must have `checkmake` in version at least 0.2.2 for it to work on non-Linux systems." - "${tools.checkmake}/bin/checkmake"; - }; - - fprettify = { - name = "fprettify"; - description = "Auto-formatter for modern Fortran code."; - types = [ "fortran " ]; - entry = "${tools.fprettify}/bin/fprettify"; - }; - - dune-fmt = { - name = "dune-fmt"; - description = "Runs Dune's formatters on the code tree."; - entry = - let - auto-promote = if settings.dune-fmt.auto-promote then "--auto-promote" else ""; - run-dune-fmt = pkgs.writeShellApplication { - name = "run-dune-fmt"; - runtimeInputs = settings.dune-fmt.extraRuntimeInputs; - text = "${tools.dune-fmt}/bin/dune-fmt ${auto-promote}"; - }; - in - "${run-dune-fmt}/bin/run-dune-fmt"; - pass_filenames = false; - }; - - headache = + typos = { - name = "headache"; - description = "Lightweight tool for managing headers in source code files."; - ## NOTE: Supported `files` are taken from - ## https://github.com/Frama-C/headache/blob/master/config_builtin.txt - files = "(\\.ml[ily]?$)|(\\.fmli?$)|(\\.[chy]$)|(\\.tex$)|(Makefile)|(README)|(LICENSE)"; + name = "typos"; + description = "Source code spell checker"; entry = - ## NOTE: `headache` made into in nixpkgs on 12 April 2023. At the - ## next NixOS release, the following code will become irrelevant. - lib.throwIf - (tools.headache == null) - "The version of nixpkgs used by pre-commit-hooks.nix does not have `ocamlPackages.headache`. Please use a more recent version of nixpkgs." - "${tools.headache}/bin/headache -h ${settings.headache.header-file}"; + let + configFile = builtins.toFile "config.toml" "${settings.typos.config}"; + cmdArgs = + mkCmdArgs + (with settings.typos; [ + [ (color != "") "--color ${color}" ] + [ (configPath != "") "--config ${configPath}" ] + [ (config != "" && configPath == "") "--config ${configFile}" ] + [ (exclude != "") "--exclude ${exclude} --force-exclude" ] + [ (format != "") "--format ${format}" ] + [ (locale != "") "--locale ${locale}" ] + [ (write && !diff) "--write-changes" ] + ]); + in + "${tools.typos}/bin/typos ${cmdArgs}${lib.optionalString settings.typos.diff " --diff"}${lib.optionalString settings.typos.hidden " --hidden"}"; + types = [ "text" ]; }; - - convco = { - name = "convco"; - entry = - let - script = pkgs.writeShellScript "precommit-convco" '' - cat $1 | ${pkgs.convco}/bin/convco check --from-stdin - ''; - # need version >= 0.4.0 for the --from-stdin flag - toolVersionCheck = lib.versionAtLeast tools.convco.version "0.4.0"; - in - lib.throwIf (tools.convco == null || !toolVersionCheck) "The version of Nixpkgs used by pre-commit-hooks.nix does not have the `convco` package (>=0.4.0). Please use a more recent version of Nixpkgs." - builtins.toString - script; - stages = [ "commit-msg" ]; - }; - - mix-format = { - name = "mix-format"; - description = "Runs the built-in Elixir syntax formatter"; - entry = "${pkgs.elixir}/bin/mix format"; - files = "\\.exs?$"; - }; - - mix-test = { - name = "mix-test"; - description = "Runs the built-in Elixir test framework"; - entry = "${pkgs.elixir}/bin/mix test"; - files = "\\.exs?$"; - }; - - credo = { - name = "credo"; - description = "Runs a static code analysis using Credo"; - entry = - let strict = if settings.credo.strict then "--strict" else ""; - in "${pkgs.elixir}/bin/mix credo"; - files = "\\.exs?$"; + typstfmt = { + name = "typstfmt"; + description = "format typst"; + entry = "${tools.typst-fmt}/bin/typst-fmt"; + files = "\\.typ$"; }; - vale = { name = "vale"; description = "A markup-aware linter for prose built with speed and extensibility in mind."; @@ -2181,93 +2164,28 @@ in "${pkgs.vale}/bin/vale${cmdArgs} ${settings.vale.flags}"; types = [ "text" ]; }; + yamllint = + { + name = "yamllint"; + description = "Yaml linter."; + types = [ "file" "yaml" ]; + entry = + let + cmdArgs = + mkCmdArgs [ + [ (settings.yamllint.relaxed) "-d relaxed" ] + [ (settings.yamllint.configPath != "") "-c ${settings.yamllint.configPath}" ] + ]; + in + "${tools.yamllint}/bin/yamllint ${cmdArgs}"; + }; + zprint = + { + name = "zprint"; + description = "Beautifully format Clojure and Clojurescript source code and s-expressions."; + entry = "${pkgs.zprint}/bin/zprint '{:search-config? true}' -w"; + types_or = [ "clojure" "clojurescript" "edn" ]; + }; - dialyzer = { - name = "dialyzer"; - description = "Runs a static code analysis using Dialyzer"; - entry = "${pkgs.elixir}/bin/mix dialyzer"; - files = "\\.exs?$"; - }; - - crystal = { - name = "crystal"; - description = "A tool that automatically formats Crystal source code"; - entry = "${tools.crystal}/bin/crystal tool format"; - files = "\\.cr$"; - }; - - lychee = { - name = "lychee"; - description = "A fast, async, stream-based link checker that finds broken hyperlinks and mail adresses inside Markdown, HTML, reStructuredText, or any other text file or website."; - entry = - let - cmdArgs = - mkCmdArgs - (with settings.lychee; [ - [ (configPath != "") " --config ${configPath}" ] - ]); - in - "${pkgs.lychee}/bin/lychee${cmdArgs} ${settings.lychee.flags}"; - types = [ "text" ]; - }; - - annex = { - name = "annex"; - description = "Runs the git-annex hook for large file support"; - entry = "${tools.git-annex}/bin/git-annex pre-commit"; - }; - - golangci-lint = { - name = "golangci-lint"; - description = "Fast linters runner for Go."; - entry = - let - script = pkgs.writeShellScript "precommit-golangci-lint" '' - set -e - for dir in $(echo "$@" | xargs -n1 dirname | sort -u); do - ${tools.golangci-lint}/bin/golangci-lint run ./"$dir" - done - ''; - in - builtins.toString script; - files = "\\.go$"; - # to avoid multiple invocations of the same directory input, provide - # all file names in a single run. - require_serial = true; - }; - - conform = { - name = "conform enforce"; - description = "Policy enforcement for commits."; - entry = "${tools.conform}/bin/conform enforce --commit-msg-file"; - stages = [ "commit-msg" ]; - }; - - typstfmt = { - name = "typstfmt"; - description = "format typst"; - entry = "${tools.typst-fmt}/bin/typst-fmt"; - files = "\\.typ$"; - }; - - juliaformatter = { - description = "Run JuliaFormatter.jl against Julia source files"; - files = "\\.jl$"; - entry = '' - ${tools.julia-bin}/bin/julia -e ' - using Pkg - Pkg.activate(".") - using JuliaFormatter - format(ARGS) - out = Cmd(`git diff --name-only`) |> read |> String - if out == "" - exit(0) - else - @error "Some files have been formatted !!!" - write(stdout, out) - exit(1) - end' - ''; - }; }; }