1 | import { walk } from 'estree-walker';
|
2 | import Scope from './ast/Scope.js';
|
3 | import attachScopes from './ast/attachScopes.js';
|
4 | import modifierNodes from './ast/modifierNodes.js';
|
5 | import isFunctionDeclaration from './ast/isFunctionDeclaration.js';
|
6 | import isReference from './ast/isReference.js';
|
7 | import getLocation from './utils/getLocation.js';
|
8 | import run from './utils/run.js';
|
9 |
|
10 | class Reference {
|
11 | constructor ( node, scope, statement ) {
|
12 | this.node = node;
|
13 | this.scope = scope;
|
14 | this.statement = statement;
|
15 |
|
16 | this.declaration = null;
|
17 |
|
18 | this.parts = [];
|
19 |
|
20 | let root = node;
|
21 | while ( root.type === 'MemberExpression' ) {
|
22 | this.parts.unshift( root.property.name );
|
23 | root = root.object;
|
24 | }
|
25 |
|
26 | this.name = root.name;
|
27 |
|
28 | this.start = node.start;
|
29 | this.end = node.start + this.name.length;
|
30 | this.rewritten = false;
|
31 | }
|
32 | }
|
33 |
|
34 | export default class Statement {
|
35 | constructor ( node, module, start, end ) {
|
36 | this.node = node;
|
37 | this.module = module;
|
38 | this.start = start;
|
39 | this.end = end;
|
40 | this.next = null;
|
41 |
|
42 | this.scope = new Scope({ statement: this });
|
43 |
|
44 | this.references = [];
|
45 | this.stringLiteralRanges = [];
|
46 |
|
47 | this.isIncluded = false;
|
48 | this.ran = false;
|
49 |
|
50 | this.isImportDeclaration = node.type === 'ImportDeclaration';
|
51 | this.isExportDeclaration = /^Export/.test( node.type );
|
52 | this.isReexportDeclaration = this.isExportDeclaration && !!node.source;
|
53 |
|
54 | this.isFunctionDeclaration = isFunctionDeclaration( node ) ||
|
55 | this.isExportDeclaration && isFunctionDeclaration( node.declaration );
|
56 | }
|
57 |
|
58 | firstPass () {
|
59 | if ( this.isImportDeclaration ) return;
|
60 |
|
61 |
|
62 | attachScopes( this );
|
63 |
|
64 |
|
65 | const statement = this;
|
66 | let { module, references, scope, stringLiteralRanges } = this;
|
67 | let readDepth = 0;
|
68 |
|
69 | walk( this.node, {
|
70 | enter ( node, parent ) {
|
71 |
|
72 | if ( node.type === 'CallExpression' && node.callee.name === 'eval' && !scope.contains( 'eval' ) ) {
|
73 | 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` );
|
74 | }
|
75 |
|
76 |
|
77 | if ( node.type === 'ExportNamedDeclaration' && node.source ) return this.skip();
|
78 |
|
79 | if ( node.type === 'TemplateElement' ) stringLiteralRanges.push([ node.start, node.end ]);
|
80 | if ( node.type === 'Literal' && typeof node.value === 'string' && /\n/.test( node.raw ) ) {
|
81 | stringLiteralRanges.push([ node.start + 1, node.end - 1 ]);
|
82 | }
|
83 |
|
84 | if ( node._scope ) scope = node._scope;
|
85 | if ( /Function/.test( node.type ) ) readDepth += 1;
|
86 |
|
87 |
|
88 |
|
89 | if ( node.type === 'Property' && node.shorthand ) {
|
90 | const reference = new Reference( node.key, scope );
|
91 | reference.isShorthandProperty = true;
|
92 | references.push( reference );
|
93 | return this.skip();
|
94 | }
|
95 |
|
96 | let isReassignment;
|
97 |
|
98 | if ( parent && parent.type in modifierNodes ) {
|
99 | let subject = parent[ modifierNodes[ parent.type ] ];
|
100 | let depth = 0;
|
101 |
|
102 | while ( subject.type === 'MemberExpression' ) {
|
103 | subject = subject.object;
|
104 | depth += 1;
|
105 | }
|
106 |
|
107 | const importDeclaration = module.imports[ subject.name ];
|
108 |
|
109 | if ( !scope.contains( subject.name ) && importDeclaration ) {
|
110 | const minDepth = importDeclaration.name === '*' ?
|
111 | 2 :
|
112 | 1;
|
113 |
|
114 | if ( depth < minDepth ) {
|
115 | const err = new Error( `Illegal reassignment to import '${subject.name}'` );
|
116 | err.file = module.id;
|
117 | err.loc = getLocation( module.magicString.toString(), subject.start );
|
118 | throw err;
|
119 | }
|
120 | }
|
121 |
|
122 | isReassignment = !depth;
|
123 | }
|
124 |
|
125 | if ( isReference( node, parent ) ) {
|
126 |
|
127 |
|
128 | const referenceScope = parent.type === 'FunctionDeclaration' && node === parent.id ?
|
129 | scope.parent :
|
130 | scope;
|
131 |
|
132 | const reference = new Reference( node, referenceScope, statement );
|
133 | reference.isReassignment = isReassignment;
|
134 |
|
135 | references.push( reference );
|
136 |
|
137 | this.skip();
|
138 | }
|
139 | },
|
140 | leave ( node ) {
|
141 | if ( node._scope ) scope = scope.parent;
|
142 | if ( /Function/.test( node.type ) ) readDepth -= 1;
|
143 | }
|
144 | });
|
145 | }
|
146 |
|
147 | mark () {
|
148 | if ( this.isIncluded ) return;
|
149 | this.isIncluded = true;
|
150 |
|
151 | this.references.forEach( reference => {
|
152 | if ( reference.declaration ) reference.declaration.use();
|
153 | });
|
154 | }
|
155 |
|
156 | run ( strongDependencies, safe ) {
|
157 | if ( ( this.ran && this.isIncluded ) || this.isImportDeclaration || this.isFunctionDeclaration ) return;
|
158 | this.ran = true;
|
159 |
|
160 | if ( run( this.node, this.scope, this, strongDependencies, false, safe ) ) {
|
161 | this.mark();
|
162 | return true;
|
163 | }
|
164 | }
|
165 |
|
166 | source () {
|
167 | return this.module.source.slice( this.start, this.end );
|
168 | }
|
169 |
|
170 | toString () {
|
171 | return this.module.magicString.slice( this.start, this.end );
|
172 | }
|
173 | }
|