in packages/angular_devkit/core/src/workspace/json/utilities.ts [113:299]
function create(
ast: JsonAstObject | JsonAstArray,
path: string,
reporter: ChangeReporter,
excluded = new Set<ProxyPropertyKey>(),
included?: Set<ProxyPropertyKey>,
base?: object,
) {
const cache = new Map<string, CacheEntry>();
const alteredNodes = new Set<JsonAstNode>();
if (!base) {
if (ast.kind === 'object') {
base = Object.create(null) as object;
} else {
base = [];
(base as Array<unknown>).length = ast.elements.length;
}
}
return new Proxy(base, {
getOwnPropertyDescriptor(target: {}, p: ProxyPropertyKey): PropertyDescriptor | undefined {
const descriptor = Reflect.getOwnPropertyDescriptor(target, p);
if (descriptor || typeof p === 'symbol') {
return descriptor;
} else if (excluded.has(p) || (included && !included.has(p))) {
return undefined;
}
const propertyPath = path + '/' + escapeKey(p);
const cacheEntry = cache.get(propertyPath);
if (cacheEntry) {
if (cacheEntry.value !== undefined) {
return createPropertyDescriptor(cacheEntry.value);
}
return undefined;
}
const { node } = findNode(ast, p);
if (node) {
return createPropertyDescriptor(node.value);
}
return undefined;
},
has(target: {}, p: ProxyPropertyKey): boolean {
if (Reflect.has(target, p)) {
return true;
} else if (typeof p === 'symbol' || excluded.has(p)) {
return false;
}
return cache.has(path + '/' + escapeKey(p)) || findNode(ast, p) !== undefined;
},
get(target: {}, p: ProxyPropertyKey): unknown {
if (typeof p === 'symbol' || Reflect.has(target, p)) {
return Reflect.get(target, p);
} else if (excluded.has(p) || (included && !included.has(p))) {
return undefined;
}
const propertyPath = path + '/' + escapeKey(p);
const cacheEntry = cache.get(propertyPath);
if (cacheEntry) {
return cacheEntry.value;
}
const { node, parent } = findNode(ast, p);
let value;
if (node) {
if (node.kind === 'object' || node.kind === 'array') {
value = create(node, propertyPath, (path, parent, vnode, old, current) => {
if (!alteredNodes.has(node)) {
reporter(path, parent, vnode, old, current);
}
});
} else {
value = node.value;
}
cache.set(propertyPath, { node, parent, value });
}
return value;
},
set(target: {}, p: ProxyPropertyKey, value: unknown): boolean {
if (value === undefined) {
// setting to undefined is equivalent to a delete
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.deleteProperty!(target, p);
}
if (typeof p === 'symbol' || Reflect.has(target, p)) {
return Reflect.set(target, p, value);
} else if (excluded.has(p) || (included && !included.has(p))) {
return false;
}
// TODO: Check if is JSON value
const jsonValue = value as JsonValue;
const propertyPath = path + '/' + escapeKey(p);
const cacheEntry = cache.get(propertyPath);
if (cacheEntry) {
const oldValue = cacheEntry.value;
cacheEntry.value = value as JsonValue;
if (cacheEntry.node && oldValue !== value) {
alteredNodes.add(cacheEntry.node);
}
reporter(propertyPath, cacheEntry.parent, cacheEntry.node, oldValue, jsonValue);
} else {
const { node, parent } = findNode(ast, p);
cache.set(propertyPath, { node, parent, value: value as JsonValue });
if (node && node.value !== value) {
alteredNodes.add(node);
}
reporter(propertyPath, parent, node, node && node.value, value as JsonValue);
}
return true;
},
deleteProperty(target: {}, p: ProxyPropertyKey): boolean {
if (typeof p === 'symbol' || Reflect.has(target, p)) {
return Reflect.deleteProperty(target, p);
} else if (excluded.has(p) || (included && !included.has(p))) {
return false;
}
const propertyPath = path + '/' + escapeKey(p);
const cacheEntry = cache.get(propertyPath);
if (cacheEntry) {
const oldValue = cacheEntry.value;
cacheEntry.value = undefined;
if (cacheEntry.node) {
alteredNodes.add(cacheEntry.node);
}
if (cacheEntry.parent.kind === 'keyvalue') {
// Remove the entire key/value pair from this JSON object
reporter(propertyPath, ast, cacheEntry.node, oldValue, undefined);
} else {
reporter(propertyPath, cacheEntry.parent, cacheEntry.node, oldValue, undefined);
}
} else {
const { node, parent } = findNode(ast, p);
if (node) {
cache.set(propertyPath, { node, parent, value: undefined });
alteredNodes.add(node);
if (parent.kind === 'keyvalue') {
// Remove the entire key/value pair from this JSON object
reporter(propertyPath, ast, node, node && node.value, undefined);
} else {
reporter(propertyPath, parent, node, node && node.value, undefined);
}
}
}
return true;
},
defineProperty(target: {}, p: ProxyPropertyKey, attributes: PropertyDescriptor): boolean {
if (typeof p === 'symbol') {
return Reflect.defineProperty(target, p, attributes);
}
return false;
},
ownKeys(target: {}): ProxyPropertyKey[] {
let keys: ProxyPropertyKey[];
if (ast.kind === 'object') {
keys = ast.properties
.map((entry) => entry.key.value)
.filter((p) => !excluded.has(p) && (!included || included.has(p)));
} else {
keys = [];
}
for (const key of cache.keys()) {
const relativeKey = key.substr(path.length + 1);
if (relativeKey.length > 0 && !relativeKey.includes('/')) {
keys.push(`${unescapeKey(relativeKey)}`);
}
}
return [...new Set([...keys, ...Reflect.ownKeys(target)])];
},
});
}