Repository: casey/just
Pull request #1737 (2023-11-22 03:43:43)

Add file to error messages


Patch:
diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs
index 035e25b1..68ff2a21 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 56670acc..5b1b2e5e 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 c035921f..95ecc571 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(&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)
diff --git a/src/lexer.rs b/src/lexer.rs
index 3dca62b9..3a9a429c 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<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()
+  }
+
+  #[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 `text`
-  fn new(src: &'src str) -> Lexer<'src> {
+  /// 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 b1cc0947..f96cd657 100644
--- a/src/loader.rs
+++ b/src/loader.rs
@@ -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)))
   }
 }
diff --git a/src/name.rs b/src/name.rs
index aec425fd..9823430e 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 613cb11d..a8a04d80 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/token.rs b/src/token.rs
index 3a339c43..9846236e 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}",


Test patch:
diff --git a/src/testing.rs b/src/testing.rs
index b526e839..91549a82 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("<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),
       };
diff --git a/tests/attributes.rs b/tests/attributes.rs
index accfde82..8560e99b 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 13283d8e..cd72eeca 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 db8e389a..359b033e 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 ab032e6a..773fec4f 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 e17a4afd..6e860273 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 7f0387b5..d1a3da95 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 150f293d..993b99d2 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 9c228327..2460a451 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 ea7d63c1..42153f51 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 7d8ff0f2..8733507c 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 e83f4c62..3d0e1e11 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 e3abb4af..aa1ec881 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 7941c24c..d33c201d 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 91384898..e99912d7 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 ab91559f..7991d1c7 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 c932634f..f7ddb05c 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 10fa25a0..ef96685d 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 be982aaf..1203a031 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):
         |               ^