UNPKG

6.15 kBJavaScriptView Raw
1import { walk } from 'estree-walker';
2import modifierNodes, { isModifierNode } from '../ast/modifierNodes.js';
3import isReference from '../ast/isReference.js';
4import flatten from '../ast/flatten';
5
6let pureFunctions = {};
7
8const arrayTypes = 'Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split( ' ' );
9const simdTypes = 'Int8x16 Int16x8 Int32x4 Float32x4 Float64x2'.split( ' ' );
10const simdMethods = 'abs add and bool check div equal extractLane fromFloat32x4 fromFloat32x4Bits fromFloat64x2 fromFloat64x2Bits fromInt16x8Bits fromInt32x4 fromInt32x4Bits fromInt8x16Bits greaterThan greaterThanOrEqual lessThan lessThanOrEqual load max maxNum min minNum mul neg not notEqual or reciprocalApproximation reciprocalSqrtApproximation replaceLane select selectBits shiftLeftByScalar shiftRightArithmeticByScalar shiftRightLogicalByScalar shuffle splat sqrt store sub swizzle xor'.split( ' ' );
11let allSimdMethods = [];
12simdTypes.forEach( t => {
13 simdMethods.forEach( m => {
14 allSimdMethods.push( `SIMD.${t}.${m}` );
15 });
16});
17
18[
19 'Array.isArray',
20 'Error', 'EvalError', 'InternalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError',
21 'isFinite', 'isNaN', 'parseFloat', 'parseInt', 'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', 'escape', 'unescape',
22 'Object', 'Object.create', 'Object.getNotifier', 'Object.getOwn', 'Object.getOwnPropertyDescriptor', 'Object.getOwnPropertyNames', 'Object.getOwnPropertySymbols', 'Object.getPrototypeOf', 'Object.is', 'Object.isExtensible', 'Object.isFrozen', 'Object.isSealed', 'Object.keys',
23 'Function', 'Boolean',
24 'Number', 'Number.isFinite', 'Number.isInteger', 'Number.isNaN', 'Number.isSafeInteger', 'Number.parseFloat', 'Number.parseInt',
25 'Symbol', 'Symbol.for', 'Symbol.keyFor',
26 'Math.abs', 'Math.acos', 'Math.acosh', 'Math.asin', 'Math.asinh', 'Math.atan', 'Math.atan2', 'Math.atanh', 'Math.cbrt', 'Math.ceil', 'Math.clz32', 'Math.cos', 'Math.cosh', 'Math.exp', 'Math.expm1', 'Math.floor', 'Math.fround', 'Math.hypot', 'Math.imul', 'Math.log', 'Math.log10', 'Math.log1p', 'Math.log2', 'Math.max', 'Math.min', 'Math.pow', 'Math.random', 'Math.round', 'Math.sign', 'Math.sin', 'Math.sinh', 'Math.sqrt', 'Math.tan', 'Math.tanh', 'Math.trunc',
27 'Date', 'Date.UTC', 'Date.now', 'Date.parse',
28 'String', 'String.fromCharCode', 'String.fromCodePoint', 'String.raw',
29 'RegExp',
30 'Map', 'Set', 'WeakMap', 'WeakSet',
31 'ArrayBuffer', 'ArrayBuffer.isView',
32 'DataView',
33 'JSON.parse', 'JSON.stringify',
34 'Promise', 'Promise.all', 'Promise.race', 'Promise.reject', 'Promise.resolve',
35 'Intl.Collator', 'Intl.Collator.supportedLocalesOf', 'Intl.DateTimeFormat', 'Intl.DateTimeFormat.supportedLocalesOf', 'Intl.NumberFormat', 'Intl.NumberFormat.supportedLocalesOf'
36
37 // TODO properties of e.g. window...
38].concat(
39 arrayTypes,
40 arrayTypes.map( t => `${t}.from` ),
41 arrayTypes.map( t => `${t}.of` ),
42 simdTypes.map( t => `SIMD.${t}` ),
43 allSimdMethods
44).forEach( name => pureFunctions[ name ] = true );
45 // TODO add others to this list from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
46
47
48
49export default function run ( node, scope, statement, strongDependencies, force ) {
50 let hasSideEffect = false;
51
52 walk( node, {
53 enter ( node, parent ) {
54 if ( !force && /Function/.test( node.type ) ) return this.skip();
55
56 if ( node._scope ) scope = node._scope;
57
58 if ( isReference( node, parent ) ) {
59 const flattened = flatten( node );
60
61 if ( flattened.name === 'arguments' ) {
62 hasSideEffect = true;
63 }
64
65 else if ( !scope.contains( flattened.name ) ) {
66 const declaration = statement.module.trace( flattened.name );
67 if ( declaration && !declaration.isExternal ) {
68 const module = declaration.module || declaration.statement.module; // TODO is this right?
69 if ( !module.isExternal && !~strongDependencies.indexOf( module ) ) strongDependencies.push( module );
70 }
71 }
72 }
73
74 else if ( node.type === 'ThrowStatement' ) {
75 // we only care about errors thrown at the top level, otherwise
76 // any function with error checking gets included if called
77 if ( scope.isTopLevel ) hasSideEffect = true;
78 }
79
80 else if ( node.type === 'CallExpression' || node.type === 'NewExpression' ) {
81 if ( node.callee.type === 'Identifier' ) {
82 const declaration = scope.findDeclaration( node.callee.name ) ||
83 statement.module.trace( node.callee.name );
84
85 if ( declaration ) {
86 if ( declaration.run( strongDependencies ) ) {
87 hasSideEffect = true;
88 }
89 } else if ( !pureFunctions[ node.callee.name ] ) {
90 hasSideEffect = true;
91 }
92 }
93
94 else if ( node.callee.type === 'MemberExpression' ) {
95 const flattened = flatten( node.callee );
96
97 if ( flattened ) {
98 // if we're calling e.g. Object.keys(thing), there are no side-effects
99 // TODO make pureFunctions configurable
100 const declaration = scope.findDeclaration( flattened.name ) || statement.module.trace( flattened.name );
101
102 if ( !!declaration || !pureFunctions[ flattened.keypath ] ) {
103 hasSideEffect = true;
104 }
105 } else {
106 // is not a keypath like `foo.bar.baz` – could be e.g.
107 // `foo[bar].baz()`. Err on the side of caution
108 hasSideEffect = true;
109 }
110 }
111
112 // otherwise we're probably dealing with a function expression
113 else if ( run( node.callee, scope, statement, strongDependencies, true ) ) {
114 hasSideEffect = true;
115 }
116 }
117
118 else if ( isModifierNode( node ) ) {
119 let subject = node[ modifierNodes[ node.type ] ];
120 while ( subject.type === 'MemberExpression' ) subject = subject.object;
121
122 let declaration = scope.findDeclaration( subject.name );
123
124 if ( declaration ) {
125 if ( declaration.isParam ) hasSideEffect = true;
126 } else {
127 declaration = statement.module.trace( subject.name );
128
129 if ( !declaration || declaration.isExternal || declaration.isUsed ) {
130 hasSideEffect = true;
131 }
132 }
133 }
134 },
135 leave ( node ) {
136 if ( node._scope ) scope = scope.parent;
137 }
138 });
139
140 return hasSideEffect;
141}