ember-source
Version:
A JavaScript framework for creating ambitious web applications
108 lines (103 loc) • 3.71 kB
JavaScript
import '../@ember/debug/index.js';
import { assert } from '../@ember/debug/lib/assert.js';
function lookupDescriptor(obj, keyName) {
let current = obj;
do {
let descriptor = Object.getOwnPropertyDescriptor(current, keyName);
if (descriptor !== undefined) {
return descriptor;
}
current = Object.getPrototypeOf(current);
} while (current !== null);
return null;
}
let setupMandatorySetter;
let teardownMandatorySetter;
let setWithMandatorySetter;
function isElementKey(key) {
return typeof key === 'number' ? isPositiveInt(key) : isStringInt(key);
}
function isStringInt(str) {
let num = parseInt(str, 10);
return isPositiveInt(num) && str === String(num);
}
function isPositiveInt(num) {
return num >= 0 && num % 1 === 0;
}
{
let SEEN_TAGS = new WeakSet();
let MANDATORY_SETTERS = new WeakMap();
let propertyIsEnumerable = function (obj, key) {
return Object.prototype.propertyIsEnumerable.call(obj, key);
};
setupMandatorySetter = function (tag, obj, keyName) {
if (SEEN_TAGS.has(tag)) {
return;
}
SEEN_TAGS.add(tag);
if (Array.isArray(obj) && isElementKey(keyName)) {
return;
}
let desc = lookupDescriptor(obj, keyName) || {};
if (desc.get || desc.set) {
// if it has a getter or setter, we can't install the mandatory setter.
// native setters are allowed, we have to assume that they will resolve
// to tracked properties.
return;
}
if (desc && (!desc.configurable || !desc.writable)) {
// if it isn't writable anyways, so we shouldn't provide the setter.
// if it isn't configurable, we can't overwrite it anyways.
return;
}
let setters = MANDATORY_SETTERS.get(obj);
if (setters === undefined) {
setters = {};
MANDATORY_SETTERS.set(obj, setters);
}
desc.hadOwnProperty = Object.hasOwnProperty.call(obj, keyName);
setters[keyName] = desc;
Object.defineProperty(obj, keyName, {
configurable: true,
enumerable: propertyIsEnumerable(obj, keyName),
get() {
if (desc.get) {
return desc.get.call(this);
} else {
return desc.value;
}
},
set(value) {
(assert(`You attempted to update ${this}.${String(keyName)} to "${String(value)}", but it is being tracked by a tracking context, such as a template, computed property, or observer. In order to make sure the context updates properly, you must invalidate the property when updating it. You can mark the property as \`@tracked\`, or use \`@ember/object#set\` to do this.`));
}
});
};
teardownMandatorySetter = function (obj, keyName) {
let setters = MANDATORY_SETTERS.get(obj);
if (setters !== undefined && setters[keyName] !== undefined) {
Object.defineProperty(obj, keyName, setters[keyName]);
delete setters[keyName];
}
};
setWithMandatorySetter = function (obj, keyName, value) {
let setters = MANDATORY_SETTERS.get(obj);
if (setters !== undefined && setters[keyName] !== undefined) {
let setter = setters[keyName];
if (setter.set) {
setter.set.call(obj, value);
} else {
setter.value = value;
// If the object didn't have own property before, it would have changed
// the enumerability after setting the value the first time.
if (!setter.hadOwnProperty) {
let desc = lookupDescriptor(obj, keyName);
desc.enumerable = true;
Object.defineProperty(obj, keyName, desc);
}
}
} else {
obj[keyName] = value;
}
};
}
export { setupMandatorySetter as a, lookupDescriptor as l, setWithMandatorySetter as s, teardownMandatorySetter as t };