[hir] Add support for string literal property keys
Mostly reuses existing analysis of an identifier property key. Adds a new type field to ObjectProperty to propogate the type of the key. This is used in codegen to correctly emit a string literal or an identifier.
Sathya Gunasekaran committed
Sep 7, 2023 at 13:56 UTC
f2027b9a4dd3e906e9853cd1d32ee1e008290f19
16 files changed
+353
-64
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+37
-24
@@ -967,6 +967,33 @@ function lowerStatement(
967
}
968
}
969
970
+function lowerObjectPropertyKey(
971
+ builder: HIRBuilder,
972
+ key: t.PrivateName | t.Expression
973
+): { name: string; type: "string" | "identifier" } | null {
974
+ if (key.type === "Identifier") {
975
+ return {
976
+ name: key.name,
977
+ type: "identifier",
978
+ };
979
+ }
980
+
981
+ if (key.type === "StringLiteral") {
982
+ return {
983
+ name: key.value,
984
+ type: "string",
985
+ };
986
+ }
987
+
988
+ builder.errors.push({
989
+ reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
990
+ severity: ErrorSeverity.Todo,
991
+ loc: key.loc ?? null,
992
+ suggestions: null,
993
+ });
994
+ return null;
995
+}
996
+
997
function lowerExpression(
998
builder: HIRBuilder,
999
exprPath: NodePath<t.Expression>
@@ -1009,19 +1036,11 @@ function lowerExpression(
1036
const properties: Array<ObjectProperty | SpreadPattern> = [];
1037
for (const propertyPath of propertyPaths) {
1038
if (propertyPath.isObjectProperty()) {
1012
- const key = propertyPath.node.key;
1013
- let keyName: string;
1014
- if (key.type === "Identifier") {
1015
- keyName = key.name;
1016
- } else if (key.type === "StringLiteral") {
1017
- keyName = key.value;
1018
- } else {
1019
- builder.errors.push({
1020
- reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
1021
- severity: ErrorSeverity.Todo,
1022
- loc: propertyPath.node.loc ?? null,
1023
- suggestions: null,
1024
- });
1039
+ const loweredKey = lowerObjectPropertyKey(
1040
+ builder,
1041
+ propertyPath.node.key
1042
+ );
1043
+ if (!loweredKey) {
1044
continue;
1045
}
1046
const valuePath = propertyPath.get("value");
@@ -1037,8 +1056,8 @@ function lowerExpression(
1056
const value = lowerExpressionToTemporary(builder, valuePath);
1057
properties.push({
1058
kind: "ObjectProperty",
1040
- name: keyName,
1059
place: value,
1060
+ ...loweredKey,
1061
});
1062
} else if (propertyPath.isSpreadElement()) {
1063
const place = lowerExpressionToTemporary(
@@ -2800,14 +2819,8 @@ function lowerAssignment(
2819
});
2820
continue;
2821
}
2803
- const key = property.get("key");
2804
- if (!key.isIdentifier()) {
2805
- builder.errors.push({
2806
- reason: `(BuildHIR::lowerAssignment) Handle ${key.type} keys in ObjectPattern`,
2807
- severity: ErrorSeverity.Todo,
2808
- loc: key.node.loc ?? null,
2809
- suggestions: null,
2810
- });
2822
+ const loweredKey = lowerObjectPropertyKey(builder, property.node.key);
2823
+ if (!loweredKey) {
2824
continue;
2825
}
2826
const element = property.get("value");
@@ -2832,8 +2845,8 @@ function lowerAssignment(
2845
}
2846
properties.push({
2847
kind: "ObjectProperty",
2835
- name: key.node.name,
2848
place: identifier,
2849
+ ...loweredKey,
2850
});
2851
} else {
2852
const temp = buildTemporaryPlace(
@@ -2842,8 +2855,8 @@ function lowerAssignment(
2855
);
2856
properties.push({
2857
kind: "ObjectProperty",
2845
- name: key.node.name,
2858
place: { ...temp },
2859
+ ...loweredKey,
2860
});
2861
followups.push({ place: temp, path: element as NodePath<t.LVal> }); // TODO remove type cast
2862
}
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+1
@@ -501,6 +501,7 @@ export type ObjectPattern = {
501
502
export type ObjectProperty = {
503
kind: "ObjectProperty";
504
+ type: "string" | "identifier";
505
name: string; // TODO: make a Place
506
place: Place;
507
};
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+16
-4
@@ -16,6 +16,7 @@ import {
16
IdentifierId,
17
InstructionKind,
18
JsxAttribute,
19
+ ObjectProperty,
20
Pattern,
21
Place,
22
ReactiveBlock,
@@ -898,14 +899,14 @@ function codegenInstructionValue(
899
const properties = [];
900
for (const property of instrValue.properties) {
901
if (property.kind === "ObjectProperty") {
901
- const key = t.identifier(property.name);
902
+ const key = codegenObjectPropertyKey(property);
903
const value = codegenPlace(cx, property.place);
904
properties.push(
905
t.objectProperty(
906
key,
907
value,
908
false,
908
- value.type === "Identifier" && value.name === key.name
909
+ value.type === "Identifier" && value.name === property.name
910
)
911
);
912
} else {
@@ -1310,6 +1311,17 @@ function convertMemberExpressionToJsx(
1311
}
1312
}
1313
1314
+function codegenObjectPropertyKey(
1315
+ property: ObjectProperty
1316
+): t.StringLiteral | t.Identifier {
1317
+ switch (property.type) {
1318
+ case "identifier":
1319
+ return t.identifier(property.name);
1320
+ case "string":
1321
+ return t.stringLiteral(property.name);
1322
+ }
1323
+}
1324
+
1325
function codegenLValue(
1326
pattern: Pattern | Place | SpreadPattern
1327
): t.ArrayPattern | t.ObjectPattern | t.RestElement | t.Identifier {
@@ -1328,13 +1340,13 @@ function codegenLValue(
1340
return t.objectPattern(
1341
pattern.properties.map((property) => {
1342
if (property.kind === "ObjectProperty") {
1331
- const key = t.identifier(property.name);
1343
+ const key = codegenObjectPropertyKey(property);
1344
const value = codegenLValue(property.place);
1345
return t.objectProperty(
1346
key,
1347
value,
1348
false,
1337
- value.type === "Identifier" && value.name === key.name
1349
+ value.type === "Identifier" && value.name === property.name
1350
);
1351
} else {
1352
return t.restElement(codegenLValue(property.place));
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-mixed-property-key-types.expect.md
new
+53
@@ -0,0 +1,53 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function foo() {
6
+ const {
7
+ "data-foo-bar": x,
8
+ a: y,
9
+ data: z,
10
+ } = { "data-foo-bar": 1, a: 2, data: 3 };
11
+ return [x, y, z];
12
+}
13
+
14
+export const FIXTURE_ENTRYPOINT = {
15
+ fn: foo,
16
+ params: [],
17
+ isComponent: false,
18
+};
19
+
20
+```
21
+
22
+## Code
23
+
24
+```javascript
25
+import { unstable_useMemoCache as useMemoCache } from "react";
26
+function foo() {
27
+ const $ = useMemoCache(2);
28
+ let t0;
29
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30
+ t0 = { "data-foo-bar": 1, a: 2, data: 3 };
31
+ $[0] = t0;
32
+ } else {
33
+ t0 = $[0];
34
+ }
35
+ const { "data-foo-bar": x, a: y, data: z } = t0;
36
+ let t1;
37
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
38
+ t1 = [x, y, z];
39
+ $[1] = t1;
40
+ } else {
41
+ t1 = $[1];
42
+ }
43
+ return t1;
44
+}
45
+
46
+export const FIXTURE_ENTRYPOINT = {
47
+ fn: foo,
48
+ params: [],
49
+ isComponent: false,
50
+};
51
+
52
+```
53
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-mixed-property-key-types.js
new
+14
@@ -0,0 +1,14 @@
1
+function foo() {
2
+ const {
3
+ "data-foo-bar": x,
4
+ a: y,
5
+ data: z,
6
+ } = { "data-foo-bar": 1, a: 2, data: 3 };
7
+ return [x, y, z];
8
+}
9
+
10
+export const FIXTURE_ENTRYPOINT = {
11
+ fn: foo,
12
+ params: [],
13
+ isComponent: false,
14
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-param-string-literal-key-invalid-identifier.expect.md
new
+32
@@ -0,0 +1,32 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function foo({ "data-foo-bar": dataTestID }) {
6
+ return dataTestID;
7
+}
8
+
9
+export const FIXTURE_ENTRYPOINT = {
10
+ fn: foo,
11
+ params: [{ "data-foo-bar": {} }],
12
+ isComponent: false,
13
+};
14
+
15
+```
16
+
17
+## Code
18
+
19
+```javascript
20
+function foo(t5) {
21
+ const { "data-foo-bar": dataTestID } = t5;
22
+ return dataTestID;
23
+}
24
+
25
+export const FIXTURE_ENTRYPOINT = {
26
+ fn: foo,
27
+ params: [{ "data-foo-bar": {} }],
28
+ isComponent: false,
29
+};
30
+
31
+```
32
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-param-string-literal-key-invalid-identifier.js
new
+9
@@ -0,0 +1,9 @@
1
+function foo({ "data-foo-bar": dataTestID }) {
2
+ return dataTestID;
3
+}
4
+
5
+export const FIXTURE_ENTRYPOINT = {
6
+ fn: foo,
7
+ params: [{ "data-foo-bar": {} }],
8
+ isComponent: false,
9
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-param-string-literal-key.expect.md
new
+32
@@ -0,0 +1,32 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function foo({ data: dataTestID }) {
6
+ return dataTestID;
7
+}
8
+
9
+export const FIXTURE_ENTRYPOINT = {
10
+ fn: foo,
11
+ params: [{ data: {} }],
12
+ isComponent: false,
13
+};
14
+
15
+```
16
+
17
+## Code
18
+
19
+```javascript
20
+function foo(t5) {
21
+ const { data: dataTestID } = t5;
22
+ return dataTestID;
23
+}
24
+
25
+export const FIXTURE_ENTRYPOINT = {
26
+ fn: foo,
27
+ params: [{ data: {} }],
28
+ isComponent: false,
29
+};
30
+
31
+```
32
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-param-string-literal-key.js
new
+9
@@ -0,0 +1,9 @@
1
+function foo({ data: dataTestID }) {
2
+ return dataTestID;
3
+}
4
+
5
+export const FIXTURE_ENTRYPOINT = {
6
+ fn: foo,
7
+ params: [{ data: {} }],
8
+ isComponent: false,
9
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-string-literal-invalid-identifier-property-key.expect.md
new
+42
@@ -0,0 +1,42 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function foo() {
6
+ const { "data-foo-bar": t } = { "data-foo-bar": 1 };
7
+ return t;
8
+}
9
+
10
+export const FIXTURE_ENTRYPOINT = {
11
+ fn: foo,
12
+ params: [],
13
+ isComponent: false,
14
+};
15
+
16
+```
17
+
18
+## Code
19
+
20
+```javascript
21
+import { unstable_useMemoCache as useMemoCache } from "react";
22
+function foo() {
23
+ const $ = useMemoCache(1);
24
+ let t0;
25
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
26
+ t0 = { "data-foo-bar": 1 };
27
+ $[0] = t0;
28
+ } else {
29
+ t0 = $[0];
30
+ }
31
+ const { "data-foo-bar": t } = t0;
32
+ return t;
33
+}
34
+
35
+export const FIXTURE_ENTRYPOINT = {
36
+ fn: foo,
37
+ params: [],
38
+ isComponent: false,
39
+};
40
+
41
+```
42
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-string-literal-invalid-identifier-property-key.js
new
+10
@@ -0,0 +1,10 @@
1
+function foo() {
2
+ const { "data-foo-bar": t } = { "data-foo-bar": 1 };
3
+ return t;
4
+}
5
+
6
+export const FIXTURE_ENTRYPOINT = {
7
+ fn: foo,
8
+ params: [],
9
+ isComponent: false,
10
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-string-literal-property-key.expect.md
new
+42
@@ -0,0 +1,42 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function foo() {
6
+ const { data: t } = { data: 1 };
7
+ return t;
8
+}
9
+
10
+export const FIXTURE_ENTRYPOINT = {
11
+ fn: foo,
12
+ params: [],
13
+ isComponent: false,
14
+};
15
+
16
+```
17
+
18
+## Code
19
+
20
+```javascript
21
+import { unstable_useMemoCache as useMemoCache } from "react";
22
+function foo() {
23
+ const $ = useMemoCache(1);
24
+ let t0;
25
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
26
+ t0 = { data: 1 };
27
+ $[0] = t0;
28
+ } else {
29
+ t0 = $[0];
30
+ }
31
+ const { data: t } = t0;
32
+ return t;
33
+}
34
+
35
+export const FIXTURE_ENTRYPOINT = {
36
+ fn: foo,
37
+ params: [],
38
+ isComponent: false,
39
+};
40
+
41
+```
42
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-string-literal-property-key.js
new
+10
@@ -0,0 +1,10 @@
1
+function foo() {
2
+ const { data: t } = { data: 1 };
3
+ return t;
4
+}
5
+
6
+export const FIXTURE_ENTRYPOINT = {
7
+ fn: foo,
8
+ params: [],
9
+ isComponent: false,
10
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-validate-identifiers-object-key.expect.md
deleted
-36
@@ -1,36 +0,0 @@
1
-
2
-## Input
3
-
4
-```javascript
5
-function Foo() {
6
- return {
7
- "a.b": 1,
8
- "a\b": 2,
9
- "a/b": 3,
10
- "a+b": 4,
11
- "a b": 5,
12
- };
13
-}
14
-
15
-export const FIXTURE_ENTRYPOINT = {
16
- fn: Foo,
17
- params: [],
18
- isComponent: false,
19
-};
20
-
21
-```
22
-
23
-
24
-## Error
25
-
26
-```
27
-Unexpected token, expected "," (2:13)
28
- 1 | import { unstable_useMemoCache as useMemoCache } from "react";function Foo() {const $ = useMemoCache(1);let t0;if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
29
-> 2 | t0 = { a.b: 1, a: 2, a/b: 3, a+b: 4, a b: 5 };$[0] = t0;} else {t0 = $[0];}return t0;}
30
- | ^
31
- 3 |
32
- 4 |
33
- 5 |
34
-```
35
-
36
-
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-validate-identifiers-object-key.expect.md
new
+46
@@ -0,0 +1,46 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Foo() {
6
+ return {
7
+ "a.b": 1,
8
+ "a\b": 2,
9
+ "a/b": 3,
10
+ "a+b": 4,
11
+ "a b": 5,
12
+ };
13
+}
14
+
15
+export const FIXTURE_ENTRYPOINT = {
16
+ fn: Foo,
17
+ params: [],
18
+ isComponent: false,
19
+};
20
+
21
+```
22
+
23
+## Code
24
+
25
+```javascript
26
+import { unstable_useMemoCache as useMemoCache } from "react";
27
+function Foo() {
28
+ const $ = useMemoCache(1);
29
+ let t0;
30
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
31
+ t0 = { "a.b": 1, "a\b": 2, "a/b": 3, "a+b": 4, "a b": 5 };
32
+ $[0] = t0;
33
+ } else {
34
+ t0 = $[0];
35
+ }
36
+ return t0;
37
+}
38
+
39
+export const FIXTURE_ENTRYPOINT = {
40
+ fn: Foo,
41
+ params: [],
42
+ isComponent: false,
43
+};
44
+
45
+```
46
+
\ No newline at end of file