I don’t know if nil’s pack code action take into its scope only the nearby attributes, or can also be set to work on the selection only, or if it takes the whole file
When I started using statix in my code editor, at first I though that repeated_keys warning is useful. But when I went to my nixos config and tried to fix these warnings, I quickly realized, that I actually prefer repeated keys.
Here are the reasons:
Tbh, it’s more readable, having that prefix gives more context and makes it easier to understand what the option does. The alternative is to either keep that context in your head, or look up using lsp features (like vscode breadcrumbs), that’s why having that context right there in the same line is imo easier to read.
Inside files I try to place things in a way so that logically connected parts are near each other. Being forced to use grouped keys would make it impossible, as I would be forced to group keys that have a common prefix together. Eg., I have these lines in my configuration.nix:
Grouped keys would require me to split these two lines into two separated sections of the file, as I also have virtualisation.docker and environment.systemPackages in that same file.
I try to split my config into files, but my configuration.nix is still pretty big littered with some random tweaks. Enforcing grouped keys would increase indentation level by up to 5 in some lines, maybe more.
I’m using treesitter-context in neovim. Here’s an example of how it looks like:
If I were to enforce grouped keys, this would result in home-manager.users.nix being split into 3 separate lines, and programs.firefox into 2 separate lines. This would waste vertical space in my editor. Though, that’s a bit specific problem, it’s not a problem in vscode where breadcrumbs are always in single line.
I do have a plan to switch to denful and put each logically-connected unit into separate file, in which case I might reconsider turning on repeated_keys warning. I think most of the issues would get alleviated this way.
Right, I forgot to mention that I also have home-manager.sharedModules, home-manager.users.root, and home-manager.users.nix.programs.ssh in that same file.
This is actually one of the reason to group, in my opinion
As soon as the keys’ names are longer than an indentation (nixfmt: 2 characters), grouping is more horizontal-space efficient (which also benefits parsing, but this is minor imo)
I very much agree with @MrQubo’s points. Repetition is actually good. It gives you valuable flexibility in where settings go in the file, and reduces the amount of structural complexity to keep track of when reading, as well as reducing the quantity of indentation overall.
In this case ddd = 222; got shifted right by 2 characters, and more horizontal space got used.
And that’s not made up. The increased indentation by 5 levels I was talking about is exactly like that. Grouping increases outer indentation levels, but the actual lines that get indented aren’t getting simplified by grouping at all.
And except for collocating multiple related attributes, I never repeat nor mix repeating / grouping, and especially not for space efficiency, since it requires actively thinking about how much space would change
In my case it’s very common. I always group when the keys are already logically connected and together in the file. Often, keys that are more nested are more likely to be logically connected. So it’s often that I repeated keys at the top-level of attrset, but I exclusively group at more nested levels.
I’ll take this file from my configuration as an example. It’s for configuring code completion in neovim. Imo, it’s concise enough to be not worth splitting into multiple files.
completions.nix
{ lib, ... }:
# TODO: Configure keybinds.
# The first click of Ctrl+n should complete the first entry on the list, not second. Like Ctrl+y, but without closing completion list.
# Bonus: The first click of Ctrl+p should complete the last entry on the list.
{
programs.nixvim = {
plugins.cmp = {
enable = true;
settings = {
completion.completeopt = "menu,menuone,noinsert"; # Preselect first entry
sources = [
{ name = "git"; }
{ name = "nvim_lsp"; }
{ name = "luasnip"; }
{ name = "path"; }
{ name = "buffer"; }
];
mapping.__raw = "cmp.mapping.preset.insert()";
snippet.expand = ''
function(args)
require('luasnip').lsp_expand(args.body)
end
'';
};
cmdline = lib.fix (self: {
"/" = {
mapping.__raw = "cmp.mapping.preset.cmdline()";
sources = [
{ name = "buffer"; }
];
};
"?" = self."/"; # Not sure if that's needed?
":" = {
mapping.__raw = "cmp.mapping.preset.cmdline()";
sources = [
{ name = "path"; }
{ name = "cmdline"; }
];
};
});
};
plugins = {
cmp-buffer.enable = true;
cmp-cmdline.enable = true;
cmp-git.enable = true;
cmp-nvim-lsp.enable = true;
cmp-path.enable = true;
cmp_luasnip.enable = true;
};
plugins.lspkind.enable = true;
plugins.lspkind.settings = {
enable = true;
cmp = {
menu = {
nvim_lsp = "[LSP]";
nvim_lua = "[api]";
path = "[path]";
luasnip = "[snip]";
buffer = "[buffer]";
};
};
symbolMap = {
Array = "[]";
Boolean = "";
Calendar = "";
Class = "";
Codeium = "";
Color = "";
Constant = "";
Constructor = "";
Copilot = "";
Enum = "";
EnumMember = "";
Event = "";
Field = "";
File = "";
Folder = "";
Function = "";
Interface = "";
Keyword = "";
Label = "";
Method = "";
Module = "";
Namespace = "";
Null = "";
Number = "";
Object = "";
Operator = "";
Package = "";
Property = "";
Reference = "";
Snippet = "";
String = "";
Struct = "";
TabNine = "";
Table = "";
Tag = "";
Text = "";
TypeParameter = "";
Unit = "";
Value = "";
Variable = "";
Watch = "";
};
};
};
}
statix reports a warning here about repeated keys plugins. Noticed that in plugins.cmp I exclusively grouped where possible. If I were to pack plugins that would cause entire plugins.cmp definition to get intedented by one more level.
Again, packing would increase indentation level of plugins.lspkind.settings. One more thing, when actually reading that code, I don’t think my brain has to actually read plugins.lspkind twice, when the keys are repeated one after another like that, you can just pattern match in you head that plugins.lspkind is the common prefix (that only works if the keys aren’t too long).
Imo, while grouping can often be a good choice, some common sense should still be applied about when repeating is okay.
Neutral here, in the sense of finding that there are plenty of cases where one or the other of these will be the Much Better Choice for a given chunk of code.
Normal coder DRY instinct probably pushes most of us towards writing our prefixes only once, and this is still my default, but especially for NixOS module config sets I think this can lead to separating things by the wrong concerns, cutting along the X axis when you should have done it by Y instead. (Anyone know if there’s a name for this antipattern? Seems like there should be.)
There are plenty of cases where it is way less readable to put all the networking. stuff in one place, and all the services. stuff off somewhere else, and what you should really be doing to have good locality for the reader is stripe them so the related bits are together. Yes, you can also partition things off by file instead, but sometimes the configs are very very small and that gets a bit silly.
DRYing it up also hurts context for readability when you’re jumping into the middle of a big attrset and trying to figure out what’s going on. Editor tooling can help, but isn’t it easier to just have the entire path to the option you’re looking at on the same line? Etc, etc.
At least for me, taking it case-by-case absolutely makes sense.
To address question of severity, for me the best would be to implement some heuristics that would result in very small number of false positives. And in that case emit as warning.
One idea for heuristic I have in mind is to warn, if there are at least 4 repeated keys, without empty lines in between them (but non-empty are fine), and those 4 keys (including values) span no more than 15 lines.