aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-04 17:51:22 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-04 17:51:22 -0800
commit4f5aa438d6799f24c7a112ca8dc0910466944f2e (patch)
treed859ea1467a7e8ae8e7ae20466d4bf16224ae55c
parentbd2f818500be18523aede09df300ef9f3af9180e (diff)
downloadbun-4f5aa438d6799f24c7a112ca8dc0910466944f2e.tar.gz
bun-4f5aa438d6799f24c7a112ca8dc0910466944f2e.tar.zst
bun-4f5aa438d6799f24c7a112ca8dc0910466944f2e.zip
[JS Parser] Support JSX prop punning
-rw-r--r--integration/bunjs-only-snippets/atob.test.js3
-rw-r--r--integration/bunjs-only-snippets/transpiler.test.js65
-rw-r--r--src/js_parser/js_parser.zig44
3 files changed, 106 insertions, 6 deletions
diff --git a/integration/bunjs-only-snippets/atob.test.js b/integration/bunjs-only-snippets/atob.test.js
index c146aa855..4945829e1 100644
--- a/integration/bunjs-only-snippets/atob.test.js
+++ b/integration/bunjs-only-snippets/atob.test.js
@@ -1,4 +1,4 @@
-import { expect, describe, it } from "bun:test";
+import { expect, it } from "bun:test";
function expectInvalidCharacters(val) {
try {
@@ -49,7 +49,6 @@ it("atob", () => {
expectInvalidCharacters("==");
expectInvalidCharacters("===");
expectInvalidCharacters("====");
-
expectInvalidCharacters("=====");
});
diff --git a/integration/bunjs-only-snippets/transpiler.test.js b/integration/bunjs-only-snippets/transpiler.test.js
index 2859c2b02..5553c1cc6 100644
--- a/integration/bunjs-only-snippets/transpiler.test.js
+++ b/integration/bunjs-only-snippets/transpiler.test.js
@@ -47,6 +47,71 @@ describe("Bun.Transpiler", () => {
`;
+ it("JSX", () => {
+ var bun = new Bun.Transpiler({
+ loader: "jsx",
+ });
+ expect(bun.transformSync("export var foo = <div foo />")).toBe(
+ `export var foo = jsx("div", {
+ foo: true
+}, undefined, false, undefined, this);
+`
+ );
+ expect(bun.transformSync("export var foo = <div foo={foo} />")).toBe(
+ `export var foo = jsx("div", {
+ foo
+}, undefined, false, undefined, this);
+`
+ );
+ expect(bun.transformSync("export var foo = <div {...foo} />")).toBe(
+ `export var foo = jsx("div", {
+ ...foo
+}, undefined, false, undefined, this);
+`
+ );
+ expect(bun.transformSync("export var hi = <div {foo} />")).toBe(
+ `export var hi = jsx("div", {
+ foo
+}, undefined, false, undefined, this);
+`
+ );
+ try {
+ bun.transformSync("export var hi = <div {foo}={foo}= />");
+ throw new Error("Expected error");
+ } catch (e) {
+ expect(e.errors[0].message.includes('Expected ">"')).toBe(true);
+ }
+
+ expect(
+ bun.transformSync("export var hi = <div {Foo}><Foo></Foo></div>")
+ ).toBe(
+ `export var hi = jsx("div", {
+ Foo,
+ children: jsx(Foo, {}, undefined, false, undefined, this)
+}, undefined, false, undefined, this);
+`
+ );
+ expect(
+ bun.transformSync("export var hi = <div {Foo}><Foo></Foo></div>")
+ ).toBe(
+ `export var hi = jsx("div", {
+ Foo,
+ children: jsx(Foo, {}, undefined, false, undefined, this)
+}, undefined, false, undefined, this);
+`
+ );
+
+ expect(bun.transformSync("export var hi = <div>{123}}</div>").trim()).toBe(
+ `export var hi = jsx("div", {
+ children: [
+ 123,
+ "}"
+ ]
+}, undefined, true, undefined, this);
+ `.trim()
+ );
+ });
+
it("require with a dynamic non-string expression", () => {
var nodeTranspiler = new Bun.Transpiler({ platform: "node" });
expect(nodeTranspiler.transformSync("require('hi' + bar)")).toBe(
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index 0ee6c5edf..b299b4498 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -11342,10 +11342,46 @@ fn NewParser_(
defer i += 1;
// Use Next() not ExpectInsideJSXElement() so we can parse "..."
try p.lexer.next();
- try p.lexer.expect(.t_dot_dot_dot);
- spread_prop_i = i;
- spread_loc = p.lexer.loc();
- try props.append(G.Property{ .value = try p.parseExpr(.comma), .kind = .spread });
+
+ switch (p.lexer.token) {
+ .t_dot_dot_dot => {
+ try p.lexer.next();
+
+ spread_prop_i = i;
+ spread_loc = p.lexer.loc();
+ try props.append(G.Property{ .value = try p.parseExpr(.comma), .kind = .spread });
+ },
+ // This implements
+ // <div {foo} />
+ // ->
+ // <div foo={foo} />
+ T.t_identifier => {
+ const name = p.lexer.identifier;
+ if (strings.eqlComptime(name, "let")) {
+ p.lexer.addError(p.source, p.lexer.loc(), "\"let\" is not allowed with JSX prop punning") catch unreachable;
+ }
+ const key = p.e(E.String{ .utf8 = name }, p.lexer.loc());
+ const ref = p.storeNameInRef(name) catch unreachable;
+ const value = p.e(E.Identifier{ .ref = ref }, key.loc);
+ try p.lexer.next();
+
+ try props.append(G.Property{ .value = value, .key = key, .kind = .normal });
+ },
+ // This implements
+ // <div {"foo"} />
+ // <div {'foo'} />
+ // ->
+ // <div foo="foo" />
+ // note: template literals are not supported, operations on strings are not supported either
+ T.t_string_literal => {
+ const key = p.e(p.lexer.toEString(), p.lexer.loc());
+ try p.lexer.next();
+ try props.append(G.Property{ .value = key, .key = key, .kind = .normal });
+ },
+
+ else => try p.lexer.unexpected(),
+ }
+
try p.lexer.nextInsideJSXElement();
},
else => {