Forbid whitespace in shell-expanded string prefixes (#2083)
This commit is contained in:
parent
9a52d6fe8f
commit
1654d14867
@ -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)`
|
/// Parse a value, e.g. `(bar)`
|
||||||
fn parse_value(&mut self) -> CompileResult<'src, Expression<'src>> {
|
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 {
|
Ok(Expression::StringLiteral {
|
||||||
string_literal: self.parse_string_literal()?,
|
string_literal: self.parse_string_literal()?,
|
||||||
})
|
})
|
||||||
@ -610,7 +628,12 @@ impl<'run, 'src> Parser<'run, 'src> {
|
|||||||
fn parse_string_literal_token(
|
fn parse_string_literal_token(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> CompileResult<'src, (Token<'src>, StringLiteral<'src>)> {
|
) -> 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)?;
|
let token = self.expect(StringToken)?;
|
||||||
|
|
||||||
@ -2362,6 +2385,7 @@ mod tests {
|
|||||||
width: 1,
|
width: 1,
|
||||||
kind: UnexpectedToken {
|
kind: UnexpectedToken {
|
||||||
expected: vec![
|
expected: vec![
|
||||||
|
Identifier,
|
||||||
StringToken,
|
StringToken,
|
||||||
],
|
],
|
||||||
found: BracketR,
|
found: BracketR,
|
||||||
@ -2404,6 +2428,7 @@ mod tests {
|
|||||||
kind: UnexpectedToken {
|
kind: UnexpectedToken {
|
||||||
expected: vec![
|
expected: vec![
|
||||||
BracketR,
|
BracketR,
|
||||||
|
Identifier,
|
||||||
StringToken,
|
StringToken,
|
||||||
],
|
],
|
||||||
found: Eof,
|
found: Eof,
|
||||||
|
@ -14,6 +14,27 @@ fn strings_are_shell_expanded() {
|
|||||||
.run();
|
.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]
|
#[test]
|
||||||
fn shell_expanded_error_messages_highlight_string_token() {
|
fn shell_expanded_error_messages_highlight_string_token() {
|
||||||
Test::new()
|
Test::new()
|
||||||
@ -97,3 +118,42 @@ fn shell_expanded_strings_can_be_used_in_mod_paths() {
|
|||||||
.test_round_trip(false)
|
.test_round_trip(false)
|
||||||
.run();
|
.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();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user