fix '\' handling, still working on ast of "${""}" and ${""} and then have to fix execution of ["echo", "", ""]

This commit is contained in:
pfg
2024-11-19 20:59:15 -08:00
parent 12465ef026
commit 38c41bc9d1
2 changed files with 61 additions and 31 deletions

View File

@@ -2711,14 +2711,11 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
}
continue;
}
// Treat newline preceded by backslash as whitespace
// Ignore newline preceeded by backslash
else if (char == '\n') {
if (comptime bun.Environment.allow_assert) {
assert(input.escaped);
}
if (self.chars.state != .Double) {
try self.break_word_impl(true, true, false);
}
continue;
}
@@ -2771,7 +2768,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
}
inline fn isImmediatelyEscapedQuote(self: *@This()) bool {
return (self.chars.state == .Double and
return ((self.chars.state == .Double or self.chars.state == .Single) 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 == '"'));
}
@@ -3516,31 +3513,37 @@ pub fn ShellCharIter(comptime encoding: StringEncoding) type {
}
pub fn read_char(self: *@This()) ?InputChar {
const indexed_value = self.src.index() orelse return null;
var char = indexed_value.char;
if (char != '\\' or self.state == .Single) return .{ .char = char };
while (true) {
const indexed_value = self.src.index() orelse return null;
var char = indexed_value.char;
if (char != '\\' or self.state == .Single) return .{ .char = char };
// Handle backslash
switch (self.state) {
.Normal => {
const peeked = self.src.indexNext() orelse return null;
char = peeked.char;
},
.Double => {
const peeked = self.src.indexNext() orelse return null;
switch (peeked.char) {
// Backslash only applies to these characters
'$', '`', '"', '\\', '\n', '#' => {
char = peeked.char;
},
else => return .{ .char = char, .escaped = false },
}
},
// We checked `self.state == .Single` above so this is impossible
.Single => unreachable,
// Handle backslash
const peeked = self.src.indexNext() orelse return null;
if (peeked.char == '\n') {
// completely ignore backslash newline, don't advance self.prev/self.current
self.src.eat(true);
continue;
}
switch (self.state) {
.Normal => {
char = peeked.char;
},
.Double => {
switch (peeked.char) {
// Backslash only applies to these characters
'$', '`', '"', '\\', '#' => {
char = peeked.char;
},
else => return .{ .char = char, .escaped = false },
}
},
// We checked `self.state == .Single` above so this is impossible
.Single => unreachable,
}
return .{ .char = char, .escaped = true };
}
return .{ .char = char, .escaped = true };
}
};
}

View File

@@ -15,11 +15,20 @@ describe("bun shell", () => {
expect(await $({ raw: ["echo " + 'a"a"'.repeat(1000000)] } as any).text()).toBe("aa".repeat(1000000) + "\n");
});
it("passes correct number of arguments with empty string substitutions", async () => {
expect(await $`echo ${"1"} ${""} ${"2"}`.text()).toBe("1 2\n");
expect(await $`echo 1 ${""} 2`.text()).toBe("1 2\n");
});
it("passes correct number of arguments with empty string quotes", async () => {
it("passes correct number of arguments with empty string substitutions 2", async () => {
expect(await $`echo 1 "${""}" 2`.text()).toBe("1 2\n");
});
it("passes correct number of arguments with empty string substitutions 3", async () => {
expect(await $`echo 1 '${""}' 2`.text()).toBe("1 2\n");
});
it("passes correct number of arguments with empty double string quotes", async () => {
expect(await $`echo "1" "" "2"`.text()).toBe("1 2\n");
});
it("passes correct number of arguments with empty single string quotes", async () => {
expect(await $`echo '1' '' '2'`.text()).toBe("1 2\n");
});
it("doesn't cause invalid js string ref error with a number after a string ref", async () => {
expect(await $`echo ${'"'}1`.text()).toBe('"1\n');
});
@@ -72,7 +81,7 @@ describe("bun shell", () => {
it("expands tilde as middle argument", async () => {
expect(await $`echo a ~ b`.text()).toBe("a " + process.env.HOME + " b\n");
});
it.todo("expands tilde as middle argument 2", async () => {
it("expands tilde as middle argument 2", async () => {
expect(
await $`echo a ~\
b`.text(),
@@ -118,6 +127,24 @@ describe("bun shell", () => {
it("does not expand tilde second", async () => {
expect(await $`echo "a"~`.text()).toBe("a~\n");
});
it("handles backslashed newline", async () => {
expect(
await $`echo a\
b`.text(),
).toBe("ab\n");
});
it("handles backslashed newline in single quotes", async () => {
expect(
await $`echo 'a\
b'`.text(),
).toBe("a\\\nb\n");
});
it("handles backslashed newline in double quotes", async () => {
expect(
await $`echo "a\
b"`.text(),
).toBe("ab\n");
});
// TODO: handle username (`~user` -> getpwnam(user) eg /home/user if the accont exists. but only if all unquoted, ie `~user"a"` <- not allowed)
it("fails for bad surrogate pairs", async () => {