Skip to content

Commit

Permalink
Properly handle path access and calls in conditional expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Nov 8, 2017
1 parent 983c089 commit 96d48b3
Show file tree
Hide file tree
Showing 15 changed files with 262 additions and 19 deletions.
87 changes: 74 additions & 13 deletions src/ast/nodes/ConditionalExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@ import Node from '../Node.js';
import { UNKNOWN_VALUE } from '../values.js';

export default class ConditionalExpression extends Node {
initialiseChildren ( parentScope ) {
if ( this.module.bundle.treeshake ) {
this.testValue = this.test.getValue();
bindAssignmentAtPath ( path, expression ) {
if ( this.testValue === UNKNOWN_VALUE ) {
this.consequent.bindAssignmentAtPath( path, expression );
this.alternate.bindAssignmentAtPath( path, expression );
} else {
this.testValue
? this.consequent.bindAssignmentAtPath( path, expression )
: this.alternate.bindAssignmentAtPath( path, expression );
}
}

if ( this.testValue === UNKNOWN_VALUE ) {
super.initialiseChildren( parentScope );
} else if ( this.testValue ) {
this.consequent.initialise( this.scope );
this.alternate = null;
} else if ( this.alternate ) {
this.alternate.initialise( this.scope );
this.consequent = null;
}
bindCallAtPath ( path, callOptions ) {
if ( this.testValue === UNKNOWN_VALUE ) {
this.consequent.bindCallAtPath( path, callOptions );
this.alternate.bindCallAtPath( path, callOptions );
} else {
super.initialiseChildren( parentScope );
this.testValue
? this.consequent.bindCallAtPath( path, callOptions )
: this.alternate.bindCallAtPath( path, callOptions );
}
}

Expand All @@ -36,6 +40,63 @@ export default class ConditionalExpression extends Node {
);
}

hasEffectsWhenAccessedAtPath ( path, options ) {
return (
this.testValue === UNKNOWN_VALUE && (
this.consequent.hasEffectsWhenAccessedAtPath( path, options )
|| this.alternate.hasEffectsWhenAccessedAtPath( path, options )
)
) || (
this.testValue
? this.consequent.hasEffectsWhenAccessedAtPath( path, options )
: this.alternate.hasEffectsWhenAccessedAtPath( path, options )
);
}

hasEffectsWhenAssignedAtPath ( path, options ) {
return (
this.testValue === UNKNOWN_VALUE && (
this.consequent.hasEffectsWhenAssignedAtPath( path, options )
|| this.alternate.hasEffectsWhenAssignedAtPath( path, options )
)
) || (
this.testValue
? this.consequent.hasEffectsWhenAssignedAtPath( path, options )
: this.alternate.hasEffectsWhenAssignedAtPath( path, options )
);
}

hasEffectsWhenCalledAtPath ( path, options ) {
return (
this.testValue === UNKNOWN_VALUE && (
this.consequent.hasEffectsWhenCalledAtPath( path, options )
|| this.alternate.hasEffectsWhenCalledAtPath( path, options )
)
) || (
this.testValue
? this.consequent.hasEffectsWhenCalledAtPath( path, options )
: this.alternate.hasEffectsWhenCalledAtPath( path, options )
);
}

initialiseChildren ( parentScope ) {
if ( this.module.bundle.treeshake ) {
this.testValue = this.test.getValue();

if ( this.testValue === UNKNOWN_VALUE ) {
super.initialiseChildren( parentScope );
} else if ( this.testValue ) {
this.consequent.initialise( this.scope );
this.alternate = null;
} else if ( this.alternate ) {
this.alternate.initialise( this.scope );
this.consequent = null;
}
} else {
super.initialiseChildren( parentScope );
}
}

render ( code, es ) {
if ( !this.module.bundle.treeshake ) {
super.render( code, es );
Expand Down
7 changes: 2 additions & 5 deletions src/ast/nodes/MemberExpression.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import relativeId from '../../utils/relativeId.js';
import Node from '../Node.js';
import isReference from 'is-reference';
import { UNKNOWN_KEY } from '../variables/StructuredAssignmentTracker';

const validProp = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
Expand Down Expand Up @@ -126,10 +125,8 @@ export default class MemberExpression extends Node {
if ( this.variable ) {
return this.variable.hasEffectsWhenCalledAtPath( path, options );
}
if ( !isReference( this ) || this.computed ) {
return true;
}
return this.object.hasEffectsWhenCalledAtPath( [ this.property.name, ...path ], options );
return this.computed
|| this.object.hasEffectsWhenCalledAtPath( [ this.property.name, ...path ], options );
}

hasEffectsWhenMutatedAtPath ( path, options ) {
Expand Down
3 changes: 3 additions & 0 deletions test/form/samples/conditional-expression-paths/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
description: 'only retain branches with side-effects'
};
22 changes: 22 additions & 0 deletions test/form/samples/conditional-expression-paths/_expected/amd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
define(function () { 'use strict';

var unknownValue = globalFunction();

// unknown branch with side-effect
var foo2 = { x: () => {} };
var bar2 = { x: () => console.log( 'effect' ) };
var a2 = (unknownValue ? foo2 : bar2).x.y.z;
var b2 = (unknownValue ? foo2 : bar2).x();
var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' );
foo2.x();
bar2.x();

var bar4 = { x: () => console.log( 'effect' ) };
var a4 = (bar4).y.z;
var b4 = (bar4).y.z;
var c4 = (bar4).x();
var d4 = (bar4).x();
var e4 = (bar4).y.z = 1;
var f4 = (bar4).y.z = 1;

});
20 changes: 20 additions & 0 deletions test/form/samples/conditional-expression-paths/_expected/cjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

var unknownValue = globalFunction();

// unknown branch with side-effect
var foo2 = { x: () => {} };
var bar2 = { x: () => console.log( 'effect' ) };
var a2 = (unknownValue ? foo2 : bar2).x.y.z;
var b2 = (unknownValue ? foo2 : bar2).x();
var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' );
foo2.x();
bar2.x();

var bar4 = { x: () => console.log( 'effect' ) };
var a4 = (bar4).y.z;
var b4 = (bar4).y.z;
var c4 = (bar4).x();
var d4 = (bar4).x();
var e4 = (bar4).y.z = 1;
var f4 = (bar4).y.z = 1;
18 changes: 18 additions & 0 deletions test/form/samples/conditional-expression-paths/_expected/es.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
var unknownValue = globalFunction();

// unknown branch with side-effect
var foo2 = { x: () => {} };
var bar2 = { x: () => console.log( 'effect' ) };
var a2 = (unknownValue ? foo2 : bar2).x.y.z;
var b2 = (unknownValue ? foo2 : bar2).x();
var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' );
foo2.x();
bar2.x();

var bar4 = { x: () => console.log( 'effect' ) };
var a4 = (bar4).y.z;
var b4 = (bar4).y.z;
var c4 = (bar4).x();
var d4 = (bar4).x();
var e4 = (bar4).y.z = 1;
var f4 = (bar4).y.z = 1;
23 changes: 23 additions & 0 deletions test/form/samples/conditional-expression-paths/_expected/iife.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
(function () {
'use strict';

var unknownValue = globalFunction();

// unknown branch with side-effect
var foo2 = { x: () => {} };
var bar2 = { x: () => console.log( 'effect' ) };
var a2 = (unknownValue ? foo2 : bar2).x.y.z;
var b2 = (unknownValue ? foo2 : bar2).x();
var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' );
foo2.x();
bar2.x();

var bar4 = { x: () => console.log( 'effect' ) };
var a4 = (bar4).y.z;
var b4 = (bar4).y.z;
var c4 = (bar4).x();
var d4 = (bar4).x();
var e4 = (bar4).y.z = 1;
var f4 = (bar4).y.z = 1;

}());
26 changes: 26 additions & 0 deletions test/form/samples/conditional-expression-paths/_expected/umd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
(factory());
}(this, (function () { 'use strict';

var unknownValue = globalFunction();

// unknown branch with side-effect
var foo2 = { x: () => {} };
var bar2 = { x: () => console.log( 'effect' ) };
var a2 = (unknownValue ? foo2 : bar2).x.y.z;
var b2 = (unknownValue ? foo2 : bar2).x();
var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' );
foo2.x();
bar2.x();

var bar4 = { x: () => console.log( 'effect' ) };
var a4 = (bar4).y.z;
var b4 = (bar4).y.z;
var c4 = (bar4).x();
var d4 = (bar4).x();
var e4 = (bar4).y.z = 1;
var f4 = (bar4).y.z = 1;

})));
39 changes: 39 additions & 0 deletions test/form/samples/conditional-expression-paths/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
var unknownValue = globalFunction();

// unknown branch without side-effects
var foo1 = { x: () => {} };
var bar1 = { x: () => {} };
var a1 = (unknownValue ? foo1 : bar1).x.y;
var b1 = (unknownValue ? foo1 : bar1).x();
var c1 = (unknownValue ? foo1 : bar1).x = () => {};
foo1.x();
bar1.x();

// unknown branch with side-effect
var foo2 = { x: () => {} };
var bar2 = { x: () => console.log( 'effect' ) };
var a2 = (unknownValue ? foo2 : bar2).x.y.z;
var b2 = (unknownValue ? foo2 : bar2).x();
var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' );
foo2.x();
bar2.x();

// no side-effects
var foo3 = { x: () => {}, y: {} };
var bar3 = { x: () => console.log( 'effect' ) };
var a3 = (true ? foo3 : bar3).y.z;
var b3 = (false ? bar3 : foo3).y.z;
var c3 = (true ? foo3 : bar3).x();
var d3 = (false ? bar3 : foo3).x();
var e3 = (true ? foo3 : bar3).y.z = 1;
var f3 = (false ? bar3 : foo3).y.z = 1;

// known side-effect
var foo4 = { x: () => {}, y: {} };
var bar4 = { x: () => console.log( 'effect' ) };
var a4 = (true ? bar4 : foo4).y.z;
var b4 = (false ? foo4 : bar4).y.z;
var c4 = (true ? bar4 : foo4).x();
var d4 = (false ? foo4 : bar4).x();
var e4 = (true ? bar4 : foo4).y.z = 1;
var f4 = (false ? foo4 : bar4).y.z = 1;
5 changes: 5 additions & 0 deletions test/form/samples/conditional-expression/_expected/amd.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ define(function () { 'use strict';
// unknown branch with side-effect
var c = unknownValue ? foo() : 2;
var d = unknownValue ? 1 : foo();
var d1 = function () {};
var d2 = function () {this.x = 1;};
(unknownValue ? d1 : d2)();

// known side-effect
var h = foo();
var h1 = (function () {this.x = 1;})();
var i = foo();
var i1 = (function () {this.x = 1;})();

});
5 changes: 5 additions & 0 deletions test/form/samples/conditional-expression/_expected/cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ var unknownValue = bar();
// unknown branch with side-effect
var c = unknownValue ? foo() : 2;
var d = unknownValue ? 1 : foo();
var d1 = function () {};
var d2 = function () {this.x = 1;};
(unknownValue ? d1 : d2)();

// known side-effect
var h = foo();
var h1 = (function () {this.x = 1;})();
var i = foo();
var i1 = (function () {this.x = 1;})();
5 changes: 5 additions & 0 deletions test/form/samples/conditional-expression/_expected/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ var unknownValue = bar();
// unknown branch with side-effect
var c = unknownValue ? foo() : 2;
var d = unknownValue ? 1 : foo();
var d1 = function () {};
var d2 = function () {this.x = 1;};
(unknownValue ? d1 : d2)();

// known side-effect
var h = foo();
var h1 = (function () {this.x = 1;})();
var i = foo();
var i1 = (function () {this.x = 1;})();
5 changes: 5 additions & 0 deletions test/form/samples/conditional-expression/_expected/iife.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@
// unknown branch with side-effect
var c = unknownValue ? foo() : 2;
var d = unknownValue ? 1 : foo();
var d1 = function () {};
var d2 = function () {this.x = 1;};
(unknownValue ? d1 : d2)();

// known side-effect
var h = foo();
var h1 = (function () {this.x = 1;})();
var i = foo();
var i1 = (function () {this.x = 1;})();

}());
5 changes: 5 additions & 0 deletions test/form/samples/conditional-expression/_expected/umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@
// unknown branch with side-effect
var c = unknownValue ? foo() : 2;
var d = unknownValue ? 1 : foo();
var d1 = function () {};
var d2 = function () {this.x = 1;};
(unknownValue ? d1 : d2)();

// known side-effect
var h = foo();
var h1 = (function () {this.x = 1;})();
var i = foo();
var i1 = (function () {this.x = 1;})();

})));
11 changes: 10 additions & 1 deletion test/form/samples/conditional-expression/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,26 @@ var unknownValue = bar();

// unknown branch without side-effects
var b = unknownValue ? 1 : 2;
var b1 = function () {};
var b2 = function () {this.x = 1;};
new (unknownValue ? b1 : b2)();

// unknown branch with side-effect
var c = unknownValue ? foo() : 2;
var d = unknownValue ? 1 : foo();
var d1 = function () {};
var d2 = function () {this.x = 1;};
(unknownValue ? d1 : d2)();

// no side-effects
var e = true ? 1 : foo();
var e1 = (true ? function () {} : function () {this.x = 1;})();
var f = false ? foo() : 2;
var f1 = (false ? function () {this.x = 1;} : function () {})();
var g = true ? 1 : 2;

// known side-effect
var h = true ? foo() : 2;
var h1 = (true ? function () {this.x = 1;} : function () {})();
var i = false ? 1 : foo();

var i1 = (false ? function () {} : function () {this.x = 1;})();

0 comments on commit 96d48b3

Please sign in to comment.