Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ functions.
* [pinact](programs/pinact.nix)
* [prettier](programs/prettier.nix)
* [protolint](programs/protolint.nix)
* [pyright](programs/pyright.nix)
* [qmlformat](programs/qmlformat.nix)
* [rstfmt](programs/rstfmt.nix)
* [rubocop](programs/rubocop.nix)
Expand Down
117 changes: 117 additions & 0 deletions programs/pyright.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
lib,
pkgs,
config,
...
}:
let
escapePath = lib.replaceStrings [ "/" "." ] [ "-" "" ];
in
{
meta.maintainers = [ "otavio" ];
Comment thread
otavio marked this conversation as resolved.
# Example contains store paths
Comment thread
otavio marked this conversation as resolved.
meta.skipExample = true;
Comment thread
otavio marked this conversation as resolved.

options.programs.pyright = {
enable = lib.mkEnableOption "pyright";
package = lib.mkPackageOption pkgs "pyright" { };
directories = lib.mkOption {
description = ''
Directories to run pyright in.

Pyright auto-discovers `pyrightconfig.json` or `pyproject.toml`
(`[tool.pyright]`) starting from the directory it is invoked in.
Use `extraPaths` in that config to add module search paths —
pyright does not read the `PYTHONPATH` environment variable.
'';
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
options = {
directory = lib.mkOption {
type = lib.types.str;
default = name;
description = ''
Directory to run pyright in, relative to the project root.
The empty string means the project root itself — no `cd`
is performed.
'';
};
options = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "--warnings" ];
description = "Options to pass to pyright";
};
modules = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "" ];
example = [
"mymodule"
"tests"
];
description = "Modules to check (passed as positional file/directory arguments)";
};
};
}
)
);
default = {
"" = { };
};
example = {
"" = {
modules = [
"mymodule"
"tests"
];
};
"subdir" = { };
};
};
};

config = lib.mkIf config.programs.pyright.enable {
settings.formatter = lib.mapAttrs' (
name: cfg:
let
nonEmptyModules = lib.filter (m: m != "") cfg.modules;
Comment thread
otavio marked this conversation as resolved.
cdLine = lib.optionalString (cfg.directory != "") "cd ${lib.escapeShellArg cfg.directory}";
Comment thread
otavio marked this conversation as resolved.
args = lib.escapeShellArgs (cfg.options ++ nonEmptyModules);
in
lib.nameValuePair "pyright${lib.optionalString (name != "") "-${escapePath name}"}" {
Comment thread
otavio marked this conversation as resolved.
command = pkgs.bash;
# Pyright resolves imports from a whole-module context, not per file,
# so the wrapper ignores the file path treefmt passes as `$0` and always
# invokes pyright on the configured `modules` (or the whole `directory`
# when `modules` is empty). With treefmt's batch-size limit this means
# pyright may be invoked more than once per directory on large file sets
# — each call redundantly type-checks the same modules.
#
# `treefmt --stdin` is not supported: pyright type-checks the on-disk
# modules regardless of what is piped in, so editor integrations should
# use pyright's language server directly. A clean opt-out will become
# possible once treefmt's Stdin Specification lands
# (numtide/treefmt#586).
options = [
"-eucx"
''
${cdLine}
exec ${lib.getExe config.programs.pyright.package} ${args}
''
];
includes = lib.concatMap (
Comment thread
otavio marked this conversation as resolved.
module:
let
prefix = lib.optional (cfg.directory != "") cfg.directory ++ lib.optional (module != "") module;
in
[
(lib.concatStringsSep "/" (prefix ++ [ "*.py" ]))
Comment thread
otavio marked this conversation as resolved.
(lib.concatStringsSep "/" (prefix ++ [ "*.pyi" ]))
]
) cfg.modules;
}
) config.programs.pyright.directories;
};
}