From 7337447d429d15c5e10da443f270c5766f2e8c6d Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 21 Nov 2023 20:17:38 -0800 Subject: [PATCH] Add file paths to error messages (#1737) --- src/assignment_resolver.rs | 1 + src/color.rs | 4 ++ src/compiler.rs | 8 ++-- src/lexer.rs | 68 +++++++++++++++++++------------ src/loader.rs | 20 +++++++-- src/name.rs | 15 ++++--- src/parser.rs | 5 ++- src/testing.rs | 5 ++- src/token.rs | 39 +++++++++++++++--- tests/attributes.rs | 3 ++ tests/byte_order_mark.rs | 2 + tests/conditional.rs | 7 ++++ tests/delimiters.rs | 3 ++ tests/error_messages.rs | 79 ++++++++++++++++++++++++++++++++---- tests/fallback.rs | 1 + tests/functions.rs | 34 ++++++++++++---- tests/includes.rs | 1 + tests/lib.rs | 2 +- tests/misc.rs | 55 ++++++++++++++++++++++++- tests/newline_escape.rs | 2 + tests/no_exit_message.rs | 5 ++- tests/recursion_limit.rs | 2 + tests/show.rs | 1 + tests/slash_operator.rs | 3 ++ tests/string.rs | 11 +++++ tests/subsequents.rs | 3 ++ tests/undefined_variables.rs | 5 +++ 27 files changed, 314 insertions(+), 70 deletions(-) diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs index 035e25b..68ff2a2 100644 --- a/src/assignment_resolver.rs +++ b/src/assignment_resolver.rs @@ -42,6 +42,7 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> { column: 0, length: 0, kind: TokenKind::Unspecified, + path: "".as_ref(), }; return Err(CompileError::new(token, Internal { message })); } diff --git a/src/color.rs b/src/color.rs index 56670ac..5b1b2e5 100644 --- a/src/color.rs +++ b/src/color.rs @@ -60,6 +60,10 @@ impl Color { self.redirect(Stream::Stdout) } + pub(crate) fn context(self) -> Self { + self.restyle(Style::new().fg(Blue).bold()) + } + pub(crate) fn doc(self) -> Self { self.restyle(Style::new().fg(Blue)) } diff --git a/src/compiler.rs b/src/compiler.rs index c035921..95ecc57 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -15,8 +15,8 @@ impl Compiler { paths.push(root.into()); while let Some(current) = paths.pop() { - let src = loader.load(¤t)?; - let tokens = Lexer::lex(src)?; + let (relative, src) = loader.load(root, ¤t)?; + let tokens = Lexer::lex(relative, src)?; let mut ast = Parser::parse(&tokens)?; srcs.insert(current.clone(), src); @@ -56,9 +56,9 @@ impl Compiler { #[cfg(test)] pub(crate) fn test_compile(src: &str) -> CompileResult { - let tokens = Lexer::lex(src)?; + let tokens = Lexer::test_lex(src)?; let ast = Parser::parse(&tokens)?; - let root = PathBuf::from(""); + let root = PathBuf::from("justfile"); let mut asts: HashMap = HashMap::new(); asts.insert(root.clone(), ast); Analyzer::analyze(&asts, &root) diff --git a/src/lexer.rs b/src/lexer.rs index 3dca62b..3a9a429 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -9,38 +9,45 @@ use {super::*, CompileErrorKind::*, TokenKind::*}; /// slight against regular expressions, the lexer was just idiosyncratically /// bad. pub(crate) struct Lexer<'src> { - /// Source text - src: &'src str, /// Char iterator chars: Chars<'src>, - /// Tokens - tokens: Vec>, - /// Current token start - token_start: Position, - /// Current token end - token_end: Position, - /// Next character to be lexed - next: Option, - /// Next indent will start a recipe body - recipe_body_pending: bool, - /// Inside recipe body - recipe_body: bool, /// Indentation stack indentation: Vec<&'src str>, /// Interpolation token start stack interpolation_stack: Vec>, + /// Next character to be lexed + next: Option, /// Current open delimiters open_delimiters: Vec<(Delimiter, usize)>, + /// Path to source file + path: &'src Path, + /// Inside recipe body + recipe_body: bool, + /// Next indent will start a recipe body + recipe_body_pending: bool, + /// Source text + src: &'src str, + /// Tokens + tokens: Vec>, + /// Current token end + token_end: Position, + /// Current token start + token_start: Position, } impl<'src> Lexer<'src> { - /// Lex `text` - pub(crate) fn lex(src: &'src str) -> CompileResult>> { - Lexer::new(src).tokenize() + /// Lex `src` + pub(crate) fn lex(path: &'src Path, src: &'src str) -> CompileResult<'src, Vec>> { + Lexer::new(path, src).tokenize() } - /// Create a new Lexer to lex `text` - fn new(src: &'src str) -> Lexer<'src> { + #[cfg(test)] + pub(crate) fn test_lex(src: &'src str) -> CompileResult<'src, Vec>> { + Lexer::new("justfile".as_ref(), src).tokenize() + } + + /// Create a new Lexer to lex `src` + fn new(path: &'src Path, src: &'src str) -> Lexer<'src> { let mut chars = src.chars(); let next = chars.next(); @@ -62,6 +69,7 @@ impl<'src> Lexer<'src> { chars, next, src, + path, } } @@ -189,6 +197,7 @@ impl<'src> Lexer<'src> { src: self.src, length: self.token_end.offset - self.token_start.offset, kind, + path: self.path, }); // Set `token_start` to point after the lexed token @@ -205,6 +214,7 @@ impl<'src> Lexer<'src> { column: self.token_end.column, length: 0, kind: Unspecified, + path: self.path, }; CompileError::new( token, @@ -240,6 +250,7 @@ impl<'src> Lexer<'src> { line: self.token_start.line, column: self.token_start.column, length, + path: self.path, }; CompileError::new(token, kind) @@ -920,7 +931,7 @@ mod tests { text.to_owned() }; - let have = Lexer::lex(&text).unwrap(); + let have = Lexer::test_lex(&text).unwrap(); let have_kinds = have .iter() @@ -1028,7 +1039,7 @@ mod tests { length: usize, kind: CompileErrorKind, ) { - match Lexer::lex(src) { + match Lexer::test_lex(src) { Ok(_) => panic!("Lexing succeeded but expected"), Err(have) => { let want = CompileError { @@ -1039,6 +1050,7 @@ mod tests { line, column, length, + path: "justfile".as_ref(), }, kind: Box::new(kind), }; @@ -2321,7 +2333,9 @@ mod tests { #[test] fn presume_error() { - let compile_error = Lexer::new("!").presume('-').unwrap_err(); + let compile_error = Lexer::new("justfile".as_ref(), "!") + .presume('-') + .unwrap_err(); assert_matches!( compile_error.token, Token { @@ -2331,6 +2345,7 @@ mod tests { length: 0, src: "!", kind: Unspecified, + path: _, } ); assert_matches!(&*compile_error.kind, @@ -2342,9 +2357,12 @@ mod tests { Error::Compile { compile_error } .color_display(Color::never()) .to_string(), - "error: Internal error, this may indicate a bug in just: \ - Lexer presumed character `-`\nconsider filing an issue: \ - https://github.com/casey/just/issues/new\n |\n1 | !\n | ^" + "error: Internal error, this may indicate a bug in just: Lexer presumed character `-` +consider filing an issue: https://github.com/casey/just/issues/new + --> justfile:1:1 + | +1 | ! + | ^" ); } } diff --git a/src/loader.rs b/src/loader.rs index b1cc094..f96cd65 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -1,22 +1,34 @@ use super::*; pub(crate) struct Loader { - arena: Arena, + srcs: Arena, + paths: Arena, } impl Loader { pub(crate) fn new() -> Self { Loader { - arena: Arena::new(), + srcs: Arena::new(), + paths: Arena::new(), } } - pub(crate) fn load<'src>(&'src self, path: &Path) -> RunResult<&'src str> { + pub(crate) fn load<'src>( + &'src self, + root: &Path, + path: &Path, + ) -> RunResult<(&'src Path, &'src str)> { let src = fs::read_to_string(path).map_err(|io_error| Error::Load { path: path.to_owned(), io_error, })?; - Ok(self.arena.alloc(src)) + let relative = if let Ok(path) = path.strip_prefix(root.parent().unwrap()) { + path + } else { + path + }; + + Ok((self.paths.alloc(relative.into()), self.srcs.alloc(src))) } } diff --git a/src/name.rs b/src/name.rs index aec425f..9823430 100644 --- a/src/name.rs +++ b/src/name.rs @@ -4,10 +4,11 @@ use super::*; /// it its own type for clarity. #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] pub(crate) struct Name<'src> { - pub(crate) offset: usize, + pub(crate) column: usize, pub(crate) length: usize, pub(crate) line: usize, - pub(crate) column: usize, + pub(crate) offset: usize, + pub(crate) path: &'src Path, pub(crate) src: &'src str, } @@ -20,11 +21,12 @@ impl<'src> Name<'src> { /// Turn this name back into a token pub(crate) fn token(&self) -> Token<'src> { Token { + column: self.column, kind: TokenKind::Identifier, - offset: self.offset, length: self.length, line: self.line, - column: self.column, + offset: self.offset, + path: self.path, src: self.src, } } @@ -32,10 +34,11 @@ impl<'src> Name<'src> { pub(crate) fn from_identifier(token: Token<'src>) -> Name { assert_eq!(token.kind, TokenKind::Identifier); Name { - offset: token.offset, + column: token.column, length: token.length, line: token.line, - column: token.column, + offset: token.offset, + path: token.path, src: token.src, } } diff --git a/src/parser.rs b/src/parser.rs index 613cb11..a8a04d8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -927,7 +927,7 @@ mod tests { fn test(text: &str, want: Tree) { let unindented = unindent(text); - let tokens = Lexer::lex(&unindented).expect("lexing failed"); + let tokens = Lexer::test_lex(&unindented).expect("lexing failed"); let justfile = Parser::parse(&tokens).expect("parsing failed"); let have = justfile.tree(); if have != want { @@ -964,7 +964,7 @@ mod tests { length: usize, kind: CompileErrorKind, ) { - let tokens = Lexer::lex(src).expect("Lexing failed in parse test..."); + let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test..."); match Parser::parse(&tokens) { Ok(_) => panic!("Parsing unexpectedly succeeded"), @@ -977,6 +977,7 @@ mod tests { line, column, length, + path: "justfile".as_ref(), }, kind: Box::new(kind), }; diff --git a/src/testing.rs b/src/testing.rs index b526e83..91549a8 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -57,11 +57,11 @@ pub(crate) fn analysis_error( length: usize, kind: CompileErrorKind, ) { - let tokens = Lexer::lex(src).expect("Lexing failed in parse test..."); + let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test..."); let ast = Parser::parse(&tokens).expect("Parsing failed in analysis test..."); - let root = PathBuf::from(""); + let root = PathBuf::from("justfile"); let mut asts: HashMap = HashMap::new(); asts.insert(root.clone(), ast); @@ -76,6 +76,7 @@ pub(crate) fn analysis_error( line, column, length, + path: "justfile".as_ref(), }, kind: Box::new(kind), }; diff --git a/src/token.rs b/src/token.rs index 3a339c4..9846236 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,12 +2,13 @@ use super::*; #[derive(Debug, PartialEq, Clone, Copy)] pub(crate) struct Token<'src> { - pub(crate) offset: usize, + pub(crate) column: usize, + pub(crate) kind: TokenKind, pub(crate) length: usize, pub(crate) line: usize, - pub(crate) column: usize, + pub(crate) offset: usize, + pub(crate) path: &'src Path, pub(crate) src: &'src str, - pub(crate) kind: TokenKind, } impl<'src> Token<'src> { @@ -52,9 +53,35 @@ impl<'src> ColorDisplay for Token<'src> { i += c.len_utf8(); } let line_number_width = line_number.to_string().len(); - writeln!(f, "{0:1$} |", "", line_number_width)?; - writeln!(f, "{line_number} | {space_line}")?; - write!(f, "{0:1$} |", "", line_number_width)?; + writeln!( + f, + "{:width$}{} {}:{}:{}", + "", + color.context().paint("-->"), + self.path.display(), + line_number, + self.column.ordinal(), + width = line_number_width + )?; + writeln!( + f, + "{:width$} {}", + "", + color.context().paint("|"), + width = line_number_width + )?; + writeln!( + f, + "{} {space_line}", + color.context().paint(&format!("{line_number} |")) + )?; + write!( + f, + "{:width$} {}", + "", + color.context().paint("|"), + width = line_number_width + )?; write!( f, " {0:1$}{2}{3:^<4$}{5}", diff --git a/tests/attributes.rs b/tests/attributes.rs index accfde8..8560e99 100644 --- a/tests/attributes.rs +++ b/tests/attributes.rs @@ -33,6 +33,7 @@ fn duplicate_attributes_are_disallowed() { .stderr( " error: Recipe attribute `no-exit-message` first used on line 1 is duplicated on line 2 + --> justfile:2:2 | 2 | [no-exit-message] | ^^^^^^^^^^^^^^^ @@ -72,6 +73,7 @@ fn multiple_attributes_one_line_error_message() { .stderr( " error: Expected ']' or ',', but found identifier + --> justfile:1:17 | 1 | [macos, windows linux] | ^^^^^ @@ -95,6 +97,7 @@ fn multiple_attributes_one_line_duplicate_check() { .stderr( " error: Recipe attribute `linux` first used on line 1 is duplicated on line 2 + --> justfile:2:2 | 2 | [linux] | ^^^^^ diff --git a/tests/byte_order_mark.rs b/tests/byte_order_mark.rs index 13283d8..cd72eec 100644 --- a/tests/byte_order_mark.rs +++ b/tests/byte_order_mark.rs @@ -27,6 +27,7 @@ fn non_leading_byte_order_mark_produces_error() { .stderr( " error: Expected \'@\', '!', \'[\', comment, end of file, end of line, or identifier, but found byte order mark + --> justfile:3:1 | 3 | \u{feff} | ^ @@ -42,6 +43,7 @@ fn dont_mention_byte_order_mark_in_errors() { .stderr( " error: Expected '@', '!', '[', comment, end of file, end of line, or identifier, but found '{' + --> justfile:1:1 | 1 | { | ^ diff --git a/tests/conditional.rs b/tests/conditional.rs index db8e389..359b033 100644 --- a/tests/conditional.rs +++ b/tests/conditional.rs @@ -61,6 +61,7 @@ test! { stdout: "", stderr: " error: Variable `b` not defined + --> justfile:1:9 | 1 | a := if b == '' { '' } else { '' } | ^ @@ -79,6 +80,7 @@ test! { stdout: "", stderr: " error: Variable `b` not defined + --> justfile:1:15 | 1 | a := if '' == b { '' } else { '' } | ^ @@ -97,6 +99,7 @@ test! { stdout: "", stderr: " error: Variable `b` not defined + --> justfile:1:20 | 1 | a := if '' == '' { b } else { '' } | ^ @@ -115,6 +118,7 @@ test! { stdout: "", stderr: " error: Variable `b` not defined + --> justfile:1:32 | 1 | a := if '' == '' { '' } else { b } | ^ @@ -133,6 +137,7 @@ test! { stdout: "", stderr: " error: Expected '!=', '==', '=~', '+', or '/', but found identifier + --> justfile:1:12 | 1 | a := if '' a '' { '' } else { b } | ^ @@ -177,6 +182,7 @@ test! { stdout: "", stderr: " error: Expected keyword `else` but found `end of line` + --> justfile:1:54 | 1 | TEST := if path_exists('/bin/bash') == 'true' {'yes'} | ^ @@ -192,6 +198,7 @@ test! { stdout: "", stderr: " error: Expected keyword `else` but found identifier `els` + --> justfile:1:55 | 1 | TEST := if path_exists('/bin/bash') == 'true' {'yes'} els {'no'} | ^^^ diff --git a/tests/delimiters.rs b/tests/delimiters.rs index ab032e6..773fec4 100644 --- a/tests/delimiters.rs +++ b/tests/delimiters.rs @@ -5,6 +5,7 @@ test! { justfile: "(]", stderr: " error: Mismatched closing delimiter `]`. (Did you mean to close the `(` on line 1?) + --> justfile:1:2 | 1 | (] | ^ @@ -17,6 +18,7 @@ test! { justfile: "]", stderr: " error: Unexpected closing delimiter `]` + --> justfile:1:1 | 1 | ] | ^ @@ -96,6 +98,7 @@ test! { stdout: "", stderr: " error: Unterminated interpolation + --> justfile:2:8 | 2 | echo {{ ( | ^^ diff --git a/tests/error_messages.rs b/tests/error_messages.rs index e17a4af..6e86027 100644 --- a/tests/error_messages.rs +++ b/tests/error_messages.rs @@ -1,15 +1,16 @@ use super::*; test! { - name: invalid_alias_attribute, - justfile: "[private]\n[linux]\nalias t := test\n\ntest:\n", - stderr: " - error: Alias t has an invalid attribute `linux` - | - 3 | alias t := test - | ^ - ", - status: EXIT_FAILURE, + name: invalid_alias_attribute, + justfile: "[private]\n[linux]\nalias t := test\n\ntest:\n", + stderr: " + error: Alias t has an invalid attribute `linux` + --> justfile:3:7 + | + 3 | alias t := test + | ^ + ", + status: EXIT_FAILURE, } test! { @@ -17,6 +18,7 @@ test! { justfile: "foo := if '' == '' { '' } arlo { '' }", stderr: " error: Expected keyword `else` but found identifier `arlo` + --> justfile:1:27 | 1 | foo := if '' == '' { '' } arlo { '' } | ^^^^ @@ -29,6 +31,7 @@ test! { justfile: "&~", stderr: " error: Expected character `&` + --> justfile:1:2 | 1 | &~ | ^ @@ -51,3 +54,61 @@ fn argument_count_mismatch() { .status(EXIT_FAILURE) .run(); } + +#[test] +fn file_path_is_indented_if_justfile_is_long() { + Test::new() + .justfile("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nfoo") + .status(EXIT_FAILURE) + .stderr( + " +error: Expected '*', ':', '$', identifier, or '+', but found end of file + --> justfile:20:4 + | +20 | foo + | ^ +", + ) + .run(); +} + +#[test] +fn file_paths_are_relative() { + Test::new() + .justfile("!include foo/bar.just") + .write("foo/bar.just", "baz") + .args(["--unstable"]) + .status(EXIT_FAILURE) + .stderr(format!( + " +error: Expected '*', ':', '$', identifier, or '+', but found end of file + --> foo{}bar.just:1:4 + | +1 | baz + | ^ +", + MAIN_SEPARATOR + )) + .run(); +} + +#[test] +fn file_paths_not_in_subdir_are_absolute() { + Test::new() + .write("foo/justfile", "!include ../bar.just") + .write("bar.just", "baz") + .no_justfile() + .args(["--unstable", "--justfile", "foo/justfile"]) + .status(EXIT_FAILURE) + .stderr_regex(format!( + " +error: Expected '*', ':', '$', identifier, or '+', but found end of file + --> {}.*{}bar.just:1:4 + | +1 | baz + | ^ +", + MAIN_SEPARATOR, MAIN_SEPARATOR + )) + .run(); +} diff --git a/tests/fallback.rs b/tests/fallback.rs index 7f0387b..d1a3da9 100644 --- a/tests/fallback.rs +++ b/tests/fallback.rs @@ -146,6 +146,7 @@ fn print_error_from_parent_if_recipe_not_found_in_current() { .stderr( " error: Variable `bar` not defined + --> justfile:2:9 | 2 | echo {{bar}} | ^^^ diff --git a/tests/functions.rs b/tests/functions.rs index 150f293..993b99d 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -85,9 +85,10 @@ foo: /bin/echo '{{we}}' "#, stdout: "", - stderr: format!("{} {}\n{}\n{}\n{}\n", + stderr: format!("{} {}\n{}\n{}\n{}\n{}\n", "error: Call to function `without_extension` failed:", "Could not extract parent from ``", + " --> justfile:1:8", " |", "1 | we := without_extension(\'\')", " | ^^^^^^^^^^^^^^^^^").as_str(), @@ -104,8 +105,9 @@ foo: /bin/echo '{{we}}' "#, stdout: "", - stderr: format!("{}\n{}\n{}\n{}\n", + stderr: format!("{}\n{}\n{}\n{}\n{}\n", "error: Call to function `extension` failed: Could not extract extension from ``", + " --> justfile:1:8", " |", "1 | we := extension(\'\')", " | ^^^^^^^^^").as_str(), @@ -122,8 +124,9 @@ foo: /bin/echo '{{we}}' "#, stdout: "", - stderr: format!("{}\n{}\n{}\n{}\n", + stderr: format!("{}\n{}\n{}\n{}\n{}\n", "error: Call to function `extension` failed: Could not extract extension from `foo`", + " --> justfile:1:8", " |", "1 | we := extension(\'foo\')", " | ^^^^^^^^^").as_str(), @@ -140,8 +143,9 @@ foo: /bin/echo '{{we}}' "#, stdout: "", - stderr: format!("{}\n{}\n{}\n{}\n", + stderr: format!("{}\n{}\n{}\n{}\n{}\n", "error: Call to function `file_stem` failed: Could not extract file stem from ``", + " --> justfile:1:8", " |", "1 | we := file_stem(\'\')", " | ^^^^^^^^^").as_str(), @@ -158,8 +162,9 @@ foo: /bin/echo '{{we}}' "#, stdout: "", - stderr: format!("{}\n{}\n{}\n{}\n", + stderr: format!("{}\n{}\n{}\n{}\n{}\n", "error: Call to function `file_name` failed: Could not extract file name from ``", + " --> justfile:1:8", " |", "1 | we := file_name(\'\')", " | ^^^^^^^^^").as_str(), @@ -176,9 +181,10 @@ foo: /bin/echo '{{we}}' "#, stdout: "", - stderr: format!("{} {}\n{}\n{}\n{}\n", + stderr: format!("{} {}\n{}\n{}\n{}\n{}\n", "error: Call to function `parent_directory` failed:", "Could not extract parent directory from ``", + " --> justfile:1:8", " |", "1 | we := parent_directory(\'\')", " | ^^^^^^^^^^^^^^^^").as_str(), @@ -195,9 +201,10 @@ foo: /bin/echo '{{we}}' "#, stdout: "", - stderr: format!("{} {}\n{}\n{}\n{}\n", + stderr: format!("{} {}\n{}\n{}\n{}\n{}\n", "error: Call to function `parent_directory` failed:", "Could not extract parent directory from `/`", + " --> justfile:1:8", " |", "1 | we := parent_directory(\'/\')", " | ^^^^^^^^^^^^^^^^").as_str(), @@ -225,6 +232,7 @@ test! { args: ("a"), stdout: "", stderr: "error: Call to function `env_var` failed: environment variable `ZADDY` not present + --> justfile:2:10 | 2 | echo {{env_var('ZADDY')}} | ^^^^^^^ @@ -395,6 +403,7 @@ test! { foo\\ ^ error: incomplete escape sequence, reached end of pattern prematurely + --> justfile:2:11 | 2 | echo {{ replace_regex('barbarbar', 'foo\\', 'foo') }} | ^^^^^^^^^^^^^ @@ -498,6 +507,7 @@ fn join_argument_count_error() { .stderr( " error: Function `join` called with 1 argument but takes 2 or more + --> justfile:1:6 | 1 | x := join(\'a\') | ^^^^ @@ -534,7 +544,15 @@ fn error_errors_with_message() { .justfile("x := error ('Thing Not Supported')") .args(["--evaluate"]) .status(1) - .stderr("error: Call to function `error` failed: Thing Not Supported\n |\n1 | x := error ('Thing Not Supported')\n | ^^^^^\n") + .stderr( + " + error: Call to function `error` failed: Thing Not Supported + --> justfile:1:6 + | + 1 | x := error ('Thing Not Supported') + | ^^^^^ + ", + ) .run(); } diff --git a/tests/includes.rs b/tests/includes.rs index 9c22832..2460a45 100644 --- a/tests/includes.rs +++ b/tests/includes.rs @@ -58,6 +58,7 @@ fn include_directive_with_no_path() { .stderr( " error: !include directive has no argument + --> justfile:1:9 | 1 | !include | ^ diff --git a/tests/lib.rs b/tests/lib.rs index ea7d63c..42153f5 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -20,7 +20,7 @@ pub(crate) use { fs, io::Write, iter, - path::{Path, PathBuf}, + path::{Path, PathBuf, MAIN_SEPARATOR}, process::{Command, Stdio}, str, }, diff --git a/tests/misc.rs b/tests/misc.rs index 7d8ff0f..8733507 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -72,6 +72,7 @@ test! { ", stderr: " error: Unknown setting `foo` + --> justfile:1:5 | 1 | set foo | ^^^ @@ -86,6 +87,7 @@ test! { ", stderr: " error: Unknown setting `if` + --> justfile:1:5 | 1 | set if := 'foo' | ^^ @@ -106,6 +108,7 @@ test! { justfile: "alias foo := bar\nalias foo := baz\n", stderr: " error: Alias `foo` first defined on line 1 is redefined on line 2 + --> justfile:2:7 | 2 | alias foo := baz | ^^^ @@ -118,6 +121,7 @@ test! { justfile: "alias foo := bar\n", stderr: " error: Alias `foo` has an unknown target `bar` + --> justfile:1:7 | 1 | alias foo := bar | ^^^ @@ -130,6 +134,7 @@ test! { justfile: "bar:\n echo bar\nalias foo := bar\nfoo:\n echo foo", stderr: " error: Alias `foo` defined on line 3 shadows recipe `foo` defined on line 4 + --> justfile:3:7 | 3 | alias foo := bar | ^^^ @@ -264,6 +269,7 @@ test! { justfile: "bar:\nhello:\nfoo: bar baaaaaaaz hello", stderr: " error: Recipe `foo` has unknown dependency `baaaaaaaz` + --> justfile:3:10 | 3 | foo: bar baaaaaaaz hello | ^^^^^^^^^ @@ -290,6 +296,7 @@ test! { justfile: "b := a\na := `exit 100`\nbar:\n echo '{{`exit 200`}}'", stderr: " error: Backtick failed with exit code 100 + --> justfile:2:6 | 2 | a := `exit 100` | ^^^^^^^^^^ @@ -302,6 +309,7 @@ test! { justfile: "b := a\na := `echo hello`\nbar:\n echo '{{`exit 200`}}'", stderr: " error: Backtick failed with exit code 200 + --> justfile:4:10 | 4 | echo '{{`exit 200`}}' | ^^^^^^^^^^ @@ -314,6 +322,7 @@ test! { justfile: "f:\n η„‘{{`exit 200`}}", stderr: " error: Backtick failed with exit code 200 + --> justfile:2:7 | 2 | η„‘{{`exit 200`}} | ^^^^^^^^^^ @@ -328,6 +337,7 @@ test! { \techo {{`exit 200`}} ", stderr: " error: Backtick failed with exit code 200 + --> justfile:2:9 | 2 | echo {{`exit 200`}} | ^^^^^^^^^^ @@ -342,6 +352,7 @@ test! { \techo {{\t`exit 200`}} ", stderr: "error: Backtick failed with exit code 200 + --> justfile:2:10 | 2 | echo {{ `exit 200`}} | ^^^^^^^^^^ @@ -357,6 +368,7 @@ test! { ", stderr: " error: Backtick failed with exit code 200 + --> justfile:2:10 | 2 | echo {{ `exit 200`}} | ^^^^^^^^^^^^^^^^^ @@ -372,6 +384,7 @@ test! { ", stderr: " error: Backtick failed with exit code 200 + --> justfile:2:13 | 2 | echo 😬{{`exit 200`}} | ^^^^^^^^^^ @@ -387,6 +400,7 @@ test! { ", stderr: " error: Backtick failed with exit code 200 + --> justfile:2:24 | 2 | echo 😬鎌鼬{{ `exit 200 # abc`}} 😬鎌鼬 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -410,6 +424,7 @@ test! { ", stderr: " error: Backtick failed with exit code 200 + --> justfile:10:10 | 10 | echo '{{`exit 200`}}' | ^^^^^^^^^^ @@ -426,6 +441,7 @@ test! { stdout: "", stderr: " error: Backtick failed with exit code 123 + --> justfile:4:9 | 4 | echo {{`exit 123`}} | ^^^^^^^^^^ @@ -442,6 +458,7 @@ test! { stderr: " echo hello error: Backtick failed with exit code 123 + --> justfile:3:9 | 3 | echo {{`exit 123`}} | ^^^^^^^^^^ @@ -458,6 +475,7 @@ a := `exit 222`", stdout: "", stderr: " error: Backtick failed with exit code 222 + --> justfile:4:6 | 4 | a := `exit 222` | ^^^^^^^^^^ @@ -573,6 +591,7 @@ test! { "#, stdout: "", stderr: "error: Unknown start of token: + --> justfile:10:1 | 10 | ??? | ^ @@ -677,8 +696,7 @@ test! { justfile: "b := a\na := `exit 100`\nbar:\n echo '{{`exit 200`}}'", args: ("--color", "always"), stdout: "", - stderr: "\u{1b}[1;31merror\u{1b}[0m: \u{1b}[1mBacktick failed with exit code 100\u{1b}[0m - |\n2 | a := `exit 100`\n | \u{1b}[1;31m^^^^^^^^^^\u{1b}[0m\n", + stderr: "\u{1b}[1;31merror\u{1b}[0m: \u{1b}[1mBacktick failed with exit code 100\u{1b}[0m\n \u{1b}[1;34m-->\u{1b}[0m justfile:2:6\n \u{1b}[1;34m|\u{1b}[0m\n\u{1b}[1;34m2 |\u{1b}[0m a := `exit 100`\n \u{1b}[1;34m|\u{1b}[0m \u{1b}[1;31m^^^^^^^^^^\u{1b}[0m\n", status: 100, } @@ -688,6 +706,7 @@ test! { args: ("--color", "never"), stdout: "", stderr: "error: Backtick failed with exit code 100 + --> justfile:2:6 | 2 | a := `exit 100` | ^^^^^^^^^^ @@ -701,6 +720,7 @@ test! { args: ("--color", "auto"), stdout: "", stderr: "error: Backtick failed with exit code 100 + --> justfile:2:6 | 2 | a := `exit 100` | ^^^^^^^^^^ @@ -739,6 +759,7 @@ test! { stdout: "", stderr: "error: Found a mix of tabs and spaces in leading whitespace: `␉␠` Leading whitespace may consist of tabs or spaces, but not both + --> justfile:2:1 | 2 | echo hello | ^^^^^ @@ -751,6 +772,7 @@ test! { justfile: "bar:\n\t\techo hello\n\t\t\techo goodbye", stdout: "", stderr: "error: Recipe line has extra leading whitespace + --> justfile:3:3 | 3 | echo goodbye | ^^^^^^^^^^^^^^^^ @@ -764,6 +786,7 @@ test! { stdout: "", stderr: "error: Recipe line has inconsistent leading whitespace. \ Recipe started with `␉␉` but found line with `␉␠` + --> justfile:3:1 | 3 | echo goodbye | ^^^^^ @@ -776,6 +799,7 @@ test! { justfile: "bar:\nhello baz arg='foo' bar:", stdout: "", stderr: "error: Non-default parameter `bar` follows default parameter + --> justfile:2:21 | 2 | hello baz arg='foo' bar: | ^^^ @@ -788,6 +812,7 @@ test! { justfile: "bar:\nhello baz +arg bar:", stdout: "", stderr: "error: Parameter `bar` follows variadic parameter + --> justfile:2:16 | 2 | hello baz +arg bar: | ^^^ @@ -800,6 +825,7 @@ test! { justfile: "bar:\nhello baz *arg bar:", stdout: "", stderr: "error: Parameter `bar` follows variadic parameter + --> justfile:2:16 | 2 | hello baz *arg bar: | ^^^ @@ -1172,6 +1198,7 @@ bar:"#, args: ("bar"), stdout: "", stderr: r#"error: Call to unknown function `foo` + --> justfile:1:8 | 1 | foo := foo() + "hello" | ^^^ @@ -1188,6 +1215,7 @@ test! { args: ("b"), stdout: "", stderr: "error: Dependency `a` got 0 arguments but takes 1 argument + --> justfile:2:4 | 2 | b: a | ^ @@ -1204,6 +1232,7 @@ test! { args: ("b"), stdout: "", stderr: "error: Dependency `a` got 0 arguments but takes at least 1 argument + --> justfile:2:4 | 2 | b: a | ^ @@ -1220,6 +1249,7 @@ test! { args: ("b"), stdout: "", stderr: "error: Dependency `a` got 3 arguments but takes at most 2 arguments + --> justfile:2:5 | 2 | b: (a '0' '1' '2') | ^ @@ -1233,6 +1263,7 @@ test! { args: ("a"), stdout: "", stderr: "error: Recipe `a` has duplicate parameter `foo` + --> justfile:1:7 | 1 | a foo foo: | ^^^ @@ -1246,6 +1277,7 @@ test! { args: ("b"), stdout: "", stderr: "error: Recipe `b` first defined on line 1 is redefined on line 2 + --> justfile:2:1 | 2 | b: | ^ @@ -1259,6 +1291,7 @@ test! { args: ("foo"), stdout: "", stderr: "error: Variable `a` has multiple definitions + --> justfile:2:1 | 2 | a := 'hello' | ^ @@ -1273,6 +1306,7 @@ test! { stdout: "", stderr: "error: Expected '&&', comment, end of file, end of line, \ identifier, or '(', but found string + --> justfile:1:6 | 1 | foo: 'bar' | ^^^^^ @@ -1286,6 +1320,7 @@ test! { args: ("foo"), stdout: "", stderr: "error: Expected '*', ':', '$', identifier, or '+', but found string + --> justfile:1:5 | 1 | foo 'bar' | ^^^^^ @@ -1299,6 +1334,7 @@ test! { args: ("a"), stdout: "", stderr: "error: Recipe `a` depends on itself + --> justfile:1:4 | 1 | a: a | ^ @@ -1312,6 +1348,7 @@ test! { args: ("a"), stdout: "", stderr: "error: Recipe `d` has circular dependency `a -> b -> c -> d -> a` + --> justfile:4:4 | 4 | d: a | ^ @@ -1325,6 +1362,7 @@ test! { args: ("a"), stdout: "", stderr: "error: Variable `z` is defined in terms of itself + --> justfile:1:1 | 1 | z := z | ^ @@ -1338,6 +1376,7 @@ test! { args: ("a"), stdout: "", stderr: "error: Variable `x` depends on its own value: `x -> y -> z -> x` + --> justfile:1:1 | 1 | x := y | ^ @@ -1357,6 +1396,7 @@ test! { args: ("a"), stdout: "", stderr: "error: Variable `x` depends on its own value: `x -> y -> x` + --> justfile:2:1 | 2 | x := y | ^ @@ -1461,6 +1501,7 @@ foo *a +b: ", stdout: "", stderr: "error: Expected \':\' or \'=\', but found \'+\' + --> justfile:1:8 | 1 | foo *a +b: | ^ @@ -1476,6 +1517,7 @@ foo +a *b: ", stdout: "", stderr: "error: Expected \':\' or \'=\', but found \'*\' + --> justfile:1:8 | 1 | foo +a *b: | ^ @@ -1509,6 +1551,7 @@ a: x y ", stdout: "", stderr: "error: Recipe `a` has unknown dependency `y` + --> justfile:3:6 | 3 | a: x y | ^ @@ -1666,6 +1709,7 @@ X := "\'" "#, stdout: "", stderr: r#"error: `\'` is not a valid escape sequence + --> justfile:1:6 | 1 | X := "\'" | ^^^^ @@ -1680,6 +1724,7 @@ test! { ", stdout: "", stderr: r#"error: Variable `bar` not defined + --> justfile:1:7 | 1 | foo x=bar: | ^^^ @@ -1694,6 +1739,7 @@ foo x=bar(): ", stdout: "", stderr: r#"error: Call to unknown function `bar` + --> justfile:1:7 | 1 | foo x=bar(): | ^^^ @@ -1750,6 +1796,7 @@ test! { ", stderr: r#" error: Unterminated interpolation + --> justfile:2:8 | 2 | echo {{ | ^^ @@ -1765,6 +1812,7 @@ test! { ", stderr: r#" error: Unterminated interpolation + --> justfile:2:8 | 2 | echo {{ | ^^ @@ -1779,6 +1827,7 @@ assembly_source_files = %(wildcard src/arch/$(arch)/*.s) ", stderr: r#" error: Unknown start of token: + --> justfile:1:25 | 1 | assembly_source_files = %(wildcard src/arch/$(arch)/*.s) | ^ @@ -1877,6 +1926,7 @@ test! { ", stderr: " error: Expected '*', ':', '$', identifier, or '+', but found '=' + --> justfile:1:5 | 1 | foo = 'bar' | ^ @@ -2074,6 +2124,7 @@ test! { stdout: "", stderr: " error: Variable `a` not defined + --> justfile:3:9 | 3 | bar a b=a: | ^ diff --git a/tests/newline_escape.rs b/tests/newline_escape.rs index e83f4c6..3d0e1e1 100644 --- a/tests/newline_escape.rs +++ b/tests/newline_escape.rs @@ -72,6 +72,7 @@ fn newline_escape_deps_invalid_esc() { .stderr( " error: `\\ ` is not a valid escape sequence + --> justfile:1:11 | 1 | default: a\\ b | ^ @@ -92,6 +93,7 @@ fn newline_escape_unpaired_linefeed() { .stderr( " error: Unpaired carriage return + --> justfile:1:9 | 1 | default:\\\ra | ^ diff --git a/tests/no_exit_message.rs b/tests/no_exit_message.rs index e3abb4a..aa1ec88 100644 --- a/tests/no_exit_message.rs +++ b/tests/no_exit_message.rs @@ -53,6 +53,7 @@ hello: "#, stderr: r#" error: Unknown attribute `unknown-attribute` + --> justfile:2:2 | 2 | [unknown-attribute] | ^^^^^^^^^^^^^^^^^ @@ -70,6 +71,7 @@ hello: "#, stderr: r#" error: Expected identifier, but found ']' + --> justfile:2:2 | 2 | [] | ^ @@ -87,6 +89,7 @@ hello: "#, stderr: r#" error: Expected '@', '[', or identifier, but found comment + --> justfile:2:1 | 2 | # This is a doc comment | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -103,7 +106,7 @@ test! { hello: @exit 100 "#, - stderr: "error: Expected '@', '[', or identifier, but found end of line\n |\n2 | \n | ^\n", + stderr: "error: Expected '@', '[', or identifier, but found end of line\n --> justfile:2:1\n |\n2 | \n | ^\n", status: EXIT_FAILURE, } diff --git a/tests/recursion_limit.rs b/tests/recursion_limit.rs index 7941c24..d33c201 100644 --- a/tests/recursion_limit.rs +++ b/tests/recursion_limit.rs @@ -16,6 +16,7 @@ fn bugfix() { #[cfg(not(windows))] const RECURSION_LIMIT_REACHED: &str = " error: Parsing recursion depth exceeded + --> justfile:1:265 | 1 | foo: (x (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( | ^ @@ -24,6 +25,7 @@ error: Parsing recursion depth exceeded #[cfg(windows)] const RECURSION_LIMIT_REACHED: &str = " error: Parsing recursion depth exceeded + --> justfile:1:57 | 1 | foo: (x (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( | ^ diff --git a/tests/show.rs b/tests/show.rs index 9138489..e99912d 100644 --- a/tests/show.rs +++ b/tests/show.rs @@ -30,6 +30,7 @@ test! { args: ("--show", "f"), stderr: " error: Alias `f` has an unknown target `foo` + --> justfile:1:7 | 1 | alias f := foo | ^ diff --git a/tests/slash_operator.rs b/tests/slash_operator.rs index ab91559..7991d1c 100644 --- a/tests/slash_operator.rs +++ b/tests/slash_operator.rs @@ -48,6 +48,7 @@ fn no_rhs_once() { .stderr( " error: Expected backtick, identifier, '(', '/', or string, but found end of file + --> justfile:1:11 | 1 | x := 'a' / | ^ @@ -69,6 +70,7 @@ fn default_un_parenthesized() { .stderr( " error: Expected '*', ':', '$', identifier, or '+', but found '/' + --> justfile:1:11 | 1 | foo x='a' / 'b': | ^ @@ -90,6 +92,7 @@ fn no_lhs_un_parenthesized() { .stderr( " error: Expected backtick, identifier, '(', or string, but found '/' + --> justfile:1:7 | 1 | foo x=/ 'a' / 'b': | ^ diff --git a/tests/string.rs b/tests/string.rs index c932634..f7ddb05 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -88,6 +88,7 @@ a:"#, args: ("a"), stdout: "", stderr: "error: `\\q` is not a valid escape sequence + --> justfile:1:6 | 1 | x := \"\\q\" | ^^^^ @@ -108,6 +109,7 @@ a: args: ("a"), stdout: "", stderr: "error: Variable `foo` not defined + --> justfile:6:11 | 6 | echo '{{foo}}' | ^^^ @@ -128,6 +130,7 @@ a: args: ("a"), stdout: "", stderr: "error: Variable `bar` not defined + --> justfile:3:13 | 3 | whatever' + bar | ^^^ @@ -165,6 +168,7 @@ a: args: ("a"), stdout: "", stderr: "error: Variable `b` not defined + --> justfile:5:10 | 5 | echo {{b}} | ^ @@ -181,6 +185,7 @@ test! { stdout: "", stderr: " error: Unterminated string + --> justfile:1:6 | 1 | a b= ': | ^ @@ -197,6 +202,7 @@ test! { stdout: "", stderr: r#" error: Unterminated string + --> justfile:1:6 | 1 | a b= ": | ^ @@ -212,6 +218,7 @@ test! { ", stderr: r#" error: Unterminated backtick + --> justfile:1:8 | 1 | foo a= `echo blaaaaaah: | ^ @@ -228,6 +235,7 @@ test! { stdout: "", stderr: " error: Unterminated string + --> justfile:1:6 | 1 | a b= ''': | ^^^ @@ -244,6 +252,7 @@ test! { stdout: "", stderr: r#" error: Unterminated string + --> justfile:1:6 | 1 | a b= """: | ^^^ @@ -259,6 +268,7 @@ test! { ", stderr: r#" error: Unterminated backtick + --> justfile:1:8 | 1 | foo a= ```echo blaaaaaah: | ^^^ @@ -374,6 +384,7 @@ test! { ", stderr: " error: Backticks may not start with `#!` + --> justfile:1:6 | 1 | x := `#!/usr/bin/env sh` | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/subsequents.rs b/tests/subsequents.rs index 10fa25a..ef96685 100644 --- a/tests/subsequents.rs +++ b/tests/subsequents.rs @@ -47,6 +47,7 @@ test! { ", stderr: " error: Recipe `foo` depends on itself + --> justfile:1:9 | 1 | foo: && foo | ^^^ @@ -61,6 +62,7 @@ test! { ", stderr: " error: Recipe `foo` has unknown dependency `bar` + --> justfile:1:9 | 1 | foo: && bar | ^^^ @@ -77,6 +79,7 @@ test! { ", stderr: " error: Variable `y` not defined + --> justfile:3:14 | 3 | foo: && (bar y) | ^ diff --git a/tests/undefined_variables.rs b/tests/undefined_variables.rs index be982aa..1203a03 100644 --- a/tests/undefined_variables.rs +++ b/tests/undefined_variables.rs @@ -7,6 +7,7 @@ fn parameter_default_unknown_variable_in_expression() { .stderr( " error: Variable `b` not defined + --> justfile:1:8 | 1 | foo a=(b+''): | ^ @@ -27,6 +28,7 @@ fn unknown_variable_in_unary_call() { .stderr( " error: Variable `a` not defined + --> justfile:1:15 | 1 | foo x=env_var(a): | ^ @@ -47,6 +49,7 @@ fn unknown_first_variable_in_binary_call() { .stderr( " error: Variable `a` not defined + --> justfile:1:26 | 1 | foo x=env_var_or_default(a, b): | ^ @@ -67,6 +70,7 @@ fn unknown_second_variable_in_binary_call() { .stderr( " error: Variable `b` not defined + --> justfile:1:30 | 1 | foo x=env_var_or_default('', b): | ^ @@ -87,6 +91,7 @@ fn unknown_variable_in_ternary_call() { .stderr( " error: Variable `a` not defined + --> justfile:1:15 | 1 | foo x=replace(a, b, c): | ^