diff options
| author | nixpkgs-ci[bot] <190413589+nixpkgs-ci[bot]@users.noreply.github.com> | 2026-02-04 00:22:54 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-04 00:22:54 +0000 |
| commit | c0bf6b8395684ae1c0c66f0d0b2198371fbea935 (patch) | |
| tree | 4549e8fb3bcb6a2b57c26aad29f9706a1d62a5e4 /lib | |
| parent | ruff: 0.14.14 -> 0.15.0 (#486654) (diff) | |
| parent | python3Packages.{datatable,typesentry}: drop (#483826) (diff) | |
| download | nixpkgs-c0bf6b8395684ae1c0c66f0d0b2198371fbea935.tar.gz | |
Merge master into staging-nixos
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/modules.nix | 44 | ||||
| -rwxr-xr-x | lib/tests/modules.sh | 8 | ||||
| -rw-r--r-- | lib/tests/modules/error-typo-deeply-nested.nix | 42 | ||||
| -rw-r--r-- | lib/tests/modules/error-typo-large-attrset.nix | 56 | ||||
| -rw-r--r-- | lib/tests/modules/error-typo-multiple-suggestions.nix | 22 | ||||
| -rw-r--r-- | lib/tests/modules/error-typo-nested.nix | 18 | ||||
| -rw-r--r-- | lib/tests/modules/error-typo-outside-with-nested.nix | 18 | ||||
| -rw-r--r-- | lib/tests/modules/error-typo-submodule.nix | 28 |
8 files changed, 235 insertions, 1 deletions
diff --git a/lib/modules.nix b/lib/modules.nix index e545f9c9707d..6d430effcc89 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -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" ] diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index d148cd1839da..aa9d3443fb6a 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -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 diff --git a/lib/tests/modules/error-typo-deeply-nested.nix b/lib/tests/modules/error-typo-deeply-nested.nix new file mode 100644 index 000000000000..978bd088494d --- /dev/null +++ b/lib/tests/modules/error-typo-deeply-nested.nix @@ -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"; + }; + }; +} diff --git a/lib/tests/modules/error-typo-large-attrset.nix b/lib/tests/modules/error-typo-large-attrset.nix new file mode 100644 index 000000000000..0991f9a2c9e2 --- /dev/null +++ b/lib/tests/modules/error-typo-large-attrset.nix @@ -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; + }; +} diff --git a/lib/tests/modules/error-typo-multiple-suggestions.nix b/lib/tests/modules/error-typo-multiple-suggestions.nix new file mode 100644 index 000000000000..d01d649c3a05 --- /dev/null +++ b/lib/tests/modules/error-typo-multiple-suggestions.nix @@ -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; + }; +} diff --git a/lib/tests/modules/error-typo-nested.nix b/lib/tests/modules/error-typo-nested.nix new file mode 100644 index 000000000000..f29cd03f803d --- /dev/null +++ b/lib/tests/modules/error-typo-nested.nix @@ -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; + }; +} diff --git a/lib/tests/modules/error-typo-outside-with-nested.nix b/lib/tests/modules/error-typo-outside-with-nested.nix new file mode 100644 index 000000000000..d4f181673c50 --- /dev/null +++ b/lib/tests/modules/error-typo-outside-with-nested.nix @@ -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; + }; +} diff --git a/lib/tests/modules/error-typo-submodule.nix b/lib/tests/modules/error-typo-submodule.nix new file mode 100644 index 000000000000..5a9db862e584 --- /dev/null +++ b/lib/tests/modules/error-typo-submodule.nix @@ -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; + }; + }; +} |
