Skip to content

Commit d104d7e

Browse files
committed
Initial draft
1 parent 62d7bf7 commit d104d7e

File tree

1 file changed

+245
-0
lines changed

1 file changed

+245
-0
lines changed

rfcs/0000-record-property-ast-node.md

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
- Repo: `babel/babel`
2+
- Start Date: 2020-06-09
3+
- RFC PR: https://github.com/babel/rfcs/pull/6
4+
- Authors: @JLHwung
5+
- Champion: @JLHwung
6+
- Implementors: @JLHwung
7+
8+
# Summary
9+
10+
This RFC proposes to change the AST shape of record property `#{ foo: 42 }` in stage-1 proposal [Record and Tuple] by introducing a new `RecordProperty` node.
11+
12+
# Motivation
13+
14+
Currently a record property is parsed as an `ObjectProperty`, for example, here is the parsed AST of `#{ foo: 42 }`.
15+
16+
```jsonc
17+
// #{ foo: 42 }
18+
{
19+
"type": "RecordExpression",
20+
"properties": [
21+
{
22+
"type": "ObjectProperty",
23+
"key": { "type": "Identifier", "name": "foo" },
24+
"value": { "type": "NumericLiteral", "value": 42 }
25+
}
26+
]
27+
}
28+
```
29+
30+
When `@babel/parser` [supported](https://github.com/babel/babel/pull/10865) record expression, it was reasonable to reuse `ObjectProperty` given that they share same syntax. However when a new proposal [Deep Path Properties in Record Literals] comes up, the current AST can not extend because this proposal has introduced new syntax features (i.e. `#{ foo.bar: 42 }`) only available for record property.
31+
32+
# Detailed design
33+
34+
Introduce a new `RecordProperty` AST node type in `@babel/types`. It has an interface same as `ObjectProperty` except `type` is `RecordProperty`.
35+
36+
```js
37+
interface RecordProperty <: Node {
38+
type: "RecordProperty";
39+
key: Expression;
40+
computed: boolean;
41+
shorthand: boolean;
42+
value: Expression;
43+
}
44+
```
45+
46+
The example above will now be parsed as
47+
48+
```jsonc
49+
// #{ foo: 42 }
50+
{
51+
"type": "RecordExpression",
52+
"properties": [
53+
{
54+
"type": "RecordProperty",
55+
"key": { "type": "Identifier", "name": "foo" },
56+
"value": { "type": "NumericLiteral", "value": 42 }
57+
}
58+
]
59+
}
60+
```
61+
62+
This approach is also [adopted](https://github.com/estree/estree/blob/master/experimental/record-tuple.md#recordproperty) by ESTree.
63+
64+
## Revision on the `RecordExpression` interface
65+
66+
`ObjectProperty` will be replaced by `RecordProperty` in allowed `properties` type of the `RecordExpression` node.
67+
68+
```diff
69+
interface RecordExpression <: Expression {
70+
- properties: [ ObjectProperty | SpreadElement ];
71+
+ properties: [ RecordProperty | SpreadElement ];
72+
}
73+
```
74+
75+
## Matching a record property
76+
Currently a plugin can test if a node is an record property by visiting `RecordExpression` its parent path and filtering its `properties` by `[type=ObjectProperty]`.
77+
78+
```js
79+
// Babel 7
80+
export default {
81+
name: "my-plugin",
82+
visitor: {
83+
RecordExpression({ node }) {
84+
for (const property of node.properties) {
85+
if (property.type === "ObjectProperty") {
86+
// property is now a record property
87+
}
88+
}
89+
}
90+
}
91+
}
92+
```
93+
94+
This RFC proposes record properties to be parsed a dedicated AST type. Now plugin authors can match them by `RecordProperty`.
95+
96+
```js
97+
// Babel 8
98+
export default {
99+
name: "my-plugin",
100+
visitor: {
101+
RecordProperty({ node }) {
102+
// node is now a record property
103+
}
104+
}
105+
}
106+
```
107+
108+
or filtering a `RecordExpression`'s `properties`
109+
110+
```js
111+
// Babel 8 alternative
112+
export default {
113+
name: "my-plugin",
114+
visitor: {
115+
RecordExpression({ node }) {
116+
for (const property of node.properties) {
117+
if (property.type === "RecordProperty") {
118+
// property is now a record property
119+
}
120+
}
121+
}
122+
}
123+
}
124+
```
125+
126+
A plugin can also support both Babel 7 and Babel 8 by checking.
127+
```js
128+
export default {
129+
name: "my-plugin",
130+
visitor: {
131+
RecordExpression({ node }) {
132+
for (const property of node.properties) {
133+
if (
134+
property.type === "RecordProperty" ||
135+
property.type === "ObjectProperty" // compatibility for Babel 7
136+
) {
137+
// property is now a record property
138+
}
139+
}
140+
}
141+
}
142+
}
143+
```
144+
145+
## Constructing `#{ foo: 42 }`
146+
147+
Currently a record expression node is constructed via `t.ObjectProperty`
148+
149+
```js
150+
const t = require("@babel/types");
151+
// #{ foo: 42 };
152+
t.RecordExpression([
153+
t.ObjectProperty(t.Identifier("foo"), t.NumericLiteral(42), false, false)
154+
]);
155+
```
156+
157+
This RFC adds a new `RecordExpression` constructor
158+
159+
```js
160+
const t = require("@babel/types");
161+
// #{ foo: 42 };
162+
t.RecordExpression([
163+
t.RecordProperty(t.Identifier("foo"), t.NumericLiteral(42), false, false)
164+
]);
165+
```
166+
167+
## Deep Path Properties in Record Literals proposal support
168+
169+
On top of this RFC, we can extend allowable types of `key` property in `RecordProperty` to support [this][Deep Path Properties in Record Literals] Proposal.
170+
171+
```js
172+
extend interface RecordProperty {
173+
key: Expression | PropertyPath;
174+
}
175+
176+
interface PropertyPath <: Node {
177+
type: "PropertyPath";
178+
computed: boolean;
179+
ancestor: PropertyPath | Identifier;
180+
property: Expression;
181+
}
182+
```
183+
184+
Here is an example.
185+
```jsonc
186+
// #{ foo.bar[qux]: 42 }
187+
{
188+
"type": "RecordExpression",
189+
"properties": [
190+
{
191+
"type": "RecordProperty",
192+
"key": {
193+
"type": "PropertyPath",
194+
"computed": true,
195+
"property": { "type": "Identifier", "name": "qux" },
196+
"ancestor": {
197+
"type": "PropertyPath",
198+
"computed": false,
199+
"ancestor": { "type": "Identifier", "name": "foo" },
200+
"property": { "type": "Identifier", "name": "bar" }
201+
}
202+
},
203+
"value": { "type": "NumericLiteral", "value": 42 }
204+
}
205+
]
206+
}
207+
```
208+
209+
There are various ways to represent `foo.bar` in `#{ foo.bar: 42 }` and it should not be addressed in this RFC. This proposal paves the way for such representations without interfering object properties.
210+
211+
212+
# Drawbacks
213+
214+
## Downstream cost
215+
216+
As with any breaking change to the AST shape, any Babel plugin operating on record properties will be impacted. For plugins that intend to support both Babel 7 and Babel 8, extra code will be needed.
217+
218+
Except for the experimental polyfill [record-tuple-polyfill], the practical cost is considerred low given that `RecordExpression` was not supported until 7.9.0.
219+
220+
## Implementation complexity
221+
222+
The parser will see extra code in order to parse a new node type.
223+
224+
# Alternatives
225+
226+
## Stay with the current situation
227+
228+
We could stay with the current situation and use `ObjectProperty` to capture record properties. This does not introduce any breaking changes, but extra validations should be done when parsing deep record properties path because deep properties is not allowed in object literals.
229+
230+
# Adoption strategy
231+
232+
TBD.
233+
234+
# How we teach this
235+
236+
TBD.
237+
238+
# Open questions
239+
240+
TBD.
241+
242+
[record-tuple-polyfill]: https://github.com/bloomberg/record-tuple-polyfill
243+
[Deep Path Properties in Record Literals]: https://github.com/tc39/proposal-deep-path-properties-for-record
244+
[Record and Tuple]: https://github.com/tc39/proposal-record-tuple
245+
[ESTree Record Tuple]: https://github.com/estree/estree/pull/218

0 commit comments

Comments
 (0)