mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-03-08 01:24:09 +01:00
lib/modules: add suggestions to invalid option name errors (#442263)
This commit is contained in:
commit
58b187378d
8 changed files with 235 additions and 1 deletions
|
|
@ -12,6 +12,7 @@ let
|
|||
concatMap
|
||||
concatStringsSep
|
||||
elem
|
||||
elemAt
|
||||
filter
|
||||
foldl'
|
||||
functionArgs
|
||||
|
|
@ -20,12 +21,14 @@ let
|
|||
head
|
||||
id
|
||||
imap1
|
||||
init
|
||||
isAttrs
|
||||
isBool
|
||||
isFunction
|
||||
oldestSupportedReleaseIsAtLeast
|
||||
isList
|
||||
isString
|
||||
last
|
||||
length
|
||||
mapAttrs
|
||||
mapAttrsToList
|
||||
|
|
@ -34,12 +37,16 @@ let
|
|||
optional
|
||||
optionalAttrs
|
||||
optionalString
|
||||
pipe
|
||||
recursiveUpdate
|
||||
remove
|
||||
reverseList
|
||||
sort
|
||||
sortOn
|
||||
seq
|
||||
setAttrByPath
|
||||
substring
|
||||
take
|
||||
throwIfNot
|
||||
trace
|
||||
typeOf
|
||||
|
|
@ -60,6 +67,8 @@ let
|
|||
;
|
||||
inherit (lib.strings)
|
||||
isConvertibleWithToString
|
||||
levenshtein
|
||||
levenshteinAtMost
|
||||
;
|
||||
|
||||
showDeclPrefix =
|
||||
|
|
@ -304,8 +313,41 @@ let
|
|||
addErrorContext
|
||||
"while evaluating the error message for definitions for `${optText}', which is an option that does not exist"
|
||||
(addErrorContext "while evaluating a definition from `${firstDef.file}'" (showDefs [ firstDef ]));
|
||||
|
||||
# absInvalidOptionParent is absolute; other variables are relative to the submodule prefix
|
||||
absInvalidOptionParent = init (prefix ++ firstDef.prefix);
|
||||
invalidOptionParent = init firstDef.prefix;
|
||||
siblingOptionNames = attrNames (attrByPath invalidOptionParent { } options);
|
||||
candidateNames =
|
||||
if invalidOptionParent == [ ] then remove "_module" siblingOptionNames else siblingOptionNames;
|
||||
invalidOptionName = last firstDef.prefix;
|
||||
# For small option sets, check all; for large sets, only check distance ≤ 2
|
||||
suggestions =
|
||||
if length candidateNames < 100 then
|
||||
pipe candidateNames [
|
||||
(sortOn (levenshtein invalidOptionName))
|
||||
(take 3)
|
||||
]
|
||||
else
|
||||
pipe candidateNames [
|
||||
# levenshteinAtMost is only fast for distance ≤ 2
|
||||
(filter (levenshteinAtMost 2 invalidOptionName))
|
||||
(sortOn (levenshtein invalidOptionName))
|
||||
(take 3)
|
||||
];
|
||||
suggestion =
|
||||
if suggestions == [ ] then
|
||||
""
|
||||
else if length suggestions == 1 then
|
||||
"\n\nDid you mean `${showOption (absInvalidOptionParent ++ [ (head suggestions) ])}'?"
|
||||
else
|
||||
"\n\nDid you mean ${
|
||||
concatStringsSep ", " (
|
||||
map (s: "`${showOption (absInvalidOptionParent ++ [ s ])}'") (init suggestions)
|
||||
)
|
||||
} or `${showOption (absInvalidOptionParent ++ [ (last suggestions) ])}'?";
|
||||
in
|
||||
"The option `${optText}' does not exist. Definition values:${defText}";
|
||||
"The option `${optText}' does not exist. Definition values:${defText}${suggestion}";
|
||||
in
|
||||
if
|
||||
attrNames options == [ "_module" ]
|
||||
|
|
|
|||
|
|
@ -870,6 +870,14 @@ checkConfigError 'A definition for option .* is not of type .*' config.addCheckF
|
|||
checkConfigOutput '^true$' config.result ./v2-check-coherence.nix
|
||||
|
||||
|
||||
# Option name suggestions
|
||||
checkConfigError 'Did you mean .set\.enable.\?' config.set ./error-typo-nested.nix
|
||||
checkConfigError 'Did you mean .set.\?' config ./error-typo-outside-with-nested.nix
|
||||
checkConfigError 'Did you mean .bar., .baz. or .foo.\?' config ./error-typo-multiple-suggestions.nix
|
||||
checkConfigError 'Did you mean .enable., .ebe. or .enabled.\?' config ./error-typo-large-attrset.nix
|
||||
checkConfigError 'Did you mean .services\.myservice\.port. or .services\.myservice\.enable.\?' config.services.myservice ./error-typo-submodule.nix
|
||||
checkConfigError 'Did you mean .services\.nginx\.virtualHosts\."example\.com"\.ssl\.certificate. or .services\.nginx\.virtualHosts\."example\.com"\.ssl\.certificateKey.\?' config.services.nginx.virtualHosts.\"example.com\" ./error-typo-deeply-nested.nix
|
||||
|
||||
cat <<EOF
|
||||
====== module tests ======
|
||||
$pass Pass
|
||||
|
|
|
|||
42
lib/tests/modules/error-typo-deeply-nested.nix
Normal file
42
lib/tests/modules/error-typo-deeply-nested.nix
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
options.services = {
|
||||
nginx = {
|
||||
enable = lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
virtualHosts = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
enableSSL = lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
ssl = {
|
||||
certificate = lib.mkOption {
|
||||
default = "";
|
||||
type = lib.types.str;
|
||||
};
|
||||
certificateKey = lib.mkOption {
|
||||
default = "";
|
||||
type = lib.types.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
services.nginx.virtualHosts."example.com" = {
|
||||
# Typo: "certficate" instead of "certificate" (nested within submodule)
|
||||
ssl.certficate = "/path/to/cert";
|
||||
};
|
||||
};
|
||||
}
|
||||
56
lib/tests/modules/error-typo-large-attrset.nix
Normal file
56
lib/tests/modules/error-typo-large-attrset.nix
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{ lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkOption concatMapAttrs;
|
||||
|
||||
ten = {
|
||||
a = null;
|
||||
b = null;
|
||||
c = null;
|
||||
d = null;
|
||||
e = null;
|
||||
f = null;
|
||||
g = null;
|
||||
h = null;
|
||||
i = null;
|
||||
j = null;
|
||||
};
|
||||
|
||||
# Generate 1000 options (10 * 10 * 10)
|
||||
generatedOptions = concatMapAttrs (
|
||||
k1: _:
|
||||
concatMapAttrs (
|
||||
k2: _:
|
||||
concatMapAttrs (k3: _: {
|
||||
"${k1}${k2}${k3}" = mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
}) ten
|
||||
) ten
|
||||
) ten;
|
||||
|
||||
# Add some sensible options that are close to our typo
|
||||
sensibleOptions = {
|
||||
enable = mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
enabled = mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
disable = mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options = generatedOptions // sensibleOptions;
|
||||
|
||||
config = {
|
||||
# Typo: "enble" is distance 1 from "enable"
|
||||
enble = true;
|
||||
};
|
||||
}
|
||||
22
lib/tests/modules/error-typo-multiple-suggestions.nix
Normal file
22
lib/tests/modules/error-typo-multiple-suggestions.nix
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
options.foo = lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
|
||||
options.bar = lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
|
||||
options.baz = lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
|
||||
config = {
|
||||
far = true;
|
||||
};
|
||||
}
|
||||
18
lib/tests/modules/error-typo-nested.nix
Normal file
18
lib/tests/modules/error-typo-nested.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
options.set = {
|
||||
enable = lib.mkOption {
|
||||
default = false;
|
||||
example = true;
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Some descriptive text
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
set.ena = true;
|
||||
};
|
||||
}
|
||||
18
lib/tests/modules/error-typo-outside-with-nested.nix
Normal file
18
lib/tests/modules/error-typo-outside-with-nested.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
options.set = {
|
||||
enable = lib.mkOption {
|
||||
default = false;
|
||||
example = true;
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Some descriptive text
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
sea.enable = true;
|
||||
};
|
||||
}
|
||||
28
lib/tests/modules/error-typo-submodule.nix
Normal file
28
lib/tests/modules/error-typo-submodule.nix
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
options.services = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
port = lib.mkOption {
|
||||
default = 8080;
|
||||
type = lib.types.int;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
};
|
||||
|
||||
config = {
|
||||
services.myservice = {
|
||||
# Typo: "prot" instead of "port"
|
||||
prot = 9000;
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue