UNPKG

5.29 kBJavaScriptView Raw
1import { walk } from 'estree-walker';
2import Scope from './ast/Scope.js';
3import attachScopes from './ast/attachScopes.js';
4import modifierNodes from './ast/modifierNodes.js';
5import isFunctionDeclaration from './ast/isFunctionDeclaration.js';
6import isReference from './ast/isReference.js';
7import getLocation from './utils/getLocation.js';
8import run from './utils/run.js';
9
10class Reference {
11 constructor ( node, scope, statement ) {
12 this.node = node;
13 this.scope = scope;
14 this.statement = statement;
15
16 this.declaration = null; // bound later
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; // can be overridden in the case of namespace members
30 this.rewritten = false;
31 }
32}
33
34export 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; // filled in later
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; // nothing to analyse
60
61 // attach scopes
62 attachScopes( this );
63
64 // find references
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 // warn about eval
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 // skip re-export declarations
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 // special case – shorthand properties. because node.key === node.value,
88 // we can't differentiate once we've descended into the node
89 if ( node.type === 'Property' && node.shorthand ) {
90 const reference = new Reference( node.key, scope );
91 reference.isShorthandProperty = true; // TODO feels a bit kludgy
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 : // cannot do e.g. `namespace.foo = bar`
112 1; // cannot do e.g. `foo = bar`, but `foo.bar = bar` is fine
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 // function declaration IDs are a special case – they're associated
127 // with the parent scope
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(); // don't descend from `foo.bar.baz` into `foo.bar`
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; // prevent infinite loops
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}