Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/js_printer/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,16 @@ pub(crate) mod __gated_printer {
}
}

/// The `.` of `a.b` and `a.#b`. A release build does not inline a plain `#[inline]` here.
#[inline(always)]
pub(crate) fn print_member_dot(&mut self) {
if self.prev_num_end == self.writer.written() {
// "1.toString" is a syntax error, so print "1 .toString" instead
self.print(b" ");
}
self.print(b".");
}

#[inline]
pub(crate) fn maybe_print_space(&mut self) {
match self.writer.prev_char() {
Expand Down Expand Up @@ -3687,11 +3697,7 @@ pub(crate) mod __gated_printer {
if is_optional_chain {
self.print(b"?.");
} else {
if self.prev_num_end == self.writer.written() {
// "1.toString" is a syntax error, so print "1 .toString" instead
self.print(b" ");
}
self.print(b".");
self.print_member_dot();
}

self.add_source_mapping(e.name_loc);
Expand Down Expand Up @@ -3757,7 +3763,7 @@ pub(crate) mod __gated_printer {
match &e.index.data {
ExprData::EPrivateIdentifier(priv_) => {
if !is_optional_chain_start {
self.print(b".");
self.print_member_dot();
}
self.add_source_mapping(e.index.loc);
self.print_symbol(priv_.ref_);
Expand Down
24 changes: 24 additions & 0 deletions test/bundler/bundler_minify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,30 @@ describe("bundler", () => {
stdout: '["inf","neg","inf","method","static"]',
},
});
// "1.#x" is a syntax error. A number literal, or a const enum member that is inlined to one,
// needs a space before the "." of a private name.
itBundled("minify/PrivateNameOfNumberLiteral", {
files: {
"/entry.ts": /* ts */ `
import { E } from "./enum";
class Foo {
#x = 1;
static literal() { try { return (1).#x; } catch (e) { return e.name; } }
static enumMember() { try { return E.One.#x; } catch (e) { return e.name; } }
}
console.log(Foo.literal(), Foo.enumMember());
`,
"enum.ts": /* ts */ `
export const enum E { One = 1 }
`,
},
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
run: {
stdout: "TypeError TypeError",
},
});
// https://github.com/oven-sh/bun/issues/40688: the same-target destructuring
// transform must apply to every eligible run of declarations, not only the
// run at the head of the list.
Expand Down
45 changes: 45 additions & 0 deletions test/bundler/transpiler/transpiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4088,6 +4088,51 @@ console.log(resolve.length)
);
});

it("private name of a number literal", () => {
const cls = body => `class Foo { #x; static f() { ${body} } }`;
const printed = body => `class Foo {\n #x;\n static f() {\n ${body};\n }\n}`;

// "1.#x" is a syntax error, because the "." is a part of the number
expectPrinted_(cls("return (1).#x"), printed("return 1 .#x"));
expectPrinted_(cls("return 1 .#x"), printed("return 1 .#x"));
expectPrinted_(cls("return 1..#x"), printed("return 1 .#x"));
expectPrinted_(cls("return (1).#x()"), printed("return 1 .#x()"));
expectPrinted_(cls("(1).#x = 2"), printed("1 .#x = 2"));

const minify = new Bun.Transpiler({ loader: "js", minifyWhitespace: true });
expect(minify.transformSync(cls("return (1).#x"))).toBe("class Foo{#x;static f(){return 1 .#x}}");

// A constant that is inlined prints as a number literal too, and so does a define
const inline = new Bun.Transpiler({ loader: "js", inline: true });
expect(inline.transformSync("const k = 1; " + cls("return k.#x"))).toBe(
"const k = 1;\n\n" + printed("return 1 .#x") + "\n",
);
const define = new Bun.Transpiler({ loader: "js", define: { "CFG.n": "10" } });
expect(define.transformSync(cls("return CFG.n.#x"))).toBe(printed("return 10 .#x") + "\n");

// These parse without the space
expectPrinted_(cls("return (1)?.#x"), printed("return 1?.#x"));
expectPrinted_(cls("return (1n).#x"), printed("return 1n.#x"));
expectPrinted_(cls("return (-1).#x"), printed("return (-1).#x"));
expectPrinted_(cls("return (1)[0].#x"), printed("return 1[0].#x"));
});

it.concurrent("private name of a number literal loads under bun -e", async () => {
// Node prints the same line for this source.
const src = `
Comment thread
robobun marked this conversation as resolved.
Resolved
const k = 1;
class Foo {
#x = 1;
static literal() { try { return (1).#x; } catch (e) { return e.name; } }
static constant() { try { return k.#x; } catch (e) { return e.name; } }
}
console.log(Foo.literal(), Foo.constant());
`;
await using proc = Bun.spawn({ cmd: [bunExe(), "-e", src], env: bunEnv, stderr: "pipe" });
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stdout, stderr, exitCode }).toEqual({ stdout: "TypeError TypeError\n", stderr: "", exitCode: 0 });
});

it("type only exports", () => {
let { expectPrinted_, expectParseError } = ts;
expectPrinted_("export type {foo, bar as baz} from 'bar'", "");
Expand Down
Loading