Skip to content

Commit c9d7713

Browse files
RReverserkripken
andauthored
[acorn-opt] Simplify skipping inner scopes. NFC (#24497)
Instead of traversing the AST and changing scope nodes to empty ones before the main traversal and then traversing yet again to restore them back, just allow the `pre` callback to say when a node should be skipped. --------- Co-authored-by: Alon Zakai <[email protected]>
1 parent 2be8d4b commit c9d7713

File tree

1 file changed

+69
-102
lines changed

1 file changed

+69
-102
lines changed

tools/acorn-optimizer.mjs

Lines changed: 69 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ function simpleWalk(node, cs) {
6060
}
6161

6262
// Full post-order walk, calling a single function for all types. If |pre| is
63-
// provided, it is called in pre-order (before children).
63+
// provided, it is called in pre-order (before children). If |pre| returns
64+
// `false`, the node and its children will be skipped.
6465
function fullWalk(node, c, pre) {
65-
if (pre) {
66-
pre(node);
66+
if (pre?.(node) !== false) {
67+
visitChildren(node, (child) => fullWalk(child, c, pre));
68+
c(node);
6769
}
68-
visitChildren(node, (child) => fullWalk(child, c, pre));
69-
c(node);
7070
}
7171

7272
// Recursive post-order walk, calling properties on an object by node type,
@@ -100,111 +100,78 @@ function dump(node) {
100100
console.log(JSON.stringify(node, null, ' '));
101101
}
102102

103-
// Mark inner scopes temporarily as empty statements. Returns
104-
// a special object that must be used to restore them.
105-
function ignoreInnerScopes(node) {
106-
const map = new WeakMap();
107-
function ignore(node) {
108-
map.set(node, node.type);
109-
emptyOut(node);
110-
}
111-
simpleWalk(node, {
112-
FunctionDeclaration(node) {
113-
ignore(node);
114-
},
115-
FunctionExpression(node) {
116-
ignore(node);
117-
},
118-
ArrowFunctionExpression(node) {
119-
ignore(node);
120-
},
121-
// TODO: arrow etc.
122-
});
123-
return map;
124-
}
125-
126-
// Mark inner scopes temporarily as empty statements.
127-
function restoreInnerScopes(node, map) {
128-
fullWalk(node, (node) => {
129-
if (map.has(node)) {
130-
node.type = map.get(node);
131-
map.delete(node);
132-
restoreInnerScopes(node, map);
133-
}
134-
});
135-
}
136-
137103
function hasSideEffects(node) {
138104
// Conservative analysis.
139-
const map = ignoreInnerScopes(node);
140105
let has = false;
141-
fullWalk(node, (node) => {
142-
switch (node.type) {
143-
case 'ExpressionStatement':
144-
if (node.directive) {
145-
has = true;
106+
fullWalk(
107+
node,
108+
(node) => {
109+
switch (node.type) {
110+
case 'ExpressionStatement':
111+
if (node.directive) {
112+
has = true;
113+
}
114+
break;
115+
// TODO: go through all the ESTree spec
116+
case 'Literal':
117+
case 'Identifier':
118+
case 'UnaryExpression':
119+
case 'BinaryExpression':
120+
case 'LogicalExpression':
121+
case 'UpdateOperator':
122+
case 'ConditionalExpression':
123+
case 'VariableDeclaration':
124+
case 'VariableDeclarator':
125+
case 'ObjectExpression':
126+
case 'Property':
127+
case 'SpreadElement':
128+
case 'BlockStatement':
129+
case 'ArrayExpression':
130+
case 'EmptyStatement': {
131+
break; // safe
146132
}
147-
break;
148-
// TODO: go through all the ESTree spec
149-
case 'Literal':
150-
case 'Identifier':
151-
case 'UnaryExpression':
152-
case 'BinaryExpression':
153-
case 'LogicalExpression':
154-
case 'UpdateOperator':
155-
case 'ConditionalExpression':
156-
case 'FunctionDeclaration':
157-
case 'FunctionExpression':
158-
case 'ArrowFunctionExpression':
159-
case 'VariableDeclaration':
160-
case 'VariableDeclarator':
161-
case 'ObjectExpression':
162-
case 'Property':
163-
case 'SpreadElement':
164-
case 'BlockStatement':
165-
case 'ArrayExpression':
166-
case 'EmptyStatement': {
167-
break; // safe
168-
}
169-
case 'MemberExpression': {
170-
// safe if on Math (or other familiar objects, TODO)
171-
if (node.object.type !== 'Identifier' || node.object.name !== 'Math') {
172-
// console.error('because member on ' + node.object.name);
173-
has = true;
133+
case 'MemberExpression': {
134+
// safe if on Math (or other familiar objects, TODO)
135+
if (node.object.type !== 'Identifier' || node.object.name !== 'Math') {
136+
// console.error('because member on ' + node.object.name);
137+
has = true;
138+
}
139+
break;
174140
}
175-
break;
176-
}
177-
case 'NewExpression': {
178-
// default to unsafe, but can be safe on some familiar objects
179-
if (node.callee.type === 'Identifier') {
180-
const name = node.callee.name;
181-
if (
182-
name === 'TextDecoder' ||
183-
name === 'ArrayBuffer' ||
184-
name === 'Int8Array' ||
185-
name === 'Uint8Array' ||
186-
name === 'Int16Array' ||
187-
name === 'Uint16Array' ||
188-
name === 'Int32Array' ||
189-
name === 'Uint32Array' ||
190-
name === 'Float32Array' ||
191-
name === 'Float64Array'
192-
) {
193-
// no side effects, but the arguments might (we walk them in
194-
// full walk as well)
195-
break;
141+
case 'NewExpression': {
142+
// default to unsafe, but can be safe on some familiar objects
143+
if (node.callee.type === 'Identifier') {
144+
const name = node.callee.name;
145+
if (
146+
name === 'TextDecoder' ||
147+
name === 'ArrayBuffer' ||
148+
name === 'Int8Array' ||
149+
name === 'Uint8Array' ||
150+
name === 'Int16Array' ||
151+
name === 'Uint16Array' ||
152+
name === 'Int32Array' ||
153+
name === 'Uint32Array' ||
154+
name === 'Float32Array' ||
155+
name === 'Float64Array'
156+
) {
157+
// no side effects, but the arguments might (we walk them in
158+
// full walk as well)
159+
break;
160+
}
196161
}
162+
// not one of the safe cases
163+
has = true;
164+
break;
165+
}
166+
default: {
167+
has = true;
197168
}
198-
// not one of the safe cases
199-
has = true;
200-
break;
201-
}
202-
default: {
203-
has = true;
204169
}
205-
}
206-
});
207-
restoreInnerScopes(node, map);
170+
},
171+
(node) =>
172+
// Ignore inner scopes.
173+
!['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(node.type),
174+
);
208175
return has;
209176
}
210177

0 commit comments

Comments
 (0)