Add file paths to error messages (#1737)

This commit is contained in:
Casey Rodarmor 2023-11-21 20:17:38 -08:00 committed by GitHub
parent f745316e88
commit 7337447d42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 314 additions and 70 deletions

View File

@ -42,6 +42,7 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
column: 0, column: 0,
length: 0, length: 0,
kind: TokenKind::Unspecified, kind: TokenKind::Unspecified,
path: "".as_ref(),
}; };
return Err(CompileError::new(token, Internal { message })); return Err(CompileError::new(token, Internal { message }));
} }

View File

@ -60,6 +60,10 @@ impl Color {
self.redirect(Stream::Stdout) self.redirect(Stream::Stdout)
} }
pub(crate) fn context(self) -> Self {
self.restyle(Style::new().fg(Blue).bold())
}
pub(crate) fn doc(self) -> Self { pub(crate) fn doc(self) -> Self {
self.restyle(Style::new().fg(Blue)) self.restyle(Style::new().fg(Blue))
} }

View File

@ -15,8 +15,8 @@ impl Compiler {
paths.push(root.into()); paths.push(root.into());
while let Some(current) = paths.pop() { while let Some(current) = paths.pop() {
let src = loader.load(&current)?; let (relative, src) = loader.load(root, &current)?;
let tokens = Lexer::lex(src)?; let tokens = Lexer::lex(relative, src)?;
let mut ast = Parser::parse(&tokens)?; let mut ast = Parser::parse(&tokens)?;
srcs.insert(current.clone(), src); srcs.insert(current.clone(), src);
@ -56,9 +56,9 @@ impl Compiler {
#[cfg(test)] #[cfg(test)]
pub(crate) fn test_compile(src: &str) -> CompileResult<Justfile> { pub(crate) fn test_compile(src: &str) -> CompileResult<Justfile> {
let tokens = Lexer::lex(src)?; let tokens = Lexer::test_lex(src)?;
let ast = Parser::parse(&tokens)?; let ast = Parser::parse(&tokens)?;
let root = PathBuf::from("<ROOT>"); let root = PathBuf::from("justfile");
let mut asts: HashMap<PathBuf, Ast> = HashMap::new(); let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
asts.insert(root.clone(), ast); asts.insert(root.clone(), ast);
Analyzer::analyze(&asts, &root) Analyzer::analyze(&asts, &root)

View File

@ -9,38 +9,45 @@ use {super::*, CompileErrorKind::*, TokenKind::*};
/// slight against regular expressions, the lexer was just idiosyncratically /// slight against regular expressions, the lexer was just idiosyncratically
/// bad. /// bad.
pub(crate) struct Lexer<'src> { pub(crate) struct Lexer<'src> {
/// Source text
src: &'src str,
/// Char iterator /// Char iterator
chars: Chars<'src>, chars: Chars<'src>,
/// Tokens
tokens: Vec<Token<'src>>,
/// Current token start
token_start: Position,
/// Current token end
token_end: Position,
/// Next character to be lexed
next: Option<char>,
/// Next indent will start a recipe body
recipe_body_pending: bool,
/// Inside recipe body
recipe_body: bool,
/// Indentation stack /// Indentation stack
indentation: Vec<&'src str>, indentation: Vec<&'src str>,
/// Interpolation token start stack /// Interpolation token start stack
interpolation_stack: Vec<Token<'src>>, interpolation_stack: Vec<Token<'src>>,
/// Next character to be lexed
next: Option<char>,
/// Current open delimiters /// Current open delimiters
open_delimiters: Vec<(Delimiter, usize)>, 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<Token<'src>>,
/// Current token end
token_end: Position,
/// Current token start
token_start: Position,
} }
impl<'src> Lexer<'src> { impl<'src> Lexer<'src> {
/// Lex `text` /// Lex `src`
pub(crate) fn lex(src: &'src str) -> CompileResult<Vec<Token<'src>>> { pub(crate) fn lex(path: &'src Path, src: &'src str) -> CompileResult<'src, Vec<Token<'src>>> {
Lexer::new(src).tokenize() Lexer::new(path, src).tokenize()
} }
/// Create a new Lexer to lex `text` #[cfg(test)]
fn new(src: &'src str) -> Lexer<'src> { pub(crate) fn test_lex(src: &'src str) -> CompileResult<'src, Vec<Token<'src>>> {
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 mut chars = src.chars();
let next = chars.next(); let next = chars.next();
@ -62,6 +69,7 @@ impl<'src> Lexer<'src> {
chars, chars,
next, next,
src, src,
path,
} }
} }
@ -189,6 +197,7 @@ impl<'src> Lexer<'src> {
src: self.src, src: self.src,
length: self.token_end.offset - self.token_start.offset, length: self.token_end.offset - self.token_start.offset,
kind, kind,
path: self.path,
}); });
// Set `token_start` to point after the lexed token // Set `token_start` to point after the lexed token
@ -205,6 +214,7 @@ impl<'src> Lexer<'src> {
column: self.token_end.column, column: self.token_end.column,
length: 0, length: 0,
kind: Unspecified, kind: Unspecified,
path: self.path,
}; };
CompileError::new( CompileError::new(
token, token,
@ -240,6 +250,7 @@ impl<'src> Lexer<'src> {
line: self.token_start.line, line: self.token_start.line,
column: self.token_start.column, column: self.token_start.column,
length, length,
path: self.path,
}; };
CompileError::new(token, kind) CompileError::new(token, kind)
@ -920,7 +931,7 @@ mod tests {
text.to_owned() text.to_owned()
}; };
let have = Lexer::lex(&text).unwrap(); let have = Lexer::test_lex(&text).unwrap();
let have_kinds = have let have_kinds = have
.iter() .iter()
@ -1028,7 +1039,7 @@ mod tests {
length: usize, length: usize,
kind: CompileErrorKind, kind: CompileErrorKind,
) { ) {
match Lexer::lex(src) { match Lexer::test_lex(src) {
Ok(_) => panic!("Lexing succeeded but expected"), Ok(_) => panic!("Lexing succeeded but expected"),
Err(have) => { Err(have) => {
let want = CompileError { let want = CompileError {
@ -1039,6 +1050,7 @@ mod tests {
line, line,
column, column,
length, length,
path: "justfile".as_ref(),
}, },
kind: Box::new(kind), kind: Box::new(kind),
}; };
@ -2321,7 +2333,9 @@ mod tests {
#[test] #[test]
fn presume_error() { fn presume_error() {
let compile_error = Lexer::new("!").presume('-').unwrap_err(); let compile_error = Lexer::new("justfile".as_ref(), "!")
.presume('-')
.unwrap_err();
assert_matches!( assert_matches!(
compile_error.token, compile_error.token,
Token { Token {
@ -2331,6 +2345,7 @@ mod tests {
length: 0, length: 0,
src: "!", src: "!",
kind: Unspecified, kind: Unspecified,
path: _,
} }
); );
assert_matches!(&*compile_error.kind, assert_matches!(&*compile_error.kind,
@ -2342,9 +2357,12 @@ mod tests {
Error::Compile { compile_error } Error::Compile { compile_error }
.color_display(Color::never()) .color_display(Color::never())
.to_string(), .to_string(),
"error: Internal error, this may indicate a bug in just: \ "error: Internal error, this may indicate a bug in just: Lexer presumed character `-`
Lexer presumed character `-`\nconsider filing an issue: \ consider filing an issue: https://github.com/casey/just/issues/new
https://github.com/casey/just/issues/new\n |\n1 | !\n | ^" --> justfile:1:1
|
1 | !
| ^"
); );
} }
} }

View File

@ -1,22 +1,34 @@
use super::*; use super::*;
pub(crate) struct Loader { pub(crate) struct Loader {
arena: Arena<String>, srcs: Arena<String>,
paths: Arena<PathBuf>,
} }
impl Loader { impl Loader {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Loader { 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 { let src = fs::read_to_string(path).map_err(|io_error| Error::Load {
path: path.to_owned(), path: path.to_owned(),
io_error, 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)))
} }
} }

View File

@ -4,10 +4,11 @@ use super::*;
/// it its own type for clarity. /// it its own type for clarity.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
pub(crate) struct Name<'src> { pub(crate) struct Name<'src> {
pub(crate) offset: usize, pub(crate) column: usize,
pub(crate) length: usize, pub(crate) length: usize,
pub(crate) line: 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) src: &'src str,
} }
@ -20,11 +21,12 @@ impl<'src> Name<'src> {
/// Turn this name back into a token /// Turn this name back into a token
pub(crate) fn token(&self) -> Token<'src> { pub(crate) fn token(&self) -> Token<'src> {
Token { Token {
column: self.column,
kind: TokenKind::Identifier, kind: TokenKind::Identifier,
offset: self.offset,
length: self.length, length: self.length,
line: self.line, line: self.line,
column: self.column, offset: self.offset,
path: self.path,
src: self.src, src: self.src,
} }
} }
@ -32,10 +34,11 @@ impl<'src> Name<'src> {
pub(crate) fn from_identifier(token: Token<'src>) -> Name { pub(crate) fn from_identifier(token: Token<'src>) -> Name {
assert_eq!(token.kind, TokenKind::Identifier); assert_eq!(token.kind, TokenKind::Identifier);
Name { Name {
offset: token.offset, column: token.column,
length: token.length, length: token.length,
line: token.line, line: token.line,
column: token.column, offset: token.offset,
path: token.path,
src: token.src, src: token.src,
} }
} }

View File

@ -927,7 +927,7 @@ mod tests {
fn test(text: &str, want: Tree) { fn test(text: &str, want: Tree) {
let unindented = unindent(text); 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 justfile = Parser::parse(&tokens).expect("parsing failed");
let have = justfile.tree(); let have = justfile.tree();
if have != want { if have != want {
@ -964,7 +964,7 @@ mod tests {
length: usize, length: usize,
kind: CompileErrorKind, 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) { match Parser::parse(&tokens) {
Ok(_) => panic!("Parsing unexpectedly succeeded"), Ok(_) => panic!("Parsing unexpectedly succeeded"),
@ -977,6 +977,7 @@ mod tests {
line, line,
column, column,
length, length,
path: "justfile".as_ref(),
}, },
kind: Box::new(kind), kind: Box::new(kind),
}; };

View File

@ -57,11 +57,11 @@ pub(crate) fn analysis_error(
length: usize, length: usize,
kind: CompileErrorKind, 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 ast = Parser::parse(&tokens).expect("Parsing failed in analysis test...");
let root = PathBuf::from("<ROOT>"); let root = PathBuf::from("justfile");
let mut asts: HashMap<PathBuf, Ast> = HashMap::new(); let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
asts.insert(root.clone(), ast); asts.insert(root.clone(), ast);
@ -76,6 +76,7 @@ pub(crate) fn analysis_error(
line, line,
column, column,
length, length,
path: "justfile".as_ref(),
}, },
kind: Box::new(kind), kind: Box::new(kind),
}; };

View File

@ -2,12 +2,13 @@ use super::*;
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
pub(crate) struct Token<'src> { pub(crate) struct Token<'src> {
pub(crate) offset: usize, pub(crate) column: usize,
pub(crate) kind: TokenKind,
pub(crate) length: usize, pub(crate) length: usize,
pub(crate) line: 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) src: &'src str,
pub(crate) kind: TokenKind,
} }
impl<'src> Token<'src> { impl<'src> Token<'src> {
@ -52,9 +53,35 @@ impl<'src> ColorDisplay for Token<'src> {
i += c.len_utf8(); i += c.len_utf8();
} }
let line_number_width = line_number.to_string().len(); let line_number_width = line_number.to_string().len();
writeln!(f, "{0:1$} |", "", line_number_width)?; writeln!(
writeln!(f, "{line_number} | {space_line}")?; f,
write!(f, "{0:1$} |", "", line_number_width)?; "{: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!( write!(
f, f,
" {0:1$}{2}{3:^<4$}{5}", " {0:1$}{2}{3:^<4$}{5}",

View File

@ -33,6 +33,7 @@ fn duplicate_attributes_are_disallowed() {
.stderr( .stderr(
" "
error: Recipe attribute `no-exit-message` first used on line 1 is duplicated on line 2 error: Recipe attribute `no-exit-message` first used on line 1 is duplicated on line 2
--> justfile:2:2
| |
2 | [no-exit-message] 2 | [no-exit-message]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
@ -72,6 +73,7 @@ fn multiple_attributes_one_line_error_message() {
.stderr( .stderr(
" "
error: Expected ']' or ',', but found identifier error: Expected ']' or ',', but found identifier
--> justfile:1:17
| |
1 | [macos, windows linux] 1 | [macos, windows linux]
| ^^^^^ | ^^^^^
@ -95,6 +97,7 @@ fn multiple_attributes_one_line_duplicate_check() {
.stderr( .stderr(
" "
error: Recipe attribute `linux` first used on line 1 is duplicated on line 2 error: Recipe attribute `linux` first used on line 1 is duplicated on line 2
--> justfile:2:2
| |
2 | [linux] 2 | [linux]
| ^^^^^ | ^^^^^

View File

@ -27,6 +27,7 @@ fn non_leading_byte_order_mark_produces_error() {
.stderr( .stderr(
" "
error: Expected \'@\', '!', \'[\', comment, end of file, end of line, or identifier, but found byte order mark error: Expected \'@\', '!', \'[\', comment, end of file, end of line, or identifier, but found byte order mark
--> justfile:3:1
| |
3 | \u{feff} 3 | \u{feff}
| ^ | ^
@ -42,6 +43,7 @@ fn dont_mention_byte_order_mark_in_errors() {
.stderr( .stderr(
" "
error: Expected '@', '!', '[', comment, end of file, end of line, or identifier, but found '{' error: Expected '@', '!', '[', comment, end of file, end of line, or identifier, but found '{'
--> justfile:1:1
| |
1 | { 1 | {
| ^ | ^

View File

@ -61,6 +61,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Variable `b` not defined error: Variable `b` not defined
--> justfile:1:9
| |
1 | a := if b == '' { '' } else { '' } 1 | a := if b == '' { '' } else { '' }
| ^ | ^
@ -79,6 +80,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Variable `b` not defined error: Variable `b` not defined
--> justfile:1:15
| |
1 | a := if '' == b { '' } else { '' } 1 | a := if '' == b { '' } else { '' }
| ^ | ^
@ -97,6 +99,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Variable `b` not defined error: Variable `b` not defined
--> justfile:1:20
| |
1 | a := if '' == '' { b } else { '' } 1 | a := if '' == '' { b } else { '' }
| ^ | ^
@ -115,6 +118,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Variable `b` not defined error: Variable `b` not defined
--> justfile:1:32
| |
1 | a := if '' == '' { '' } else { b } 1 | a := if '' == '' { '' } else { b }
| ^ | ^
@ -133,6 +137,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Expected '!=', '==', '=~', '+', or '/', but found identifier error: Expected '!=', '==', '=~', '+', or '/', but found identifier
--> justfile:1:12
| |
1 | a := if '' a '' { '' } else { b } 1 | a := if '' a '' { '' } else { b }
| ^ | ^
@ -177,6 +182,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Expected keyword `else` but found `end of line` error: Expected keyword `else` but found `end of line`
--> justfile:1:54
| |
1 | TEST := if path_exists('/bin/bash') == 'true' {'yes'} 1 | TEST := if path_exists('/bin/bash') == 'true' {'yes'}
| ^ | ^
@ -192,6 +198,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Expected keyword `else` but found identifier `els` error: Expected keyword `else` but found identifier `els`
--> justfile:1:55
| |
1 | TEST := if path_exists('/bin/bash') == 'true' {'yes'} els {'no'} 1 | TEST := if path_exists('/bin/bash') == 'true' {'yes'} els {'no'}
| ^^^ | ^^^

View File

@ -5,6 +5,7 @@ test! {
justfile: "(]", justfile: "(]",
stderr: " stderr: "
error: Mismatched closing delimiter `]`. (Did you mean to close the `(` on line 1?) error: Mismatched closing delimiter `]`. (Did you mean to close the `(` on line 1?)
--> justfile:1:2
| |
1 | (] 1 | (]
| ^ | ^
@ -17,6 +18,7 @@ test! {
justfile: "]", justfile: "]",
stderr: " stderr: "
error: Unexpected closing delimiter `]` error: Unexpected closing delimiter `]`
--> justfile:1:1
| |
1 | ] 1 | ]
| ^ | ^
@ -96,6 +98,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Unterminated interpolation error: Unterminated interpolation
--> justfile:2:8
| |
2 | echo {{ ( 2 | echo {{ (
| ^^ | ^^

View File

@ -5,6 +5,7 @@ test! {
justfile: "[private]\n[linux]\nalias t := test\n\ntest:\n", justfile: "[private]\n[linux]\nalias t := test\n\ntest:\n",
stderr: " stderr: "
error: Alias t has an invalid attribute `linux` error: Alias t has an invalid attribute `linux`
--> justfile:3:7
| |
3 | alias t := test 3 | alias t := test
| ^ | ^
@ -17,6 +18,7 @@ test! {
justfile: "foo := if '' == '' { '' } arlo { '' }", justfile: "foo := if '' == '' { '' } arlo { '' }",
stderr: " stderr: "
error: Expected keyword `else` but found identifier `arlo` error: Expected keyword `else` but found identifier `arlo`
--> justfile:1:27
| |
1 | foo := if '' == '' { '' } arlo { '' } 1 | foo := if '' == '' { '' } arlo { '' }
| ^^^^ | ^^^^
@ -29,6 +31,7 @@ test! {
justfile: "&~", justfile: "&~",
stderr: " stderr: "
error: Expected character `&` error: Expected character `&`
--> justfile:1:2
| |
1 | &~ 1 | &~
| ^ | ^
@ -51,3 +54,61 @@ fn argument_count_mismatch() {
.status(EXIT_FAILURE) .status(EXIT_FAILURE)
.run(); .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();
}

View File

@ -146,6 +146,7 @@ fn print_error_from_parent_if_recipe_not_found_in_current() {
.stderr( .stderr(
" "
error: Variable `bar` not defined error: Variable `bar` not defined
--> justfile:2:9
| |
2 | echo {{bar}} 2 | echo {{bar}}
| ^^^ | ^^^

View File

@ -85,9 +85,10 @@ foo:
/bin/echo '{{we}}' /bin/echo '{{we}}'
"#, "#,
stdout: "", stdout: "",
stderr: format!("{} {}\n{}\n{}\n{}\n", stderr: format!("{} {}\n{}\n{}\n{}\n{}\n",
"error: Call to function `without_extension` failed:", "error: Call to function `without_extension` failed:",
"Could not extract parent from ``", "Could not extract parent from ``",
" --> justfile:1:8",
" |", " |",
"1 | we := without_extension(\'\')", "1 | we := without_extension(\'\')",
" | ^^^^^^^^^^^^^^^^^").as_str(), " | ^^^^^^^^^^^^^^^^^").as_str(),
@ -104,8 +105,9 @@ foo:
/bin/echo '{{we}}' /bin/echo '{{we}}'
"#, "#,
stdout: "", 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 ``", "error: Call to function `extension` failed: Could not extract extension from ``",
" --> justfile:1:8",
" |", " |",
"1 | we := extension(\'\')", "1 | we := extension(\'\')",
" | ^^^^^^^^^").as_str(), " | ^^^^^^^^^").as_str(),
@ -122,8 +124,9 @@ foo:
/bin/echo '{{we}}' /bin/echo '{{we}}'
"#, "#,
stdout: "", 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`", "error: Call to function `extension` failed: Could not extract extension from `foo`",
" --> justfile:1:8",
" |", " |",
"1 | we := extension(\'foo\')", "1 | we := extension(\'foo\')",
" | ^^^^^^^^^").as_str(), " | ^^^^^^^^^").as_str(),
@ -140,8 +143,9 @@ foo:
/bin/echo '{{we}}' /bin/echo '{{we}}'
"#, "#,
stdout: "", 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 ``", "error: Call to function `file_stem` failed: Could not extract file stem from ``",
" --> justfile:1:8",
" |", " |",
"1 | we := file_stem(\'\')", "1 | we := file_stem(\'\')",
" | ^^^^^^^^^").as_str(), " | ^^^^^^^^^").as_str(),
@ -158,8 +162,9 @@ foo:
/bin/echo '{{we}}' /bin/echo '{{we}}'
"#, "#,
stdout: "", 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 ``", "error: Call to function `file_name` failed: Could not extract file name from ``",
" --> justfile:1:8",
" |", " |",
"1 | we := file_name(\'\')", "1 | we := file_name(\'\')",
" | ^^^^^^^^^").as_str(), " | ^^^^^^^^^").as_str(),
@ -176,9 +181,10 @@ foo:
/bin/echo '{{we}}' /bin/echo '{{we}}'
"#, "#,
stdout: "", stdout: "",
stderr: format!("{} {}\n{}\n{}\n{}\n", stderr: format!("{} {}\n{}\n{}\n{}\n{}\n",
"error: Call to function `parent_directory` failed:", "error: Call to function `parent_directory` failed:",
"Could not extract parent directory from ``", "Could not extract parent directory from ``",
" --> justfile:1:8",
" |", " |",
"1 | we := parent_directory(\'\')", "1 | we := parent_directory(\'\')",
" | ^^^^^^^^^^^^^^^^").as_str(), " | ^^^^^^^^^^^^^^^^").as_str(),
@ -195,9 +201,10 @@ foo:
/bin/echo '{{we}}' /bin/echo '{{we}}'
"#, "#,
stdout: "", stdout: "",
stderr: format!("{} {}\n{}\n{}\n{}\n", stderr: format!("{} {}\n{}\n{}\n{}\n{}\n",
"error: Call to function `parent_directory` failed:", "error: Call to function `parent_directory` failed:",
"Could not extract parent directory from `/`", "Could not extract parent directory from `/`",
" --> justfile:1:8",
" |", " |",
"1 | we := parent_directory(\'/\')", "1 | we := parent_directory(\'/\')",
" | ^^^^^^^^^^^^^^^^").as_str(), " | ^^^^^^^^^^^^^^^^").as_str(),
@ -225,6 +232,7 @@ test! {
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: Call to function `env_var` failed: environment variable `ZADDY` not present stderr: "error: Call to function `env_var` failed: environment variable `ZADDY` not present
--> justfile:2:10
| |
2 | echo {{env_var('ZADDY')}} 2 | echo {{env_var('ZADDY')}}
| ^^^^^^^ | ^^^^^^^
@ -395,6 +403,7 @@ test! {
foo\\ foo\\
^ ^
error: incomplete escape sequence, reached end of pattern prematurely error: incomplete escape sequence, reached end of pattern prematurely
--> justfile:2:11
| |
2 | echo {{ replace_regex('barbarbar', 'foo\\', 'foo') }} 2 | echo {{ replace_regex('barbarbar', 'foo\\', 'foo') }}
| ^^^^^^^^^^^^^ | ^^^^^^^^^^^^^
@ -498,6 +507,7 @@ fn join_argument_count_error() {
.stderr( .stderr(
" "
error: Function `join` called with 1 argument but takes 2 or more error: Function `join` called with 1 argument but takes 2 or more
--> justfile:1:6
| |
1 | x := join(\'a\') 1 | x := join(\'a\')
| ^^^^ | ^^^^
@ -534,7 +544,15 @@ fn error_errors_with_message() {
.justfile("x := error ('Thing Not Supported')") .justfile("x := error ('Thing Not Supported')")
.args(["--evaluate"]) .args(["--evaluate"])
.status(1) .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(); .run();
} }

View File

@ -58,6 +58,7 @@ fn include_directive_with_no_path() {
.stderr( .stderr(
" "
error: !include directive has no argument error: !include directive has no argument
--> justfile:1:9
| |
1 | !include 1 | !include
| ^ | ^

View File

@ -20,7 +20,7 @@ pub(crate) use {
fs, fs,
io::Write, io::Write,
iter, iter,
path::{Path, PathBuf}, path::{Path, PathBuf, MAIN_SEPARATOR},
process::{Command, Stdio}, process::{Command, Stdio},
str, str,
}, },

View File

@ -72,6 +72,7 @@ test! {
", ",
stderr: " stderr: "
error: Unknown setting `foo` error: Unknown setting `foo`
--> justfile:1:5
| |
1 | set foo 1 | set foo
| ^^^ | ^^^
@ -86,6 +87,7 @@ test! {
", ",
stderr: " stderr: "
error: Unknown setting `if` error: Unknown setting `if`
--> justfile:1:5
| |
1 | set if := 'foo' 1 | set if := 'foo'
| ^^ | ^^
@ -106,6 +108,7 @@ test! {
justfile: "alias foo := bar\nalias foo := baz\n", justfile: "alias foo := bar\nalias foo := baz\n",
stderr: " stderr: "
error: Alias `foo` first defined on line 1 is redefined on line 2 error: Alias `foo` first defined on line 1 is redefined on line 2
--> justfile:2:7
| |
2 | alias foo := baz 2 | alias foo := baz
| ^^^ | ^^^
@ -118,6 +121,7 @@ test! {
justfile: "alias foo := bar\n", justfile: "alias foo := bar\n",
stderr: " stderr: "
error: Alias `foo` has an unknown target `bar` error: Alias `foo` has an unknown target `bar`
--> justfile:1:7
| |
1 | alias foo := bar 1 | alias foo := bar
| ^^^ | ^^^
@ -130,6 +134,7 @@ test! {
justfile: "bar:\n echo bar\nalias foo := bar\nfoo:\n echo foo", justfile: "bar:\n echo bar\nalias foo := bar\nfoo:\n echo foo",
stderr: " stderr: "
error: Alias `foo` defined on line 3 shadows recipe `foo` defined on line 4 error: Alias `foo` defined on line 3 shadows recipe `foo` defined on line 4
--> justfile:3:7
| |
3 | alias foo := bar 3 | alias foo := bar
| ^^^ | ^^^
@ -264,6 +269,7 @@ test! {
justfile: "bar:\nhello:\nfoo: bar baaaaaaaz hello", justfile: "bar:\nhello:\nfoo: bar baaaaaaaz hello",
stderr: " stderr: "
error: Recipe `foo` has unknown dependency `baaaaaaaz` error: Recipe `foo` has unknown dependency `baaaaaaaz`
--> justfile:3:10
| |
3 | foo: bar baaaaaaaz hello 3 | foo: bar baaaaaaaz hello
| ^^^^^^^^^ | ^^^^^^^^^
@ -290,6 +296,7 @@ test! {
justfile: "b := a\na := `exit 100`\nbar:\n echo '{{`exit 200`}}'", justfile: "b := a\na := `exit 100`\nbar:\n echo '{{`exit 200`}}'",
stderr: " stderr: "
error: Backtick failed with exit code 100 error: Backtick failed with exit code 100
--> justfile:2:6
| |
2 | a := `exit 100` 2 | a := `exit 100`
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -302,6 +309,7 @@ test! {
justfile: "b := a\na := `echo hello`\nbar:\n echo '{{`exit 200`}}'", justfile: "b := a\na := `echo hello`\nbar:\n echo '{{`exit 200`}}'",
stderr: " stderr: "
error: Backtick failed with exit code 200 error: Backtick failed with exit code 200
--> justfile:4:10
| |
4 | echo '{{`exit 200`}}' 4 | echo '{{`exit 200`}}'
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -314,6 +322,7 @@ test! {
justfile: "f:\n 無{{`exit 200`}}", justfile: "f:\n 無{{`exit 200`}}",
stderr: " stderr: "
error: Backtick failed with exit code 200 error: Backtick failed with exit code 200
--> justfile:2:7
| |
2 | {{`exit 200`}} 2 | {{`exit 200`}}
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -328,6 +337,7 @@ test! {
\techo {{`exit 200`}} \techo {{`exit 200`}}
", ",
stderr: " error: Backtick failed with exit code 200 stderr: " error: Backtick failed with exit code 200
--> justfile:2:9
| |
2 | echo {{`exit 200`}} 2 | echo {{`exit 200`}}
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -342,6 +352,7 @@ test! {
\techo {{\t`exit 200`}} \techo {{\t`exit 200`}}
", ",
stderr: "error: Backtick failed with exit code 200 stderr: "error: Backtick failed with exit code 200
--> justfile:2:10
| |
2 | echo {{ `exit 200`}} 2 | echo {{ `exit 200`}}
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -357,6 +368,7 @@ test! {
", ",
stderr: " stderr: "
error: Backtick failed with exit code 200 error: Backtick failed with exit code 200
--> justfile:2:10
| |
2 | echo {{ `exit 200`}} 2 | echo {{ `exit 200`}}
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
@ -372,6 +384,7 @@ test! {
", ",
stderr: " stderr: "
error: Backtick failed with exit code 200 error: Backtick failed with exit code 200
--> justfile:2:13
| |
2 | echo 😬{{`exit 200`}} 2 | echo 😬{{`exit 200`}}
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -387,6 +400,7 @@ test! {
", ",
stderr: " stderr: "
error: Backtick failed with exit code 200 error: Backtick failed with exit code 200
--> justfile:2:24
| |
2 | echo 😬{{ `exit 200 # abc`}} 😬 2 | echo 😬{{ `exit 200 # abc`}} 😬
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -410,6 +424,7 @@ test! {
", ",
stderr: " stderr: "
error: Backtick failed with exit code 200 error: Backtick failed with exit code 200
--> justfile:10:10
| |
10 | echo '{{`exit 200`}}' 10 | echo '{{`exit 200`}}'
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -426,6 +441,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Backtick failed with exit code 123 error: Backtick failed with exit code 123
--> justfile:4:9
| |
4 | echo {{`exit 123`}} 4 | echo {{`exit 123`}}
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -442,6 +458,7 @@ test! {
stderr: " stderr: "
echo hello echo hello
error: Backtick failed with exit code 123 error: Backtick failed with exit code 123
--> justfile:3:9
| |
3 | echo {{`exit 123`}} 3 | echo {{`exit 123`}}
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -458,6 +475,7 @@ a := `exit 222`",
stdout: "", stdout: "",
stderr: " stderr: "
error: Backtick failed with exit code 222 error: Backtick failed with exit code 222
--> justfile:4:6
| |
4 | a := `exit 222` 4 | a := `exit 222`
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -573,6 +591,7 @@ test! {
"#, "#,
stdout: "", stdout: "",
stderr: "error: Unknown start of token: stderr: "error: Unknown start of token:
--> justfile:10:1
| |
10 | ??? 10 | ???
| ^ | ^
@ -677,8 +696,7 @@ test! {
justfile: "b := a\na := `exit 100`\nbar:\n echo '{{`exit 200`}}'", justfile: "b := a\na := `exit 100`\nbar:\n echo '{{`exit 200`}}'",
args: ("--color", "always"), args: ("--color", "always"),
stdout: "", stdout: "",
stderr: "\u{1b}[1;31merror\u{1b}[0m: \u{1b}[1mBacktick failed with exit code 100\u{1b}[0m 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",
|\n2 | a := `exit 100`\n | \u{1b}[1;31m^^^^^^^^^^\u{1b}[0m\n",
status: 100, status: 100,
} }
@ -688,6 +706,7 @@ test! {
args: ("--color", "never"), args: ("--color", "never"),
stdout: "", stdout: "",
stderr: "error: Backtick failed with exit code 100 stderr: "error: Backtick failed with exit code 100
--> justfile:2:6
| |
2 | a := `exit 100` 2 | a := `exit 100`
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -701,6 +720,7 @@ test! {
args: ("--color", "auto"), args: ("--color", "auto"),
stdout: "", stdout: "",
stderr: "error: Backtick failed with exit code 100 stderr: "error: Backtick failed with exit code 100
--> justfile:2:6
| |
2 | a := `exit 100` 2 | a := `exit 100`
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -739,6 +759,7 @@ test! {
stdout: "", stdout: "",
stderr: "error: Found a mix of tabs and spaces in leading whitespace: `␉␠` stderr: "error: Found a mix of tabs and spaces in leading whitespace: `␉␠`
Leading whitespace may consist of tabs or spaces, but not both Leading whitespace may consist of tabs or spaces, but not both
--> justfile:2:1
| |
2 | echo hello 2 | echo hello
| ^^^^^ | ^^^^^
@ -751,6 +772,7 @@ test! {
justfile: "bar:\n\t\techo hello\n\t\t\techo goodbye", justfile: "bar:\n\t\techo hello\n\t\t\techo goodbye",
stdout: "", stdout: "",
stderr: "error: Recipe line has extra leading whitespace stderr: "error: Recipe line has extra leading whitespace
--> justfile:3:3
| |
3 | echo goodbye 3 | echo goodbye
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
@ -764,6 +786,7 @@ test! {
stdout: "", stdout: "",
stderr: "error: Recipe line has inconsistent leading whitespace. \ stderr: "error: Recipe line has inconsistent leading whitespace. \
Recipe started with `` but found line with `` Recipe started with `` but found line with ``
--> justfile:3:1
| |
3 | echo goodbye 3 | echo goodbye
| ^^^^^ | ^^^^^
@ -776,6 +799,7 @@ test! {
justfile: "bar:\nhello baz arg='foo' bar:", justfile: "bar:\nhello baz arg='foo' bar:",
stdout: "", stdout: "",
stderr: "error: Non-default parameter `bar` follows default parameter stderr: "error: Non-default parameter `bar` follows default parameter
--> justfile:2:21
| |
2 | hello baz arg='foo' bar: 2 | hello baz arg='foo' bar:
| ^^^ | ^^^
@ -788,6 +812,7 @@ test! {
justfile: "bar:\nhello baz +arg bar:", justfile: "bar:\nhello baz +arg bar:",
stdout: "", stdout: "",
stderr: "error: Parameter `bar` follows variadic parameter stderr: "error: Parameter `bar` follows variadic parameter
--> justfile:2:16
| |
2 | hello baz +arg bar: 2 | hello baz +arg bar:
| ^^^ | ^^^
@ -800,6 +825,7 @@ test! {
justfile: "bar:\nhello baz *arg bar:", justfile: "bar:\nhello baz *arg bar:",
stdout: "", stdout: "",
stderr: "error: Parameter `bar` follows variadic parameter stderr: "error: Parameter `bar` follows variadic parameter
--> justfile:2:16
| |
2 | hello baz *arg bar: 2 | hello baz *arg bar:
| ^^^ | ^^^
@ -1172,6 +1198,7 @@ bar:"#,
args: ("bar"), args: ("bar"),
stdout: "", stdout: "",
stderr: r#"error: Call to unknown function `foo` stderr: r#"error: Call to unknown function `foo`
--> justfile:1:8
| |
1 | foo := foo() + "hello" 1 | foo := foo() + "hello"
| ^^^ | ^^^
@ -1188,6 +1215,7 @@ test! {
args: ("b"), args: ("b"),
stdout: "", stdout: "",
stderr: "error: Dependency `a` got 0 arguments but takes 1 argument stderr: "error: Dependency `a` got 0 arguments but takes 1 argument
--> justfile:2:4
| |
2 | b: a 2 | b: a
| ^ | ^
@ -1204,6 +1232,7 @@ test! {
args: ("b"), args: ("b"),
stdout: "", stdout: "",
stderr: "error: Dependency `a` got 0 arguments but takes at least 1 argument stderr: "error: Dependency `a` got 0 arguments but takes at least 1 argument
--> justfile:2:4
| |
2 | b: a 2 | b: a
| ^ | ^
@ -1220,6 +1249,7 @@ test! {
args: ("b"), args: ("b"),
stdout: "", stdout: "",
stderr: "error: Dependency `a` got 3 arguments but takes at most 2 arguments stderr: "error: Dependency `a` got 3 arguments but takes at most 2 arguments
--> justfile:2:5
| |
2 | b: (a '0' '1' '2') 2 | b: (a '0' '1' '2')
| ^ | ^
@ -1233,6 +1263,7 @@ test! {
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: Recipe `a` has duplicate parameter `foo` stderr: "error: Recipe `a` has duplicate parameter `foo`
--> justfile:1:7
| |
1 | a foo foo: 1 | a foo foo:
| ^^^ | ^^^
@ -1246,6 +1277,7 @@ test! {
args: ("b"), args: ("b"),
stdout: "", stdout: "",
stderr: "error: Recipe `b` first defined on line 1 is redefined on line 2 stderr: "error: Recipe `b` first defined on line 1 is redefined on line 2
--> justfile:2:1
| |
2 | b: 2 | b:
| ^ | ^
@ -1259,6 +1291,7 @@ test! {
args: ("foo"), args: ("foo"),
stdout: "", stdout: "",
stderr: "error: Variable `a` has multiple definitions stderr: "error: Variable `a` has multiple definitions
--> justfile:2:1
| |
2 | a := 'hello' 2 | a := 'hello'
| ^ | ^
@ -1273,6 +1306,7 @@ test! {
stdout: "", stdout: "",
stderr: "error: Expected '&&', comment, end of file, end of line, \ stderr: "error: Expected '&&', comment, end of file, end of line, \
identifier, or '(', but found string identifier, or '(', but found string
--> justfile:1:6
| |
1 | foo: 'bar' 1 | foo: 'bar'
| ^^^^^ | ^^^^^
@ -1286,6 +1320,7 @@ test! {
args: ("foo"), args: ("foo"),
stdout: "", stdout: "",
stderr: "error: Expected '*', ':', '$', identifier, or '+', but found string stderr: "error: Expected '*', ':', '$', identifier, or '+', but found string
--> justfile:1:5
| |
1 | foo 'bar' 1 | foo 'bar'
| ^^^^^ | ^^^^^
@ -1299,6 +1334,7 @@ test! {
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: Recipe `a` depends on itself stderr: "error: Recipe `a` depends on itself
--> justfile:1:4
| |
1 | a: a 1 | a: a
| ^ | ^
@ -1312,6 +1348,7 @@ test! {
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: Recipe `d` has circular dependency `a -> b -> c -> d -> a` stderr: "error: Recipe `d` has circular dependency `a -> b -> c -> d -> a`
--> justfile:4:4
| |
4 | d: a 4 | d: a
| ^ | ^
@ -1325,6 +1362,7 @@ test! {
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: Variable `z` is defined in terms of itself stderr: "error: Variable `z` is defined in terms of itself
--> justfile:1:1
| |
1 | z := z 1 | z := z
| ^ | ^
@ -1338,6 +1376,7 @@ test! {
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: Variable `x` depends on its own value: `x -> y -> z -> x` stderr: "error: Variable `x` depends on its own value: `x -> y -> z -> x`
--> justfile:1:1
| |
1 | x := y 1 | x := y
| ^ | ^
@ -1357,6 +1396,7 @@ test! {
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: Variable `x` depends on its own value: `x -> y -> x` stderr: "error: Variable `x` depends on its own value: `x -> y -> x`
--> justfile:2:1
| |
2 | x := y 2 | x := y
| ^ | ^
@ -1461,6 +1501,7 @@ foo *a +b:
", ",
stdout: "", stdout: "",
stderr: "error: Expected \':\' or \'=\', but found \'+\' stderr: "error: Expected \':\' or \'=\', but found \'+\'
--> justfile:1:8
| |
1 | foo *a +b: 1 | foo *a +b:
| ^ | ^
@ -1476,6 +1517,7 @@ foo +a *b:
", ",
stdout: "", stdout: "",
stderr: "error: Expected \':\' or \'=\', but found \'*\' stderr: "error: Expected \':\' or \'=\', but found \'*\'
--> justfile:1:8
| |
1 | foo +a *b: 1 | foo +a *b:
| ^ | ^
@ -1509,6 +1551,7 @@ a: x y
", ",
stdout: "", stdout: "",
stderr: "error: Recipe `a` has unknown dependency `y` stderr: "error: Recipe `a` has unknown dependency `y`
--> justfile:3:6
| |
3 | a: x y 3 | a: x y
| ^ | ^
@ -1666,6 +1709,7 @@ X := "\'"
"#, "#,
stdout: "", stdout: "",
stderr: r#"error: `\'` is not a valid escape sequence stderr: r#"error: `\'` is not a valid escape sequence
--> justfile:1:6
| |
1 | X := "\'" 1 | X := "\'"
| ^^^^ | ^^^^
@ -1680,6 +1724,7 @@ test! {
", ",
stdout: "", stdout: "",
stderr: r#"error: Variable `bar` not defined stderr: r#"error: Variable `bar` not defined
--> justfile:1:7
| |
1 | foo x=bar: 1 | foo x=bar:
| ^^^ | ^^^
@ -1694,6 +1739,7 @@ foo x=bar():
", ",
stdout: "", stdout: "",
stderr: r#"error: Call to unknown function `bar` stderr: r#"error: Call to unknown function `bar`
--> justfile:1:7
| |
1 | foo x=bar(): 1 | foo x=bar():
| ^^^ | ^^^
@ -1750,6 +1796,7 @@ test! {
", ",
stderr: r#" stderr: r#"
error: Unterminated interpolation error: Unterminated interpolation
--> justfile:2:8
| |
2 | echo {{ 2 | echo {{
| ^^ | ^^
@ -1765,6 +1812,7 @@ test! {
", ",
stderr: r#" stderr: r#"
error: Unterminated interpolation error: Unterminated interpolation
--> justfile:2:8
| |
2 | echo {{ 2 | echo {{
| ^^ | ^^
@ -1779,6 +1827,7 @@ assembly_source_files = %(wildcard src/arch/$(arch)/*.s)
", ",
stderr: r#" stderr: r#"
error: Unknown start of token: error: Unknown start of token:
--> justfile:1:25
| |
1 | assembly_source_files = %(wildcard src/arch/$(arch)/*.s) 1 | assembly_source_files = %(wildcard src/arch/$(arch)/*.s)
| ^ | ^
@ -1877,6 +1926,7 @@ test! {
", ",
stderr: " stderr: "
error: Expected '*', ':', '$', identifier, or '+', but found '=' error: Expected '*', ':', '$', identifier, or '+', but found '='
--> justfile:1:5
| |
1 | foo = 'bar' 1 | foo = 'bar'
| ^ | ^
@ -2074,6 +2124,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Variable `a` not defined error: Variable `a` not defined
--> justfile:3:9
| |
3 | bar a b=a: 3 | bar a b=a:
| ^ | ^

View File

@ -72,6 +72,7 @@ fn newline_escape_deps_invalid_esc() {
.stderr( .stderr(
" "
error: `\\ ` is not a valid escape sequence error: `\\ ` is not a valid escape sequence
--> justfile:1:11
| |
1 | default: a\\ b 1 | default: a\\ b
| ^ | ^
@ -92,6 +93,7 @@ fn newline_escape_unpaired_linefeed() {
.stderr( .stderr(
" "
error: Unpaired carriage return error: Unpaired carriage return
--> justfile:1:9
| |
1 | default:\\\ra 1 | default:\\\ra
| ^ | ^

View File

@ -53,6 +53,7 @@ hello:
"#, "#,
stderr: r#" stderr: r#"
error: Unknown attribute `unknown-attribute` error: Unknown attribute `unknown-attribute`
--> justfile:2:2
| |
2 | [unknown-attribute] 2 | [unknown-attribute]
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
@ -70,6 +71,7 @@ hello:
"#, "#,
stderr: r#" stderr: r#"
error: Expected identifier, but found ']' error: Expected identifier, but found ']'
--> justfile:2:2
| |
2 | [] 2 | []
| ^ | ^
@ -87,6 +89,7 @@ hello:
"#, "#,
stderr: r#" stderr: r#"
error: Expected '@', '[', or identifier, but found comment error: Expected '@', '[', or identifier, but found comment
--> justfile:2:1
| |
2 | # This is a doc comment 2 | # This is a doc comment
| ^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^
@ -103,7 +106,7 @@ test! {
hello: hello:
@exit 100 @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, status: EXIT_FAILURE,
} }

View File

@ -16,6 +16,7 @@ fn bugfix() {
#[cfg(not(windows))] #[cfg(not(windows))]
const RECURSION_LIMIT_REACHED: &str = " const RECURSION_LIMIT_REACHED: &str = "
error: Parsing recursion depth exceeded error: Parsing recursion depth exceeded
--> justfile:1:265
| |
1 | foo: (x (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( 1 | foo: (x ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
| ^ | ^
@ -24,6 +25,7 @@ error: Parsing recursion depth exceeded
#[cfg(windows)] #[cfg(windows)]
const RECURSION_LIMIT_REACHED: &str = " const RECURSION_LIMIT_REACHED: &str = "
error: Parsing recursion depth exceeded error: Parsing recursion depth exceeded
--> justfile:1:57
| |
1 | foo: (x (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( 1 | foo: (x ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
| ^ | ^

View File

@ -30,6 +30,7 @@ test! {
args: ("--show", "f"), args: ("--show", "f"),
stderr: " stderr: "
error: Alias `f` has an unknown target `foo` error: Alias `f` has an unknown target `foo`
--> justfile:1:7
| |
1 | alias f := foo 1 | alias f := foo
| ^ | ^

View File

@ -48,6 +48,7 @@ fn no_rhs_once() {
.stderr( .stderr(
" "
error: Expected backtick, identifier, '(', '/', or string, but found end of file error: Expected backtick, identifier, '(', '/', or string, but found end of file
--> justfile:1:11
| |
1 | x := 'a' / 1 | x := 'a' /
| ^ | ^
@ -69,6 +70,7 @@ fn default_un_parenthesized() {
.stderr( .stderr(
" "
error: Expected '*', ':', '$', identifier, or '+', but found '/' error: Expected '*', ':', '$', identifier, or '+', but found '/'
--> justfile:1:11
| |
1 | foo x='a' / 'b': 1 | foo x='a' / 'b':
| ^ | ^
@ -90,6 +92,7 @@ fn no_lhs_un_parenthesized() {
.stderr( .stderr(
" "
error: Expected backtick, identifier, '(', or string, but found '/' error: Expected backtick, identifier, '(', or string, but found '/'
--> justfile:1:7
| |
1 | foo x=/ 'a' / 'b': 1 | foo x=/ 'a' / 'b':
| ^ | ^

View File

@ -88,6 +88,7 @@ a:"#,
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: `\\q` is not a valid escape sequence stderr: "error: `\\q` is not a valid escape sequence
--> justfile:1:6
| |
1 | x := \"\\q\" 1 | x := \"\\q\"
| ^^^^ | ^^^^
@ -108,6 +109,7 @@ a:
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: Variable `foo` not defined stderr: "error: Variable `foo` not defined
--> justfile:6:11
| |
6 | echo '{{foo}}' 6 | echo '{{foo}}'
| ^^^ | ^^^
@ -128,6 +130,7 @@ a:
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: Variable `bar` not defined stderr: "error: Variable `bar` not defined
--> justfile:3:13
| |
3 | whatever' + bar 3 | whatever' + bar
| ^^^ | ^^^
@ -165,6 +168,7 @@ a:
args: ("a"), args: ("a"),
stdout: "", stdout: "",
stderr: "error: Variable `b` not defined stderr: "error: Variable `b` not defined
--> justfile:5:10
| |
5 | echo {{b}} 5 | echo {{b}}
| ^ | ^
@ -181,6 +185,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Unterminated string error: Unterminated string
--> justfile:1:6
| |
1 | a b= ': 1 | a b= ':
| ^ | ^
@ -197,6 +202,7 @@ test! {
stdout: "", stdout: "",
stderr: r#" stderr: r#"
error: Unterminated string error: Unterminated string
--> justfile:1:6
| |
1 | a b= ": 1 | a b= ":
| ^ | ^
@ -212,6 +218,7 @@ test! {
", ",
stderr: r#" stderr: r#"
error: Unterminated backtick error: Unterminated backtick
--> justfile:1:8
| |
1 | foo a= `echo blaaaaaah: 1 | foo a= `echo blaaaaaah:
| ^ | ^
@ -228,6 +235,7 @@ test! {
stdout: "", stdout: "",
stderr: " stderr: "
error: Unterminated string error: Unterminated string
--> justfile:1:6
| |
1 | a b= ''': 1 | a b= ''':
| ^^^ | ^^^
@ -244,6 +252,7 @@ test! {
stdout: "", stdout: "",
stderr: r#" stderr: r#"
error: Unterminated string error: Unterminated string
--> justfile:1:6
| |
1 | a b= """: 1 | a b= """:
| ^^^ | ^^^
@ -259,6 +268,7 @@ test! {
", ",
stderr: r#" stderr: r#"
error: Unterminated backtick error: Unterminated backtick
--> justfile:1:8
| |
1 | foo a= ```echo blaaaaaah: 1 | foo a= ```echo blaaaaaah:
| ^^^ | ^^^
@ -374,6 +384,7 @@ test! {
", ",
stderr: " stderr: "
error: Backticks may not start with `#!` error: Backticks may not start with `#!`
--> justfile:1:6
| |
1 | x := `#!/usr/bin/env sh` 1 | x := `#!/usr/bin/env sh`
| ^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^

View File

@ -47,6 +47,7 @@ test! {
", ",
stderr: " stderr: "
error: Recipe `foo` depends on itself error: Recipe `foo` depends on itself
--> justfile:1:9
| |
1 | foo: && foo 1 | foo: && foo
| ^^^ | ^^^
@ -61,6 +62,7 @@ test! {
", ",
stderr: " stderr: "
error: Recipe `foo` has unknown dependency `bar` error: Recipe `foo` has unknown dependency `bar`
--> justfile:1:9
| |
1 | foo: && bar 1 | foo: && bar
| ^^^ | ^^^
@ -77,6 +79,7 @@ test! {
", ",
stderr: " stderr: "
error: Variable `y` not defined error: Variable `y` not defined
--> justfile:3:14
| |
3 | foo: && (bar y) 3 | foo: && (bar y)
| ^ | ^

View File

@ -7,6 +7,7 @@ fn parameter_default_unknown_variable_in_expression() {
.stderr( .stderr(
" "
error: Variable `b` not defined error: Variable `b` not defined
--> justfile:1:8
| |
1 | foo a=(b+''): 1 | foo a=(b+''):
| ^ | ^
@ -27,6 +28,7 @@ fn unknown_variable_in_unary_call() {
.stderr( .stderr(
" "
error: Variable `a` not defined error: Variable `a` not defined
--> justfile:1:15
| |
1 | foo x=env_var(a): 1 | foo x=env_var(a):
| ^ | ^
@ -47,6 +49,7 @@ fn unknown_first_variable_in_binary_call() {
.stderr( .stderr(
" "
error: Variable `a` not defined error: Variable `a` not defined
--> justfile:1:26
| |
1 | foo x=env_var_or_default(a, b): 1 | foo x=env_var_or_default(a, b):
| ^ | ^
@ -67,6 +70,7 @@ fn unknown_second_variable_in_binary_call() {
.stderr( .stderr(
" "
error: Variable `b` not defined error: Variable `b` not defined
--> justfile:1:30
| |
1 | foo x=env_var_or_default('', b): 1 | foo x=env_var_or_default('', b):
| ^ | ^
@ -87,6 +91,7 @@ fn unknown_variable_in_ternary_call() {
.stderr( .stderr(
" "
error: Variable `a` not defined error: Variable `a` not defined
--> justfile:1:15
| |
1 | foo x=replace(a, b, c): 1 | foo x=replace(a, b, c):
| ^ | ^