JSXNamespacedName support

This is kind of a hack, but i think it's worth it given that JSXNamespacedName 
is relatively uncommon. Adding a new InstructionValue variant to represent a 
namespaced name is one option, but then that isn't a valid expression and can't 
appear as an operand anywhere else. Instead, we lower namespaced names as a 
primitive (string) as `${namespace}:${name}` — exploiting the fact the namespace 
and name can't have a colon, and non-namespaced tagnames also can't have colons. 

It's a bit of a hack but it's contained to the JSX processing code. If folks 
have strong opinions on this i'm happy to change but this felt reasonable as a 
quick and reliable way to unblock support. 

NOTE: there is a larger question of what to do about compiling `fbt` tags. 
Before we can do anything with them, though, we need to parse them.
This commit is contained in:
Joe Savona
2023-03-27 10:34:09 -07:00
parent 027f773179
commit 179b24b56e
6 changed files with 233 additions and 159 deletions
+47 -10
View File
@@ -1518,14 +1518,22 @@ function lowerExpression(
});
continue;
}
const name = attribute.get("name");
if (!name.isJSXIdentifier()) {
builder.errors.push({
reason: `(BuildHIR::lowerExpression) Handle ${name.type} attribute names in JSXElement`,
severity: ErrorSeverity.Todo,
nodePath: name,
});
continue;
const namePath = attribute.get("name");
let propName;
if (namePath.isJSXIdentifier()) {
propName = namePath.node.name;
if (propName.indexOf(":") !== -1) {
builder.errors.push({
reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name '${name}'`,
severity: ErrorSeverity.Todo,
nodePath: namePath,
});
}
} else {
invariant(namePath.isJSXNamespacedName(), "Refinement");
const namespace = namePath.node.namespace.name;
const name = namePath.node.name.name;
propName = `${namespace}:${name}`;
}
const valueExpr = attribute.get("value");
let value;
@@ -1551,8 +1559,7 @@ function lowerExpression(
}
value = lowerExpressionToTemporary(builder, expression);
}
const prop: string = name.node.name;
props.push({ kind: "JsxAttribute", name: prop, place: value });
props.push({ kind: "JsxAttribute", name: propName, place: value });
}
return {
kind: "JsxExpression",
@@ -1922,6 +1929,13 @@ function lowerJsxElementName(
if (tag.match(/^[A-Z]/)) {
return lowerIdentifier(builder, exprPath);
} else {
if (tag.indexOf(":") !== -1) {
builder.errors.push({
reason: `(BuildHIR::lowerJsxElementName) JSXIdentifier to have no colons, got '${tag}'`,
severity: ErrorSeverity.InvalidInput,
nodePath: exprPath,
});
}
const place: Place = buildTemporaryPlace(builder, exprLoc);
builder.push({
id: makeInstructionId(0),
@@ -1937,6 +1951,29 @@ function lowerJsxElementName(
}
} else if (exprPath.isJSXMemberExpression()) {
return lowerJsxMemberExpression(builder, exprPath);
} else if (exprPath.isJSXNamespacedName()) {
const namespace = exprPath.node.namespace.name;
const name = exprPath.node.name.name;
const tag = `${namespace}:${name}`;
if (namespace.indexOf(":") !== -1 || name.indexOf(":") !== -1) {
builder.errors.push({
reason: `(BuildHIR::lowerJsxElementName) Expected JSXNamespacedName to have no colons in the namespace or name, got '${namespace}' : '${name}'`,
severity: ErrorSeverity.InvalidInput,
nodePath: exprPath,
});
}
const place: Place = buildTemporaryPlace(builder, exprLoc);
builder.push({
id: makeInstructionId(0),
value: {
kind: "Primitive",
value: tag,
loc: exprLoc,
},
loc: exprLoc,
lvalue: { ...place },
});
return place;
} else {
builder.errors.push({
reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`,
@@ -658,9 +658,19 @@ function codegenInstructionValue(
for (const attribute of instrValue.props) {
switch (attribute.kind) {
case "JsxAttribute": {
let propName: t.JSXIdentifier | t.JSXNamespacedName;
if (attribute.name.indexOf(":") === -1) {
propName = t.jsxIdentifier(attribute.name);
} else {
const [namespace, name] = attribute.name.split(":", 2);
propName = t.jsxNamespacedName(
t.jsxIdentifier(namespace),
t.jsxIdentifier(name)
);
}
attributes.push(
t.jsxAttribute(
t.jsxIdentifier(attribute.name),
propName,
t.jsxExpressionContainer(codegenPlace(cx, attribute.place))
)
);
@@ -681,9 +691,17 @@ function codegenInstructionValue(
}
}
let tagValue = codegenPlace(cx, instrValue.tag);
let tag: t.JSXIdentifier | t.JSXMemberExpression;
let tag: t.JSXIdentifier | t.JSXNamespacedName | t.JSXMemberExpression;
if (tagValue.type === "Identifier") {
tag = t.jsxIdentifier(tagValue.name);
if (tagValue.name.indexOf(":") >= 0) {
const [namespace, name] = tagValue.name.split(":", 2);
tag = t.jsxNamespacedName(
t.jsxIdentifier(namespace),
t.jsxIdentifier(name)
);
} else {
tag = t.jsxIdentifier(tagValue.name);
}
} else if (tagValue.type === "MemberExpression") {
tag = convertMemberExpressionToJsx(tagValue);
} else {
@@ -16,10 +16,8 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
const g = { b() {}, c: () => {} };
const { z, aa = "aa" } = useCustom();
<Button xlink:href="localhost:3000"></Button>;
<Button haha={1}></Button>;
<Button>{/** empty */}</Button>;
<DesignSystem.Button />;
const j = function bar([quz, qux], ...args) {};
@@ -127,7 +125,7 @@ let moduleLocal = false;
| ^^^^^^
13 | const { z, aa = "aa" } = useCustom();
14 |
15 | <Button xlink:href="localhost:3000"></Button>;
15 | <Button haha={1}></Button>;
[ReactForget] TodoError: (BuildHIR::lowerAssignment) Handle AssignmentPattern assignments
11 |
@@ -135,215 +133,206 @@ let moduleLocal = false;
> 13 | const { z, aa = "aa" } = useCustom();
| ^^^^^^^^^
14 |
15 | <Button xlink:href="localhost:3000"></Button>;
16 | <Button haha={1}></Button>;
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXNamespacedName attribute names in JSXElement
13 | const { z, aa = "aa" } = useCustom();
14 |
> 15 | <Button xlink:href="localhost:3000"></Button>;
| ^^^^^^^^^^
16 | <Button haha={1}></Button>;
17 | <Button>{/** empty */}</Button>;
18 | <DesignSystem.Button />;
15 | <Button haha={1}></Button>;
16 | <Button>{/** empty */}</Button>;
[ReactForget] TodoError: (BuildHIR::lowerJsxElement) Handle JSXEmptyExpression expressions
15 | <Button xlink:href="localhost:3000"></Button>;
16 | <Button haha={1}></Button>;
> 17 | <Button>{/** empty */}</Button>;
14 |
15 | <Button haha={1}></Button>;
> 16 | <Button>{/** empty */}</Button>;
| ^^^^^^^^^^^^
18 | <DesignSystem.Button />;
17 |
18 | const j = function bar([quz, qux], ...args) {};
19 |
20 | const j = function bar([quz, qux], ...args) {};
[ReactForget] TodoError: (BuildHIR::lower) Handle RestElement params
18 | <DesignSystem.Button />;
19 |
> 20 | const j = function bar([quz, qux], ...args) {};
16 | <Button>{/** empty */}</Button>;
17 |
> 18 | const j = function bar([quz, qux], ...args) {};
| ^^^^^^^
21 |
22 | for (; i < 3; i += 1) {
23 | x.push(i);
19 |
20 | for (; i < 3; i += 1) {
21 | x.push(i);
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
20 | const j = function bar([quz, qux], ...args) {};
21 |
> 22 | for (; i < 3; i += 1) {
18 | const j = function bar([quz, qux], ...args) {};
19 |
> 20 | for (; i < 3; i += 1) {
| ^
23 | x.push(i);
24 | }
25 | for (; i < 3; ) {
21 | x.push(i);
22 | }
23 | for (; i < 3; ) {
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
23 | x.push(i);
24 | }
> 25 | for (; i < 3; ) {
21 | x.push(i);
22 | }
> 23 | for (; i < 3; ) {
| ^
26 | break;
27 | }
28 | for (;;) {
24 | break;
25 | }
26 | for (;;) {
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
23 | x.push(i);
24 | }
> 25 | for (; i < 3; ) {
21 | x.push(i);
22 | }
> 23 | for (; i < 3; ) {
| ^
26 | break;
27 | }
28 | for (;;) {
24 | break;
25 | }
26 | for (;;) {
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
26 | break;
27 | }
> 28 | for (;;) {
24 | break;
25 | }
> 26 | for (;;) {
| ^
29 | break;
30 | }
31 |
27 | break;
28 | }
29 |
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
26 | break;
27 | }
> 28 | for (;;) {
24 | break;
25 | }
> 26 | for (;;) {
| ^
29 | break;
30 | }
31 |
27 | break;
28 | }
29 |
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty test in ForStatement
26 | break;
27 | }
> 28 | for (;;) {
24 | break;
25 | }
> 26 | for (;;) {
| ^
29 | break;
30 | }
31 |
27 | break;
28 | }
29 |
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle tagged template with interpolations
30 | }
31 |
> 32 | graphql`
28 | }
29 |
> 30 | graphql`
| ^
33 | ${g}
34 | `;
35 |
31 | ${g}
32 | `;
33 |
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value
34 | `;
35 |
> 36 | graphql`\\t\n`;
32 | `;
33 |
> 34 | graphql`\\t\n`;
| ^^^^^^^^^^^^^^
37 |
38 | for (const c of [1, 2]) {
39 | }
35 |
36 | for (const c of [1, 2]) {
37 | }
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForOfStatement statements
36 | graphql`\\t\n`;
37 |
> 38 | for (const c of [1, 2]) {
34 | graphql`\\t\n`;
35 |
> 36 | for (const c of [1, 2]) {
| ^
39 | }
40 |
41 | for (let x in { a: 1 }) {
37 | }
38 |
39 | for (let x in { a: 1 }) {
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForInStatement statements
39 | }
40 |
> 41 | for (let x in { a: 1 }) {
37 | }
38 |
> 39 | for (let x in { a: 1 }) {
| ^
42 | }
43 |
44 | let updateIdentifier = 0;
40 | }
41 |
42 | let updateIdentifier = 0;
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression
43 |
44 | let updateIdentifier = 0;
> 45 | --updateIdentifier;
41 |
42 | let updateIdentifier = 0;
> 43 | --updateIdentifier;
| ^^^^^^^^^^^^^^^^^^
46 | ++updateIdentifier;
47 | updateIdentifier.y++;
48 | updateIdentifier.y--;
44 | ++updateIdentifier;
45 | updateIdentifier.y++;
46 | updateIdentifier.y--;
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression
44 | let updateIdentifier = 0;
45 | --updateIdentifier;
> 46 | ++updateIdentifier;
42 | let updateIdentifier = 0;
43 | --updateIdentifier;
> 44 | ++updateIdentifier;
| ^^^^^^^^^^^^^^^^^^
47 | updateIdentifier.y++;
48 | updateIdentifier.y--;
49 |
45 | updateIdentifier.y++;
46 | updateIdentifier.y--;
47 |
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument
45 | --updateIdentifier;
46 | ++updateIdentifier;
> 47 | updateIdentifier.y++;
43 | --updateIdentifier;
44 | ++updateIdentifier;
> 45 | updateIdentifier.y++;
| ^^^^^^^^^^^^^^^^^^^^
48 | updateIdentifier.y--;
49 |
50 | switch (i) {
46 | updateIdentifier.y--;
47 |
48 | switch (i) {
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument
46 | ++updateIdentifier;
47 | updateIdentifier.y++;
> 48 | updateIdentifier.y--;
44 | ++updateIdentifier;
45 | updateIdentifier.y++;
> 46 | updateIdentifier.y--;
| ^^^^^^^^^^^^^^^^^^^^
49 |
50 | switch (i) {
51 | case 1 + 1: {
47 |
48 | switch (i) {
49 | case 1 + 1: {
[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
53 | case foo(): {
54 | }
> 55 | case x.y: {
51 | case foo(): {
52 | }
> 53 | case x.y: {
| ^^^
56 | }
57 | default: {
58 | }
[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
51 | case 1 + 1: {
52 | }
> 53 | case foo(): {
| ^^^^^
54 | }
55 | case x.y: {
55 | default: {
56 | }
[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
49 |
50 | switch (i) {
> 51 | case 1 + 1: {
49 | case 1 + 1: {
50 | }
> 51 | case foo(): {
| ^^^^^
52 | }
53 | case foo(): {
53 | case x.y: {
54 | }
[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
47 |
48 | switch (i) {
> 49 | case 1 + 1: {
| ^^^^^
50 | }
51 | case foo(): {
52 | }
[ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.
60 |
61 | // Cannot assign to globals
> 62 | someUnknownGlobal = true;
58 |
59 | // Cannot assign to globals
> 60 | someUnknownGlobal = true;
| ^^^^^^^^^^^^^^^^^
63 | moduleLocal = true;
64 |
65 | function component(a) {
61 | moduleLocal = true;
62 |
63 | function component(a) {
[ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.
61 | // Cannot assign to globals
62 | someUnknownGlobal = true;
> 63 | moduleLocal = true;
59 | // Cannot assign to globals
60 | someUnknownGlobal = true;
> 61 | moduleLocal = true;
| ^^^^^^^^^^^
64 |
65 | function component(a) {
66 | // Add support for function declarations once we support `var` hoisting.
62 |
63 | function component(a) {
64 | // Add support for function declarations once we support `var` hoisting.
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle FunctionDeclaration statements
63 | moduleLocal = true;
64 |
> 65 | function component(a) {
61 | moduleLocal = true;
62 |
> 63 | function component(a) {
| ^
66 | // Add support for function declarations once we support `var` hoisting.
67 | function t() {}
68 | t();
64 | // Add support for function declarations once we support `var` hoisting.
65 | function t() {}
66 | t();
```
@@ -12,10 +12,8 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
const g = { b() {}, c: () => {} };
const { z, aa = "aa" } = useCustom();
<Button xlink:href="localhost:3000"></Button>;
<Button haha={1}></Button>;
<Button>{/** empty */}</Button>;
<DesignSystem.Button />;
const j = function bar([quz, qux], ...args) {};
@@ -0,0 +1,29 @@
## Input
```javascript
function Component(props) {
return <xml:http protocol:version={props.version} />;
}
```
## Code
```javascript
function Component(props) {
const $ = React.unstable_useMemoCache(2);
const c_0 = $[0] !== props.version;
let t0;
if (c_0) {
t0 = <xml:http protocol:version={props.version} />;
$[0] = props.version;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
```
@@ -0,0 +1,3 @@
function Component(props) {
return <xml:http protocol:version={props.version} />;
}