Repository: casey/just
Pull request #1551 (2023-02-17 20:40:52)

Support escaping newlines
Fixes https://github.com/casey/just/issues/1157.

ids1024: Updated.

Patch:
diff --git a/src/lexer.rs b/src/lexer.rs
index 55de4567..aaa3bbdc 100644
--- a/src/lexer.rs
+++ b/src/lexer.rs
@@ -481,6 +481,7 @@ impl<'src> Lexer<'src> {
       ',' => self.lex_single(Comma),
       '/' => self.lex_single(Slash),
       ':' => self.lex_colon(),
+      '\\' => self.lex_escape(),
       '=' => self.lex_choices('=', &[('=', EqualsEquals), ('~', EqualsTilde)], Equals),
       '@' => self.lex_single(At),
       '[' => self.lex_delimiter(BracketL),
@@ -709,6 +710,31 @@ impl<'src> Lexer<'src> {
     Ok(())
   }
 
+  /// Lex an token starting with '\' escape
+  fn lex_escape(&mut self) -> CompileResult<'src, ()> {
+    self.presume('\\')?;
+
+    // Treat newline escaped with \ as whitespace
+    if self.accepted('\n')? {
+      while self.next_is_whitespace() {
+        self.advance()?;
+      }
+      self.token(Whitespace);
+    } else if self.accepted('\r')? {
+      if !self.accepted('\n')? {
+        return Err(self.error(UnpairedCarriageReturn));
+      }
+      while self.next_is_whitespace() {
+        self.advance()?;
+      }
+      self.token(Whitespace);
+    } else if let Some(character) = self.next {
+      return Err(self.error(CompileErrorKind::InvalidEscapeSequence { character }));
+    }
+
+    Ok(())
+  }
+
   /// Lex a carriage return and line feed
   fn lex_eol(&mut self) -> CompileResult<'src, ()> {
     if self.accepted('\r')? {


Test patch:
diff --git a/tests/lib.rs b/tests/lib.rs
index 23f5a720..6f119d0c 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -63,6 +63,7 @@ mod json;
 mod line_prefixes;
 mod misc;
 mod multibyte_char;
+mod newline_escape;
 mod no_cd;
 mod no_exit_message;
 mod os_attributes;
diff --git a/tests/newline_escape.rs b/tests/newline_escape.rs
new file mode 100644
index 00000000..2b278fbb
--- /dev/null
+++ b/tests/newline_escape.rs
@@ -0,0 +1,77 @@
+use super::*;
+
+#[test]
+fn newline_escape_deps() {
+  Test::new()
+    .justfile(
+      "
+      default: a \\
+               b \\
+               c
+      a:
+        echo a
+      b:
+        echo b
+      c:
+        echo c
+    ",
+    )
+    .stdout("a\nb\nc\n")
+    .stderr("echo a\necho b\necho c\n")
+    .run();
+}
+
+#[test]
+fn newline_escape_deps_no_indent() {
+  Test::new()
+    .justfile(
+      "
+      default: a\\
+      b\\
+      c
+      a:
+        echo a
+      b:
+        echo b
+      c:
+        echo c
+    ",
+    )
+    .stdout("a\nb\nc\n")
+    .stderr("echo a\necho b\necho c\n")
+    .run();
+}
+
+#[test]
+fn newline_escape_deps_linefeed() {
+  Test::new()
+    .justfile(
+      "
+      default: a\\\r
+               b
+      a:
+        echo a
+      b:
+        echo b
+    ",
+    )
+    .stdout("a\nb\n")
+    .stderr("echo a\necho b\n")
+    .run();
+}
+
+#[test]
+fn newline_escape_deps_invalid_esc() {
+  Test::new()
+    .justfile(
+      "
+      default: a\\ b
+    ",
+    )
+    .stdout("")
+    .stderr(
+      "error: `\\ ` is not a valid escape sequence\n  |\n1 | default: a\\ b\n  |           ^\n",
+    )
+    .status(EXIT_FAILURE)
+    .run();
+}