Support JsxMemberExpression
Joe Savona committed
Mar 17, 2023 at 16:18 UTC
a7ac20973f52f09ad1f087c4bd54067e92e7bf3a
5 files changed
+127
-37
compiler/forget/src/HIR/BuildHIR.ts
+53
-16
@@ -1760,7 +1760,27 @@ function lowerJsxElementName(
1760
): Place {
1761
const exprNode = exprPath.node;
1762
const exprLoc = exprNode.loc ?? GeneratedSource;
1763
- if (!exprPath.isJSXIdentifier()) {
1763
+ if (exprPath.isJSXIdentifier()) {
1764
+ const tag: string = exprPath.node.name;
1765
+ if (tag.match(/^[A-Z]/)) {
1766
+ return lowerIdentifier(builder, exprPath);
1767
+ } else {
1768
+ const place: Place = buildTemporaryPlace(builder, exprLoc);
1769
+ builder.push({
1770
+ id: makeInstructionId(0),
1771
+ value: {
1772
+ kind: "Primitive",
1773
+ value: tag,
1774
+ loc: exprLoc,
1775
+ },
1776
+ loc: exprLoc,
1777
+ lvalue: { ...place },
1778
+ });
1779
+ return place;
1780
+ }
1781
+ } else if (exprPath.isJSXMemberExpression()) {
1782
+ return lowerJsxMemberExpression(builder, exprPath);
1783
+ } else {
1784
builder.errors.push({
1785
reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`,
1786
severity: ErrorSeverity.Todo,
@@ -1779,23 +1799,40 @@ function lowerJsxElementName(
1799
});
1800
return { ...place };
1801
}
1782
- const tag: string = exprPath.node.name;
1783
- if (tag.match(/^[A-Z]/)) {
1784
- return lowerIdentifier(builder, exprPath);
1802
+}
1803
+
1804
+function lowerJsxMemberExpression(
1805
+ builder: HIRBuilder,
1806
+ exprPath: NodePath<t.JSXMemberExpression>
1807
+): Place {
1808
+ const loc = exprPath.node.loc ?? GeneratedSource;
1809
+ const object = exprPath.get("object");
1810
+ let objectPlace: Place;
1811
+ if (object.isJSXMemberExpression()) {
1812
+ objectPlace = lowerJsxMemberExpression(builder, object);
1813
} else {
1786
- const place: Place = buildTemporaryPlace(builder, exprLoc);
1787
- builder.push({
1788
- id: makeInstructionId(0),
1789
- value: {
1790
- kind: "Primitive",
1791
- value: tag,
1792
- loc: exprLoc,
1793
- },
1794
- loc: exprLoc,
1795
- lvalue: { ...place },
1796
- });
1797
- return place;
1814
+ invariant(
1815
+ object.isJSXIdentifier(),
1816
+ "TypeScript refinement fail: expected 'JsxIdentifier', got '%s'",
1817
+ object.node.type
1818
+ );
1819
+ objectPlace = lowerIdentifier(builder, object);
1820
}
1821
+ const place = buildTemporaryPlace(builder, loc);
1822
+ const property = exprPath.get("property").node.name;
1823
+ builder.push({
1824
+ id: makeInstructionId(0),
1825
+ lvalue: { ...place },
1826
+ value: {
1827
+ kind: "PropertyLoad",
1828
+ object: objectPlace,
1829
+ property,
1830
+ optional: false,
1831
+ loc,
1832
+ },
1833
+ loc,
1834
+ });
1835
+ return place;
1836
}
1837
1838
function lowerJsxElement(
compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+29
-12
@@ -651,29 +651,26 @@ function codegenInstructionValue(
651
}
652
}
653
let tagValue = codegenPlace(cx, instrValue.tag);
654
- let tag: string;
654
+ let tag: t.JSXIdentifier | t.JSXMemberExpression;
655
if (tagValue.type === "Identifier") {
656
- tag = tagValue.name;
656
+ tag = t.jsxIdentifier(tagValue.name);
657
+ } else if (tagValue.type === "MemberExpression") {
658
+ tag = convertMemberExpressionToJsx(tagValue);
659
} else {
660
invariant(
661
tagValue.type === "StringLiteral",
660
- "Expected JSX tag to be an identifier or string"
662
+ "Expected JSX tag to be an identifier or string, got '%s'",
663
+ tagValue.type
664
);
662
- tag = tagValue.value;
665
+ tag = t.jsxIdentifier(tagValue.value);
666
}
667
const children =
668
instrValue.children !== null
669
? instrValue.children.map((child) => codegenJsxElement(cx, child))
670
: [];
671
value = t.jsxElement(
669
- t.jsxOpeningElement(
670
- t.jsxIdentifier(tag),
671
- attributes,
672
- instrValue.children === null
673
- ),
674
- instrValue.children !== null
675
- ? t.jsxClosingElement(t.jsxIdentifier(tag))
676
- : null,
672
+ t.jsxOpeningElement(tag, attributes, instrValue.children === null),
673
+ instrValue.children !== null ? t.jsxClosingElement(tag) : null,
674
children,
675
instrValue.children === null
676
);
@@ -896,6 +893,26 @@ function codegenJsxElement(
893
}
894
}
895
896
+function convertMemberExpressionToJsx(
897
+ expr: t.MemberExpression
898
+): t.JSXMemberExpression {
899
+ invariant(
900
+ expr.property.type === "Identifier",
901
+ "Expected JSX member expression property to be a string"
902
+ );
903
+ const property = t.jsxIdentifier(expr.property.name);
904
+ if (expr.object.type === "Identifier") {
905
+ return t.jsxMemberExpression(t.jsxIdentifier(expr.object.name), property);
906
+ } else {
907
+ invariant(
908
+ expr.object.type === "MemberExpression",
909
+ "Expected JSX member expression to be an identifier or nested member expression"
910
+ );
911
+ const object = convertMemberExpressionToJsx(expr.object);
912
+ return t.jsxMemberExpression(object, property);
913
+ }
914
+}
915
+
916
function codegenLValue(
917
pattern: Pattern | Place | SpreadPattern
918
): t.ArrayPattern | t.ObjectPattern | t.RestElement | t.Identifier {
compiler/forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md
-9
@@ -166,15 +166,6 @@ let moduleLocal = false;
166
20 |
167
21 | const j = function bar([quz, qux], ...args) {};
168
169
-[ReactForget] TodoError: (BuildHIR::lowerJsxElementName) Handle JSXMemberExpression tags
170
- 17 | <Button haha={1}></Button>;
171
- 18 | <Button>{/** empty */}</Button>;
172
-> 19 | <DesignSystem.Button />;
173
- | ^^^^^^^^^^^^^^^^^^^
174
- 20 |
175
- 21 | const j = function bar([quz, qux], ...args) {};
176
- 22 |
177
-
169
[ReactForget] TodoError: (BuildHIR::lower) Handle RestElement params
170
19 | <DesignSystem.Button />;
171
20 |
compiler/forget/src/__tests__/fixtures/compiler/jsx-member-expression.expect.md
new
+38
@@ -0,0 +1,38 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ return (
7
+ <Sathya.Codes.Forget>
8
+ <Foo.Bar.Baz />
9
+ </Sathya.Codes.Forget>
10
+ );
11
+}
12
+
13
+```
14
+
15
+## Code
16
+
17
+```javascript
18
+function Component(props) {
19
+ const $ = React.unstable_useMemoCache(2);
20
+ let t0;
21
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
22
+ t0 = <Foo.Bar.Baz></Foo.Bar.Baz>;
23
+ $[0] = t0;
24
+ } else {
25
+ t0 = $[0];
26
+ }
27
+ let t1;
28
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
29
+ t1 = <Sathya.Codes.Forget>{t0}</Sathya.Codes.Forget>;
30
+ $[1] = t1;
31
+ } else {
32
+ t1 = $[1];
33
+ }
34
+ return t1;
35
+}
36
+
37
+```
38
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/jsx-member-expression.js
new
+7
@@ -0,0 +1,7 @@
1
+function Component(props) {
2
+ return (
3
+ <Sathya.Codes.Forget>
4
+ <Foo.Bar.Baz />
5
+ </Sathya.Codes.Forget>
6
+ );
7
+}