UNPKG

5.08 kBJavaScriptView Raw
1
2/**
3 * Copyright (c) Facebook, Inc. and its affiliates.
4 *
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9'use strict';
10
11const recast = require('recast');
12
13const builders = recast.types.builders;
14const types = recast.types.namedTypes;
15
16function splice(arr, element, replacement) {
17 arr.splice.apply(arr, [arr.indexOf(element), 1].concat(replacement));
18}
19
20function cleanLocation(node) {
21 delete node.start;
22 delete node.end;
23 delete node.loc;
24 return node;
25}
26
27function ensureStatement(node) {
28 return types.Statement.check(node) ?
29 // Removing the location information seems to ensure that the node is
30 // correctly reprinted with a trailing semicolon
31 cleanLocation(node) :
32 builders.expressionStatement(node);
33}
34
35function getVistor(varNames, nodes) {
36 return {
37 visitIdentifier: function(path) {
38 this.traverse(path);
39 const node = path.node;
40 const parent = path.parent.node;
41
42 // If this identifier is not one of our generated ones, do nothing
43 const varIndex = varNames.indexOf(node.name);
44 if (varIndex === -1) {
45 return;
46 }
47
48 let replacement = nodes[varIndex];
49 nodes[varIndex] = null;
50
51 // If the replacement is an array, we need to explode the nodes in context
52 if (Array.isArray(replacement)) {
53
54 if (types.Function.check(parent) &&
55 parent.params.indexOf(node) > -1) {
56 // Function parameters: function foo(${bar}) {}
57 splice(parent.params, node, replacement);
58 } else if (types.VariableDeclarator.check(parent)) {
59 // Variable declarations: var foo = ${bar}, baz = 42;
60 splice(
61 path.parent.parent.node.declarations,
62 parent,
63 replacement
64 );
65 } else if (types.ArrayExpression.check(parent)) {
66 // Arrays: var foo = [${bar}, baz];
67 splice(parent.elements, node, replacement);
68 } else if (types.Property.check(parent) && parent.shorthand) {
69 // Objects: var foo = {${bar}, baz: 42};
70 splice(
71 path.parent.parent.node.properties,
72 parent,
73 replacement
74 );
75 } else if (types.CallExpression.check(parent) &&
76 parent.arguments.indexOf(node) > -1) {
77 // Function call arguments: foo(${bar}, baz)
78 splice(parent.arguments, node, replacement);
79 } else if (types.ExpressionStatement.check(parent)) {
80 // Generic sequence of statements: { ${foo}; bar; }
81 path.parent.replace.apply(
82 path.parent,
83 replacement.map(ensureStatement)
84 );
85 } else {
86 // Every else, let recast take care of it
87 path.replace.apply(path, replacement);
88 }
89 } else if (types.ExpressionStatement.check(parent)) {
90 path.parent.replace(ensureStatement(replacement));
91 } else {
92 path.replace(replacement);
93 }
94 }
95 };
96}
97
98function replaceNodes(src, varNames, nodes, parser) {
99 const ast = recast.parse(src, {parser});
100 recast.visit(ast, getVistor(varNames, nodes));
101 return ast;
102}
103
104let varNameCounter = 0;
105function getUniqueVarName() {
106 return `$jscodeshift${varNameCounter++}$`;
107}
108
109
110module.exports = function withParser(parser) {
111 function statements(template/*, ...nodes*/) {
112 template = Array.from(template);
113 const nodes = Array.from(arguments).slice(1);
114 const varNames = nodes.map(() => getUniqueVarName());
115 const src = template.reduce(
116 (result, elem, i) => result + varNames[i - 1] + elem
117 );
118
119 return replaceNodes(
120 src,
121 varNames,
122 nodes,
123 parser
124 ).program.body;
125 }
126
127 function statement(/*template, ...nodes*/) {
128 return statements.apply(null, arguments)[0];
129 }
130
131 function expression(template/*, ...nodes*/) {
132 // wrap code in `(...)` to force evaluation as expression
133 template = Array.from(template);
134 if (template.length > 0) {
135 template[0] = '(' + template[0];
136 template[template.length - 1] += ')';
137 }
138
139 const expression = statement.apply(
140 null,
141 [template].concat(Array.from(arguments).slice(1))
142 ).expression;
143
144 // Remove added parens
145 if (expression.extra) {
146 expression.extra.parenthesized = false;
147 }
148
149 return expression;
150 }
151
152 function asyncExpression(template/*, ...nodes*/) {
153 template = Array.from(template);
154 if (template.length > 0) {
155 template[0] = 'async () => (' + template[0];
156 template[template.length - 1] += ')';
157 }
158
159 const expression = statement.apply(
160 null,
161 [template].concat(Array.from(arguments).slice(1))
162 ).expression.body;
163
164 // Remove added parens
165 if (expression.extra) {
166 expression.extra.parenthesized = false;
167 }
168
169 return expression;
170 }
171
172 return {statements, statement, expression, asyncExpression};
173}