UNPKG

proxyequal

Version:

A proxy based usage tracking and comparison

559 lines (434 loc) 15.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.proxyfy = proxyfy; exports.withProxiesDisabled = exports.collectValuables = exports.getProxyKey = exports.isKnownObject = exports.isProxyfied = exports.deproxify = exports.get = exports.proxyCompare = exports.proxyState = exports.proxyShallowEqual = exports.proxyEqual = exports.PROXY_REST = exports.deepDeproxify = exports.sourceMutationsEnabled = exports.spreadGuardsEnabled = void 0; var _crc = require("crc-32"); var _shouldInstrument = require("./shouldInstrument"); var _weakMemoize = require("./weakMemoize"); var _objectTrie = require("./objectTrie"); var _differs = require("./differs"); var _proxyPolyfill = require("./proxy-polyfill"); var _escapeKey = require("./escapeKey"); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } var hasProxy = typeof Proxy !== 'undefined'; var ProxyConstructor = hasProxy ? Proxy : (0, _proxyPolyfill.proxyPolyfill)(); var spreadMarker = '!SPREAD'; var __proxyequal_scanEnd = '__proxyequal_scanEnd'; var spreadActivation = '__proxyequal_spreadActivation'; var objectKeysMarker = '!Keys'; var areSpreadGuardsEnabled = false; var areSourceMutationsEnabled = false; var DISABLE_ALL_PROXIES = false; var spreadGuardsEnabled = function spreadGuardsEnabled(flag) { return areSpreadGuardsEnabled = flag; }; exports.spreadGuardsEnabled = spreadGuardsEnabled; var sourceMutationsEnabled = function sourceMutationsEnabled(flag) { return areSourceMutationsEnabled = flag; }; exports.sourceMutationsEnabled = sourceMutationsEnabled; var ProxyToState = new WeakMap(); var ProxyToFinderPrint = new WeakMap(); var KnownObjects = new WeakSet(); var isProxyfied = function isProxyfied(object) { return object && _typeof(object) === 'object' ? ProxyToState.has(object) : false; }; exports.isProxyfied = isProxyfied; var isKnownObject = function isKnownObject(object) { return object && _typeof(object) === 'object' ? KnownObjects.has(object) : false; }; exports.isKnownObject = isKnownObject; var deproxify = function deproxify(object) { return object && _typeof(object) === 'object' ? ProxyToState.get(object) : object || object; }; exports.deproxify = deproxify; var deepDeproxify = function deepDeproxify(object) { if (object && _typeof(object) === 'object') { var current = object; while (ProxyToState.has(current)) { current = ProxyToState.get(current); } return current; } return object; }; exports.deepDeproxify = deepDeproxify; var getProxyKey = function getProxyKey(object) { return object && _typeof(object) === 'object' ? ProxyToFinderPrint.get(object) : {}; }; exports.getProxyKey = getProxyKey; var prepareObject = function prepareObject(state) { if (Object.isFrozen(state)) { // unfreeze if (Array.isArray(state)) { return state.slice(0); } if (state.constructor.name === 'Object') { var clone = Object.assign({}, state); Object.setPrototypeOf(clone, Object.getPrototypeOf(state)); return clone; } } return state; }; var PROXY_REST = Symbol('PROXY_REST'); exports.PROXY_REST = PROXY_REST; var shouldProxy = function shouldProxy(type) { return type === 'object'; }; function proxyfy(state, report) { var suffix = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; var fingerPrint = arguments.length > 3 ? arguments[3] : undefined; var ProxyMap = arguments.length > 4 ? arguments[4] : undefined; var control = arguments.length > 5 ? arguments[5] : undefined; if (!state) { return state; } var alreadyProxy = isProxyfied(state); if (!alreadyProxy) { KnownObjects.add(state); } if (!alreadyProxy && !(0, _shouldInstrument.shouldInstrument)(state)) { return state; } var hasCollectionHandlers = !alreadyProxy && (0, _shouldInstrument.getCollectionHandlers)(state); var storedValue = ProxyMap.get(state) || {}; if (storedValue[suffix]) { return storedValue[suffix]; } var theBaseObject = alreadyProxy ? state : prepareObject(state); var shouldHookOwnKeys = areSpreadGuardsEnabled && !isProxyfied(state); var iterable = function iterable(key, iterator) { var _ref; var index = 0; var next = function next() { var nextItem = iterator.next(); var subKey = key + '.' + index; index++; return _objectSpread({}, nextItem, { get value() { if (nextItem.done && !nextItem.value) { return; } return proxyValue(subKey, subKey, nextItem.value); } }); }; return _ref = {}, _defineProperty(_ref, Symbol.iterator, function () { return { next: next }; }), _defineProperty(_ref, "next", next), _ref; }; var proxyValue = function proxyValue(key, reportValue, value) { var thisId = report(suffix, reportValue); var type = _typeof(value); if (shouldProxy(type)) { return proxyfy(value, control.report, thisId, fingerPrint, ProxyMap, control); } if (hasCollectionHandlers) { switch (key) { case 'get': return function (key) { return proxyValue(key, (0, _escapeKey.escapeKey)(key), state.get(key)); }; case 'has': return function (key) { return proxyValue(key, (0, _escapeKey.escapeKey)(key), state.has(key)); }; case 'keys': return function () { return state.keys(); }; case 'values': return function () { return iterable(key, state.values()); }; case 'entries': return function () { return iterable(key, state.entries()); }; case [Symbol.iterator]: return iterable(key, state[Symbol.iterator]); } } return value; }; var hooks = { set: function set(target, prop, value) { var thisId = report(suffix, prop); if (areSourceMutationsEnabled) { state[prop] = value; return true; } else { /* eslint-disable-next-line */ console.error('Source object mutations are disabled, but you tried to set', value, 'on key', thisId, 'on', state); return false; } }, get: function get(target, prop) { if (process.env.NODE_ENV !== 'production') { if (prop === __proxyequal_scanEnd) { report(suffix, spreadMarker, suffix); return false; } } var storedValue = state[prop]; if (DISABLE_ALL_PROXIES) { return storedValue; } if (typeof prop === 'string') { return proxyValue(prop, (0, _escapeKey.escapeKey)(prop), storedValue); } return storedValue; } }; hooks['ownKeys'] = function () { var keys = [].concat(Object.getOwnPropertyNames(state), Object.getOwnPropertySymbols(state)); if (!DISABLE_ALL_PROXIES) { report(suffix, objectKeysMarker, theBaseObject); if (shouldHookOwnKeys) { report(suffix, spreadActivation, theBaseObject); keys.push(__proxyequal_scanEnd); } } return keys; }; var proxy = new ProxyConstructor(theBaseObject, hooks); storedValue[suffix] = proxy; ProxyMap.set(state, storedValue); ProxyToState.set(proxy, state); ProxyToFinderPrint.set(proxy, { suffix: suffix, fingerPrint: fingerPrint, report: report, ProxyMap: ProxyMap, control: control }); return proxy; } var withProxiesDisabled = function withProxiesDisabled(fn) { if (DISABLE_ALL_PROXIES) { return fn(); } DISABLE_ALL_PROXIES = true; try { return fn(); } finally { DISABLE_ALL_PROXIES = false; } }; exports.withProxiesDisabled = withProxiesDisabled; var collectValuables = function collectValuables(lines) { var values = []; for (var i = 0; i < lines.length; ++i) { var line = lines[i]; var index = line.lastIndexOf('.'); if (index < 0 && values.indexOf(line) < 0) { // no "." and new value values.push(line); continue; } while (index >= 0) { line = line.slice(0, index); if (values.indexOf(line) < 0) { values.push(line); index = line.lastIndexOf('.'); } else { break; // done that } } } return lines.filter(function (line) { return values.indexOf(line) < 0; }); }; exports.collectValuables = collectValuables; var memoizedCollectValuables = (0, _weakMemoize.weakMemoizeArray)(collectValuables); var get = function get(target, path) { var result = target; for (var i = 1; i < path.length && result; ++i) { var key = (0, _escapeKey.unescapeKey)(path[i]); if (key[0] === '!') { if (key === objectKeysMarker) { return Object.keys(result).map(_crc.str).reduce(function (acc, x) { return acc ^ x; }, 0); } } result = result[key]; } return result; }; exports.get = get; var proxyCompare = function proxyCompare(a, b, locations) { DISABLE_ALL_PROXIES = true; var ret = function () { for (var i = 0; i < locations.length; ++i) { var key = locations[i]; var path = key.split('.'); var la = get(a, path); var lb = get(b, path); if (la === lb || deepDeproxify(la) === deepDeproxify(lb)) {// nope } else { (0, _differs.addDiffer)([key, 'differs', la, lb]); return false; } } return true; }(); DISABLE_ALL_PROXIES = false; return ret; }; exports.proxyCompare = proxyCompare; var getterHelper = ['', '']; var walkDiffer = []; var walk = function walk(la, lb, node) { if (la === lb || deepDeproxify(la) === deepDeproxify(lb)) { return true; } if (node === _objectTrie.EDGE) { return false; } var items = Object.keys(node); for (var i = 0; i < items.length; ++i) { var item = items[i]; getterHelper[1] = item; if (!walk(get(la, getterHelper), get(lb, getterHelper), node[item])) { walkDiffer.unshift(item); return false; } } return true; }; var proxyShallowEqual = function proxyShallowEqual(a, b, locations) { DISABLE_ALL_PROXIES = true; walkDiffer = []; (0, _differs.resetDiffers)(); var ret = function () { var root = (0, _objectTrie.memoizedBuildTrie)(locations); return walk(a, b, root); }(); DISABLE_ALL_PROXIES = false; if (!ret) { walkDiffer.unshift(''); (0, _differs.addDiffer)([walkDiffer.join('.'), 'not equal']); } return ret; }; exports.proxyShallowEqual = proxyShallowEqual; var proxyEqual = function proxyEqual(a, b, affected) { (0, _differs.resetDiffers)(); return proxyCompare(a, b, memoizedCollectValuables(affected)); }; exports.proxyEqual = proxyEqual; var proxyState = function proxyState(state) { var fingerPrint = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; var _ProxyMap = arguments.length > 2 ? arguments[2] : undefined; var lastAffected = null; var affected = []; var affectedEqualToLast = true; var set = new Set(); var ProxyMap = _ProxyMap || new WeakMap(); var spreadDetected = false; var speadActiveOn = []; var sealed = 0; var addSpreadTest = function addSpreadTest(location) { if (process.env.NODE_ENV !== 'production') { Object.defineProperty(location, __proxyequal_scanEnd, { value: 'this is secure guard', configurable: true, enumerable: true }); } }; var removeSpreadTest = function removeSpreadTest() { if (process.env.NODE_ENV !== 'production') { speadActiveOn.forEach(function (target) { return Object.defineProperty(target, __proxyequal_scanEnd, { value: 'here was spread guard', configurable: true, enumerable: false }); }); speadActiveOn = []; } }; var onKeyUse = function onKeyUse(suffix, keyName, location) { var key = suffix + '.' + keyName; if (!sealed) { if (keyName === spreadActivation) { if (process.env.NODE_ENV !== 'production') { addSpreadTest(location); speadActiveOn.push(location); } } else if (keyName === spreadMarker) { if (process.env.NODE_ENV !== 'production') { spreadDetected = spreadDetected || location; } } else { if (!set.has(key)) { set.add(key); affected.push(key); if (lastAffected) { var position = affected.length - 1; if (lastAffected[position] !== affected[position]) { affectedEqualToLast = false; } } } } } return key; }; var shouldUseLastAffected = function shouldUseLastAffected() { return lastAffected && affectedEqualToLast && lastAffected.length === affected.length; }; var control = { get affected() { return shouldUseLastAffected() ? lastAffected : affected; }, get spreadDetected() { return spreadDetected; }, replaceState: function replaceState(state) { this.state = createState(state); spreadDetected = false; this.unseal(); sealed = 0; return this; }, seal: function seal() { sealed++; removeSpreadTest(); }, unseal: function unseal() { sealed--; }, reset: function reset() { if (!shouldUseLastAffected()) { lastAffected = affected; } affectedEqualToLast = true; affected = []; spreadDetected = false; sealed = 0; set.clear(); }, isKnownObject: function isKnownObject(ref) { return ProxyMap.has(ref); }, report: onKeyUse }; var createState = function createState(state) { return proxyfy(state, onKeyUse, '', fingerPrint, ProxyMap, control); }; control.state = createState(state); return control; }; exports.proxyState = proxyState;