grantcarthew/node-console-probe

View on GitHub
src/console-probe.js

Summary

Maintainability
A
1 hr
Test Coverage
const chalk = require('chalk')
const stripAnsi = require('strip-ansi')
const archy = require('archy')
const types = require('./types')
const valid = require('./valid')

module.exports = function probe (obj) {
  if (!valid(obj)) { return }

  let tree = null
  let currentNode = newNode('root')

  for (;obj != null; obj = Object.getPrototypeOf(obj)) {
    let node = newNode(genHeader(obj))
    node.nodes = Object.getOwnPropertyNames(obj)
    node.nodes.push(...Object.getOwnPropertySymbols(obj))
    node = processNode(node, obj)
    tree = tree || node
    currentNode.nodes.push(node)
    currentNode = node
  }
  console.log(archy(tree))
}

function processNode (node, obj) {
  for (let i = 0; i < node.nodes.length; i++) {
    let focusObj = null
    let propertyIsGetter = false
    let type
    try {
      propertyIsGetter = isGetter(obj, node.nodes[i])
      focusObj = obj[node.nodes[i]]
      type = getTypeString(focusObj)
    } catch (err) {
      type = propertyIsGetter ? types.Getter : types.Unknown
      focusObj = err
    }
    node.nodes[i] = getNodeString(type, focusObj, node.nodes[i])
  }
  node.nodes.sort((a, b) => {
    return stripAnsi(a).localeCompare(stripAnsi(b))
  })
  return node
}

function getNodeString (type, obj, node) {
  let prefix = applyChalk(type, `[${type}]`)
  const postfix = genPostfix(type, obj)
  const isSymbolKey = getTypeString(node) === types.Symbol
  if (isSymbolKey) {
    const symDesc = getSymbolDescription(node)
    prefix = applyChalk(types.Symbol, `[${types.Symbol}]`) + prefix
    if (symDesc) {
      return `${prefix} ${symDesc} ${postfix}`
    } else {
      return `${prefix} ${postfix}`
    }
  }
  return `${prefix} ${node} ${postfix}`
}

function newNode (label) {
  return {
    label,
    nodes: []
  }
}

function genHeader (obj) {
  const constName = obj.constructor.name ? obj.constructor.name : ''
  let objName = isGetter(obj, 'name') ? applyChalk(types.Getter, `[${types.Getter}] name ()`) : ''
  try { objName = obj.name && obj.name } catch (err) { }
  const objSignature = genSignature(obj)
  let header = constName ? `[${constName}]` : `[${typeof obj}]`
  header = chalk.red(header)
  header += objName ? ` ${objName}` : ''
  header += objSignature ? ` ${objSignature}` : ''
  return header
}

function isGetter (obj, propertyName) {
  const descriptor = Object.getOwnPropertyDescriptor(obj, propertyName)
  return !!(descriptor && descriptor.get && typeof descriptor.get === 'function')
}

function getTypeString (obj) {
  if (Number.isNaN(obj)) return types.NaN
  return Object.prototype.toString.call(obj).slice(8, -1)
}

function genPostfix (type, obj) {
  let postfix = ''
  let symDesc = ''
  switch (type) {
    case types.Infinity:
    case types.NaN:
    case types.Undefined:
    case types.Null:
      break
    case types.Object:
      postfix = applyChalk(type, `[keys: ${Object.getOwnPropertyNames(obj).length}]`)
      break
    case types.Function:
    case types.GeneratorFunction:
    case types.AsyncFunction:
      postfix = applyChalk(type, genSignature(obj))
      break
    case types.Boolean:
      postfix = applyChalk(type, `[${obj.toString()}]`)
      break
    case types.Symbol:
      symDesc = getSymbolDescription(obj)
      if (symDesc) {
        postfix = applyChalk(type, `[desc: ${symDesc}]`)
      }
      break
    case types.Error:
      postfix = obj.message && applyChalk(type, `[${cleanString(obj.message)}]`)
      break
    case types.Number:
    case types.BigInt:
      postfix = applyChalk(type, `[${obj.toString()}]`)
      break
    case types.Date:
      postfix = applyChalk(type, `[${obj.toString()}]`)
      break
    case types.String:
      postfix = applyChalk(type, `[${cleanString(obj)}]`)
      break
    case types.RegExp:
      postfix = applyChalk(type, `[${limitString(obj.toString())}]`)
      break
    case types.Array:
    case types.Int8Array:
    case types.Uint8Array:
    case types.Uint8ClampedArray:
    case types.Int16Array:
    case types.Uint16Array:
    case types.Int32Array:
    case types.Uint32Array:
    case types.Float32Array:
    case types.Float64Array:
      postfix = applyChalk(type, `[len: ${obj.length}]`)
      break
    case types.Map:
    case types.Set:
      postfix = applyChalk(type, `[size: ${obj.size}]`)
      break
    case types.ArrayBuffer:
    case types.SharedArrayBuffer:
    case types.DataView:
      postfix = applyChalk(type, `[len: ${obj.byteLength}]`)
      break
    default:
      break
  }
  return postfix
}

function cleanString (value) {
  const str = value.replace(/(?:\r\n|\r|\n)/g, '')
  return limitString(str)
}

function limitString (value) {
  const limit = 15
  return value.length > limit ? value.substring(0, limit) + '...' : value
}

function getSymbolDescription (sym) {
  return String(sym).slice(7, -1)
}

function applyChalk (type, str) {
  let result
  switch (type) {
    // Null Types
    case types.Infinity:
    case types.NaN:
    case types.Undefined:
    case types.Null:
      result = str // No colour
      break
    // Objects and Properties
    case types.Object:
    case types.Symbol:
    case types.Date:
      result = chalk.yellow(str)
      break
    // Collections and Arrays
    case types.Array:
    case types.Int8Array:
    case types.Uint8Array:
    case types.Uint8ClampedArray:
    case types.Int16Array:
    case types.Uint16Array:
    case types.Int32Array:
    case types.Uint32Array:
    case types.Float32Array:
    case types.Float64Array:
    case types.Map:
    case types.Set:
    case types.WeakMap:
    case types.WeakSet:
      result = chalk.blue(str)
      break
    // Structured Data and Boolean
    case types.Boolean:
    case types.ArrayBuffer:
    case types.SharedArrayBuffer:
    case types.Atomics:
    case types.DataView:
      result = chalk.cyan(str)
      break
    // Functions and Control Objects
    case types.Getter:
    case types.Function:
    case types.Promise:
    case types.Generator:
    case types.GeneratorFunction:
    case types.AsyncFunction:
      result = chalk.green(str)
      break
    // Number Types
    case types.Number:
    case types.BigInt:
      result = chalk.blue(str)
      break
    // Strings
    case types.String:
    case types.RegExp:
      result = chalk.magenta(str)
      break
    // Errors
    case types.Error:
      result = chalk.red(str)
      break
    default:
      result = str
      break
  }
  return result
}

function genSignature (obj) {
  let funString = ''
  if (!isFunction(obj)) { return funString }
  try {
    funString = Function.prototype.toString.call(obj)
    funString = funString.slice(funString.indexOf('('), funString.indexOf(')') + 1)
  } catch (err) { }
  return funString.slice(funString.indexOf('('), funString.indexOf(')') + 1)
}

function isFunction (obj) {
  const type = getTypeString(obj)
  return type === types.Function || type === types.GeneratorFunction || type === types.AsyncFunction
}