Skip to content

Commit 7f5a259

Browse files
committed
cell destructuring
1 parent 09901d0 commit 7f5a259

26 files changed

+794
-25
lines changed

src/features.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ import {simple} from "acorn-walk";
22
import walk from "./walk.js";
33

44
export default function findFeatures(cell, featureName) {
5-
const ast = {type: "Program", body: [cell.body]};
65
const features = new Map();
76
const {references} = cell;
87

98
simple(
10-
ast,
9+
cell,
1110
{
1211
CallExpression: node => {
1312
const {callee, arguments: args} = node;

src/parse.js

+59-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {getLineInfo, TokContext, tokTypes as tt, Parser} from "acorn";
1+
import {getLineInfo, TokContext, Token, tokTypes as tt, Parser} from "acorn";
22
import defaultGlobals from "./globals.js";
33
import findReferences from "./references.js";
44
import findFeatures from "./features.js";
@@ -106,6 +106,7 @@ export class CellParser extends Parser {
106106
this.O_function = 0;
107107
this.O_async = false;
108108
this.O_generator = false;
109+
this.O_destructuring = null;
109110
this.strict = true;
110111
this.enterScope(SCOPE_FUNCTION | SCOPE_ASYNC | SCOPE_GENERATOR);
111112
}
@@ -133,8 +134,23 @@ export class CellParser extends Parser {
133134

134135
// A non-empty cell?
135136
else if (token.type !== tt.eof && token.type !== tt.semi) {
136-
// A named cell?
137-
if (token.type === tt.name) {
137+
138+
// A destructuring cell, maybe?
139+
// (But not an object expression or arrow function!)
140+
if (token.type === tt.parenL) {
141+
id = this.parseParenAndDistinguishExpression(true);
142+
if (id.type !== "ArrowFunctionExpression" && this.eat(tt.eq)) {
143+
id = this.toAssignable(id, true, this.O_destructuring);
144+
this.checkCellDeclaration(id);
145+
} else {
146+
body = id;
147+
id = null;
148+
}
149+
token = new Token(this);
150+
}
151+
152+
// A simple named cell?
153+
else if (token.type === tt.name) {
138154
if (token.value === "viewof" || token.value === "mutable") {
139155
token = lookahead.getToken();
140156
if (token.type !== tt.name) {
@@ -152,22 +168,20 @@ export class CellParser extends Parser {
152168
}
153169
}
154170

155-
// A block?
156-
if (token.type === tt.braceL) {
157-
body = this.parseBlock();
171+
// A block or an expression?
172+
if (body === null) {
173+
body = token.type === tt.braceL
174+
? this.parseBlock()
175+
: this.parseExpression();
158176
}
159177

160-
// An expression?
161-
// Possibly a function or class declaration?
162-
else {
163-
body = this.parseExpression();
164-
if (
165-
id === null &&
166-
(body.type === "FunctionExpression" ||
167-
body.type === "ClassExpression")
168-
) {
169-
id = body.id;
170-
}
178+
// Promote the name of a function or class declaration?
179+
if (
180+
id === null &&
181+
(body.type === "FunctionExpression" ||
182+
body.type === "ClassExpression")
183+
) {
184+
id = body.id;
171185
}
172186
}
173187

@@ -184,12 +198,40 @@ export class CellParser extends Parser {
184198
? node
185199
: super.toAssignable(node, isBinding, refDestructuringErrors);
186200
}
201+
checkCellDeclaration(node) {
202+
switch (node.type) {
203+
case "Identifier":
204+
break;
205+
case "ObjectPattern":
206+
for (const p of node.properties) this.checkCellDeclaration(p);
207+
break;
208+
case "ArrayPattern":
209+
for (const e of node.elements) e && this.checkCellDeclaration(e);
210+
break;
211+
case "Property":
212+
this.checkCellDeclaration(node.value);
213+
break;
214+
case "RestElement":
215+
this.checkCellDeclaration(node.argument);
216+
break;
217+
case "AssignmentPattern":
218+
this.checkCellDeclaration(node.left);
219+
break;
220+
default:
221+
this.unexpected();
222+
break;
223+
}
224+
}
187225
checkLocal(id) {
188226
const node = id.id || id;
189227
if (defaultGlobals.has(node.name) || node.name === "arguments") {
190228
this.raise(node.start, `Identifier '${node.name}' is reserved`);
191229
}
192230
}
231+
checkExpressionErrors(refDestructuringErrors, andThrow) {
232+
this.O_destructuring = refDestructuringErrors;
233+
return super.checkExpressionErrors(refDestructuringErrors, andThrow);
234+
}
193235
checkUnreserved(node) {
194236
if (node.name === "viewof" || node.name === "mutable") {
195237
this.raise(node.start, `Unexpected keyword '${node.name}'`);

src/references.js

+22-5
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ function declaresArguments(node) {
2626
}
2727

2828
export default function findReferences(cell, globals) {
29-
const ast = {type: "Program", body: [cell.body]};
3029
const locals = new Map;
3130
const globalSet = new Set(globals);
3231
const references = [];
@@ -81,11 +80,11 @@ export default function findReferences(cell, globals) {
8180
}
8281

8382
function declareModuleSpecifier(node) {
84-
declareLocal(ast, node.local);
83+
declareLocal(cell.body, node.local);
8584
}
8685

8786
ancestor(
88-
ast,
87+
cell.body,
8988
{
9089
VariableDeclaration: (node, parents) => {
9190
let parent = null;
@@ -154,8 +153,26 @@ export default function findReferences(cell, globals) {
154153
}
155154
}
156155

156+
function assignmentIdentifier(node, parents) {
157+
if (parents.length > 2 && parents[1].type === "AssignmentPattern" && parents[2] === parents[1].right) {
158+
identifier(node, parents);
159+
}
160+
}
161+
162+
if (cell.id && (cell.id.type === "ArrayPattern" || cell.id.type === "ObjectPattern")) {
163+
declarePattern(cell.id, cell.id);
164+
ancestor(
165+
cell.id,
166+
{
167+
VariablePattern: assignmentIdentifier,
168+
Identifier: assignmentIdentifier
169+
},
170+
walk
171+
);
172+
}
173+
157174
ancestor(
158-
ast,
175+
cell.body,
159176
{
160177
VariablePattern: identifier,
161178
Identifier: identifier
@@ -210,7 +227,7 @@ export default function findReferences(cell, globals) {
210227
}
211228

212229
ancestor(
213-
ast,
230+
cell.body,
214231
{
215232
AssignmentExpression: checkConstLeft,
216233
AssignmentPattern: checkConstLeft,

src/walk.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import {make} from "acorn-walk";
22

33
export default make({
4-
Import() {},
4+
Cell(node, st, c) {
5+
if (node.id) c(node.id, st);
6+
c(node.body, st);
7+
},
8+
CellTag(node, st, c) {
9+
c(node.body, st);
10+
},
511
ViewExpression(node, st, c) {
612
c(node.id, st, "Identifier");
713
},
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
([foo, ...bar]) = [1, 2, 3]

test/input/destructure-block.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
({foo, bar}) = {
2+
return {foo: 1, bar: 2};
3+
}

test/input/destructure-computed.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
({["foo"]}) = 42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
([x, y = x + await z]) = [1]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
([x, y = Secret("foo")]) = [1]
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
([x, y = 2]) = [1]
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
({default: confetti}) = import("https://cdn.skypack.dev/canvas-confetti")

test/input/destructure-keyword.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
([mutable]) = [42]

test/input/destructure-mutable.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
([mutable foo]) = [42]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
({foo: {bar: {baz: qux}}}) = ({})
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
({foo, bar}) = ({foo: 1, bar: 2})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"type": "Cell",
3+
"start": 0,
4+
"end": 28,
5+
"id": {
6+
"type": "ArrayPattern",
7+
"start": 1,
8+
"end": 14,
9+
"elements": [
10+
{
11+
"type": "Identifier",
12+
"start": 2,
13+
"end": 5,
14+
"name": "foo"
15+
},
16+
{
17+
"type": "RestElement",
18+
"start": 7,
19+
"end": 13,
20+
"argument": {
21+
"type": "Identifier",
22+
"start": 10,
23+
"end": 13,
24+
"name": "bar"
25+
}
26+
}
27+
]
28+
},
29+
"body": {
30+
"type": "ArrayExpression",
31+
"start": 18,
32+
"end": 27,
33+
"elements": [
34+
{
35+
"type": "Literal",
36+
"start": 19,
37+
"end": 20,
38+
"value": 1,
39+
"raw": "1"
40+
},
41+
{
42+
"type": "Literal",
43+
"start": 22,
44+
"end": 23,
45+
"value": 2,
46+
"raw": "2"
47+
},
48+
{
49+
"type": "Literal",
50+
"start": 25,
51+
"end": 26,
52+
"value": 3,
53+
"raw": "3"
54+
}
55+
]
56+
},
57+
"async": false,
58+
"generator": false,
59+
"references": [],
60+
"fileAttachments": [],
61+
"databaseClients": [],
62+
"secrets": []
63+
}

0 commit comments

Comments
 (0)