'use strict';
/** @returns whether value is a non-null, non-array object */
function isObject (value) {
  return value !== null && typeof value === 'object' && !Array.isArray(value);
}
/** @returns the enumerable (optionally including inherited) keys of an object */
function getKeys (obj, includeInherited = false) {
  if (!includeInherited) return Object.keys(obj);
  const keys = new Set();
  let o = obj;
  while (o !== null) {
    for (const key of Object.keys(o)) keys.add(key);
    o = Object.getPrototypeOf(o);
  }
  return [...keys];
}
/**
 * @returns an array of strings representing all traversible branches
 * of child objects, each formatted as a combined path of dot-notation
 * property accessors
 */
function findObjectPaths (
  obj,
  {
    includeInherited = false,
    currentPath = '',
    paths = new Set(),
    skipReturn = false,
  } = {},
) {
  for (const key of getKeys(obj, includeInherited)) {
    // Append the current dot-notation property accessor
    // to the existing path of this object:
    const path = `${currentPath}.${key}`;
    // Add it to the set:
    paths.add(path);
    const o = obj[key];
    // Recurse if the child value is an object:
    if (isObject(o)) {
      findObjectPaths(o, {
        includeInherited,
        currentPath: path,
        paths,
        skipReturn: true,
      });
    }
  }
  // If this is not a sub-cycle (it's the top-level invocation), then convert
  // the set to an array and remove the first "." from each string
  if (!skipReturn) return [...paths].map(p => p.slice(1));
}
// Use:
const obj = {
  props: {
    abc: {
      def: 1,
    },
    ghi: {
      jkl: 2,
    },
  },
  xyz: 3,
};
let someKnownProps = ['props.abc', 'xyz'];
let objectPaths = findObjectPaths(obj);
let hasOtherProps = objectPaths.some(path => !someKnownProps.includes(path));
console.log(hasOtherProps); // true
// An example of all of the paths in the object above:
someKnownProps = [
  'props',
  'props.abc',
  'props.abc.def',
  'props.ghi',
  'props.ghi.jkl',
  'xyz',
];
objectPaths = findObjectPaths(obj);
hasOtherProps = objectPaths.some(path => !someKnownProps.includes(path));
console.log(hasOtherProps); // false
// Finally, comparing the results of inherited vs non-inherited enumeration:
const objWithoutOwnProps = Object.create({
  props: {
    abc: {
      def: 1,
    },
    ghi: {
      jkl: 2,
    },
  },
  xyz: 3,
});
console.log(
  'Non-inherited props:',
  findObjectPaths(objWithoutOwnProps),
);
console.log(
  'Inherited props:',
  findObjectPaths(objWithoutOwnProps, {includeInherited: true}),
);