mirror of
https://github.com/casey/just.git
synced 2025-12-23 11:37:29 +00:00
Allow newlines in interpolations and } to abut interpolation }} (#2992)
This commit is contained in:
parent
c85d9916cd
commit
ea349d8ed0
6 changed files with 175 additions and 52 deletions
|
|
@ -2418,9 +2418,6 @@ bar foo:
|
|||
echo {{ if foo == "bar" { "hello" } else { "goodbye" } }}
|
||||
```
|
||||
|
||||
Note the space after the final `}`! Without the space, the interpolation will
|
||||
be prematurely closed.
|
||||
|
||||
Multiple conditionals can be chained:
|
||||
|
||||
```just
|
||||
|
|
|
|||
57
src/lexer.rs
57
src/lexer.rs
|
|
@ -351,6 +351,18 @@ impl<'src> Lexer<'src> {
|
|||
|
||||
let whitespace = &self.rest()[..nonblank_index];
|
||||
|
||||
if self.open_delimiters_or_interpolation() {
|
||||
if !whitespace.is_empty() {
|
||||
while self.next_is_whitespace() {
|
||||
self.advance()?;
|
||||
}
|
||||
|
||||
self.token(Whitespace);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let body_whitespace = &whitespace[..whitespace
|
||||
.char_indices()
|
||||
.take(self.indentation().chars().count())
|
||||
|
|
@ -454,15 +466,11 @@ impl<'src> Lexer<'src> {
|
|||
self.advance()?;
|
||||
}
|
||||
|
||||
if self.open_delimiters() {
|
||||
self.token(Whitespace);
|
||||
} else {
|
||||
let indentation = self.lexeme();
|
||||
self.indentation.push(indentation);
|
||||
self.token(Indent);
|
||||
if self.recipe_body_pending {
|
||||
self.recipe_body = true;
|
||||
}
|
||||
let indentation = self.lexeme();
|
||||
self.indentation.push(indentation);
|
||||
self.token(Indent);
|
||||
if self.recipe_body_pending {
|
||||
self.recipe_body = true;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -530,18 +538,17 @@ impl<'src> Lexer<'src> {
|
|||
interpolation_start: Token<'src>,
|
||||
start: char,
|
||||
) -> CompileResult<'src> {
|
||||
if self.rest_starts_with("}}") {
|
||||
if self.rest_starts_with("}}") && self.open_delimiters.is_empty() {
|
||||
// end current interpolation
|
||||
if self.interpolation_stack.pop().is_none() {
|
||||
self.advance()?;
|
||||
self.advance()?;
|
||||
self.presume_str("}}")?;
|
||||
return Err(self.internal_error(
|
||||
"Lexer::lex_interpolation found `}}` but was called with empty interpolation stack.",
|
||||
));
|
||||
}
|
||||
// Emit interpolation end token
|
||||
self.lex_double(InterpolationEnd)
|
||||
} else if self.at_eol_or_eof() {
|
||||
} else if self.at_eof() && self.open_delimiters.is_empty() {
|
||||
// Return unterminated interpolation error that highlights the opening
|
||||
// {{
|
||||
Err(Self::unterminated_interpolation_error(interpolation_start))
|
||||
|
|
@ -712,8 +719,8 @@ impl<'src> Lexer<'src> {
|
|||
}
|
||||
|
||||
/// Return true if there are any unclosed delimiters
|
||||
fn open_delimiters(&self) -> bool {
|
||||
!self.open_delimiters.is_empty()
|
||||
fn open_delimiters_or_interpolation(&self) -> bool {
|
||||
!self.open_delimiters.is_empty() || !self.interpolation_stack.is_empty()
|
||||
}
|
||||
|
||||
/// Lex a two-character digraph
|
||||
|
|
@ -794,9 +801,8 @@ impl<'src> Lexer<'src> {
|
|||
self.presume('\n')?;
|
||||
}
|
||||
|
||||
// Emit an eol if there are no open delimiters, otherwise emit a whitespace
|
||||
// token.
|
||||
if self.open_delimiters() {
|
||||
// Emit eol if there are no open delimiters, otherwise emit whitespace.
|
||||
if self.open_delimiters_or_interpolation() {
|
||||
self.token(Whitespace);
|
||||
} else {
|
||||
self.token(Eol);
|
||||
|
|
@ -1085,6 +1091,7 @@ mod tests {
|
|||
};
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn error(
|
||||
src: &str,
|
||||
offset: usize,
|
||||
|
|
@ -2426,6 +2433,20 @@ mod tests {
|
|||
},
|
||||
}
|
||||
|
||||
error! {
|
||||
name: unclosed_parenthesis_in_interpolation,
|
||||
input: "a:\n echo {{foo(}}",
|
||||
offset: 15,
|
||||
line: 1,
|
||||
column: 12,
|
||||
width: 0,
|
||||
kind: MismatchedClosingDelimiter {
|
||||
close: Delimiter::Brace,
|
||||
open: Delimiter::Paren,
|
||||
open_line: 1,
|
||||
},
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presume_error() {
|
||||
let compile_error = Lexer::new("justfile".as_ref(), "!")
|
||||
|
|
|
|||
|
|
@ -1411,6 +1411,7 @@ mod tests {
|
|||
};
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn error(
|
||||
src: &str,
|
||||
offset: usize,
|
||||
|
|
@ -2720,26 +2721,6 @@ mod tests {
|
|||
},
|
||||
}
|
||||
|
||||
error! {
|
||||
name: unclosed_parenthesis_in_interpolation,
|
||||
input: "a:\n echo {{foo(}}",
|
||||
offset: 15,
|
||||
line: 1,
|
||||
column: 12,
|
||||
width: 2,
|
||||
kind: UnexpectedToken{
|
||||
expected: vec![
|
||||
Backtick,
|
||||
Identifier,
|
||||
ParenL,
|
||||
ParenR,
|
||||
Slash,
|
||||
StringToken,
|
||||
],
|
||||
found: InterpolationEnd,
|
||||
},
|
||||
}
|
||||
|
||||
error! {
|
||||
name: plus_following_parameter,
|
||||
input: "a b c+:",
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ fn dependency_continuation() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn no_interpolation_continuation() {
|
||||
fn interpolation_continuation() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
|
|
@ -122,15 +122,7 @@ fn no_interpolation_continuation() {
|
|||
'a' + 'b')}}
|
||||
",
|
||||
)
|
||||
.stderr(
|
||||
"
|
||||
error: Unterminated interpolation
|
||||
——▶ justfile:2:8
|
||||
│
|
||||
2 │ echo {{ (
|
||||
│ ^^
|
||||
",
|
||||
)
|
||||
.status(EXIT_FAILURE)
|
||||
.stderr("echo ab\n")
|
||||
.stdout("ab\n")
|
||||
.run();
|
||||
}
|
||||
|
|
|
|||
131
tests/interpolation.rs
Normal file
131
tests/interpolation.rs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn closing_curly_brace_can_abut_interpolation_close() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
echo {{if 'a' == 'b' { 'c' } else { 'd' }}}
|
||||
",
|
||||
)
|
||||
.stderr("echo d\n")
|
||||
.stdout("d\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eol_with_continuation_in_interpolation() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
echo {{(
|
||||
'a'
|
||||
)}}
|
||||
",
|
||||
)
|
||||
.stderr("echo a\n")
|
||||
.stdout("a\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eol_without_continuation_in_interpolation() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
echo {{
|
||||
'a'
|
||||
}}
|
||||
",
|
||||
)
|
||||
.stderr("echo a\n")
|
||||
.stdout("a\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_in_interopolation() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
echo {{ # hello
|
||||
'a'
|
||||
}}
|
||||
",
|
||||
)
|
||||
.stderr(
|
||||
"
|
||||
error: Expected backtick, identifier, '(', '/', or string, but found comment
|
||||
——▶ justfile:2:11
|
||||
│
|
||||
2 │ echo {{ # hello
|
||||
│ ^^^^^^^
|
||||
",
|
||||
)
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indent_and_dedent_are_ignored_in_interpolation() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
echo {{
|
||||
'a'
|
||||
+ 'b'
|
||||
+ 'c'
|
||||
}}
|
||||
echo foo
|
||||
",
|
||||
)
|
||||
.stderr("echo abc\necho foo\n")
|
||||
.stdout("abc\nfoo\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shebang_line_numbers_are_correct_with_multi_line_interpolations() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
#!/usr/bin/env cat
|
||||
echo {{
|
||||
'a'
|
||||
+ 'b'
|
||||
+ 'c'
|
||||
}}
|
||||
echo foo
|
||||
",
|
||||
)
|
||||
.stdout(if cfg!(windows) {
|
||||
"
|
||||
|
||||
|
||||
echo abc
|
||||
|
||||
|
||||
|
||||
|
||||
echo foo
|
||||
"
|
||||
} else {
|
||||
"
|
||||
#!/usr/bin/env cat
|
||||
|
||||
echo abc
|
||||
|
||||
|
||||
|
||||
|
||||
echo foo
|
||||
"
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
|
@ -83,6 +83,7 @@ mod groups;
|
|||
mod ignore_comments;
|
||||
mod imports;
|
||||
mod init;
|
||||
mod interpolation;
|
||||
mod invocation_directory;
|
||||
mod json;
|
||||
mod line_prefixes;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue