Home Reference Source

packages/remote-context/src/helpers/descriptors.js

/**
 * Copyright 2017 Moshe Simantov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Many methods in this file inspire from the ES5 shim for ES6 Reflect and Proxy objects:
// {@link https://github.com/tvcutsem/harmony-reflect} (Apache-2.0 License)

const kCachedValues = Symbol('_cachedValues');

// export function isCacheProperty(property) {
//   return property === kCachedValues;
// }

export function isDataDescriptor(descriptor) {
  return 'value' in descriptor || 'writable' in descriptor;
}

export function isAccessorDescriptor(descriptor) {
  return 'get' in descriptor || 'set' in descriptor;
}

export function equalValue(value1, value2) {
  return value1 === value2 || (isNaN(value1) && isNaN(value2));
}

export function equalDescriptors(desc1, desc2) {
  if (typeof desc1 !== 'object') {
    return desc1 === desc2;
  }

  return (
    typeof desc2 === 'object' &&
    equalValue(desc1.value, desc2.value) &&
    desc1.configurable === desc2.configurable &&
    desc1.writable === desc2.writable &&
    desc1.enumerable === desc2.enumerable &&
    desc1.get === desc2.get &&
    desc1.set === desc2.set
  );
}

export function hasOwnProperty(object, property) {
  return Object.prototype.hasOwnProperty.call(object, property);
}

export function getPropertyDescriptor(object, property) {
  let proto = object;

  do {
    const desc = Object.getOwnPropertyDescriptor(proto, property);
    if (desc !== undefined) return desc;

    proto = Object.getPrototypeOf(proto);
  } while (proto !== null);

  return undefined;
}

export function getOwnProperties(object) {
  return Object.getOwnPropertyNames(object).concat(
    Object.getOwnPropertySymbols(object)
  );
}

export const getOwnPropertyDescriptors =
  Object.getOwnPropertyDescriptors ||
  function getOwnPropertyDescriptors(object) {
    const descriptors = {};

    getOwnProperties(object).forEach(property => {
      descriptors[property] = Object.getOwnPropertyDescriptor(object, property);
    });

    return descriptors;
  };

function getCachePropertyDescriptors(object) {
  let proto = object;
  const descriptors = {};

  do {
    const properties = getOwnProperties(proto);

    // eslint-disable-next-line no-loop-func
    properties.forEach(property => {
      if (hasOwnProperty(descriptors, property)) return;

      descriptors[property] = Object.getOwnPropertyDescriptor(proto, property);
    });

    proto = Object.getPrototypeOf(proto);
  } while (
    proto !== Object.prototype &&
    proto !== null &&
    proto !== Function.prototype
  );

  return descriptors;
}

function isObjectCachable(object) {
  return !object.constructor || object.constructor.prototype !== object;
}

function isDescriptorCachable(property, descriptor) {
  return (
    isAccessorDescriptor(descriptor) &&
    typeof descriptor.get === 'function' &&
    typeof property === 'string' &&
    property[0] !== '_'
  );
}

export function cacheGetters(object) {
  if (!isObjectCachable(object)) return {};

  const descriptors = getCachePropertyDescriptors(object);
  const cache = {};

  Object.keys(descriptors).forEach(property => {
    const desc = descriptors[property];
    if (!isDescriptorCachable(property, desc)) return;

    try {
      cache[property] = desc.get.call(object); // assumes Function.prototype.call
    } catch (error) {
      // ignore getter errors
    }
  });

  return cache;
}

// export function setCachedGetters(object, cachedValues) {
//   object[kCachedValues] = cachedValues; // eslint-disable-line no-param-reassign
// }

export function setCachedGetter(object, property, cachedValue) {
  let cachedValues = object[kCachedValues];
  if (!cachedValues) {
    cachedValues = {};
    object[kCachedValues] = cachedValues; // eslint-disable-line no-param-reassign
  }

  cachedValues[property] = cachedValue;
}

export function getCachedGetter(object, property) {
  const cachedValues = object[kCachedValues];
  if (!cachedValues || !hasOwnProperty(cachedValues, property)) {
    throw new TypeError(
      `Couldn't find property cache for getter "${property}", ` +
        'please resolve this remote value again.'
    );
  }

  return cachedValues[property];
}

export function deleteCachedGetter(object, property) {
  const cachedValues = object[kCachedValues];
  if (!cachedValues) return;

  delete cachedValues[property];
}