• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ObjectKeys,
5} = primordials;
6
7const acorn = require('internal/deps/acorn/acorn/dist/acorn');
8const walk = require('internal/deps/acorn/acorn-walk/dist/walk');
9const privateMethods =
10  require('internal/deps/acorn-plugins/acorn-private-methods/index');
11const classFields =
12  require('internal/deps/acorn-plugins/acorn-class-fields/index');
13const numericSeparator =
14  require('internal/deps/acorn-plugins/acorn-numeric-separator/index');
15const staticClassFeatures =
16  require('internal/deps/acorn-plugins/acorn-static-class-features/index');
17
18const parser = acorn.Parser.extend(
19  privateMethods,
20  classFields,
21  numericSeparator,
22  staticClassFeatures
23);
24
25const noop = () => {};
26const visitorsWithoutAncestors = {
27  ClassDeclaration(node, state, c) {
28    if (state.ancestors[state.ancestors.length - 2] === state.body) {
29      state.prepend(node, `${node.id.name}=`);
30    }
31    walk.base.ClassDeclaration(node, state, c);
32  },
33  ForOfStatement(node, state, c) {
34    if (node.await === true) {
35      state.containsAwait = true;
36    }
37    walk.base.ForOfStatement(node, state, c);
38  },
39  FunctionDeclaration(node, state, c) {
40    state.prepend(node, `${node.id.name}=`);
41  },
42  FunctionExpression: noop,
43  ArrowFunctionExpression: noop,
44  MethodDefinition: noop,
45  AwaitExpression(node, state, c) {
46    state.containsAwait = true;
47    walk.base.AwaitExpression(node, state, c);
48  },
49  ReturnStatement(node, state, c) {
50    state.containsReturn = true;
51    walk.base.ReturnStatement(node, state, c);
52  },
53  VariableDeclaration(node, state, c) {
54    if (node.kind === 'var' ||
55        state.ancestors[state.ancestors.length - 2] === state.body) {
56      if (node.declarations.length === 1) {
57        state.replace(node.start, node.start + node.kind.length, 'void');
58      } else {
59        state.replace(node.start, node.start + node.kind.length, 'void (');
60      }
61
62      for (const decl of node.declarations) {
63        state.prepend(decl, '(');
64        state.append(decl, decl.init ? ')' : '=undefined)');
65      }
66
67      if (node.declarations.length !== 1) {
68        state.append(node.declarations[node.declarations.length - 1], ')');
69      }
70    }
71
72    walk.base.VariableDeclaration(node, state, c);
73  }
74};
75
76const visitors = {};
77for (const nodeType of ObjectKeys(walk.base)) {
78  const callback = visitorsWithoutAncestors[nodeType] || walk.base[nodeType];
79  visitors[nodeType] = (node, state, c) => {
80    const isNew = node !== state.ancestors[state.ancestors.length - 1];
81    if (isNew) {
82      state.ancestors.push(node);
83    }
84    callback(node, state, c);
85    if (isNew) {
86      state.ancestors.pop();
87    }
88  };
89}
90
91function processTopLevelAwait(src) {
92  const wrapped = `(async () => { ${src} })()`;
93  const wrappedArray = wrapped.split('');
94  let root;
95  try {
96    root = parser.parse(wrapped, { ecmaVersion: 11 });
97  } catch {
98    return null;
99  }
100  const body = root.body[0].expression.callee.body;
101  const state = {
102    body,
103    ancestors: [],
104    replace(from, to, str) {
105      for (let i = from; i < to; i++) {
106        wrappedArray[i] = '';
107      }
108      if (from === to) str += wrappedArray[from];
109      wrappedArray[from] = str;
110    },
111    prepend(node, str) {
112      wrappedArray[node.start] = str + wrappedArray[node.start];
113    },
114    append(node, str) {
115      wrappedArray[node.end - 1] += str;
116    },
117    containsAwait: false,
118    containsReturn: false
119  };
120
121  walk.recursive(body, state, visitors);
122
123  // Do not transform if
124  // 1. False alarm: there isn't actually an await expression.
125  // 2. There is a top-level return, which is not allowed.
126  if (!state.containsAwait || state.containsReturn) {
127    return null;
128  }
129
130  const last = body.body[body.body.length - 1];
131  if (last.type === 'ExpressionStatement') {
132    // For an expression statement of the form
133    // ( expr ) ;
134    // ^^^^^^^^^^   // last
135    //   ^^^^       // last.expression
136    //
137    // We do not want the left parenthesis before the `return` keyword;
138    // therefore we prepend the `return (` to `last`.
139    //
140    // On the other hand, we do not want the right parenthesis after the
141    // semicolon. Since there can only be more right parentheses between
142    // last.expression.end and the semicolon, appending one more to
143    // last.expression should be fine.
144    state.prepend(last, 'return (');
145    state.append(last.expression, ')');
146  }
147
148  return wrappedArray.join('');
149}
150
151module.exports = {
152  processTopLevelAwait
153};
154