diff --git a/plugins/default.nix b/plugins/default.nix index 3739e462..1810bd3d 100644 --- a/plugins/default.nix +++ b/plugins/default.nix @@ -45,6 +45,7 @@ ./languages/julia/julia-cell.nix ./languages/lean.nix ./languages/ledger.nix + ./languages/lint.nix ./languages/markdown-preview.nix ./languages/nix.nix ./languages/nvim-jdtls.nix diff --git a/plugins/languages/lint.nix b/plugins/languages/lint.nix new file mode 100644 index 00000000..ed646c1d --- /dev/null +++ b/plugins/languages/lint.nix @@ -0,0 +1,302 @@ +{ + lib, + helpers, + config, + pkgs, + ... +}: +with lib; let + cfg = config.plugins.lint; + + linterOptions = with types; { + cmd = { + type = str; + description = "The command to call the linter"; + example = "linter_cmd"; + mandatory = true; + }; + + stdin = { + type = bool; + description = '' + Whether this parser supports content input via stdin. + In that case the filename is automatically added to the arguments. + + Default: `true` + ''; + example = false; + }; + + append_fname = { + type = bool; + description = '' + Automatically append the file name to `args` if `stdin = false` + Whether this parser supports content input via stdin. + In that case the filename is automatically added to the arguments. + + Default: `true` + ''; + example = false; + }; + + args = { + type = listOf (either str helpers.rawType); + description = '' + List of arguments. + Can contain functions with zero arguments that will be evaluated once the linter is used. + + Default: `[]` + ''; + }; + + stream = { + type = enum ["stdout" "stderr" "both"]; + description = '' + configure the stream to which the linter outputs the linting result. + + Default: `"stdout"` + ''; + example = "stderr"; + }; + + ignore_exitcode = { + type = bool; + description = '' + Whether the linter exiting with a code !=0 should be considered normal. + + Default: `false` + ''; + example = true; + }; + + env = { + type = attrsOf str; + description = '' + Custom environment table to use with the external process. + Note that this replaces the **entire** environment, it is not additive. + ''; + example = { + FOO = "bar"; + }; + }; + + parser = { + type = str; + description = "The code for your parser function."; + example = '' + require('lint.parser').from_pattern(pattern, groups, severity_map, defaults, opts) + ''; + apply = s: helpers.ifNonNull' s (helpers.mkRaw s); + mandatory = true; + }; + }; + + mkLinterOpts = noDefaults: + types.submodule { + freeformType = types.attrs; + + options = + mapAttrs + ( + optionName: ( + { + mandatory ? false, + apply ? x: x, + example ? null, + type, + description, + }: + mkOption ( + { + inherit + apply + description + example + ; + } + // ( + if noDefaults && mandatory + # Make this option mandatory + then { + inherit type; + } + # make it optional + else { + type = types.nullOr type; + default = null; + } + ) + ) + ) + ) + linterOptions; + }; +in { + options.plugins.lint = + helpers.extraOptionsOptions + // { + enable = mkEnableOption "nvim-lint"; + + package = helpers.mkPackageOption "nvim-lint" pkgs.vimPlugins.nvim-lint; + + lintersByFt = mkOption { + type = with types; attrsOf (listOf str); + default = {}; + description = '' + Configure the linters you want to run per file type. + + Default: + ```nix + { + text = ["vale"]; + json = ["jsonlint"]; + markdown = ["vale"]; + rst = ["vale"]; + ruby = ["ruby"]; + janet = ["janet"]; + inko = ["inko"]; + clojure = ["clj-kondo"]; + dockerfile = ["hadolint"]; + terraform = ["tflint"]; + } + ``` + ''; + example = { + markdown = ["vale"]; + }; + }; + + linters = mkOption { + type = with types; + attrsOf + (mkLinterOpts false); + default = {}; + description = '' + Customize the existing linters by overriding some of their properties. + ''; + example = { + phpcs.args = [ + "-q" + "--report=json" + "-" + ]; + }; + }; + + customLinters = mkOption { + type = with types; + attrsOf + ( + either + str + (mkLinterOpts true) + ); + default = {}; + description = '' + Configure the linters you want to run per file type. + It can be both an attrs or a string containing the lua code that returns the appropriate + table. + ''; + example = { + }; + }; + + autoCmd = let + defaultEvent = "BufWritePost"; + defaultCallback = helpers.mkRaw '' + function() + require('lint').try_lint() + end + ''; + in + mkOption { + type = with types; + nullOr (submodule { + options = + helpers.autocmd.autoCmdOptions + // { + event = mkOption { + type = with types; + nullOr + ( + either + str + (listOf str) + ); + default = defaultEvent; + description = "The event or events that should trigger linting."; + }; + + callback = mkOption { + type = with types; + nullOr (either str helpers.rawType); + default = defaultCallback; + description = "What action to perform for linting"; + }; + }; + }); + description = '' + The configuration for the linting autocommand. + You can disable it by setting this option to `null`. + ''; + default = { + event = defaultEvent; + callback = defaultCallback; + }; + }; + }; + + config = mkIf cfg.enable { + extraPlugins = [cfg.package]; + + extraConfigLua = + '' + __lint = require('lint') + __lint.linters_by_ft = ${helpers.toLuaObject cfg.lintersByFt} + '' + + ( + optionalString + (cfg.linters != {}) + ( + concatLines + ( + flatten + ( + mapAttrsToList + ( + linter: linterConfig: + mapAttrsToList + ( + propName: propValue: + optionalString + (propValue != null) + "__lint.linters.${linter}.${propName} = ${helpers.toLuaObject propValue}" + ) + linterConfig + ) + cfg.linters + ) + ) + ) + ) + + ( + optionalString + (cfg.customLinters != {}) + ( + concatLines + ( + mapAttrsToList + (customLinter: linterConfig: let + linterConfig' = + if isString linterConfig + then helpers.mkRaw linterConfig + else linterConfig; + in "__lint.linters.${customLinter} = ${helpers.toLuaObject linterConfig'}") + cfg.customLinters + ) + ) + ); + + # autoCmd = optional (cfg.autoCmd != null) cfg.autoCmd; + }; +} diff --git a/tests/test-sources/plugins/languages/lint.nix b/tests/test-sources/plugins/languages/lint.nix new file mode 100644 index 00000000..d43bc578 --- /dev/null +++ b/tests/test-sources/plugins/languages/lint.nix @@ -0,0 +1,69 @@ +{ + empty = { + plugins.lint.enable = true; + }; + + example = { + plugins.lint = { + enable = true; + + lintersByFt = { + text = ["vale"]; + json = ["jsonlint"]; + markdown = ["vale"]; + rst = ["vale"]; + ruby = ["ruby"]; + janet = ["janet"]; + inko = ["inko"]; + clojure = ["clj-kondo"]; + dockerfile = ["hadolint"]; + terraform = ["tflint"]; + }; + linters = { + phpcs.args = [ + "-q" + "--report=json" + "-" + ]; + }; + customLinters = { + foo = { + cmd = "foo_cmd"; + stdin = true; + append_fname = false; + args = []; + stream = "stderr"; + ignore_exitcode = false; + env = { + FOO = "bar"; + }; + parser = '' + require('lint.parser').from_pattern(pattern, groups, severity_map, defaults, opts) + ''; + }; + foo2 = { + cmd = "foo2_cmd"; + parser = '' + require('lint.parser').from_pattern(pattern, groups, severity_map, defaults, opts) + ''; + }; + bar.__raw = '' + function() + return { + cmd = "foo_cmd", + stdin = true, + append_fname = false, + args = {}, + stream = "stderr", + ignore_exitcode = false, + env = { + ["FOO"] = "bar", + }, + parser = require('lint.parser').from_pattern(pattern, groups, severity_map, defaults, opts), + } + end + ''; + }; + }; + }; +}