diff --git a/src/libexpr/include/nix/expr/eval-settings.hh b/src/libexpr/include/nix/expr/eval-settings.hh index 25ba84ac9..4c9db0c73 100644 --- a/src/libexpr/include/nix/expr/eval-settings.hh +++ b/src/libexpr/include/nix/expr/eval-settings.hh @@ -327,6 +327,21 @@ struct EvalSettings : Config This option can be enabled by setting `NIX_ABORT_ON_WARN=1` in the environment. )"}; + + Setting warnShortPathLiterals{ + this, + false, + "warn-short-path-literals", + R"( + If set to true, the Nix evaluator will warn when encountering relative path literals + that don't start with `./` or `../`. + + For example, with this setting enabled, `foo/bar` would emit a warning + suggesting to use `./foo/bar` instead. + + This is useful for improving code readability and making path literals + more explicit. + )"}; }; /** diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 8878b86c2..2b2566208 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -365,6 +365,15 @@ string_parts_interpolated path_start : PATH { std::string_view literal({$1.p, $1.l}); + + /* check for short path literals */ + if (state->settings.warnShortPathLiterals && literal.front() != '/' && literal.front() != '.') { + logWarning({ + .msg = HintFmt("relative path literal '%s' should be prefixed with '.' for clarity: './%s'. (" ANSI_BOLD "warn-short-path-literals" ANSI_NORMAL " = true)", literal, literal), + .pos = state->positions[CUR_POS] + }); + } + Path path(absPath(literal, state->basePath.path.abs())); /* add back in the trailing '/' to the first segment */ if (literal.size() > 1 && literal.back() == '/') diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 0e2004219..8f2b1ff59 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -112,6 +112,7 @@ suites = [ 'impure-eval.sh', 'pure-eval.sh', 'eval.sh', + 'short-path-literals.sh', 'repl.sh', 'binary-cache-build-remote.sh', 'search.sh', diff --git a/tests/functional/short-path-literals.sh b/tests/functional/short-path-literals.sh new file mode 100644 index 000000000..f74044dda --- /dev/null +++ b/tests/functional/short-path-literals.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +source common.sh + +clearStoreIfPossible + +# Test 1: Without the setting (default), no warnings should be produced +nix eval --expr 'test/subdir' 2>"$TEST_ROOT"/stderr +grepQuietInverse < "$TEST_ROOT/stderr" -E "relative path|path literal" || fail "Should not produce warnings by default" + +# Test 2: With the setting enabled, warnings should be produced for short path literals +nix eval --warn-short-path-literals --expr 'test/subdir' 2>"$TEST_ROOT"/stderr +grepQuiet "relative path literal 'test/subdir' should be prefixed with '.' for clarity: './test/subdir'" "$TEST_ROOT/stderr" + +# Test 3: Different short path literals should all produce warnings +nix eval --warn-short-path-literals --expr 'foo/bar' 2>"$TEST_ROOT"/stderr +grepQuiet "relative path literal 'foo/bar' should be prefixed with '.' for clarity: './foo/bar'" "$TEST_ROOT/stderr" + +nix eval --warn-short-path-literals --expr 'a/b/c/d' 2>"$TEST_ROOT"/stderr +grepQuiet "relative path literal 'a/b/c/d' should be prefixed with '.' for clarity: './a/b/c/d'" "$TEST_ROOT/stderr" + +# Test 4: Paths starting with ./ should NOT produce warnings +nix eval --warn-short-path-literals --expr './test/subdir' 2>"$TEST_ROOT"/stderr +grepQuietInverse "relative path literal" "$TEST_ROOT/stderr" + +# Test 5: Paths starting with ../ should NOT produce warnings +nix eval --warn-short-path-literals --expr '../test/subdir' 2>"$TEST_ROOT"/stderr +grepQuietInverse "relative path literal" "$TEST_ROOT/stderr" + +# Test 6: Absolute paths should NOT produce warnings +nix eval --warn-short-path-literals --expr '/absolute/path' 2>"$TEST_ROOT"/stderr +grepQuietInverse "relative path literal" "$TEST_ROOT/stderr" + +# Test 7: Test that the warning is at the correct position +nix eval --warn-short-path-literals --expr 'foo/bar' 2>"$TEST_ROOT"/stderr +grepQuiet "at «string»:1:1:" "$TEST_ROOT/stderr" + +# Test 8: Test that evaluation still works correctly despite the warning +result=$(nix eval --warn-short-path-literals --expr 'test/subdir' 2>/dev/null) +expected="$PWD/test/subdir" +[[ "$result" == "$expected" ]] || fail "Evaluation result should be correct despite warning" + +# Test 9: Test with nix-instantiate as well +nix-instantiate --warn-short-path-literals --eval -E 'foo/bar' 2>"$TEST_ROOT"/stderr +grepQuiet "relative path literal 'foo/bar' should be prefixed" "$TEST_ROOT/stderr" + +# Test 10: Test that the setting can be set via configuration +NIX_CONFIG='warn-short-path-literals = true' nix eval --expr 'test/file' 2>"$TEST_ROOT"/stderr +grepQuiet "relative path literal 'test/file' should be prefixed" "$TEST_ROOT/stderr" + +# Test 11: Test that command line flag overrides config +NIX_CONFIG='warn-short-path-literals = true' nix eval --no-warn-short-path-literals --expr 'test/file' 2>"$TEST_ROOT"/stderr +grepQuietInverse "relative path literal" "$TEST_ROOT/stderr" + +echo "short-path-literals test passed!"