proxyequal
Version:
A proxy based usage tracking and comparison
559 lines (434 loc) • 15.2 kB
JavaScript
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;
;