From 75059ad030a9d06dd8274585e0fd953bdc99f7d6 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sat, 11 Jun 2011 21:47:57 -0400 Subject: [PATCH] Added util.get{Own,}PropertyDescriptor --- lib/util.js | 79 +++++++++++++ test/test-util-get-property-descriptor.js | 137 ++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 test/test-util-get-property-descriptor.js diff --git a/lib/util.js b/lib/util.js index afd599d..d14de40 100644 --- a/lib/util.js +++ b/lib/util.js @@ -419,6 +419,85 @@ exports.arrayShrink = function( items ) }; +/** + * Uses Object.getOwnPropertyDescriptor if available, otherwise provides our own + * implementation to fall back on + * + * If the environment does not support retrieving property descriptors (ES5), + * then the following will be true: + * - get/set will always be undefined + * - writable, enumerable and configurable will always be true + * - value will be the value of the requested property on the given object + * + * @param {Object} obj object to check property on + * @param {string} prop property to retrieve descriptor for + * + * @return {Object} descriptor for requested property or undefined if not found + */ +exports.getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor + || function( obj, prop ) + { + if ( !Object.prototype.hasOwnProperty.call( obj, prop ) ) + { + return undefined; + } + + // fallback response + return { + get: undefined, + set: undefined, + + writable: true, + enumerable: true, + configurable: true, + + value: obj[ prop ], + }; + }; + + +/** + * Travels down the prototype chain of the given object in search of the + * requested property and returns its descriptor + * + * This operates as Object.getOwnPropertyDescriptor(), except that it traverses + * the prototype chain. For environments that do not support __proto__, it will + * not traverse the prototype chain and essentially serve as an alias for + * getOwnPropertyDescriptor(). + * + * @param {Object} obj object to check property on + * @param {string} prop property to retrieve descriptor for + * + * @return {Object} descriptor for requested property or undefined if not found + */ +exports.getPropertyDescriptor = function( obj, prop ) +{ + // note that this uses util's function, not Object's + var desc = exports.getOwnPropertyDescriptor( obj, prop ); + + // if we didn't find a descriptor and a prototype is available, recurse down + // the prototype chain + if ( !desc && obj.__proto__ ) + { + return exports.getPropertyDescriptor( obj.__proto__, prop ); + } + + // return the descriptor or undefined if no prototype is available + return desc; +}; + + +/** + * Indicates whether or not the getPropertyDescriptor method is capable of + * traversing the prototype chain + * + * @type {boolean} + */ +exports.defineSecureProp( exports.getPropertyDescriptor, 'canTraverse', + ( {}.__proto__ ) ? true : false +); + + /** * Appropriately returns defineSecureProp implementation to avoid check on each * invocation diff --git a/test/test-util-get-property-descriptor.js b/test/test-util-get-property-descriptor.js new file mode 100644 index 0000000..c33d914 --- /dev/null +++ b/test/test-util-get-property-descriptor.js @@ -0,0 +1,137 @@ +/** + * Tests util.getPropertyDescriptor + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package test + */ + +var common = require( './common' ), + assert = require( 'assert' ), + util = common.require( 'util' ), + + get_set = !( util.definePropertyFallback() ) +; + + +/** + * If Object.getOwnPropertyDescriptor is provided by our environment, it should + * be used by util + */ +( function testUtilGetOwnPropertyDescriptorIsObjectsIfAvailable() +{ + if ( Object.getOwnPropertyDescriptor ) + { + assert.strictEqual( + util.getOwnPropertyDescriptor, + Object.getOwnPropertyDescriptor, + "Util should use Object.getOwnPropertyDescriptor if available" + ); + } +} )(); + + +/** + * The function should provide a boolean value indicating whether it can + * traverse the prototype chain + */ +( function testIndicatesWhetherTraversalIsPossible() +{ + var traversable = ( {}.__proto__ ) ? true : false; + + assert.equal( util.getPropertyDescriptor.canTraverse, traversable, + "Indicates whether traversal is possible" + ); +} )(); + + +/** + * We don't want tricksters to get funky with our system + */ +( function testTraversablePropertyIsNonWritable() +{ + var getDesc; + + if ( get_set ) + { + assert.equal( + Object.getOwnPropertyDescriptor( + util.getPropertyDescriptor, 'canTraverse' + ).writable, + false, + "Should not be able to alter canTravese value" + ); + } +} )(); + + +/** + * The return value should mimic Object.getOwnPropertyDescriptor() if we're not + * having to traverse the prototype chain + */ +( function testActsExactlyAsGetOwnPropertyDescriptorInEs5SystemsOnSameObject() +{ + var obj = { foo: 'bar' }, + desc1 = util.getOwnPropertyDescriptor( obj, 'foo' ), + desc2 = util.getPropertyDescriptor( obj, 'foo' ) + ; + + assert.deepEqual( desc1, desc2, + "When operating one level deep, should return same as " + + "Object.getOwnPropertyDescriptor" + ); +} )(); + + +/** + * If we *do* have to start traversing the prototype chain (which + * Object.getOwnPropertyDescriptor() cannot do), then it should be as if we + * called Object.getOwnPropertyDescriptor() on the object in the prototype chain + * containing the requested property. + */ +( function testTraversesThePrototypeChain() +{ + // if we cannot traverse the prototype chain, this test is pointless + if ( !util.getPropertyDescriptor.canTraverse ) + { + return; + } + + var proto = { foo: 'bar' }, + obj = function() {} + ; + + obj.prototype = proto; + + // to give ourselves the prototype chain (we don't want to set __proto__ + // because this test will also be run on pre-ES5 engines) + var inst = new obj(), + + // get the actual descriptor + expected = util.getOwnPropertyDescriptor( proto, 'foo' ), + + // attempt to gather the descriptor from the prototype chain + given = util.getPropertyDescriptor( inst, 'foo' ) + ; + + assert.deepEqual( given, expected, + "Properly traverses the prototype chain to retrieve the descriptor" + ); +} )(); +