mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 21:01:52 +00:00
shell: handle operators and delimiters better (#11165)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> Co-authored-by: Jarred-Sumner <Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
@@ -2482,7 +2482,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
|
||||
|
||||
if (self.chars.state == .Single) break :escaped;
|
||||
if (self.in_subshell == .backtick) {
|
||||
try self.break_word(true);
|
||||
try self.break_word_operator();
|
||||
if (self.last_tok_tag()) |toktag| {
|
||||
if (toktag != .Delimit) try self.tokens.append(.Delimit);
|
||||
}
|
||||
@@ -2588,7 +2588,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
|
||||
comptime assertSpecialChar('|');
|
||||
|
||||
if (self.chars.state == .Single or self.chars.state == .Double) break :escaped;
|
||||
try self.break_word(true);
|
||||
try self.break_word_operator();
|
||||
|
||||
const next = self.peek() orelse {
|
||||
self.add_error("Unexpected EOF");
|
||||
@@ -2610,7 +2610,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
|
||||
comptime assertSpecialChar('>');
|
||||
|
||||
if (self.chars.state == .Single or self.chars.state == .Double) break :escaped;
|
||||
try self.break_word_impl(true, false, true);
|
||||
try self.break_word_operator();
|
||||
const redirect = self.eat_simple_redirect(.out);
|
||||
try self.tokens.append(.{ .Redirect = redirect });
|
||||
continue;
|
||||
@@ -2619,7 +2619,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
|
||||
comptime assertSpecialChar('<');
|
||||
|
||||
if (self.chars.state == .Single or self.chars.state == .Double) break :escaped;
|
||||
try self.break_word_impl(true, false, true);
|
||||
try self.break_word_operator();
|
||||
const redirect = self.eat_simple_redirect(.in);
|
||||
try self.tokens.append(.{ .Redirect = redirect });
|
||||
continue;
|
||||
@@ -2628,7 +2628,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
|
||||
comptime assertSpecialChar('&');
|
||||
|
||||
if (self.chars.state == .Single or self.chars.state == .Double) break :escaped;
|
||||
try self.break_word(true);
|
||||
try self.break_word_operator();
|
||||
|
||||
const next = self.peek() orelse {
|
||||
try self.tokens.append(.Ampersand);
|
||||
@@ -2751,13 +2751,18 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
|
||||
return try self.break_word_impl(add_delimiter, false, false);
|
||||
}
|
||||
|
||||
/// NOTE: this adds a delimiter
|
||||
fn break_word_operator(self: *@This()) !void {
|
||||
return try self.break_word_impl(true, false, true);
|
||||
}
|
||||
|
||||
inline fn isImmediatelyEscapedQuote(self: *@This()) bool {
|
||||
return (self.chars.state == .Double and
|
||||
(self.chars.current != null and !self.chars.current.?.escaped and self.chars.current.?.char == '"') and
|
||||
(self.chars.prev != null and !self.chars.prev.?.escaped and self.chars.prev.?.char == '"'));
|
||||
}
|
||||
|
||||
fn break_word_impl(self: *@This(), add_delimiter: bool, in_normal_space: bool, in_redirect_operator: bool) !void {
|
||||
fn break_word_impl(self: *@This(), add_delimiter: bool, in_normal_space: bool, in_operator: bool) !void {
|
||||
const start: u32 = self.word_start;
|
||||
const end: u32 = self.j;
|
||||
if (start != end or
|
||||
@@ -2773,7 +2778,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
|
||||
if (add_delimiter) {
|
||||
try self.tokens.append(.Delimit);
|
||||
}
|
||||
} else if ((in_normal_space or in_redirect_operator) and self.tokens.items.len > 0 and
|
||||
} else if ((in_normal_space or in_operator) and self.tokens.items.len > 0 and
|
||||
// whether or not to add a delimiter token
|
||||
switch (self.tokens.items[self.tokens.items.len - 1]) {
|
||||
.Var,
|
||||
|
||||
@@ -379,6 +379,14 @@ describe("bunshell", () => {
|
||||
expect(stdout.toString()).toEqual("LMAO\n");
|
||||
});
|
||||
|
||||
describe("operators no spaces", async () => {
|
||||
TestBuilder.command`echo LMAO|cat`.stdout("LMAO\n").runAsTest("pipeline");
|
||||
TestBuilder.command`echo foo&&echo hi`.stdout("foo\nhi\n").runAsTest("&&");
|
||||
TestBuilder.command`echo foo||echo hi`.stdout("foo\n").runAsTest("||");
|
||||
TestBuilder.command`echo foo>hi.txt`.ensureTempDir().fileEquals("hi.txt", "foo\n").runAsTest("||");
|
||||
TestBuilder.command`echo hifriends#lol`.stdout("hifriends#lol\n").runAsTest("#");
|
||||
});
|
||||
|
||||
test("cmd subst", async () => {
|
||||
const haha = "noice";
|
||||
const { stdout } = await $`echo $(echo noice)`;
|
||||
|
||||
Reference in New Issue
Block a user