From 4cd3707e00ef28b903f8764ae688f8f4e25bf593 Mon Sep 17 00:00:00 2001 From: traxys Date: Sun, 27 Aug 2023 20:49:23 +0200 Subject: [PATCH] plugin/efmls-configs: init + tests (#542) --- helpers/default.nix | 2 + helpers/efmls-configs/default.nix | 37 ++ helpers/efmls-configs/extract.py | 25 ++ plugins/lsp/language-servers/default.nix | 1 + .../language-servers/efmls-configs-tools.json | 330 ++++++++++++++++++ .../lsp/language-servers/efmls-configs.nix | 198 +++++++++++ .../plugins/lsp/efmls-configs.nix | 37 ++ 7 files changed, 630 insertions(+) create mode 100644 helpers/efmls-configs/default.nix create mode 100755 helpers/efmls-configs/extract.py create mode 100644 plugins/lsp/language-servers/efmls-configs-tools.json create mode 100644 plugins/lsp/language-servers/efmls-configs.nix create mode 100644 tests/test-sources/plugins/lsp/efmls-configs.nix diff --git a/helpers/default.nix b/helpers/default.nix index 628d3117..c418916c 100644 --- a/helpers/default.nix +++ b/helpers/default.nix @@ -1,5 +1,6 @@ pkgs: rec { rust-analyzer-config = pkgs.callPackage ./rust-analyzer {}; + efmls-configs-tools = pkgs.callPackage ./efmls-configs {}; autogenerated-configs = pkgs.callPackage ({stdenv}: stdenv.mkDerivation { pname = "autogenerated-configs"; @@ -11,6 +12,7 @@ pkgs: rec { installPhase = '' mkdir -p $out cp ${rust-analyzer-config}/share/* $out + cp ${efmls-configs-tools}/share/* $out ''; }) {}; } diff --git a/helpers/efmls-configs/default.nix b/helpers/efmls-configs/default.nix new file mode 100644 index 00000000..dd12d812 --- /dev/null +++ b/helpers/efmls-configs/default.nix @@ -0,0 +1,37 @@ +{ + stdenv, + python3, + vimPlugins, +}: let + extract = stdenv.mkDerivation { + pname = "extract_efmls_tools"; + version = "1"; + + src = ./extract.py; + + dontUnpack = true; + dontBuild = true; + + buildInputs = [python3]; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/extract_efmls_tools.py + ''; + }; +in + stdenv.mkDerivation { + pname = "efmls-configs-tools"; + inherit (vimPlugins.efmls-configs-nvim) version src; + + nativeBuildInputs = [extract]; + + buildPhase = '' + extract_efmls_tools.py ./lua/efmls-configs > efmls-configs-tools.json + ''; + + installPhase = '' + mkdir -p $out/share + cp efmls-configs-tools.json $out/share + ''; + } diff --git a/helpers/efmls-configs/extract.py b/helpers/efmls-configs/extract.py new file mode 100755 index 00000000..d645f0e8 --- /dev/null +++ b/helpers/efmls-configs/extract.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +import sys +import os +import json + +tool_path = sys.argv[1] + +tools = { + "linters": {}, + "formatters": {}, +} + +for kind in ["linters", "formatters"]: + for file in os.listdir(tool_path + "/" + kind): + tool_name = file.removesuffix(".lua") + languages = [] + with open(tool_path + "/" + kind + "/" + file) as f: + for line in f.readlines(): + if line.startswith("-- languages:"): + languages = line.split(":")[1].strip().split(",") + break + tools[kind][tool_name] = languages + +print(json.dumps(tools, indent=4)) diff --git a/plugins/lsp/language-servers/default.nix b/plugins/lsp/language-servers/default.nix index 292433e7..dd05c8ba 100644 --- a/plugins/lsp/language-servers/default.nix +++ b/plugins/lsp/language-servers/default.nix @@ -537,5 +537,6 @@ in { ./ccls.nix ./pylsp.nix ./svelte.nix + ./efmls-configs.nix ]; } diff --git a/plugins/lsp/language-servers/efmls-configs-tools.json b/plugins/lsp/language-servers/efmls-configs-tools.json new file mode 100644 index 00000000..e5a0bdb6 --- /dev/null +++ b/plugins/lsp/language-servers/efmls-configs-tools.json @@ -0,0 +1,330 @@ +{ + "linters": { + "alex": [ + "misc" + ], + "ameba": [ + "crystal" + ], + "ansible_lint": [ + "yaml" + ], + "bashate": [ + "bash" + ], + "clang_tidy": [ + "c", + "c++" + ], + "clazy": [ + "c++" + ], + "clj_kondo": [ + "clojure" + ], + "cppcheck": [ + "c", + "c++" + ], + "cpplint": [ + "c", + "c++" + ], + "dartanalyzer": [ + "dart" + ], + "debride": [ + "ruby" + ], + "djlint": [ + "python", + "go", + "php", + "html" + ], + "dmd": [ + "d" + ], + "eslint": [ + "javascript", + "typescript" + ], + "eslint_d": [ + "javascript", + "typescript" + ], + "fecs": [ + "javascript" + ], + "fish": [ + "fish" + ], + "flake8": [ + "python" + ], + "flawfinder": [ + "c" + ], + "gcc": [ + "c", + "c++" + ], + "go_revive": [ + "go" + ], + "golangci_lint": [ + "go" + ], + "golint": [ + "go" + ], + "hadolint": [ + "docker" + ], + "joker": [ + "clojure" + ], + "js_standard": [ + "javascript" + ], + "languagetool": [ + "misc" + ], + "luacheck": [ + "lua" + ], + "mcs": [ + "c#" + ], + "phan": [ + "php" + ], + "php": [ + "php" + ], + "phpcs": [ + "php" + ], + "phpstan": [ + "php" + ], + "proselint": [ + "misc" + ], + "psalm": [ + "php" + ], + "pylint": [ + "python" + ], + "redpen": [ + "misc" + ], + "reek": [ + "ruby" + ], + "rubocop": [ + "ruby" + ], + "shellcheck": [ + "sh", + "bash" + ], + "slither": [ + "solidity" + ], + "solhint": [ + "solidity" + ], + "sorbet": [ + "ruby" + ], + "staticcheck": [ + "go" + ], + "statix": [ + "nix" + ], + "stylelint": [ + "css", + "scss", + "sass", + "less" + ], + "textlint": [ + "misc" + ], + "vale": [ + "misc" + ], + "vint": [ + "vim" + ], + "vulture": [ + "python" + ], + "write_good": [ + "misc" + ], + "xo": [ + "javascript", + "typescript" + ], + "yamllint": [ + "yaml" + ] + }, + "formatters": { + "astyle": [ + "c", + "c++" + ], + "autopep8": [ + "python" + ], + "black": [ + "python" + ], + "cbfmt": [ + "markdown" + ], + "clang_format": [ + "c", + "c++" + ], + "clang_tidy": [ + "c", + "c++" + ], + "dartfmt": [ + "dart" + ], + "dfmt": [ + "d" + ], + "dotnet_format": [ + "c#" + ], + "dprint": [ + "javascript", + "typescript", + "json", + "markdown", + "toml", + "rust", + "roslyn" + ], + "eslint": [ + "javascript", + "typescript" + ], + "eslint_d": [ + "javascript", + "typescript" + ], + "fecs": [ + "javascript", + "typescript", + "css", + "html" + ], + "fish_indent": [ + "fish" + ], + "forge_fmt": [ + "solidity" + ], + "gofmt": [ + "go" + ], + "goimports": [ + "go" + ], + "golines": [ + "go" + ], + "joker": [ + "clojure" + ], + "js_standard": [ + "javascript" + ], + "lua_format": [ + "lua" + ], + "nixfmt": [ + "nix" + ], + "php_cs_fixer": [ + "php" + ], + "phpcbf": [ + "php" + ], + "pint": [ + "php" + ], + "prettier": [ + "javascript", + "typescript", + "css", + "scss", + "sass", + "less", + "html", + "json" + ], + "prettier_d": [ + "javascript", + "typescript", + "css", + "scss", + "sass", + "less", + "html", + "json" + ], + "prettier_eslint": [ + "javascript", + "typescript" + ], + "prettier_standard": [ + "javascript", + "typescript" + ], + "rustfmt": [ + "rust" + ], + "shfmt": [ + "sh", + "bash" + ], + "smlfmt": [ + "sml" + ], + "stylua": [ + "lua" + ], + "terraform_fmt": [ + "terraform" + ], + "uncrustify": [ + "c", + "c++", + "c#", + "d", + "java", + "objective-c", + "objective-c++", + "pawn", + "vala" + ], + "xo": [ + "javascript", + "typescript" + ], + "yapf": [ + "python" + ] + } +} diff --git a/plugins/lsp/language-servers/efmls-configs.nix b/plugins/lsp/language-servers/efmls-configs.nix new file mode 100644 index 00000000..0bc748d5 --- /dev/null +++ b/plugins/lsp/language-servers/efmls-configs.nix @@ -0,0 +1,198 @@ +{ + config, + pkgs, + lib, + ... +}: +with lib; let + helpers = import ../../helpers.nix {inherit lib;}; + + tools = trivial.importJSON ./efmls-configs-tools.json; + inherit (tools) linters formatters; + + searchLanguages = tools: (lists.unique (builtins.concatLists (builtins.attrValues tools))); + languages = + lists.filter + (v: v != "misc") (lists.unique ((searchLanguages linters) ++ (searchLanguages formatters))); +in { + options.plugins.efmls-configs = { + enable = mkEnableOption "efmls-configs, premade configurations for efm-langserver"; + + package = helpers.mkPackageOption "efmls-configs-nvim" pkgs.vimPlugins.efmls-configs-nvim; + + /* + Users can set the options as follows: + + { + c = { + linter = "cppcheck"; + formatter = ["clang-format" "uncrustify"]; + }; + go = { + linter = ["djlint" "golangci_lint"]; + }; + } + */ + setup = let + languageTools = lang: tools: + builtins.attrNames (attrsets.filterAttrs (_: lists.any (e: e == lang)) tools); + + miscLinters = languageTools "misc" linters; + miscFormatters = languageTools "misc" formatters; + + mkChooseOption = lang: kind: possible: let + toolType = with types; either (enum possible) helpers.rawType; + in + mkOption { + type = with types; either toolType (listOf toolType); + default = []; + description = "${kind} tools for ${lang}"; + }; + in + mkOption { + type = types.submodule { + freeformType = types.attrs; + + options = + (builtins.listToAttrs (builtins.map (lang: let + langTools = languageTools lang; + in { + name = lang; + value = { + linter = mkChooseOption lang "linter" ((langTools linters) ++ miscLinters); + formatter = mkChooseOption lang "formatter" ((langTools formatters) ++ miscFormatters); + }; + }) + languages)) + // { + all = { + linter = mkChooseOption "all languages" "linter" miscLinters; + formatter = mkChooseOption "all languages" "formatter" miscFormatters; + }; + }; + }; + description = "Configuration for each filetype. Use `all` to match any filetype."; + default = {}; + }; + }; + config = let + cfg = config.plugins.efmls-configs; + + # Mapping of tool name to the nixpkgs package (if any) + toolPkgs = { + inherit + (pkgs) + ameba + astyle + bashate + black + cbfmt + clazy + cppcheck + cpplint + dmd + dprint + fish + flawfinder + gcc + golines + golint + hadolint + joker + languagetool + nixfmt + php + prettierd + proselint + pylint + rubocop + rustfmt + shellcheck + shfmt + smlfmt + statix + stylua + uncrustify + vale + yamllint + yapf + ; + inherit (pkgs.python3.pkgs) autopep8 flake8 vulture; + inherit (pkgs.nodePackages) eslint eslint_d prettier alex stylelint textlint write-good; + inherit (pkgs.phpPackages) phpcbf phan phpcs phpstan psalm; + inherit (pkgs.luaPackages) luacheck; + ansible_lint = pkgs.ansible-lint; + clang_format = pkgs.clang-tools; + clang_tidy = pkgs.clang-tools; + clj_kondo = pkgs.clj-kondo; + dartfmt = pkgs.dart; + dotnet_format = pkgs.dotnet-runtime; + fish_indent = pkgs.fish; + gofmt = pkgs.go; + goimports = pkgs.go-tools; + golangci_lint = pkgs.golangci-lint; + go_revive = pkgs.revive; + lua_format = pkgs.luaformatter; + mcs = pkgs.mono; + php_cs_fixer = pkgs.phpPackages.php-cs-fixer; + slither = pkgs.slither-analyzer; + staticcheck = pkgs.go-tools; + terraform_fmt = pkgs.terraform; + vint = pkgs.vim-vint; + }; + toolAsList = tools: + if builtins.isList tools + then tools + else [tools]; + + # Tools that have been selected by the user + tools = lists.unique (builtins.filter builtins.isString ( + builtins.concatLists ( + builtins.map ({ + linter ? [], + formatter ? [], + }: + (toolAsList linter) ++ (toolAsList formatter)) + (builtins.attrValues cfg.setup) + ) + )); + + pkgsToInstall = + builtins.map (v: toolPkgs.${v}) + (builtins.filter (v: builtins.hasAttr v toolPkgs) tools); + + mkToolOption = kind: opt: + builtins.map + (tool: + if builtins.isString tool + then helpers.mkRaw "require 'efmls-configs.${kind}.${tool}'" + else tool) + (toolAsList opt); + + setupOptions = + (builtins.mapAttrs ( + _: { + linter ? [], + formatter ? [], + }: + (mkToolOption "linters" linter) + ++ (mkToolOption "formatters" formatter) + ) + (attrsets.filterAttrs (v: _: v != "all") cfg.setup)) + // { + "=" = + (mkToolOption "linters" cfg.setup.all.linter) + ++ (mkToolOption "formatters" cfg.setup.all.formatter); + }; + in + mkIf cfg.enable { + extraPlugins = [cfg.package]; + + plugins.lsp.servers.efm = { + enable = true; + extraOptions.settings.languages = setupOptions; + }; + + extraPackages = [pkgs.efm-langserver] ++ pkgsToInstall; + }; +} diff --git a/tests/test-sources/plugins/lsp/efmls-configs.nix b/tests/test-sources/plugins/lsp/efmls-configs.nix new file mode 100644 index 00000000..7aec1d81 --- /dev/null +++ b/tests/test-sources/plugins/lsp/efmls-configs.nix @@ -0,0 +1,37 @@ +{ + empty = { + plugins.efmls-configs.enable = true; + }; + + example = { + extraConfigLuaPre = '' + local efm_fs = require('efmls-configs.fs') + local djlint_fmt = { + formatCommand = string.format('%s --reformat ''${INPUT} -', efm_fs.executable('djlint')), + formatStdin = true, + } + ''; + + plugins.efmls-configs = { + enable = true; + + setup = { + # Setup for all languages + all = { + linter = "vale"; + }; + + # Only accepts known tools, or raw strings + html = { + formatter = ["prettier" {__raw = "djlint_fmt";}]; + }; + + # Unknown filetype, accepts anything + htmldjango = { + formatter = [{__raw = "djlint_fmt";}]; + linter = "djlint"; + }; + }; + }; + }; +}