UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

197 lines 8.82 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isObjectOrArray = isObjectOrArray; exports.isPlainObject = isPlainObject; exports.deepMergeObject = deepMergeObject; exports.deepMergeObjectInPlace = deepMergeObjectInPlace; exports.compactRecord = compactRecord; exports.deepClonePreserveUnclonable = deepClonePreserveUnclonable; exports.looselyCompareObjects = looselyCompareObjects; const json_1 = require("./json"); const log_1 = require("./log"); /** * checks if `item` is an object (it may be an array, ...) */ function isObjectOrArray(item) { return typeof item === 'object'; } /** * checks if `item` is a record with keys, i.e. an object that is neither `null` nor an array * @see {@link isObjectOrArray} to allow arrays as well */ function isPlainObject(item) { return typeof item === 'object' && item !== null && !Array.isArray(item); } function deepMergeObject(base, addon) { if (!base) { return addon; } else if (!addon) { return base; } else if (typeof base !== 'object' || typeof addon !== 'object') { // this case should be guarded by type guards, but in case we do not know throw new Error('illegal types for deepMergeObject!'); } assertSameType(base, addon); const result = { ...base }; const baseIsArray = Array.isArray(base); const addonIsArray = Array.isArray(addon); if (!baseIsArray && !addonIsArray) { deepMergeObjectWithResult(addon, base, result); } else if (baseIsArray && addonIsArray) { return base.concat(addon); } else { throw new Error('cannot merge object with array!'); } return result; } function deepMergeObjectWithResult(addon, base, result) { for (const key of Object.keys(addon)) { // values that are undefined (like from a partial object) should NOT be overwritten if (addon[key] === undefined) { continue; } if (typeof addon[key] === 'object') { if (key in base) { result[key] = deepMergeObject(base[key], addon[key]); } else { result[key] = addon[key]; } } else { assertSameType(result[key], addon[key]); result[key] = addon[key]; } } } function deepMergeObjectInPlace(base, addon) { if (!base) { return addon; } else if (!addon) { return base; } else if (typeof base !== 'object' || typeof addon !== 'object') { // this case should be guarded by type guards, but in case we do not know throw new Error('illegal types for deepMergeObjectInPlace!'); } assertSameType(base, addon); const baseIsArray = Array.isArray(base); const addonIsArray = Array.isArray(addon); if (!baseIsArray && !addonIsArray) { deepMergeObjectWithResult(addon, base, base); } else if (baseIsArray && addonIsArray) { for (const item of addon) { (base).push(item); } } else { throw new Error('cannot merge object with array!'); } return base; } function assertSameType(base, addon) { if (base !== undefined && addon !== undefined && typeof base !== typeof addon) { throw new Error(`cannot merge different types! ${typeof base} (${JSON.stringify(base, json_1.jsonReplacer)}) !== ${typeof addon} (${JSON.stringify(addon, json_1.jsonReplacer)})`); } } /** from a record take only the keys that are not undefined */ function compactRecord(record) { if (record === undefined) { return undefined; } const result = {}; for (const key of Object.keys(record)) { if (record[key] !== undefined) { result[key] = record[key]; } } return result; } /** * This is a version of a deep clone that preserves unclonable values (like functions, symbols, ...) by keeping the same reference to them. */ function deepClonePreserveUnclonable(obj) { if (typeof obj !== 'object' || obj === null) { return obj; } else if (Array.isArray(obj)) { return obj.map(deepClonePreserveUnclonable); } else if (obj instanceof Date) { return new Date(obj.getTime()); } else if (obj instanceof Map) { return new Map(obj.entries().map(([k, v]) => [deepClonePreserveUnclonable(k), deepClonePreserveUnclonable(v)])); } else if (obj instanceof Set) { return new Set(obj.values().map(deepClonePreserveUnclonable)); } else { const result = {}; for (const key of Object.keys(obj)) { result[key] = deepClonePreserveUnclonable(obj[key]); } return result; } } /** * Compares the two passed objects deeply using the loose comparison system designed for the {@link FlowrFilter.MatchesEnrichment}. For this system in use, see {@link FlowrFilter.MatchesEnrichment} in use. * @param obj - The real object which we want to test against. * @param expected - The object to test the real value {@link obj} against, which should be an object in the shape of {@link obj} with each value to test for replaced by a {@link RegExp} or value to match against. The test will pass if the partial structure matches and the value at each {@link RegExp}, string or primitive location matches the corresponding regular expression. For array entries, {@link arrayMatch} determines whether every element in the array has to match the given expected value, or only some. * @param arrayMatch - For array entries, the expected value in {@link test} is compared against each array entry in the real value. This property determines whether every element in the array has to match, or only some. If unset, this defaults to `some`. * @param logger - The logger to use for trace debugging. */ function looselyCompareObjects(obj, expected, arrayMatch, logger) { (0, log_1.expensiveTrace)(logger, () => `Comparing ${JSON.stringify(obj)} against ${JSON.stringify(expected)}`); for (const [expectedKey, expectedValue] of Object.entries(expected)) { const realValue = obj[expectedKey]; if (!realValue) { (0, log_1.expensiveTrace)(logger, () => `Real value ${JSON.stringify(realValue)} does not exist for expected key ${expectedKey}`); return false; } if (Array.isArray(realValue)) { const match = typeof expectedValue === 'object' ? expectedValue instanceof RegExp ? // if we expect a regular expression but an array is supplied, test each value (value) => expectedValue.test(typeof value === 'string' ? value : String(value)) : // if we expect an object that is not a regular expression, match against our expected structure (value) => looselyCompareObjects(value, expectedValue, arrayMatch, logger) : // in any other case (primitives!), match against the exact value (value) => expectedValue === value; if (!(arrayMatch === 'every' ? realValue.every(match) : realValue.some(match))) { (0, log_1.expensiveTrace)(logger, () => `Array ${JSON.stringify(realValue)} does not match expected value ${JSON.stringify(expectedValue)} (array match ${arrayMatch})`); return false; } } else if (typeof realValue === 'object') { // for objects, we recursively match if (!looselyCompareObjects(realValue, expectedValue, arrayMatch, logger)) { (0, log_1.expensiveTrace)(logger, () => `Object ${JSON.stringify(realValue)} does not match expected object ${JSON.stringify(expectedValue)}`); return false; } } else if (expectedValue instanceof RegExp) { // for anything else, we match with our regular expression or string // (arrays and objects are handled above, so only primitives reach this point) const realPrimitive = realValue; if (!expectedValue.test(typeof realPrimitive === 'string' ? realPrimitive : String(realPrimitive))) { (0, log_1.expensiveTrace)(logger, () => `Value ${JSON.stringify(realValue)} does not match expected regular expression ${expectedValue}`); return false; } } else if (typeof expectedValue !== 'object') { if (expectedValue !== realValue) { (0, log_1.expensiveTrace)(logger, () => `Value ${JSON.stringify(realValue)} does not match expected string ${JSON.stringify(expectedValue)}`); return false; } } } (0, log_1.expensiveTrace)(logger, () => `Object ${JSON.stringify(obj)} matches ${JSON.stringify(expected)}`); return true; } //# sourceMappingURL=objects.js.map