Home Manual Reference Source Test Repository

src/module/traits/TraitUtil.js

import ASTParser from '../../utils/ast/ASTParser';

export default class TraitUtil
{
   /**
    * Provides safe array creation from a given input.
    *
    * @param {*}  value - A value to potentially convert into a safe array.
    *
    * @returns {Array}
    */
   static safeArray(value)
   {
      return typeof value === 'undefined' || value === null ? [] : Array.isArray(value) ? value : [value];
   }

   /**
    * Provides a utility method that determines the name of a method for ESTree / Babylon AST nodes. For ESTree the
    * parent node must be accessed for class methods. If the name is a computed value and not a string literal then
    * `astParse` is invoked to determine the computed name and is output as `<computed~${computed expression}>`.
    *
    * Note; ESTree has a parent node which defines the method name with a child FunctionExpression /
    * FunctionDeclaration. Babylon AST only has ClassMethod with a child `key` providing the method name.
    *
    * @param {object}   node - The current AST node.
    * @param {object}   parent - The parent AST node.
    *
    * @returns {string}
    */
   static safeComputedName(node, parent)
   {
      let name;

      // Handle ESTree case.
      if (parent && parent.type === 'MethodDefinition')
      {
         if (typeof parent.computed === 'boolean' && parent.computed)
         {
            // The following will pick up a single literal computed value (string); expressions return
            // `<computed>`.
            name = parent.key.type === 'Literal' ? TraitUtil.safeValue(parent.key) :
             `<computed~${ASTParser.parse(parent.key).source}>`;
         }
         else // Parent is not computed and `parent.key` is an `Identifier` node.
         {
            name = TraitUtil.safeName(parent.key);
         }
      }

      // Handle Babylon AST ClassMethod node.
      if (node.type === 'ClassMethod')
      {
         if (typeof node.computed === 'boolean' && node.computed)
         {
            name = node.key.type === 'StringLiteral' ? TraitUtil.safeValue(node.key) :
             `<computed~${ASTParser.parse(node.key).source}>`;
         }
         else // ClassMethod is not computed and is an `Identifier` node.
         {
            name = TraitUtil.safeName(node.key);
         }
      }

      // Last chance assignment handles other node types / expressions: arrow, yield, etc.
      if (typeof name !== 'string')
      {
         name = TraitUtil.safeName(node.id || node.key);
      }

      return name;
   }

   /**
    * Provides a utility method that determines the operands of a method for ESTree / Babylon AST nodes. For ESTree the
    * parent node must be accessed for class methods. If the name is a computed value and not a string literal then
    * `astParse` is invoked to determine the computed operands.
    *
    * Note; ESTree has a parent node which defines the method name with a child FunctionExpression /
    * FunctionDeclaration. Babylon AST only has ClassMethod with a child `key` providing the method name.
    *
    * @param {object}   node - The current AST node.
    * @param {object}   parent - The parent AST node.
    *
    * @returns {Array<*>}
    */
   static safeComputedOperands(node, parent)
   {
      const operands = [];

      // Handle ESTree case.
      if (parent && parent.type === 'MethodDefinition')
      {
         if (typeof parent.computed === 'boolean' && parent.computed)
         {
            // The following will pick up a single literal computed value (string).
            if (parent.key.type === 'Literal')
            {
               operands.push(TraitUtil.safeValue(parent.key));
            }
            else // Fully evaluate AST node and children for computed operands.
            {
               operands.push(...ASTParser.parse(parent.key).operands);
            }
         }
         else // Parent is not computed and `parent.key` is an `Identifier` node.
         {
            operands.push(TraitUtil.safeNameOrValue(parent.key));
         }
      }

      // Handle Babylon AST ClassMethod node.
      if (node.type === 'ClassMethod')
      {
         if (typeof node.computed === 'boolean' && node.computed)
         {
            // The following will pick up a single literal computed value (string).
            if (node.key.type === 'StringLiteral')
            {
               operands.push(TraitUtil.safeValue(node.key));
            }
            else // Fully evaluate AST node and children for computed operands.
            {
               operands.push(...ASTParser.parse(node.key).operands);
            }
         }
         else // Parent is not computed and `parent.key` is an `Identifier` node.
         {
            operands.push(TraitUtil.safeNameOrValue(node.key));
         }
      }

      if (operands.length === 0)
      {
         operands.push(TraitUtil.safeName(node.id || node.key));
      }

      return operands;
   }

   /**
    * Provides a utility method that determines the operators of a method for ESTree / Babylon AST nodes. For ESTree the
    * parent node must be accessed for class methods. If the node is computed then `astParse` is invoked to determine
    * the computed operators.
    *
    * Note; ESTree has a parent node which defines the method name with a child FunctionExpression /
    * FunctionDeclaration. Babylon AST only has ClassMethod with a child `key` providing the method name.
    *
    * @param {object}   node - The current AST node.
    * @param {object}   parent - The parent AST node.
    *
    * @returns {Array<*>}
    */
   static safeComputedOperators(node, parent)
   {
      const operators = [];

      // Handle ESTree case.
      if (parent && parent.type === 'MethodDefinition' && typeof parent.computed === 'boolean' && parent.computed)
      {
         operators.push(...ASTParser.parse(parent.key).operators);
      }

      // Handle Babylon AST ClassMethod node.
      if (node.type === 'ClassMethod' && typeof node.computed === 'boolean' && node.computed)
      {
         operators.push(...ASTParser.parse(node.key).operators);
      }

      return operators;
   }

   /**
    * Provides a utility method that defers to `object.name` if it exists or fallback to `defaultName` or `anonymous`.
    *
    * @param {object}   object - The target object to provide safe name coverage.
    * @param {string}   defaultName - A default name to fallback to if `object.name` is missing.
    *
    * @returns {string}
    */
   static safeName(object, defaultName = '')
   {
      if (object !== null && typeof object === 'object' && typeof object.name === 'string' && object.name !== '')
      {
         return object.name;
      }

      if (typeof defaultName === 'string' && defaultName !== '') { return defaultName; }

      return '<anonymous>';
   }

   /**
    * Provides a utility method that defers to `object.name` if it exists then falls back to 'object.value' if it exists
    * or falls back to `defaultNameOrValue` or `anonymous`.
    *
    * @param {object}   object - The target object to provide safe name / value coverage.
    *
    * @param {string}   defaultNameOrValue - A default name / value to fallback to if `object.name` or `object.value` is
    *                                        missing.
    *
    * @returns {string}
    */
   static safeNameOrValue(object, defaultNameOrValue = '')
   {
      if (object !== null && typeof object === 'object')
      {
         if (typeof object.name === 'string' && object.name !== '') { return object.name; }
         if (typeof object.value === 'string' && object.value !== '') { return object.value; }
      }

      if (typeof defaultNameOrValue === 'string' && defaultNameOrValue !== '') { return defaultNameOrValue; }

      return '<anonymous>';
   }

   /**
    * Provides a utility method that defers to `object.value` if it exists or fallback to `defaultValue` or `anonymous`.
    *
    * @param {object}   object - The target object to provide safe name coverage.
    * @param {string}   defaultValue - A default value to fallback to if `object.value` is missing.
    *
    * @returns {string}
    */
   static safeValue(object, defaultValue = '')
   {
      if (object !== null && typeof object === 'object' && typeof object.value === 'string' && object.value !== '')
      {
         return object.value;
      }

      if (typeof defaultValue === 'string' && defaultValue !== '') { return defaultValue; }

      return '<anonymous>';
   }
}