UNPKG

4.86 kBJavaScriptView Raw
1import { walk } from 'estree-walker';
2import Scope from './ast/Scope.js';
3import attachScopes from './ast/attachScopes.js';
4import modifierNodes, { isModifierNode } 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';
9import { Reference } from './Reference.js';
10
11export default class Statement {
12 constructor ( node, module, start, end ) {
13 this.node = node;
14 this.module = module;
15 this.start = start;
16 this.end = end;
17 this.next = null; // filled in later
18
19 this.scope = new Scope({ statement: this });
20
21 this.references = [];
22 this.stringLiteralRanges = [];
23
24 this.isIncluded = false;
25 this.ran = false;
26
27 this.isImportDeclaration = node.type === 'ImportDeclaration';
28 this.isExportDeclaration = /^Export/.test( node.type );
29 this.isReexportDeclaration = this.isExportDeclaration && !!node.source;
30
31 this.isFunctionDeclaration = isFunctionDeclaration( node ) ||
32 this.isExportDeclaration && isFunctionDeclaration( node.declaration );
33 }
34
35 firstPass () {
36 if ( this.isImportDeclaration ) return; // nothing to analyse
37
38 // attach scopes
39 attachScopes( this );
40
41 // find references
42 const statement = this;
43 let { module, references, scope, stringLiteralRanges } = this;
44 let readDepth = 0;
45
46 walk( this.node, {
47 enter ( node, parent ) {
48 // warn about eval
49 if ( node.type === 'CallExpression' && node.callee.name === 'eval' && !scope.contains( 'eval' ) ) {
50 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` );
51 }
52
53 // skip re-export declarations
54 if ( node.type === 'ExportNamedDeclaration' && node.source ) return this.skip();
55
56 if ( node.type === 'TemplateElement' ) stringLiteralRanges.push([ node.start, node.end ]);
57 if ( node.type === 'Literal' && typeof node.value === 'string' && /\n/.test( node.raw ) ) {
58 stringLiteralRanges.push([ node.start + 1, node.end - 1 ]);
59 }
60
61 if ( node._scope ) scope = node._scope;
62 if ( /Function/.test( node.type ) ) readDepth += 1;
63
64 // special case – shorthand properties. because node.key === node.value,
65 // we can't differentiate once we've descended into the node
66 if ( node.type === 'Property' && node.shorthand && parent.type !== 'ObjectPattern' ) {
67 const reference = new Reference( node.key, scope );
68 reference.isShorthandProperty = true; // TODO feels a bit kludgy
69 references.push( reference );
70 return this.skip();
71 }
72
73 let isReassignment;
74
75 if ( parent && isModifierNode( parent ) ) {
76 let subject = parent[ modifierNodes[ parent.type ] ];
77 let depth = 0;
78
79 while ( subject.type === 'MemberExpression' ) {
80 subject = subject.object;
81 depth += 1;
82 }
83
84 const importDeclaration = module.imports[ subject.name ];
85
86 if ( !scope.contains( subject.name ) && importDeclaration ) {
87 const minDepth = importDeclaration.name === '*' ?
88 2 : // cannot do e.g. `namespace.foo = bar`
89 1; // cannot do e.g. `foo = bar`, but `foo.bar = bar` is fine
90
91 if ( depth < minDepth ) {
92 const err = new Error( `Illegal reassignment to import '${subject.name}'` );
93 err.file = module.id;
94 err.loc = getLocation( module.magicString.toString(), subject.start );
95 throw err;
96 }
97 }
98
99 isReassignment = !depth;
100 }
101
102 if ( isReference( node, parent ) ) {
103 // function declaration IDs are a special case – they're associated
104 // with the parent scope
105 const referenceScope = parent.type === 'FunctionDeclaration' && node === parent.id ?
106 scope.parent :
107 scope;
108
109 const reference = new Reference( node, referenceScope, statement );
110 reference.isReassignment = isReassignment;
111
112 references.push( reference );
113
114 this.skip(); // don't descend from `foo.bar.baz` into `foo.bar`
115 }
116 },
117 leave ( node ) {
118 if ( node._scope ) scope = scope.parent;
119 if ( /Function/.test( node.type ) ) readDepth -= 1;
120 }
121 });
122 }
123
124 mark () {
125 if ( this.isIncluded ) return; // prevent infinite loops
126 this.isIncluded = true;
127
128 this.references.forEach( reference => {
129 if ( reference.declaration ) reference.declaration.use();
130 });
131 }
132
133 run ( strongDependencies ) {
134 if ( ( this.ran && this.isIncluded ) || this.isImportDeclaration || this.isFunctionDeclaration ) return;
135 this.ran = true;
136
137 if ( run( this.node, this.scope, this, strongDependencies, false ) ) {
138 this.mark();
139 return true;
140 }
141 }
142
143 source () {
144 return this.module.source.slice( this.start, this.end );
145 }
146
147 toString () {
148 return this.module.magicString.slice( this.start, this.end );
149 }
150}