Forbid whitespace in shell-expanded string prefixes (#2083)

This commit is contained in:
Casey Rodarmor 2024-05-24 20:56:03 -07:00 committed by GitHub
parent 9a52d6fe8f
commit 1654d14867
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 87 additions and 2 deletions

View file

@ -554,9 +554,27 @@ impl<'run, 'src> Parser<'run, 'src> {
})
}
// Check if the next tokens are a shell-expanded string, i.e., `x"foo"`.
//
// This function skips initial whitespace tokens, but thereafter is
// whitespace-sensitive, so `x"foo"` is a shell-expanded string, whereas `x
// "foo"` is not.
fn next_is_shell_expanded_string(&self) -> bool {
let mut tokens = self
.tokens
.iter()
.skip(self.next_token)
.skip_while(|token| token.kind == Whitespace);
tokens
.next()
.is_some_and(|token| token.kind == Identifier && token.lexeme() == "x")
&& tokens.next().is_some_and(|token| token.kind == StringToken)
}
/// Parse a value, e.g. `(bar)`
fn parse_value(&mut self) -> CompileResult<'src, Expression<'src>> {
if self.next_is(StringToken) || self.next_are(&[Identifier, StringToken]) {
if self.next_is(StringToken) || self.next_is_shell_expanded_string() {
Ok(Expression::StringLiteral {
string_literal: self.parse_string_literal()?,
})
@ -610,7 +628,12 @@ impl<'run, 'src> Parser<'run, 'src> {
fn parse_string_literal_token(
&mut self,
) -> CompileResult<'src, (Token<'src>, StringLiteral<'src>)> {
let expand = self.accepted_keyword(Keyword::X)?;
let expand = if self.next_is(Identifier) {
self.expect_keyword(Keyword::X)?;
true
} else {
false
};
let token = self.expect(StringToken)?;
@ -2362,6 +2385,7 @@ mod tests {
width: 1,
kind: UnexpectedToken {
expected: vec![
Identifier,
StringToken,
],
found: BracketR,
@ -2404,6 +2428,7 @@ mod tests {
kind: UnexpectedToken {
expected: vec![
BracketR,
Identifier,
StringToken,
],
found: Eof,

View file

@ -14,6 +14,27 @@ fn strings_are_shell_expanded() {
.run();
}
#[test]
fn shell_expanded_strings_must_not_have_whitespace() {
Test::new()
.justfile(
"
x := x '$JUST_TEST_VARIABLE'
",
)
.status(1)
.stderr(
"
error: Expected comment, end of file, end of line, '(', '+', or '/', but found string
justfile:1:8
1 x := x '$JUST_TEST_VARIABLE'
^^^^^^^^^^^^^^^^^^^^^
",
)
.run();
}
#[test]
fn shell_expanded_error_messages_highlight_string_token() {
Test::new()
@ -97,3 +118,42 @@ fn shell_expanded_strings_can_be_used_in_mod_paths() {
.test_round_trip(false)
.run();
}
#[test]
fn shell_expanded_strings_do_not_conflict_with_dependencies() {
Test::new()
.justfile(
"
foo a b:
@echo {{a}}{{b}}
bar a b: (foo a 'c')
",
)
.args(["bar", "A", "B"])
.stdout("Ac\n")
.run();
Test::new()
.justfile(
"
foo a b:
@echo {{a}}{{b}}
bar a b: (foo a'c')
",
)
.args(["bar", "A", "B"])
.stdout("Ac\n")
.run();
Test::new()
.justfile(
"
foo a b:
@echo {{a}}{{b}}
bar x b: (foo x 'c')
",
)
.args(["bar", "A", "B"])
.stdout("Ac\n")
.run();
}