@samitouri / QOS-React-2 / commits / 179b24b56e

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.

Joe Savona committed Mar 27, 2023 at 10:34 UTC 179b24b56e310cf69618653d8d13bbf784ca3939
6 files changed +231 -157
compiler/forget/src/HIR/BuildHIR.ts
+47 -10
@@ -1518,14 +1518,22 @@ function lowerExpression(
1518 });
1519 continue;
1520 }
1521 - const name = attribute.get("name");
1522 - if (!name.isJSXIdentifier()) {
1523 - builder.errors.push({
1524 - reason: `(BuildHIR::lowerExpression) Handle ${name.type} attribute names in JSXElement`,
1525 - severity: ErrorSeverity.Todo,
1526 - nodePath: name,
1527 - });
1528 - continue;
1521 + const namePath = attribute.get("name");
1522 + let propName;
1523 + if (namePath.isJSXIdentifier()) {
1524 + propName = namePath.node.name;
1525 + if (propName.indexOf(":") !== -1) {
1526 + builder.errors.push({
1527 + reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name '${name}'`,
1528 + severity: ErrorSeverity.Todo,
1529 + nodePath: namePath,
1530 + });
1531 + }
1532 + } else {
1533 + invariant(namePath.isJSXNamespacedName(), "Refinement");
1534 + const namespace = namePath.node.namespace.name;
1535 + const name = namePath.node.name.name;
1536 + propName = `${namespace}:${name}`;
1537 }
1538 const valueExpr = attribute.get("value");
1539 let value;
@@ -1551,8 +1559,7 @@ function lowerExpression(
1559 }
1560 value = lowerExpressionToTemporary(builder, expression);
1561 }
1554 - const prop: string = name.node.name;
1555 - props.push({ kind: "JsxAttribute", name: prop, place: value });
1562 + props.push({ kind: "JsxAttribute", name: propName, place: value });
1563 }
1564 return {
1565 kind: "JsxExpression",
@@ -1922,6 +1929,13 @@ function lowerJsxElementName(
1929 if (tag.match(/^[A-Z]/)) {
1930 return lowerIdentifier(builder, exprPath);
1931 } else {
1932 + if (tag.indexOf(":") !== -1) {
1933 + builder.errors.push({
1934 + reason: `(BuildHIR::lowerJsxElementName) JSXIdentifier to have no colons, got '${tag}'`,
1935 + severity: ErrorSeverity.InvalidInput,
1936 + nodePath: exprPath,
1937 + });
1938 + }
1939 const place: Place = buildTemporaryPlace(builder, exprLoc);
1940 builder.push({
1941 id: makeInstructionId(0),
@@ -1937,6 +1951,29 @@ function lowerJsxElementName(
1951 }
1952 } else if (exprPath.isJSXMemberExpression()) {
1953 return lowerJsxMemberExpression(builder, exprPath);
1954 + } else if (exprPath.isJSXNamespacedName()) {
1955 + const namespace = exprPath.node.namespace.name;
1956 + const name = exprPath.node.name.name;
1957 + const tag = `${namespace}:${name}`;
1958 + if (namespace.indexOf(":") !== -1 || name.indexOf(":") !== -1) {
1959 + builder.errors.push({
1960 + reason: `(BuildHIR::lowerJsxElementName) Expected JSXNamespacedName to have no colons in the namespace or name, got '${namespace}' : '${name}'`,
1961 + severity: ErrorSeverity.InvalidInput,
1962 + nodePath: exprPath,
1963 + });
1964 + }
1965 + const place: Place = buildTemporaryPlace(builder, exprLoc);
1966 + builder.push({
1967 + id: makeInstructionId(0),
1968 + value: {
1969 + kind: "Primitive",
1970 + value: tag,
1971 + loc: exprLoc,
1972 + },
1973 + loc: exprLoc,
1974 + lvalue: { ...place },
1975 + });
1976 + return place;
1977 } else {
1978 builder.errors.push({
1979 reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`,
compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+21 -3
@@ -658,9 +658,19 @@ function codegenInstructionValue(
658 for (const attribute of instrValue.props) {
659 switch (attribute.kind) {
660 case "JsxAttribute": {
661 + let propName: t.JSXIdentifier | t.JSXNamespacedName;
662 + if (attribute.name.indexOf(":") === -1) {
663 + propName = t.jsxIdentifier(attribute.name);
664 + } else {
665 + const [namespace, name] = attribute.name.split(":", 2);
666 + propName = t.jsxNamespacedName(
667 + t.jsxIdentifier(namespace),
668 + t.jsxIdentifier(name)
669 + );
670 + }
671 attributes.push(
672 t.jsxAttribute(
663 - t.jsxIdentifier(attribute.name),
673 + propName,
674 t.jsxExpressionContainer(codegenPlace(cx, attribute.place))
675 )
676 );
@@ -681,9 +691,17 @@ function codegenInstructionValue(
691 }
692 }
693 let tagValue = codegenPlace(cx, instrValue.tag);
684 - let tag: t.JSXIdentifier | t.JSXMemberExpression;
694 + let tag: t.JSXIdentifier | t.JSXNamespacedName | t.JSXMemberExpression;
695 if (tagValue.type === "Identifier") {
686 - tag = t.jsxIdentifier(tagValue.name);
696 + if (tagValue.name.indexOf(":") >= 0) {
697 + const [namespace, name] = tagValue.name.split(":", 2);
698 + tag = t.jsxNamespacedName(
699 + t.jsxIdentifier(namespace),
700 + t.jsxIdentifier(name)
701 + );
702 + } else {
703 + tag = t.jsxIdentifier(tagValue.name);
704 + }
705 } else if (tagValue.type === "MemberExpression") {
706 tag = convertMemberExpressionToJsx(tagValue);
707 } else {
compiler/forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md
+131 -142
@@ -16,10 +16,8 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
16 const g = { b() {}, c: () => {} };
17 const { z, aa = "aa" } = useCustom();
18
19 - <Button xlink:href="localhost:3000"></Button>;
19 <Button haha={1}></Button>;
20 <Button>{/** empty */}</Button>;
22 - <DesignSystem.Button />;
21
22 const j = function bar([quz, qux], ...args) {};
23
@@ -127,7 +125,7 @@ let moduleLocal = false;
125 | ^^^^^^
126 13 | const { z, aa = "aa" } = useCustom();
127 14 |
130 - 15 | <Button xlink:href="localhost:3000"></Button>;
128 + 15 | <Button haha={1}></Button>;
129
130 [ReactForget] TodoError: (BuildHIR::lowerAssignment) Handle AssignmentPattern assignments
131 11 |
@@ -135,215 +133,206 @@ let moduleLocal = false;
133 > 13 | const { z, aa = "aa" } = useCustom();
134 | ^^^^^^^^^
135 14 |
138 - 15 | <Button xlink:href="localhost:3000"></Button>;
139 - 16 | <Button haha={1}></Button>;
140 -
141 -[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXNamespacedName attribute names in JSXElement
142 - 13 | const { z, aa = "aa" } = useCustom();
143 - 14 |
144 -> 15 | <Button xlink:href="localhost:3000"></Button>;
145 - | ^^^^^^^^^^
146 - 16 | <Button haha={1}></Button>;
147 - 17 | <Button>{/** empty */}</Button>;
148 - 18 | <DesignSystem.Button />;
136 + 15 | <Button haha={1}></Button>;
137 + 16 | <Button>{/** empty */}</Button>;
138
139 [ReactForget] TodoError: (BuildHIR::lowerJsxElement) Handle JSXEmptyExpression expressions
151 - 15 | <Button xlink:href="localhost:3000"></Button>;
152 - 16 | <Button haha={1}></Button>;
153 -> 17 | <Button>{/** empty */}</Button>;
140 + 14 |
141 + 15 | <Button haha={1}></Button>;
142 +> 16 | <Button>{/** empty */}</Button>;
143 | ^^^^^^^^^^^^
155 - 18 | <DesignSystem.Button />;
144 + 17 |
145 + 18 | const j = function bar([quz, qux], ...args) {};
146 19 |
157 - 20 | const j = function bar([quz, qux], ...args) {};
147
148 [ReactForget] TodoError: (BuildHIR::lower) Handle RestElement params
160 - 18 | <DesignSystem.Button />;
161 - 19 |
162 -> 20 | const j = function bar([quz, qux], ...args) {};
149 + 16 | <Button>{/** empty */}</Button>;
150 + 17 |
151 +> 18 | const j = function bar([quz, qux], ...args) {};
152 | ^^^^^^^
164 - 21 |
165 - 22 | for (; i < 3; i += 1) {
166 - 23 | x.push(i);
153 + 19 |
154 + 20 | for (; i < 3; i += 1) {
155 + 21 | x.push(i);
156
157 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
169 - 20 | const j = function bar([quz, qux], ...args) {};
170 - 21 |
171 -> 22 | for (; i < 3; i += 1) {
158 + 18 | const j = function bar([quz, qux], ...args) {};
159 + 19 |
160 +> 20 | for (; i < 3; i += 1) {
161 | ^
173 - 23 | x.push(i);
174 - 24 | }
175 - 25 | for (; i < 3; ) {
162 + 21 | x.push(i);
163 + 22 | }
164 + 23 | for (; i < 3; ) {
165
166 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
178 - 23 | x.push(i);
179 - 24 | }
180 -> 25 | for (; i < 3; ) {
167 + 21 | x.push(i);
168 + 22 | }
169 +> 23 | for (; i < 3; ) {
170 | ^
182 - 26 | break;
183 - 27 | }
184 - 28 | for (;;) {
171 + 24 | break;
172 + 25 | }
173 + 26 | for (;;) {
174
175 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
187 - 23 | x.push(i);
188 - 24 | }
189 -> 25 | for (; i < 3; ) {
176 + 21 | x.push(i);
177 + 22 | }
178 +> 23 | for (; i < 3; ) {
179 | ^
191 - 26 | break;
192 - 27 | }
193 - 28 | for (;;) {
180 + 24 | break;
181 + 25 | }
182 + 26 | for (;;) {
183
184 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
196 - 26 | break;
197 - 27 | }
198 -> 28 | for (;;) {
185 + 24 | break;
186 + 25 | }
187 +> 26 | for (;;) {
188 | ^
200 - 29 | break;
201 - 30 | }
202 - 31 |
189 + 27 | break;
190 + 28 | }
191 + 29 |
192
193 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
205 - 26 | break;
206 - 27 | }
207 -> 28 | for (;;) {
194 + 24 | break;
195 + 25 | }
196 +> 26 | for (;;) {
197 | ^
209 - 29 | break;
210 - 30 | }
211 - 31 |
198 + 27 | break;
199 + 28 | }
200 + 29 |
201
202 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty test in ForStatement
214 - 26 | break;
215 - 27 | }
216 -> 28 | for (;;) {
203 + 24 | break;
204 + 25 | }
205 +> 26 | for (;;) {
206 | ^
218 - 29 | break;
219 - 30 | }
220 - 31 |
207 + 27 | break;
208 + 28 | }
209 + 29 |
210
211 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle tagged template with interpolations
223 - 30 | }
224 - 31 |
225 -> 32 | graphql`
212 + 28 | }
213 + 29 |
214 +> 30 | graphql`
215 | ^
227 - 33 | ${g}
228 - 34 | `;
229 - 35 |
216 + 31 | ${g}
217 + 32 | `;
218 + 33 |
219
220 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value
232 - 34 | `;
233 - 35 |
234 -> 36 | graphql`\\t\n`;
221 + 32 | `;
222 + 33 |
223 +> 34 | graphql`\\t\n`;
224 | ^^^^^^^^^^^^^^
236 - 37 |
237 - 38 | for (const c of [1, 2]) {
238 - 39 | }
225 + 35 |
226 + 36 | for (const c of [1, 2]) {
227 + 37 | }
228
229 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForOfStatement statements
241 - 36 | graphql`\\t\n`;
242 - 37 |
243 -> 38 | for (const c of [1, 2]) {
230 + 34 | graphql`\\t\n`;
231 + 35 |
232 +> 36 | for (const c of [1, 2]) {
233 | ^
245 - 39 | }
246 - 40 |
247 - 41 | for (let x in { a: 1 }) {
234 + 37 | }
235 + 38 |
236 + 39 | for (let x in { a: 1 }) {
237
238 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForInStatement statements
250 - 39 | }
251 - 40 |
252 -> 41 | for (let x in { a: 1 }) {
239 + 37 | }
240 + 38 |
241 +> 39 | for (let x in { a: 1 }) {
242 | ^
254 - 42 | }
255 - 43 |
256 - 44 | let updateIdentifier = 0;
243 + 40 | }
244 + 41 |
245 + 42 | let updateIdentifier = 0;
246
247 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression
259 - 43 |
260 - 44 | let updateIdentifier = 0;
261 -> 45 | --updateIdentifier;
248 + 41 |
249 + 42 | let updateIdentifier = 0;
250 +> 43 | --updateIdentifier;
251 | ^^^^^^^^^^^^^^^^^^
263 - 46 | ++updateIdentifier;
264 - 47 | updateIdentifier.y++;
265 - 48 | updateIdentifier.y--;
252 + 44 | ++updateIdentifier;
253 + 45 | updateIdentifier.y++;
254 + 46 | updateIdentifier.y--;
255
256 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression
268 - 44 | let updateIdentifier = 0;
269 - 45 | --updateIdentifier;
270 -> 46 | ++updateIdentifier;
257 + 42 | let updateIdentifier = 0;
258 + 43 | --updateIdentifier;
259 +> 44 | ++updateIdentifier;
260 | ^^^^^^^^^^^^^^^^^^
272 - 47 | updateIdentifier.y++;
273 - 48 | updateIdentifier.y--;
274 - 49 |
261 + 45 | updateIdentifier.y++;
262 + 46 | updateIdentifier.y--;
263 + 47 |
264
265 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument
277 - 45 | --updateIdentifier;
278 - 46 | ++updateIdentifier;
279 -> 47 | updateIdentifier.y++;
266 + 43 | --updateIdentifier;
267 + 44 | ++updateIdentifier;
268 +> 45 | updateIdentifier.y++;
269 | ^^^^^^^^^^^^^^^^^^^^
281 - 48 | updateIdentifier.y--;
282 - 49 |
283 - 50 | switch (i) {
270 + 46 | updateIdentifier.y--;
271 + 47 |
272 + 48 | switch (i) {
273
274 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument
286 - 46 | ++updateIdentifier;
287 - 47 | updateIdentifier.y++;
288 -> 48 | updateIdentifier.y--;
275 + 44 | ++updateIdentifier;
276 + 45 | updateIdentifier.y++;
277 +> 46 | updateIdentifier.y--;
278 | ^^^^^^^^^^^^^^^^^^^^
290 - 49 |
291 - 50 | switch (i) {
292 - 51 | case 1 + 1: {
279 + 47 |
280 + 48 | switch (i) {
281 + 49 | case 1 + 1: {
282
283 [ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
295 - 53 | case foo(): {
296 - 54 | }
297 -> 55 | case x.y: {
284 + 51 | case foo(): {
285 + 52 | }
286 +> 53 | case x.y: {
287 | ^^^
288 + 54 | }
289 + 55 | default: {
290 56 | }
300 - 57 | default: {
301 - 58 | }
291
292 [ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
304 - 51 | case 1 + 1: {
305 - 52 | }
306 -> 53 | case foo(): {
293 + 49 | case 1 + 1: {
294 + 50 | }
295 +> 51 | case foo(): {
296 | ^^^^^
297 + 52 | }
298 + 53 | case x.y: {
299 54 | }
309 - 55 | case x.y: {
310 - 56 | }
300
301 [ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
313 - 49 |
314 - 50 | switch (i) {
315 -> 51 | case 1 + 1: {
302 + 47 |
303 + 48 | switch (i) {
304 +> 49 | case 1 + 1: {
305 | ^^^^^
306 + 50 | }
307 + 51 | case foo(): {
308 52 | }
318 - 53 | case foo(): {
319 - 54 | }
309
310 [ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.
322 - 60 |
323 - 61 | // Cannot assign to globals
324 -> 62 | someUnknownGlobal = true;
311 + 58 |
312 + 59 | // Cannot assign to globals
313 +> 60 | someUnknownGlobal = true;
314 | ^^^^^^^^^^^^^^^^^
326 - 63 | moduleLocal = true;
327 - 64 |
328 - 65 | function component(a) {
315 + 61 | moduleLocal = true;
316 + 62 |
317 + 63 | function component(a) {
318
319 [ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.
331 - 61 | // Cannot assign to globals
332 - 62 | someUnknownGlobal = true;
333 -> 63 | moduleLocal = true;
320 + 59 | // Cannot assign to globals
321 + 60 | someUnknownGlobal = true;
322 +> 61 | moduleLocal = true;
323 | ^^^^^^^^^^^
335 - 64 |
336 - 65 | function component(a) {
337 - 66 | // Add support for function declarations once we support `var` hoisting.
324 + 62 |
325 + 63 | function component(a) {
326 + 64 | // Add support for function declarations once we support `var` hoisting.
327
328 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle FunctionDeclaration statements
340 - 63 | moduleLocal = true;
341 - 64 |
342 -> 65 | function component(a) {
329 + 61 | moduleLocal = true;
330 + 62 |
331 +> 63 | function component(a) {
332 | ^
344 - 66 | // Add support for function declarations once we support `var` hoisting.
345 - 67 | function t() {}
346 - 68 | t();
333 + 64 | // Add support for function declarations once we support `var` hoisting.
334 + 65 | function t() {}
335 + 66 | t();
336 ```
337
338
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.js
-2
@@ -12,10 +12,8 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
12 const g = { b() {}, c: () => {} };
13 const { z, aa = "aa" } = useCustom();
14
15 - <Button xlink:href="localhost:3000"></Button>;
15 <Button haha={1}></Button>;
16 <Button>{/** empty */}</Button>;
18 - <DesignSystem.Button />;
17
18 const j = function bar([quz, qux], ...args) {};
19
compiler/forget/src/__tests__/fixtures/compiler/jsx-namespaced-name.expect.md new
+29
@@ -0,0 +1,29 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + return <xml:http protocol:version={props.version} />;
7 +}
8 +
9 +```
10 +
11 +## Code
12 +
13 +```javascript
14 +function Component(props) {
15 + const $ = React.unstable_useMemoCache(2);
16 + const c_0 = $[0] !== props.version;
17 + let t0;
18 + if (c_0) {
19 + t0 = <xml:http protocol:version={props.version} />;
20 + $[0] = props.version;
21 + $[1] = t0;
22 + } else {
23 + t0 = $[1];
24 + }
25 + return t0;
26 +}
27 +
28 +```
29 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/jsx-namespaced-name.js new
+3
@@ -0,0 +1,3 @@
1 +function Component(props) {
2 + return <xml:http protocol:version={props.version} />;
3 +}