1 | import { blank, 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, index ) {
|
12 | this.node = node;
|
13 | this.module = module;
|
14 | this.magicString = magicString;
|
15 | this.index = index;
|
16 |
|
17 | this.scope = new Scope();
|
18 | this.defines = blank();
|
19 | this.modifies = blank();
|
20 | this.dependsOn = blank();
|
21 |
|
22 | this.isIncluded = false;
|
23 |
|
24 | this.leadingComments = [];
|
25 | this.trailingComment = null;
|
26 | this.margin = [ 0, 0 ];
|
27 |
|
28 |
|
29 | this.isImportDeclaration = node.type === 'ImportDeclaration';
|
30 | this.isExportDeclaration = /^Export/.test( node.type );
|
31 | }
|
32 |
|
33 | analyse () {
|
34 | if ( this.isImportDeclaration ) return;
|
35 |
|
36 | const statement = this;
|
37 | const magicString = this.magicString;
|
38 |
|
39 | let scope = this.scope;
|
40 |
|
41 | walk( this.node, {
|
42 | enter ( node ) {
|
43 | let newScope;
|
44 |
|
45 | magicString.addSourcemapLocation( node.start );
|
46 |
|
47 | switch ( node.type ) {
|
48 | case 'FunctionExpression':
|
49 | case 'FunctionDeclaration':
|
50 | case 'ArrowFunctionExpression':
|
51 | if ( node.type === 'FunctionDeclaration' ) {
|
52 | scope.addDeclaration( node.id.name, node );
|
53 | }
|
54 |
|
55 | newScope = new Scope({
|
56 | parent: scope,
|
57 | params: node.params,
|
58 | block: false
|
59 | });
|
60 |
|
61 |
|
62 |
|
63 | if ( node.type === 'FunctionExpression' && node.id ) {
|
64 | newScope.addDeclaration( node.id.name, node );
|
65 | }
|
66 |
|
67 | break;
|
68 |
|
69 | case 'BlockStatement':
|
70 | newScope = new Scope({
|
71 | parent: scope,
|
72 | block: true
|
73 | });
|
74 |
|
75 | break;
|
76 |
|
77 | case 'CatchClause':
|
78 | newScope = new Scope({
|
79 | parent: scope,
|
80 | params: [ node.param ],
|
81 | block: true
|
82 | });
|
83 |
|
84 | break;
|
85 |
|
86 | case 'VariableDeclaration':
|
87 | node.declarations.forEach( declarator => {
|
88 | scope.addDeclaration( declarator.id.name, node );
|
89 | });
|
90 | break;
|
91 |
|
92 | case 'ClassDeclaration':
|
93 | scope.addDeclaration( node.id.name, node );
|
94 | break;
|
95 | }
|
96 |
|
97 | if ( newScope ) {
|
98 | Object.defineProperty( node, '_scope', { value: newScope });
|
99 | scope = newScope;
|
100 | }
|
101 | },
|
102 | leave ( node ) {
|
103 | if ( node._scope ) {
|
104 | scope = scope.parent;
|
105 | }
|
106 | }
|
107 | });
|
108 |
|
109 | if ( !this.isImportDeclaration ) {
|
110 | walk( this.node, {
|
111 | enter: ( node, parent ) => {
|
112 | if ( node._scope ) scope = node._scope;
|
113 |
|
114 | this.checkForReads( scope, node, parent );
|
115 | this.checkForWrites( scope, node );
|
116 | },
|
117 | leave: ( node ) => {
|
118 | if ( node._scope ) scope = scope.parent;
|
119 | }
|
120 | });
|
121 | }
|
122 |
|
123 | keys( scope.declarations ).forEach( name => {
|
124 | statement.defines[ name ] = true;
|
125 | });
|
126 | }
|
127 |
|
128 | checkForReads ( scope, node, parent ) {
|
129 | if ( node.type === 'Identifier' ) {
|
130 |
|
131 | if ( parent.type === 'MemberExpression' && node !== parent.object ) {
|
132 | return;
|
133 | }
|
134 |
|
135 |
|
136 | if ( parent.type === 'Property' && node !== parent.value ) {
|
137 | return;
|
138 | }
|
139 |
|
140 | const definingScope = scope.findDefiningScope( node.name );
|
141 |
|
142 | if ( ( !definingScope || definingScope.depth === 0 ) && !this.defines[ node.name ] ) {
|
143 | this.dependsOn[ node.name ] = true;
|
144 | }
|
145 | }
|
146 | }
|
147 |
|
148 | checkForWrites ( scope, node ) {
|
149 | const addNode = ( node, disallowImportReassignments ) => {
|
150 | let depth = 0;
|
151 |
|
152 | while ( node.type === 'MemberExpression' ) {
|
153 | node = node.object;
|
154 | depth += 1;
|
155 | }
|
156 |
|
157 |
|
158 | if ( disallowImportReassignments ) {
|
159 | const importSpecifier = this.module.imports[ node.name ];
|
160 |
|
161 | if ( importSpecifier && !scope.contains( node.name ) ) {
|
162 | const minDepth = importSpecifier.name === '*' ?
|
163 | 2 :
|
164 | 1;
|
165 |
|
166 | if ( depth < minDepth ) {
|
167 | const err = new Error( `Illegal reassignment to import '${node.name}'` );
|
168 | err.file = this.module.path;
|
169 | err.loc = getLocation( this.module.magicString.toString(), node.start );
|
170 | throw err;
|
171 | }
|
172 | }
|
173 | }
|
174 |
|
175 | if ( node.type === 'Identifier' ) {
|
176 | this.modifies[ node.name ] = true;
|
177 | }
|
178 |
|
179 |
|
180 |
|
181 |
|
182 | if ( this.module.exports.default && this.module.exports.default.identifier === node.name ) {
|
183 |
|
184 |
|
185 | if ( !!scope.parent || node.start > this.module.exports.default.statement.node.start ) {
|
186 | this.module.exports.default.isModified = true;
|
187 | }
|
188 | }
|
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 | if ( node.callee.type === 'MemberExpression' ) {
|
204 | addNode( node.callee );
|
205 | }
|
206 | }
|
207 | }
|
208 |
|
209 | expand () {
|
210 | if ( this.isIncluded ) return emptyArrayPromise;
|
211 | this.isIncluded = true;
|
212 |
|
213 | let result = [];
|
214 |
|
215 |
|
216 |
|
217 | const dependencies = Object.keys( this.dependsOn );
|
218 |
|
219 | return sequence( dependencies, name => {
|
220 | return this.module.define( name ).then( definition => {
|
221 | result.push.apply( result, definition );
|
222 | });
|
223 | })
|
224 |
|
225 |
|
226 | .then( () => {
|
227 | result.push( this );
|
228 | })
|
229 |
|
230 |
|
231 |
|
232 | .then( () => {
|
233 | return sequence( keys( this.defines ), name => {
|
234 | const modifications = this.module.modifications[ name ];
|
235 |
|
236 | if ( modifications ) {
|
237 | return sequence( modifications, statement => {
|
238 | if ( !statement.isIncluded ) {
|
239 | return statement.expand()
|
240 | .then( statements => {
|
241 | result.push.apply( result, statements );
|
242 | });
|
243 | }
|
244 | });
|
245 | }
|
246 | });
|
247 | })
|
248 |
|
249 |
|
250 |
|
251 | .then( () => {
|
252 | return result;
|
253 | });
|
254 | }
|
255 |
|
256 | replaceIdentifiers ( names, bundleExports ) {
|
257 | const module = this.module;
|
258 |
|
259 | const magicString = this.magicString.clone();
|
260 | const replacementStack = [ names ];
|
261 | const nameList = keys( names );
|
262 |
|
263 | let deshadowList = [];
|
264 | nameList.forEach( name => {
|
265 | const replacement = names[ name ];
|
266 | deshadowList.push( replacement.split( '.' )[0] );
|
267 | });
|
268 |
|
269 | if ( nameList.length > 0 || keys( bundleExports ).length ) {
|
270 | let topLevel = true;
|
271 |
|
272 | walk( this.node, {
|
273 | enter ( node, parent ) {
|
274 | if ( node._skip ) return this.skip();
|
275 |
|
276 |
|
277 |
|
278 | if ( topLevel ) {
|
279 | if ( node.type === 'VariableDeclaration' ) {
|
280 |
|
281 |
|
282 | const name = node.declarations[0].id.name;
|
283 | if ( node.declarations.length === 1 && bundleExports[ name ] ) {
|
284 | magicString.overwrite( node.start, node.declarations[0].id.end, bundleExports[ name ] );
|
285 | node.declarations[0].id._skip = true;
|
286 | }
|
287 |
|
288 |
|
289 | else {
|
290 | const exportInitialisers = node.declarations
|
291 | .map( declarator => declarator.id.name )
|
292 | .filter( name => !!bundleExports[ name ] )
|
293 | .map( name => `\n${bundleExports[name]} = ${name};` )
|
294 | .join( '' );
|
295 |
|
296 |
|
297 | try {
|
298 | magicString.insert( node.end, exportInitialisers );
|
299 | } catch ( err ) {
|
300 | magicString.append( exportInitialisers );
|
301 | }
|
302 | }
|
303 | }
|
304 | }
|
305 |
|
306 | const scope = node._scope;
|
307 |
|
308 | if ( scope ) {
|
309 | topLevel = false;
|
310 |
|
311 | let newNames = blank();
|
312 | let hasReplacements;
|
313 |
|
314 | keys( names ).forEach( key => {
|
315 | if ( !scope.declarations[ key ] ) {
|
316 | newNames[ key ] = names[ key ];
|
317 | hasReplacements = true;
|
318 | }
|
319 | });
|
320 |
|
321 | deshadowList.forEach( name => {
|
322 | if ( ~scope.declarations[ name ] ) {
|
323 | newNames[ name ] = name + '$$';
|
324 | hasReplacements = true;
|
325 | }
|
326 | });
|
327 |
|
328 | if ( !hasReplacements ) {
|
329 | return this.skip();
|
330 | }
|
331 |
|
332 | names = newNames;
|
333 | replacementStack.push( newNames );
|
334 | }
|
335 |
|
336 |
|
337 | if ( node.type !== 'Identifier' ) return;
|
338 | if ( parent.type === 'MemberExpression' && !parent.computed && node !== parent.object ) return;
|
339 | if ( parent.type === 'Property' && node !== parent.value ) return;
|
340 |
|
341 |
|
342 | const name = names[ node.name ];
|
343 |
|
344 | if ( name && name !== node.name ) {
|
345 | magicString.overwrite( node.start, node.end, name );
|
346 | }
|
347 | },
|
348 |
|
349 | leave ( node ) {
|
350 | if ( node._scope ) {
|
351 | replacementStack.pop();
|
352 | names = replacementStack[ replacementStack.length - 1 ];
|
353 | }
|
354 | }
|
355 | });
|
356 | }
|
357 |
|
358 | return magicString;
|
359 | }
|
360 | }
|