Reference Source Test

src/Util/ASTUtil.js

import babelTraverse from 'babel-traverse';

/**
 * Utility for AST.
 */
export default class ASTUtil {
  /**
   * sanitize node.
   * change node type to `Identifier` and empty comment.
   * @param {ASTNode} node - target node.
   */
  static sanitize(node) {
    if (!node) return;
    node.type = 'Identifier';
    node.name = '_';
    node.leadingComments = [];
    node.trailingComments = [];
  }

  /**
   * traverse ast nodes.
   * @param {AST} ast - target AST.
   * @param {function(node: Object, parent: Object, path: Object)} callback - this is called with each node.
   */
  static traverse(ast, callback) {
    babelTraverse(ast, {
      noScope: true,
      enter: function(path) {
        callback(path.node, path.parent, path);
      }
    });
  }

  /**
   * find file path in import declaration by name.
   * e.g. can find ``./foo/bar.js`` from ``import Bar from './foo/bar.js'`` by ``Bar``.
   * @param {AST} ast - target AST.
   * @param {string} name - identifier name.
   * @returns {string|null} file path.
   */
  static findPathInImportDeclaration(ast, name) {
    let path = null;

    babelTraverse(ast, {
      noScope: true,
      enter: function(_path) {
        const node = _path.node;
        if (node.type !== 'ImportDeclaration') return;

        for (const spec of node.specifiers) {
          const localName = spec.local.name;
          if (localName === name) {
            path = node.source.value;
            _path.stop();
          }
        }
      }
    });

    return path;
  }

  /**
   * find VariableDeclaration node which has NewExpression.
   * @param {string} name - variable name.
   * @param {AST} ast - find in this ast.
   * @returns {ASTNode|null} found ast node.
   */
  static findVariableDeclarationAndNewExpressionNode(name, ast) {
    if (!name) return null;

    for (const node of ast.program.body) {
      if (node.type === 'VariableDeclaration' &&
        node.declarations[0].init &&
        node.declarations[0].init.type === 'NewExpression' &&
        node.declarations[0].id.name === name) {
        return node;
      }
    }

    return null;
  }

  /**
   * find ClassDeclaration node.
   * @param {string} name - class name.
   * @param {AST} ast - find in this ast.
   * @returns {{classNode: ASTNode|null, exported: boolean|null}} found ast node.
   */
  static findClassDeclarationNode(name, ast) {
    if (!name) return {classNode: null, exported: null};

    for (const node of ast.program.body) {
      if (node.type === 'ClassDeclaration' && node.id.name === name) return {classNode: node, exported: false};

      if (node.type === 'ExportDefaultDeclaration' || node.type === 'ExportNamedDeclaration') {
        if (node.declaration && node.declaration.type === 'ClassDeclaration' && node.declaration.id && node.declaration.id.name === name) return {classNode: node, exported: true};
      }
    }

    return {classNode: null, exported: null};
  }

  /**
   * find FunctionDeclaration node.
   * @param {string} name - function name.
   * @param {AST} ast - find in this ast.
   * @returns {ASTNode|null} found ast node.
   */
  static findFunctionDeclarationNode(name, ast) {
    if (!name) return null;

    for (const node of ast.program.body) {
      if (node.type === 'FunctionDeclaration' && node.id.name === name) return node;
    }

    return null;
  }

  /**
   * find VariableDeclaration node.
   * @param {string} name - variable name.
   * @param {AST} ast - find in this ast.
   * @returns {ASTNode|null} found ast node.
   */
  static findVariableDeclarationNode(name, ast) {
    if (!name) return null;

    for (const node of ast.program.body) {
      if (node.type === 'VariableDeclaration' && node.declarations[0].id.name === name) return node;
    }

    return null;
  }

  /**
   * create VariableDeclaration node which has NewExpression.
   * @param {string} name - variable name.
   * @param {string} className - class name.
   * @param {Object} loc - location.
   * @returns {ASTNode} created node.
   */
  static createVariableDeclarationAndNewExpressionNode(name, className, loc) {
    const node = {
      type: 'VariableDeclaration',
      kind: 'let',
      loc: loc,
      declarations: [
        {
          type: 'VariableDeclarator',
          id: {type: 'Identifier', name: name},
          init: {type: 'NewExpression', callee: {type: 'Identifier', name: className}}
        }
      ]
    };

    return node;
  }

  // /**
  //  * flatten name of MemberExpression.
  //  * @param {ASTNode} memberExpression - MemberExpression Node.
  //  * @returns {string} flatten node name.
  //  */
  // static flattenMemberExpression(memberExpression) {
  //   const names = [];
  //   let object = memberExpression;
  //   while (object) {
  //     if (object.name) {
  //       names.push(object.name);
  //       break;
  //     } else {
  //       names.push(object.property.name);
  //       object = object.object;
  //     }
  //   }
  //   return names.reverse().join('.');
  // }
}