aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-05 19:15:25 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-05 19:33:25 -0800
commit093807391a9563ad36c2b04a286da23d09fad835 (patch)
tree5485bda684d4f04b6a5af9a12f509fd47e906be1
parent18c923596a5b6480fa5a841bd0a6adfdb365b831 (diff)
downloadbun-093807391a9563ad36c2b04a286da23d09fad835.tar.gz
bun-093807391a9563ad36c2b04a286da23d09fad835.tar.zst
bun-093807391a9563ad36c2b04a286da23d09fad835.zip
[JS Parser] dot property shorthand for JSX
This is a non-standard backwards-compatible feature that I suspect other tooling will soon adopt (and expect to help other tooling adopt it) ```jsx var hello = {hi: 'yo'}; export const Foo = () => <Bar {hello.hi} /> ``` Desugars into: ```jsx var hello = {hi: 'yo'}; export const Foo = () => <Bar hi={hello.hi} /> ``` This works with defines and macros too. ```jsx export const Foo = () => <Bar {process.env.NODE_ENV} /> ``` ```jsx export const Foo = () => <Bar NODE_ENV="development" /> ```
-rw-r--r--README.md2
-rw-r--r--integration/bunjs-only-snippets/transpiler.test.js53
-rw-r--r--src/js_parser/js_parser.zig37
3 files changed, 82 insertions, 10 deletions
diff --git a/README.md b/README.md
index 78e1e358e..c06a5604d 100644
--- a/README.md
+++ b/README.md
@@ -261,7 +261,7 @@ bun is a project with incredibly large scope, and it’s early days.
| [Hash components for Fast Refresh](https://github.com/Jarred-Sumner/bun/issues/18) | JSX Transpiler |
| Source Maps | JavaScript |
| Source Maps | CSS |
-| JavaScript Minifier | JavaScript |
+| JavaScript Minifier | JS Transpiler |
| CSS Minifier | CSS |
| CSS Parser (it only bundles) | CSS |
| Tree-shaking | JavaScript |
diff --git a/integration/bunjs-only-snippets/transpiler.test.js b/integration/bunjs-only-snippets/transpiler.test.js
index 5553c1cc6..1162c65a7 100644
--- a/integration/bunjs-only-snippets/transpiler.test.js
+++ b/integration/bunjs-only-snippets/transpiler.test.js
@@ -50,6 +50,9 @@ describe("Bun.Transpiler", () => {
it("JSX", () => {
var bun = new Bun.Transpiler({
loader: "jsx",
+ define: {
+ "process.env.NODE_ENV": JSON.stringify("development"),
+ },
});
expect(bun.transformSync("export var foo = <div foo />")).toBe(
`export var foo = jsx("div", {
@@ -69,12 +72,62 @@ describe("Bun.Transpiler", () => {
}, undefined, false, undefined, this);
`
);
+
expect(bun.transformSync("export var hi = <div {foo} />")).toBe(
`export var hi = jsx("div", {
foo
}, undefined, false, undefined, this);
`
);
+ expect(bun.transformSync("export var hi = <div {foo.bar.baz} />")).toBe(
+ `export var hi = jsx("div", {
+ baz: foo.bar.baz
+}, undefined, false, undefined, this);
+`
+ );
+ expect(bun.transformSync("export var hi = <div {foo?.bar?.baz} />")).toBe(
+ `export var hi = jsx("div", {
+ baz: foo?.bar?.baz
+}, undefined, false, undefined, this);
+`
+ );
+ expect(
+ bun.transformSync("export var hi = <div {foo['baz'].bar?.baz} />")
+ ).toBe(
+ `export var hi = jsx("div", {
+ baz: foo["baz"].bar?.baz
+}, undefined, false, undefined, this);
+`
+ );
+
+ // cursed
+ expect(
+ bun.transformSync(
+ "export var hi = <div {foo[{name: () => true}.name].hi} />"
+ )
+ ).toBe(
+ `export var hi = jsx("div", {
+ hi: foo[{ name: () => true }.name].hi
+}, undefined, false, undefined, this);
+`
+ );
+ expect(
+ bun.transformSync("export var hi = <Foo {process.env.NODE_ENV} />")
+ ).toBe(
+ `export var hi = jsx(Foo, {
+ NODE_ENV: "development"
+}, undefined, false, undefined, this);
+`
+ );
+
+ expect(
+ bun.transformSync("export var hi = <div {foo['baz'].bar?.baz} />")
+ ).toBe(
+ `export var hi = jsx("div", {
+ baz: foo["baz"].bar?.baz
+}, undefined, false, undefined, this);
+`
+ );
try {
bun.transformSync("export var hi = <div {foo}={foo}= />");
throw new Error("Expected error");
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index b299b4498..afe429e9e 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -11356,16 +11356,35 @@ fn NewParser_(
// ->
// <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();
+ // we need to figure out what the key they mean is
+ // to do that, we must determine the key name
+ const expr = try p.parseExpr(Level.lowest);
+
+ const key = brk: {
+ switch (expr.data) {
+ .e_import_identifier => |ident| {
+ break :brk p.e(E.String{ .utf8 = p.loadNameFromRef(ident.ref) }, expr.loc);
+ },
+ .e_identifier => |ident| {
+ break :brk p.e(E.String{ .utf8 = p.loadNameFromRef(ident.ref) }, expr.loc);
+ },
+ .e_dot => |dot| {
+ break :brk p.e(E.String{ .utf8 = dot.name }, dot.name_loc);
+ },
+ .e_index => |index| {
+ if (index.index.data == .e_string) {
+ break :brk index.index;
+ }
+ },
+ else => {},
+ }
+
+ // If we get here, it's invalid
+ try p.log.addError(p.source, expr.loc, "Invalid JSX prop shorthand, must be identifier, dot or string");
+ return error.SyntaxError;
+ };
- try props.append(G.Property{ .value = value, .key = key, .kind = .normal });
+ try props.append(G.Property{ .value = expr, .key = key, .kind = .normal });
},
// This implements
// <div {"foo"} />