From e3c526b89d00157b95ac1efda81aafbe20cc1027 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 13 Apr 2011 23:06:24 -0400 Subject: [PATCH] Writes to public static properties now work properly --- lib/class_builder.js | 33 ++++++++++++++++---- test/test-class_builder-static.js | 50 ++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/lib/class_builder.js b/lib/class_builder.js index 8058fd0..d78a32b 100644 --- a/lib/class_builder.js +++ b/lib/class_builder.js @@ -614,13 +614,34 @@ function attachStatic( ctor, members, base, inheriting ) { ctor.___$$sprops$$ = props; - ctor.$ = function( prop ) + // provide a method to access static properties + ctor.$ = function( prop, val ) { - // attempt to return property if it is our own, otherwise look up on - // parent (the parent will look up its parent as well, if necessary) - return props[ 'public' ][ prop ] - || base.$( prop ) - ; + // we use hasOwnProperty to ensure that undefined values will not + // cause us to continue checking the parent, thereby potentially + // failing to set perfectly legal values + var has = Object.prototype.hasOwnProperty.call( + props[ 'public' ], prop + ); + + // if we don't own the property, let the parent(s) handle it + if ( !has ) + { + return base.$.apply( this, arguments ); + } + + // if a value was provided, this method should be treated as a + // setter rather than a getter + if ( val ) + { + props[ 'public' ][ prop ] = val; + return; + } + else + { + // return the value + return props[ 'public' ][ prop ]; + } }; } diff --git a/test/test-class_builder-static.js b/test/test-class_builder-static.js index 7511973..ae82a63 100644 --- a/test/test-class_builder-static.js +++ b/test/test-class_builder-static.js @@ -308,7 +308,9 @@ var common = require( './common' ), */ ( function testPublicStaticPropertyReferencesAreInheritedBySubtypes() { - var val = [ 1, 2, 3 ], + var val = [ 1, 2, 3 ], + val2 = [ 'a', 'b', 'c' ], + Foo = builder.build( { 'public static bar': val, @@ -320,5 +322,51 @@ var common = require( './common' ), assert.ok( SubFoo.$('bar') === Foo.$('bar'), "Inherited static properties should share references" ); + + // setting a property on Foo should set the property on SubFoo and + // vice-versa + Foo.$( 'bar', val2 ); + assert.deepEqual( Foo.$( 'bar' ), val2, + "Can set static property values" + ); + + assert.ok( Foo.$( 'bar' ) === SubFoo.$( 'bar' ), + "Setting a static property value on a supertype also sets the value " + + "on subtypes" + ); + + SubFoo.$( 'bar', val ); + assert.ok( Foo.$( 'bar' ) === SubFoo.$( 'bar' ) ); +} )(); + + +/** + * Static members do not have the benefit of prototype chains. We must + * implement our own means of traversing the inheritance tree. This is done by + * checking to see if a class has defined the requested property, then + * forwarding the call to the parent if it has not. + * + * The process of looking up the property is very important. hasOwnProperty is + * used rather than checking for undefined, because they have drastically + * different results. Setting a value to undefined (if hasOwnProperty were not + * used) would effectively forward all requests to the base class (since no + * property would be found), thereby preventing it from ever being written to + * again. + */ +( function testSettingsStaticPropertiesToUndefinedWillNotCorruptLookupProcess() +{ + var val = 'baz', + Foo = builder.build( + { + 'public static foo': '', + } ) + ; + + Foo.$( 'foo', undefined ); + Foo.$( 'foo', val ); + + assert.equal( Foo.$( 'foo' ), val, + "Setting static property to undefined does not corrupt lookup process" + ); } )();