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,
length: 0,
kind: TokenKind::Unspecified,
path: "".as_ref(),
};
return Err(CompileError::new(token, Internal { message }));
}

View File

@ -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))
}

View File

@ -15,8 +15,8 @@ impl Compiler {
paths.push(root.into());
while let Some(current) = paths.pop() {
let src = loader.load(&current)?;
let tokens = Lexer::lex(src)?;
let (relative, src) = loader.load(root, &current)?;
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<Justfile> {
let tokens = Lexer::lex(src)?;
let tokens = Lexer::test_lex(src)?;
let ast = Parser::parse(&tokens)?;
let root = PathBuf::from("<ROOT>");
let root = PathBuf::from("justfile");
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
asts.insert(root.clone(), ast);
Analyzer::analyze(&asts, &root)

View File

@ -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<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: Vec<&'src str>,
/// Interpolation token start stack
interpolation_stack: Vec<Token<'src>>,
/// Next character to be lexed
next: Option<char>,
/// 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<Token<'src>>,
/// 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<Vec<Token<'src>>> {
Lexer::new(src).tokenize()
/// Lex `src`
pub(crate) fn lex(path: &'src Path, src: &'src str) -> CompileResult<'src, Vec<Token<'src>>> {
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<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 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 | !
| ^"
);
}
}

View File

@ -1,22 +1,34 @@
use super::*;
pub(crate) struct Loader {
arena: Arena<String>,
srcs: Arena<String>,
paths: Arena<PathBuf>,
}
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)))
}
}

View File

@ -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,
}
}

View File

@ -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),
};

View File

@ -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("<ROOT>");
let root = PathBuf::from("justfile");
let mut asts: HashMap<PathBuf, Ast> = 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),
};

View File

@ -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}",

View File

@ -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]
| ^^^^^

View File

@ -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 | {
| ^

View File

@ -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'}
| ^^^

View File

@ -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 {{ (
| ^^

View File

@ -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();
}

View File

@ -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}}
| ^^^

View File

@ -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();
}

View File

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

View File

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

View File

@ -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:
| ^

View File

@ -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
| ^

View File

@ -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,
}

View File

@ -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 ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
| ^

View File

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

View File

@ -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':
| ^

View File

@ -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`
| ^^^^^^^^^^^^^^^^^^^

View File

@ -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)
| ^

View File

@ -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):
| ^