Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Support getter return values and setter parameters.
  • Loading branch information
lukastaegert committed Nov 8, 2017
1 parent dcfe5d4 commit e2daaec
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 30 deletions.
6 changes: 6 additions & 0 deletions src/ast/nodes/CallExpression.js
Expand Up @@ -17,6 +17,11 @@ export default class CallExpression extends Node {
node => node.bindCallAtPath( path, callOptions ) );
}

forEachReturnExpressionWhenCalledAtPath ( path, callOptions, callback ) {
this.callee.forEachReturnExpressionWhenCalledAtPath( [], this._callOptions,
node => node.forEachReturnExpressionWhenCalledAtPath( path, callOptions, callback ) );
}

bindNode () {
if ( this.callee.type === 'Identifier' ) {
const variable = this.scope.findVariable( this.callee.name );
Expand Down Expand Up @@ -65,6 +70,7 @@ export default class CallExpression extends Node {

initialiseNode () {
this._callOptions = CallOptions.create( { withNew: false, args: this.arguments } );
// To avoid infinite recursions when dealing with recursive functions
this._boundExpressions = new StructuredAssignmentTracker();
this._boundCalls = new StructuredAssignmentTracker();
}
Expand Down
34 changes: 15 additions & 19 deletions src/ast/nodes/ObjectExpression.js
Expand Up @@ -7,18 +7,22 @@ const PROPERTY_KINDS_WRITE = [ 'init', 'set' ];
export default class ObjectExpression extends Node {
bindAssignmentAtPath ( path, expression ) {
if ( path.length === 0 ) return;
this._getPossiblePropertiesWithName( path[ 0 ], PROPERTY_KINDS_WRITE ).properties.forEach( property =>
property.bindAssignmentAtPath( path.slice( 1 ), expression ) );

this._getPossiblePropertiesWithName(
path[ 0 ], path.length === 1 ? PROPERTY_KINDS_WRITE : PROPERTY_KINDS_READ )
.properties.forEach( property => property.bindAssignmentAtPath( path.slice( 1 ), expression ) );
}

bindCallAtPath ( path, callOptions ) {
if ( path.length === 0 ) return;

this._getPossiblePropertiesWithName( path[ 0 ], PROPERTY_KINDS_READ ).properties.forEach( property =>
property.bindCallAtPath( path.slice( 1 ), callOptions ) );
}

forEachReturnExpressionWhenCalledAtPath ( path, callOptions, callback ) {
if ( path.length === 0 ) return;

this._getPossiblePropertiesWithName( path[ 0 ], PROPERTY_KINDS_READ ).properties.forEach( property =>
property.forEachReturnExpressionWhenCalledAtPath( path.slice( 1 ), callOptions, callback ) );
}
Expand All @@ -45,43 +49,35 @@ export default class ObjectExpression extends Node {
}

hasEffectsWhenAccessedAtPath ( path, options ) {
if ( path.length === 0 ) {
return false;
}
const { properties, hasCertainHit } = this._getPossiblePropertiesWithName( path[ 0 ], PROPERTY_KINDS_READ );
if ( path.length === 0 ) return false;

const { properties, hasCertainHit } = this._getPossiblePropertiesWithName( path[ 0 ], PROPERTY_KINDS_READ );
return (path.length > 1 && !hasCertainHit)
|| properties.some( property => property.hasEffectsWhenAccessedAtPath( path.slice( 1 ), options ) );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
if ( path.length === 0 ) {
return false;
}
const { properties, hasCertainHit } = this._getPossiblePropertiesWithName( path[ 0 ],
path.length === 1 ? PROPERTY_KINDS_WRITE : PROPERTY_KINDS_READ );
if ( path.length === 0 ) return false;

const { properties, hasCertainHit } = this._getPossiblePropertiesWithName(
path[ 0 ], path.length === 1 ? PROPERTY_KINDS_WRITE : PROPERTY_KINDS_READ );
return (path.length > 1 && !hasCertainHit)
|| properties.some( property => (path.length > 1 || property.kind === 'set')
&& property.hasEffectsWhenAssignedAtPath( path.slice( 1 ), options ) );
}

hasEffectsWhenCalledAtPath ( path, callOptions, options ) {
if ( path.length === 0 ) {
return true;
}
const { properties, hasCertainHit } = this._getPossiblePropertiesWithName( path[ 0 ], PROPERTY_KINDS_READ );
if ( path.length === 0 ) return true;

const { properties, hasCertainHit } = this._getPossiblePropertiesWithName( path[ 0 ], PROPERTY_KINDS_READ );
return !hasCertainHit || properties.some( property =>
property.hasEffectsWhenCalledAtPath( path.slice( 1 ), callOptions, options ) );
}

someReturnExpressionWhenCalledAtPath ( path, callOptions, predicateFunction, options ) {
if ( path.length === 0 ) {
return true;
}
const { properties, hasCertainHit } = this._getPossiblePropertiesWithName( path[ 0 ], PROPERTY_KINDS_READ );
if ( path.length === 0 ) return true;

const { properties, hasCertainHit } = this._getPossiblePropertiesWithName( path[ 0 ], PROPERTY_KINDS_READ );
return !hasCertainHit || properties.some( property =>
property.someReturnExpressionWhenCalledAtPath( path.slice( 1 ), callOptions, predicateFunction, options ) );
}
Expand Down
44 changes: 33 additions & 11 deletions src/ast/nodes/Property.js
Expand Up @@ -4,15 +4,32 @@ import { UNKNOWN_ASSIGNMENT } from '../values';

export default class Property extends Node {
bindAssignmentAtPath ( path, expression ) {
this.value.bindAssignmentAtPath( path, expression );
if ( this.kind === 'get' ) {
path.length > 0 && this.value.forEachReturnExpressionWhenCalledAtPath( [], this._accessorCallOptions,
node => node.bindAssignmentAtPath( path, expression ) );
} else if ( this.kind === 'set' ) {
path.length === 0 && this.value.bindCallAtPath( [], CallOptions.create( { withNew: false, args: [ expression ] } ) );
} else {
this.value.bindAssignmentAtPath( path, expression );
}
}

bindCallAtPath ( path, callOptions ) {
this.value.bindCallAtPath( path, callOptions );
if ( this.kind === 'get' ) {
this.value.forEachReturnExpressionWhenCalledAtPath( [], this._accessorCallOptions,
node => node.bindCallAtPath( path, callOptions ) );
} else {
this.value.bindCallAtPath( path, callOptions );
}
}

forEachReturnExpressionWhenCalledAtPath ( path, callOptions, callback ) {
this.value.forEachReturnExpressionWhenCalledAtPath( path, callOptions, callback );
if ( this.kind === 'get' ) {
this.value.forEachReturnExpressionWhenCalledAtPath( [], this._accessorCallOptions,
node => node.forEachReturnExpressionWhenCalledAtPath( path, callOptions, callback ) );
} else {
this.value.forEachReturnExpressionWhenCalledAtPath( path, callOptions, callback );
}
}

hasEffects ( options ) {
Expand All @@ -22,9 +39,9 @@ export default class Property extends Node {

hasEffectsWhenAccessedAtPath ( path, options ) {
if ( this.kind === 'get' ) {
return this.value.hasEffectsWhenCalledAtPath( [], this._getterCallOptions, options.getHasEffectsWhenCalledOptions() )
return this.value.hasEffectsWhenCalledAtPath( [], this._accessorCallOptions, options.getHasEffectsWhenCalledOptions() )
|| (!options.hasReturnExpressionBeenAccessedAtPath( path, this )
&& this.value.someReturnExpressionWhenCalledAtPath( [], this._getterCallOptions, innerOptions => node =>
&& this.value.someReturnExpressionWhenCalledAtPath( [], this._accessorCallOptions, innerOptions => node =>
node.hasEffectsWhenAccessedAtPath( path, innerOptions.addAccessedReturnExpressionAtPath( path, this ) ), options ));
}
return this.value.hasEffectsWhenAccessedAtPath( path, options );
Expand All @@ -33,16 +50,21 @@ export default class Property extends Node {
hasEffectsWhenAssignedAtPath ( path, options ) {
if ( this.kind === 'set' ) {
return path.length > 0
|| this.value.hasEffectsWhenCalledAtPath( [], this._getterCallOptions, options.getHasEffectsWhenCalledOptions() );
|| this.value.hasEffectsWhenCalledAtPath( [], this._accessorCallOptions, options.getHasEffectsWhenCalledOptions() );
}
if ( this.kind === 'get' ) {
return path.length === 0
|| this.value.someReturnExpressionWhenCalledAtPath( [], this._accessorCallOptions, innerOptions => node =>
node.hasEffectsWhenAssignedAtPath( path, innerOptions.addAssignedReturnExpressionAtPath( path, this ) ), options );
}
return this.value.hasEffectsWhenAssignedAtPath( path, options );
}

hasEffectsWhenCalledAtPath ( path, callOptions, options ) {
if ( this.kind === 'get' ) {
return this.value.hasEffectsWhenCalledAtPath( [], this._getterCallOptions, options.getHasEffectsWhenCalledOptions() )
return this.value.hasEffectsWhenCalledAtPath( [], this._accessorCallOptions, options.getHasEffectsWhenCalledOptions() )
|| (!options.hasReturnExpressionBeenCalledAtPath( path, this )
&& this.value.someReturnExpressionWhenCalledAtPath( [], this._getterCallOptions, innerOptions => node =>
&& this.value.someReturnExpressionWhenCalledAtPath( [], this._accessorCallOptions, innerOptions => node =>
node.hasEffectsWhenCalledAtPath( path, callOptions, innerOptions.addCalledReturnExpressionAtPath( path, this ) ), options ));
}
return this.value.hasEffectsWhenCalledAtPath( path, callOptions, options );
Expand All @@ -56,7 +78,7 @@ export default class Property extends Node {
}

initialiseNode () {
this._getterCallOptions = CallOptions.create( { withNew: false } );
this._accessorCallOptions = CallOptions.create( { withNew: false } );
}

render ( code, es ) {
Expand All @@ -68,8 +90,8 @@ export default class Property extends Node {

someReturnExpressionWhenCalledAtPath ( path, callOptions, predicateFunction, options ) {
if ( this.kind === 'get' ) {
return this.value.hasEffectsWhenCalledAtPath( [], this._getterCallOptions, options.getHasEffectsWhenCalledOptions() )
|| this.value.someReturnExpressionWhenCalledAtPath( [], this._getterCallOptions, innerOptions => node =>
return this.value.hasEffectsWhenCalledAtPath( [], this._accessorCallOptions, options.getHasEffectsWhenCalledOptions() )
|| this.value.someReturnExpressionWhenCalledAtPath( [], this._accessorCallOptions, innerOptions => node =>
node.someReturnExpressionWhenCalledAtPath( path, callOptions, predicateFunction, innerOptions ), options );
}
return this.value.someReturnExpressionWhenCalledAtPath( path, callOptions, predicateFunction, options );
Expand Down
@@ -0,0 +1,8 @@
var assert = require( 'assert' );

module.exports = {
description: 'Associates function return values of returned functions',
exports: function ( exports ) {
assert.equal( exports.bar, 'present' );
}
};
11 changes: 11 additions & 0 deletions test/function/samples/associate-function-return-values-3/main.js
@@ -0,0 +1,11 @@
const foo = { mightBeExported: {} };
const exported = {};

function getGetFoo () {
return () => foo;
}

getGetFoo()().mightBeExported = exported;
foo.mightBeExported.bar = 'present';

export default exported;
@@ -0,0 +1,8 @@
var assert = require( 'assert' );

module.exports = {
description: 'Associates getter return values with regard to calls',
exports: function ( exports ) {
assert.equal( exports.bar, 'present' );
}
};
@@ -0,0 +1,8 @@
const foo = { mightBeExported: {} };
const exported = {};
const fooContainer = { get getFoo () {return () => foo;} };

fooContainer.getFoo().mightBeExported = exported;
foo.mightBeExported.bar = 'present';

export default exported;
@@ -0,0 +1,8 @@
var assert = require( 'assert' );

module.exports = {
description: 'Associates getter return values with regard to mutations',
exports: function ( exports ) {
assert.equal( exports.bar, 'present' );
}
};
8 changes: 8 additions & 0 deletions test/function/samples/associate-getter-return-values/main.js
@@ -0,0 +1,8 @@
const foo = { mightBeExported: {} };
const exported = {};
const fooContainer = { get foo () {return foo;} };

fooContainer.foo.mightBeExported = exported;
foo.mightBeExported.bar = 'present';

export default exported;
8 changes: 8 additions & 0 deletions test/function/samples/associate-setter-parameters/_config.js
@@ -0,0 +1,8 @@
var assert = require( 'assert' );

module.exports = {
description: 'Associates getter return values with regard to mutations',
exports: function ( exports ) {
assert.equal( exports.bar, 'present' );
}
};
12 changes: 12 additions & 0 deletions test/function/samples/associate-setter-parameters/main.js
@@ -0,0 +1,12 @@
const foo = { mightBeExported: {} };
const exported = {};
const fooContainer = {
set addMightBeExported ( obj ) {
obj.mightBeExported = exported;
}
};

fooContainer.addMightBeExported = foo;
foo.mightBeExported.bar = 'present';

export default exported;

0 comments on commit e2daaec

Please sign in to comment.