use crate::oracle_tests;
use crate::tests_common::{assert_parse_error_contains, assert_parse_rejected, test_ast_format};
oracle_tests! {
test_ast_format;
regression_string_interpolation_selectors => [
r#"x."y""#,
r#"x.${"y"}"#,
r"x.${foo}",
],
regression_or_as_identifier => ["or"],
regression_language_annotation => [
"/* python */\n\n\"x\"",
"/* python */\n\"x\"",
"/* python */ '' ''",
],
regression_chained_prefix_operators => [
"(--1)",
"(---1)",
"(!!a)",
"(!!!!a)",
],
regression_float_literals => [
".5",
"5.",
"1.0e2",
".5e2",
"00.5",
],
regression_attrset_string_interpolated_key => [
r#"{"a" = 1;}"#,
r#"{${"a"} = 1;}"#,
],
regression_import_relative_path => [
r#"{
a = import common/acme/server/snakeoil-certs.nix;
b = common/file.nix;
c = foo-bar/baz.nix;
d = metaCommon // { mainProgram = "gopeed"; };
e = (a / b);
f = ((targetPodcastSize + lameMp3FileAdjust) / (lameMp3Bitrate / 8));
}
"#,
],
regression_let_string_interpolated_key => [
r#"let "foo" = 1; in foo"#,
r#"let ${"foo"} = 1; in foo"#,
],
regression_import_path_application => ["import ./foo.nix self"],
regression_multiline_string_indentation => ["''\n case\n ;;\n''\n"],
regression_trailing_comment => ["{ test = foo; # trailing comment\n}"],
test_sourceline_multiline_list => ["[\n \"foo\"\n]"],
regression_comment_before_and_with_selectors => [
r#"{
x =
lib.optionalAttrs
(
self.packages.${system}.isLinux
# comment 1
&& self.packages.${system}.isPower64
# comment 2
&& system != "armv6l-linux"
# comment 3
&& system != "riscv64-linux"
)
{
tests = {};
};
}"#,
],
regression_emptyline_pretrivia_inline => ["\n\nlet x = 1; in x"],
regression_not_member_check => ["!a ? b"],
regression_implies_precedence => ["a || b -> c"],
regression_mixed_add_sub_associativity => ["1 + 2 - 3"],
regression_chained_string_concatenation => [
r"''
line1
''
+ lib.optionalString cond1 ''
line2
''
+ lib.optionalString cond2 ''
line3
''
+ lib.optionalString cond3 ''
line4
''
+ cfg.extra",
],
regression_empty_container_with_comment => [
"{\n # comment\n}",
"[\n # comment\n]",
"let\n # comment\nin x",
],
regression_ansi_escape_codes_in_strings => [
"\"\x1b[1;31mtest\x1b[0m\"",
"\"\t\"",
],
regression_dot_selector_on_newline => [
r"{
armv6l-linux = ./foo.nix;
}
.${system}",
],
regression_context_parameter_variants => [
"{ }@args: args",
"{...}@args: args",
],
regression_inline_comments_after_strings_and_paths => [
r#"[
"simple" # comment after simple string
''
indented
'' # comment after indented string
./path # comment after path
"end"
]"#,
],
regression_old_style_let => ["let { body = 1; }"],
regression_unicode_escape_in_string => [
"\"famous \u{200B}AAlib library\"",
"\"\u{00AD}~~~\"",
],
regression_identifier_slash_path => ["mkDefault /tmp"],
regression_unquoted_url => ["{ url = http://example.com/path; }"],
regression_decorated_multiline_comment => [
r#"/*
* Multiline, decorated comments
* # This ain't a nest'd comm'nt
*/
"x""#,
],
regression_trailing_empty_line_before_close => [
"let {\n x = 1;\n \n}\n",
"{\n foo = 1;\n\n}\n",
],
regression_chained_comparison_operators => ["2 > 1 == 1 < 2"],
regression_multiline_string_unicode_line_numbers => [
r#"{
x = ''
line1
ä"§
'';
}"#,
],
regression_indented_string_to_simple => [
"''hello ${x} '''quoted''' ''$var''",
r#"''has"quote''"#,
r"''back\slash''",
],
regression_empty_simple_string => [r#""""#],
regression_string_multiple_interpolations => [r#""${a} and ${b}""#],
regression_string_dollar_dollar => [r#""$$test""#],
regression_empty_indented_string => ["''''"],
regression_indented_string_escape_sequences => [r"''test ''$ and ''' and ''\ ''"],
regression_path_relative_dotdot => ["../foo"],
regression_path_with_interpolation => ["./foo/${bar}/baz"],
regression_subtraction_not_application => ["f -5"],
regression_application_with_parenthesized_negation => ["f (-5)"],
regression_inherit_multiple_names => ["{ inherit pkgs lib stdenv; }"],
regression_concat_right_associative => ["[1] ++ [2] ++ [3]"],
regression_update_right_associative => ["{a=1;} // {b=2;} // {c=3;}"],
regression_plus_right_associative => ["1 + 2 + 3"],
regression_minus_left_associative => ["1 - 2 - 3"],
regression_empty_set_parameter => ["{}: 42"],
regression_pipe_forward_operator => ["a |> b"],
regression_pipe_backward_operator => ["a <| b"],
regression_member_check_on_operation => ["(x + y) ? foo"],
regression_crlf_between_tokens => ["x\r\n+\r\ny"],
}
#[test]
fn regression_or_operator_deprecated_syntax() {
assert!(
crate::parse("let or = 1; in [ (x: x) or ]").is_ok(),
"we currently accept this but parse it incorrectly"
);
}
#[test]
fn regression_comparison_chain_should_fail() {
assert_parse_rejected("a == b == c");
}
#[test]
fn regression_path_trailing_slash_current() {
assert_parse_rejected("./");
}
#[test]
fn regression_crlf_line_endings() {
let input = "rec {\n x =\n # Comment\r y;\n}\n";
let result = crate::parse(input);
assert!(
result.is_ok(),
"Failed to parse input with CRLF/bare CR: {:?}",
result.err()
);
}
#[test]
fn regression_or_operator_with_application() {
let out = crate::format("(fold or [] [true false false])").unwrap();
assert!(
out.contains("or") && out.contains("[ ]"),
"`or [ ]` must not be dropped, got: {out}"
);
}
#[test]
fn regression_utf8_identifier() {
assert_parse_rejected("123 é 4");
}
#[test]
fn regression_duplicate_function_formals() {
assert_parse_rejected("{x, y, x}: x");
}
#[test]
fn regression_pattern_shadows_formal() {
assert_parse_rejected("args@{args, x, y, z}: x");
}
#[test]
fn regression_at_without_colon_error() {
assert_parse_error_contains("x @ y", "expected '{', found 'y'");
assert_parse_error_contains("x @ { }", "@ is only valid in lambda parameters");
}
#[test]
fn regression_context_parameter_shape() {
assert_parse_error_contains("a@b@{ }: 1", "expected '{'");
assert_parse_error_contains("{ }@a@b: 1", "expected ':'");
assert_parse_error_contains("a@{ }@b: 1", "expected ':'");
assert_parse_error_contains("a@b: 1", "expected '{'");
assert_parse_error_contains("{ }@{ }: 1", "expected identifier");
}
#[test]
fn regression_or_after_selector_requires_default() {
assert_parse_error_contains("[ a.b or ]", "expected expression");
assert_parse_error_contains("{ x = a.b or; }", "expected expression");
crate::parse("[ a or ]").expect("`or` without preceding selector is the identifier");
crate::parse("a or").expect("`or` without preceding selector is the identifier");
}
#[test]
fn regression_single_ampersand_error() {
assert_parse_error_contains("a & b", "expected '&&', found '&'");
}
#[test]
fn regression_single_pipe_error() {
assert_parse_error_contains("a | b", "expected one of '||', '|>', found '|'");
}
#[test]
fn regression_ellipsis_without_colon_error() {
assert_parse_error_contains("{ ... }", "{ ... } must be followed by ':' or '@'");
}
#[test]
fn regression_set_parameter_without_colon_error() {
assert_parse_rejected("{ x, y }");
}
#[test]
fn regression_single_dollar_error() {
let err = crate::parse("$x").unwrap_err().to_string();
assert!(err.contains("unexpected '$'") || err.contains("expected '${'"));
}
#[test]
fn regression_unexpected_character_error() {
let err = crate::parse("x ^ y").unwrap_err().to_string();
assert!(err.contains("unexpected character") || err.contains("'^'"));
}
#[test]
fn regression_non_utf8_input() {
let result = crate::parse("builtins.toJSON \"_invalid UTF-8: �_\"");
assert!(
result.is_ok(),
"Parser should handle Unicode replacement character"
);
}
#[test]
fn regression_inherit_interpolation_restricted() {
assert!(crate::parse(r#"{ inherit ${"ok"}; }"#).is_ok());
assert_parse_rejected(r"{ inherit ${bar}; }");
}