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 |
|
104 | if ( node.type === 'ExportNamedDeclaration' && node.source ) return this.skip();
|
105 |
|
106 | if ( node.type === 'TemplateElement' ) stringLiteralRanges.push([ node.start, node.end ]);
|
107 | if ( node.type === 'Literal' && typeof node.value === 'string' && /\n/.test( node.raw ) ) {
|
108 | stringLiteralRanges.push([ node.start + 1, node.end - 1 ]);
|
109 | }
|
110 |
|
111 | if ( node._scope ) scope = node._scope;
|
112 | if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth += 1;
|
113 |
|
114 |
|
115 |
|
116 | if ( node.type === 'Property' && node.shorthand ) {
|
117 | const reference = new Reference( node.key, scope );
|
118 | reference.isShorthandProperty = true;
|
119 | references.push( reference );
|
120 | return this.skip();
|
121 | }
|
122 |
|
123 | let isReassignment;
|
124 |
|
125 | if ( parent && parent.type in modifierNodes ) {
|
126 | let subject = parent[ modifierNodes[ parent.type ] ];
|
127 | let depth = 0;
|
128 |
|
129 | while ( subject.type === 'MemberExpression' ) {
|
130 | subject = subject.object;
|
131 | depth += 1;
|
132 | }
|
133 |
|
134 | const importDeclaration = module.imports[ subject.name ];
|
135 |
|
136 | if ( !scope.contains( subject.name ) && importDeclaration ) {
|
137 | const minDepth = importDeclaration.name === '*' ?
|
138 | 2 :
|
139 | 1;
|
140 |
|
141 | if ( depth < minDepth ) {
|
142 | const err = new Error( `Illegal reassignment to import '${subject.name}'` );
|
143 | err.file = module.id;
|
144 | err.loc = getLocation( module.magicString.toString(), subject.start );
|
145 | throw err;
|
146 | }
|
147 | }
|
148 |
|
149 | isReassignment = !depth;
|
150 | }
|
151 |
|
152 | if ( isReference( node, parent ) ) {
|
153 |
|
154 |
|
155 | const referenceScope = parent.type === 'FunctionDeclaration' && node === parent.id ?
|
156 | scope.parent :
|
157 | scope;
|
158 |
|
159 | const reference = new Reference( node, referenceScope );
|
160 | references.push( reference );
|
161 |
|
162 | reference.isImmediatelyUsed = !readDepth;
|
163 | reference.isReassignment = isReassignment;
|
164 |
|
165 | this.skip();
|
166 | }
|
167 | },
|
168 | leave ( node, parent ) {
|
169 | if ( node._scope ) scope = scope.parent;
|
170 | if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth -= 1;
|
171 | }
|
172 | });
|
173 | }
|
174 |
|
175 | mark () {
|
176 | if ( this.isIncluded ) return;
|
177 | this.isIncluded = true;
|
178 |
|
179 | this.references.forEach( reference => {
|
180 | if ( reference.declaration ) reference.declaration.use();
|
181 | });
|
182 | }
|
183 |
|
184 | markSideEffect () {
|
185 | if ( this.isIncluded ) return;
|
186 |
|
187 | const statement = this;
|
188 | let hasSideEffect = false;
|
189 |
|
190 | walk( this.node, {
|
191 | enter ( node, parent ) {
|
192 | if ( /Function/.test( node.type ) && !isIife( node, parent ) ) return this.skip();
|
193 |
|
194 |
|
195 |
|
196 | if ( node.type === 'CallExpression' || node.type === 'NewExpression' ) {
|
197 | hasSideEffect = true;
|
198 | }
|
199 |
|
200 | else if ( node.type in modifierNodes ) {
|
201 | let subject = node[ modifierNodes[ node.type ] ];
|
202 | while ( subject.type === 'MemberExpression' ) subject = subject.object;
|
203 |
|
204 | const declaration = statement.module.trace( subject.name );
|
205 |
|
206 | if ( !declaration || declaration.isExternal || declaration.statement.isIncluded ) {
|
207 | hasSideEffect = true;
|
208 | }
|
209 | }
|
210 |
|
211 | if ( hasSideEffect ) this.skip();
|
212 | }
|
213 | });
|
214 |
|
215 | if ( hasSideEffect ) statement.mark();
|
216 | return hasSideEffect;
|
217 | }
|
218 |
|
219 | source () {
|
220 | return this.module.source.slice( this.start, this.end );
|
221 | }
|
222 |
|
223 | toString () {
|
224 | return this.module.magicString.slice( this.start, this.end );
|
225 | }
|
226 | }
|