const lodashSet = require("lodash/set"); const lodashGet = require("lodash/get"); const lodashIsPlainObject = require("lodash/isPlainObject"); /* Calculates computed data using Proxies */ class ComputedDataProxy { constructor(computedKeys) { if (Array.isArray(computedKeys)) { this.computedKeys = new Set(computedKeys); } else { this.computedKeys = computedKeys; } } isArrayOrPlainObject(data) { return Array.isArray(data) || lodashIsPlainObject(data); } getProxyData(data, keyRef) { // Set defaults for keys not already set on parent data let undefinedValue = "__11TY_UNDEFINED__"; if (this.computedKeys) { for (let key of this.computedKeys) { if (lodashGet(data, key, undefinedValue) === undefinedValue) { lodashSet(data, key, ""); } } } let proxyData = this._getProxyData(data, keyRef); return proxyData; } _getProxyForObject(dataObj, keyRef, parentKey = "") { return new Proxy( {}, { get: (obj, key) => { if (typeof key !== "string") { return obj[key]; } let newKey = `${parentKey ? `${parentKey}.` : ""}${key}`; // Issue #1137 // Special case for Collections, always return an Array for collection keys // so they it works fine with Array methods like `filter`, `map`, etc if (newKey === "collections") { keyRef.add(newKey); return new Proxy( {}, { get: (target, key) => { if (typeof key === "string") { keyRef.add(`collections.${key}`); return []; } return target[key]; }, } ); } let newData = this._getProxyData(dataObj[key], keyRef, newKey); if (!this.isArrayOrPlainObject(newData)) { keyRef.add(newKey); } return newData; }, } ); } _getProxyForArray(dataArr, keyRef, parentKey = "") { return new Proxy(new Array(dataArr.length), { get: (obj, key) => { if (Array.prototype.hasOwnProperty(key)) { // remove `filter`, `constructor`, `map`, etc keyRef.add(parentKey); return obj[key]; } // Hm, this needs to be better if (key === "then") { keyRef.add(parentKey); return; } let newKey = `${parentKey}[${key}]`; let newData = this._getProxyData(dataArr[key], keyRef, newKey); if (!this.isArrayOrPlainObject(newData)) { keyRef.add(newKey); } return newData; }, }); } _getProxyData(data, keyRef, parentKey = "") { if (lodashIsPlainObject(data)) { return this._getProxyForObject(data, keyRef, parentKey); } else if (Array.isArray(data)) { return this._getProxyForArray(data, keyRef, parentKey); } // everything else! return data; } async findVarsUsed(fn, data = {}) { let keyRef = new Set(); // careful, logging proxyData will mess with test results! let proxyData = this.getProxyData(data, keyRef); // squelch console logs for this fake proxy data pass 😅 // let savedLog = console.log; // console.log = () => {}; await fn(proxyData); // console.log = savedLog; return Array.from(keyRef); } } module.exports = ComputedDataProxy;