1 | import { walk } from 'estree-walker';
|
2 | import Scope from './ast/Scope.js';
|
3 | import attachScopes from './ast/attachScopes.js';
|
4 | import getLocation from './utils/getLocation.js';
|
5 |
|
6 | const modifierNodes = {
|
7 | AssignmentExpression: 'left',
|
8 | UpdateExpression: 'argument'
|
9 | };
|
10 |
|
11 | function isIife ( node, parent ) {
|
12 | return parent && parent.type === 'CallExpression' && node === parent.callee;
|
13 | }
|
14 |
|
15 | function isReference ( node, parent ) {
|
16 | if ( node.type === 'MemberExpression' ) {
|
17 | return !node.computed && isReference( node.object, node );
|
18 | }
|
19 |
|
20 | if ( node.type === 'Identifier' ) {
|
21 |
|
22 | if ( parent.type === 'MemberExpression' ) return parent.computed || node === parent.object;
|
23 |
|
24 |
|
25 | if ( parent.type === 'Property' && node !== parent.value ) return false;
|
26 |
|
27 |
|
28 | if ( parent.type === 'MethodDefinition' ) return false;
|
29 |
|
30 |
|
31 | if ( parent.type === 'ExportSpecifier' && node !== parent.local ) return;
|
32 |
|
33 | return true;
|
34 | }
|
35 | }
|
36 |
|
37 | class Reference {
|
38 | constructor ( node, scope ) {
|
39 | this.node = node;
|
40 | this.scope = scope;
|
41 |
|
42 | this.declaration = null;
|
43 |
|
44 | this.parts = [];
|
45 |
|
46 | let root = node;
|
47 | while ( root.type === 'MemberExpression' ) {
|
48 | this.parts.unshift( root.property.name );
|
49 | root = root.object;
|
50 | }
|
51 |
|
52 | this.name = root.name;
|
53 |
|
54 | this.start = node.start;
|
55 | this.end = node.start + this.name.length;
|
56 | this.rewritten = false;
|
57 | }
|
58 | }
|
59 |
|
60 | export default class Statement {
|
61 | constructor ( node, module, start, end ) {
|
62 | this.node = node;
|
63 | this.module = module;
|
64 | this.start = start;
|
65 | this.end = end;
|
66 | this.next = null;
|
67 |
|
68 | this.scope = new Scope();
|
69 |
|
70 | this.references = [];
|
71 | this.stringLiteralRanges = [];
|
72 |
|
73 | this.isIncluded = false;
|
74 |
|
75 | this.isImportDeclaration = node.type === 'ImportDeclaration';
|
76 | this.isExportDeclaration = /^Export/.test( node.type );
|
77 | this.isReexportDeclaration = this.isExportDeclaration && !!node.source;
|
78 | }
|
79 |
|
80 | analyse () {
|
81 | if ( this.isImportDeclaration ) return;
|
82 |
|
83 |
|
84 | attachScopes( this );
|
85 |
|
86 |
|
87 |
|
88 | this.scope.eachDeclaration( ( name, declaration ) => {
|
89 | declaration.statement = this;
|
90 | });
|
91 |
|
92 |
|
93 | let { module, references, scope, stringLiteralRanges } = this;
|
94 | let readDepth = 0;
|
95 |
|
96 | walk( this.node, {
|
97 | enter ( node, parent ) {
|
98 |
|
99 | if ( node.type === 'CallExpression' && node.callee.name === 'eval' && !scope.contains( 'eval' ) ) {
|
100 | module.bundle.onwarn( `Use of \`eval\` (in ${module.id}) is discouraged, as it may cause issues with minification. See https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval for more details` );
|
101 | }
|
102 |
|
103 | if ( node.type === 'TemplateElement' ) stringLiteralRanges.push([ node.start, node.end ]);
|
104 | if ( node.type === 'Literal' && typeof node.value === 'string' && /\n/.test( node.raw ) ) {
|
105 | stringLiteralRanges.push([ node.start + 1, node.end - 1 ]);
|
106 | }
|
107 |
|
108 | if ( node._scope ) scope = node._scope;
|
109 | if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth += 1;
|
110 |
|
111 |
|
112 |
|
113 | if ( node.type === 'Property' && node.shorthand ) {
|
114 | const reference = new Reference( node.key, scope );
|
115 | reference.isShorthandProperty = true;
|
116 | references.push( reference );
|
117 | return this.skip();
|
118 | }
|
119 |
|
120 | let isReassignment;
|
121 |
|
122 | if ( parent && parent.type in modifierNodes ) {
|
123 | let subject = parent[ modifierNodes[ parent.type ] ];
|
124 | let depth = 0;
|
125 |
|
126 | while ( subject.type === 'MemberExpression' ) {
|
127 | subject = subject.object;
|
128 | depth += 1;
|
129 | }
|
130 |
|
131 | const importDeclaration = module.imports[ subject.name ];
|
132 |
|
133 | if ( !scope.contains( subject.name ) && importDeclaration ) {
|
134 | const minDepth = importDeclaration.name === '*' ?
|
135 | 2 :
|
136 | 1;
|
137 |
|
138 | if ( depth < minDepth ) {
|
139 | const err = new Error( `Illegal reassignment to import '${subject.name}'` );
|
140 | err.file = module.id;
|
141 | err.loc = getLocation( module.magicString.toString(), subject.start );
|
142 | throw err;
|
143 | }
|
144 | }
|
145 |
|
146 | isReassignment = !depth;
|
147 | }
|
148 |
|
149 | if ( isReference( node, parent ) ) {
|
150 |
|
151 |
|
152 | const referenceScope = parent.type === 'FunctionDeclaration' && node === parent.id ?
|
153 | scope.parent :
|
154 | scope;
|
155 |
|
156 | const reference = new Reference( node, referenceScope );
|
157 | references.push( reference );
|
158 |
|
159 | reference.isImmediatelyUsed = !readDepth;
|
160 | reference.isReassignment = isReassignment;
|
161 |
|
162 | this.skip();
|
163 | }
|
164 | },
|
165 | leave ( node, parent ) {
|
166 | if ( node._scope ) scope = scope.parent;
|
167 | if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth -= 1;
|
168 | }
|
169 | });
|
170 | }
|
171 |
|
172 | mark () {
|
173 | if ( this.isIncluded ) return;
|
174 | this.isIncluded = true;
|
175 |
|
176 | this.references.forEach( reference => {
|
177 | if ( reference.declaration ) reference.declaration.use();
|
178 | });
|
179 | }
|
180 |
|
181 | markSideEffect () {
|
182 | if ( this.isIncluded ) return;
|
183 |
|
184 | const statement = this;
|
185 | let hasSideEffect = false;
|
186 |
|
187 | walk( this.node, {
|
188 | enter ( node, parent ) {
|
189 | if ( /Function/.test( node.type ) && !isIife( node, parent ) ) return this.skip();
|
190 |
|
191 |
|
192 |
|
193 | if ( node.type === 'CallExpression' || node.type === 'NewExpression' ) {
|
194 | hasSideEffect = true;
|
195 | }
|
196 |
|
197 | else if ( node.type in modifierNodes ) {
|
198 | let subject = node[ modifierNodes[ node.type ] ];
|
199 | while ( subject.type === 'MemberExpression' ) subject = subject.object;
|
200 |
|
201 | const declaration = statement.module.trace( subject.name );
|
202 |
|
203 | if ( !declaration || declaration.isExternal || declaration.statement.isIncluded ) {
|
204 | hasSideEffect = true;
|
205 | }
|
206 | }
|
207 |
|
208 | if ( hasSideEffect ) this.skip();
|
209 | }
|
210 | });
|
211 |
|
212 | if ( hasSideEffect ) statement.mark();
|
213 | return hasSideEffect;
|
214 | }
|
215 |
|
216 | source () {
|
217 | return this.module.source.slice( this.start, this.end );
|
218 | }
|
219 |
|
220 | toString () {
|
221 | return this.module.magicString.slice( this.start, this.end );
|
222 | }
|
223 | }
|