1 | import { has, keys } from './utils/object';
|
2 | import { sequence } from './utils/promise';
|
3 | import { getName } from './utils/map-helpers';
|
4 | import getLocation from './utils/getLocation';
|
5 | import walk from './ast/walk';
|
6 | import Scope from './ast/Scope';
|
7 |
|
8 | const emptyArrayPromise = Promise.resolve([]);
|
9 |
|
10 | export default class Statement {
|
11 | constructor ( node, magicString, module ) {
|
12 | this.node = node;
|
13 | this.module = module;
|
14 | this.magicString = magicString;
|
15 |
|
16 | this.scope = new Scope();
|
17 | this.defines = {};
|
18 | this.modifies = {};
|
19 | this.dependsOn = {};
|
20 |
|
21 | this.isIncluded = false;
|
22 |
|
23 | this.leadingComments = [];
|
24 | this.trailingComment = null;
|
25 | this.margin = [ 0, 0 ];
|
26 |
|
27 |
|
28 | this.isImportDeclaration = node.type === 'ImportDeclaration';
|
29 | this.isExportDeclaration = /^Export/.test( node.type );
|
30 | }
|
31 |
|
32 | analyse () {
|
33 | if ( this.isImportDeclaration ) return;
|
34 |
|
35 | const statement = this;
|
36 | const magicString = this.magicString;
|
37 |
|
38 | let scope = this.scope;
|
39 |
|
40 | function addToScope ( declarator ) {
|
41 | var name = declarator.id.name;
|
42 | scope.add( name, false );
|
43 |
|
44 | if ( !scope.parent ) {
|
45 | statement.defines[ name ] = true;
|
46 | }
|
47 | }
|
48 |
|
49 | function addToBlockScope ( declarator ) {
|
50 | var name = declarator.id.name;
|
51 | scope.add( name, true );
|
52 |
|
53 | if ( !scope.parent ) {
|
54 | statement.defines[ name ] = true;
|
55 | }
|
56 | }
|
57 |
|
58 | walk( this.node, {
|
59 | enter ( node ) {
|
60 | let newScope;
|
61 |
|
62 | magicString.addSourcemapLocation( node.start );
|
63 |
|
64 | switch ( node.type ) {
|
65 | case 'FunctionExpression':
|
66 | case 'FunctionDeclaration':
|
67 | case 'ArrowFunctionExpression':
|
68 | let names = node.params.map( getName );
|
69 |
|
70 | if ( node.type === 'FunctionDeclaration' ) {
|
71 | addToScope( node );
|
72 | } else if ( node.type === 'FunctionExpression' && node.id ) {
|
73 | names.push( node.id.name );
|
74 | }
|
75 |
|
76 | newScope = new Scope({
|
77 | parent: scope,
|
78 | params: names,
|
79 | block: false
|
80 | });
|
81 |
|
82 | break;
|
83 |
|
84 | case 'BlockStatement':
|
85 | newScope = new Scope({
|
86 | parent: scope,
|
87 | block: true
|
88 | });
|
89 |
|
90 | break;
|
91 |
|
92 | case 'CatchClause':
|
93 | newScope = new Scope({
|
94 | parent: scope,
|
95 | params: [ node.param.name ],
|
96 | block: true
|
97 | });
|
98 |
|
99 | break;
|
100 |
|
101 | case 'VariableDeclaration':
|
102 | node.declarations.forEach( node.kind === 'let' ? addToBlockScope : addToScope );
|
103 | break;
|
104 |
|
105 | case 'ClassDeclaration':
|
106 | addToScope( node );
|
107 | break;
|
108 | }
|
109 |
|
110 | if ( newScope ) {
|
111 | Object.defineProperty( node, '_scope', { value: newScope });
|
112 | scope = newScope;
|
113 | }
|
114 | },
|
115 | leave ( node ) {
|
116 | if ( node._scope ) {
|
117 | scope = scope.parent;
|
118 | }
|
119 | }
|
120 | });
|
121 |
|
122 | if ( !this.isImportDeclaration ) {
|
123 | walk( this.node, {
|
124 | enter: ( node, parent ) => {
|
125 | if ( node._scope ) scope = node._scope;
|
126 |
|
127 | this.checkForReads( scope, node, parent );
|
128 | this.checkForWrites( scope, node );
|
129 | },
|
130 | leave: ( node ) => {
|
131 | if ( node._scope ) scope = scope.parent;
|
132 | }
|
133 | });
|
134 | }
|
135 | }
|
136 |
|
137 | checkForReads ( scope, node, parent ) {
|
138 | if ( node.type === 'Identifier' ) {
|
139 |
|
140 | if ( parent.type === 'MemberExpression' && node !== parent.object ) {
|
141 | return;
|
142 | }
|
143 |
|
144 |
|
145 | if ( parent.type === 'Property' && node !== parent.value ) {
|
146 | return;
|
147 | }
|
148 |
|
149 | const definingScope = scope.findDefiningScope( node.name );
|
150 |
|
151 | if ( ( !definingScope || definingScope.depth === 0 ) && !this.defines[ node.name ] ) {
|
152 | this.dependsOn[ node.name ] = true;
|
153 | }
|
154 | }
|
155 | }
|
156 |
|
157 | checkForWrites ( scope, node ) {
|
158 | const addNode = ( node, disallowImportReassignments ) => {
|
159 | let depth = 0;
|
160 |
|
161 | while ( node.type === 'MemberExpression' ) {
|
162 | node = node.object;
|
163 | depth += 1;
|
164 | }
|
165 |
|
166 |
|
167 | if ( disallowImportReassignments ) {
|
168 | const importSpecifier = this.module.imports[ node.name ];
|
169 |
|
170 | if ( importSpecifier && !scope.contains( node.name ) ) {
|
171 | const minDepth = importSpecifier.name === '*' ?
|
172 | 2 :
|
173 | 1;
|
174 |
|
175 | if ( depth < minDepth ) {
|
176 | const err = new Error( `Illegal reassignment to import '${node.name}'` );
|
177 | err.file = this.module.path;
|
178 | err.loc = getLocation( this.module.magicString.toString(), node.start );
|
179 | throw err;
|
180 | }
|
181 | }
|
182 | }
|
183 |
|
184 | if ( node.type !== 'Identifier' ) {
|
185 | return;
|
186 | }
|
187 |
|
188 | this.modifies[ node.name ] = true;
|
189 | };
|
190 |
|
191 | if ( node.type === 'AssignmentExpression' ) {
|
192 | addNode( node.left, true );
|
193 | }
|
194 |
|
195 | else if ( node.type === 'UpdateExpression' ) {
|
196 | addNode( node.argument, true );
|
197 | }
|
198 |
|
199 | else if ( node.type === 'CallExpression' ) {
|
200 | node.arguments.forEach( arg => addNode( arg, false ) );
|
201 | }
|
202 | }
|
203 |
|
204 | expand () {
|
205 | if ( this.isIncluded ) return emptyArrayPromise;
|
206 | this.isIncluded = true;
|
207 |
|
208 | let result = [];
|
209 |
|
210 |
|
211 |
|
212 | const dependencies = Object.keys( this.dependsOn );
|
213 |
|
214 | return sequence( dependencies, name => {
|
215 | return this.module.define( name ).then( definition => {
|
216 | result.push.apply( result, definition );
|
217 | });
|
218 | })
|
219 |
|
220 |
|
221 | .then( () => {
|
222 | result.push( this );
|
223 | })
|
224 |
|
225 |
|
226 |
|
227 | .then( () => {
|
228 | return sequence( keys( this.defines ), name => {
|
229 | const modifications = has( this.module.modifications, name ) && this.module.modifications[ name ];
|
230 |
|
231 | if ( modifications ) {
|
232 | return sequence( modifications, statement => {
|
233 | if ( !statement.isIncluded ) {
|
234 | return statement.expand()
|
235 | .then( statements => {
|
236 | result.push.apply( result, statements );
|
237 | });
|
238 | }
|
239 | });
|
240 | }
|
241 | });
|
242 | })
|
243 |
|
244 |
|
245 | .then( () => {
|
246 | return result;
|
247 | });
|
248 | }
|
249 |
|
250 | replaceIdentifiers ( names ) {
|
251 | const magicString = this.magicString.clone().trim();
|
252 | const replacementStack = [ names ];
|
253 | const nameList = keys( names );
|
254 |
|
255 | let deshadowList = [];
|
256 | nameList.forEach( name => {
|
257 | const replacement = names[ name ];
|
258 | deshadowList.push( replacement.split( '.' )[0] );
|
259 | });
|
260 |
|
261 | if ( nameList.length > 0 ) {
|
262 | walk( this.node, {
|
263 | enter ( node, parent ) {
|
264 | const scope = node._scope;
|
265 |
|
266 | if ( scope ) {
|
267 | let newNames = {};
|
268 | let hasReplacements;
|
269 |
|
270 | keys( names ).forEach( key => {
|
271 | if ( !~scope.names.indexOf( key ) ) {
|
272 | newNames[ key ] = names[ key ];
|
273 | hasReplacements = true;
|
274 | }
|
275 | });
|
276 |
|
277 | deshadowList.forEach( name => {
|
278 | if ( ~scope.names.indexOf( name ) ) {
|
279 | newNames[ name ] = name + '$$';
|
280 | hasReplacements = true;
|
281 | }
|
282 | });
|
283 |
|
284 | if ( !hasReplacements ) {
|
285 | return this.skip();
|
286 | }
|
287 |
|
288 | names = newNames;
|
289 | replacementStack.push( newNames );
|
290 | }
|
291 |
|
292 |
|
293 | if ( node.type !== 'Identifier' ) return;
|
294 | if ( parent.type === 'MemberExpression' && !parent.computed && node !== parent.object ) return;
|
295 | if ( parent.type === 'Property' && node !== parent.value ) return;
|
296 |
|
297 |
|
298 | const name = has( names, node.name ) && names[ node.name ];
|
299 |
|
300 | if ( name && name !== node.name ) {
|
301 | magicString.overwrite( node.start, node.end, name );
|
302 | }
|
303 | },
|
304 |
|
305 | leave ( node ) {
|
306 | if ( node._scope ) {
|
307 | replacementStack.pop();
|
308 | names = replacementStack[ replacementStack.length - 1 ];
|
309 | }
|
310 | }
|
311 | });
|
312 | }
|
313 |
|
314 | return magicString;
|
315 | }
|
316 | }
|