/**
  * There is a possible case that 'implements' section is incomplete (e.g. ends with comma). We may
  * want to align lbrace to the comma then.
  *
  * @param alignment block alignment
  * @param baseNode base AST node
  * @return alignment strategy to use for the given node
  */
 private static AlignmentStrategy getAlignmentStrategy(
     Alignment alignment, ASTNode baseNode, @NotNull CommonCodeStyleSettings settings) {
   if (baseNode.getElementType() != JavaElementType.CLASS
       || !settings.ALIGN_MULTILINE_EXTENDS_LIST) {
     return AlignmentStrategy.wrap(alignment);
   }
   for (ASTNode node = baseNode.getLastChildNode();
       node != null;
       node = FormatterUtil.getPreviousNonWhitespaceSibling(node)) {
     if (node.getElementType() != JavaElementType.IMPLEMENTS_LIST) {
       continue;
     }
     ASTNode lastChildNode = node.getLastChildNode();
     if (lastChildNode != null && lastChildNode.getElementType() == TokenType.ERROR_ELEMENT) {
       Alignment alignmentToUse = alignment;
       if (alignment == null) {
         alignmentToUse = Alignment.createAlignment();
       }
       return AlignmentStrategy.wrap(
           alignmentToUse,
           false,
           JavaTokenType.LBRACE,
           JavaElementType.JAVA_CODE_REFERENCE,
           node.getElementType());
     }
     break;
   }
   return AlignmentStrategy.wrap(alignment);
 }
  @Override
  public boolean shouldCreateStub(ASTNode node) {
    // @see getNameIdentifier(ElixirAlias)
    boolean shouldCreateStub = false;

    ASTNode parent = node.getTreeParent();
    IElementType parentElementType = parent.getElementType();

    if (parentElementType == ElixirTypes.ACCESS_EXPRESSION) {
      ASTNode grandParent = parent.getTreeParent();
      IElementType grandParentElementType = grandParent.getElementType();

      if (grandParentElementType == ElixirTypes.NO_PARENTHESES_ONE_ARGUMENT) {
        ASTNode greatGrandParent = grandParent.getTreeParent();
        IElementType greatGrandParentElementType = greatGrandParent.getElementType();

        if (greatGrandParentElementType == ElixirTypes.UNMATCHED_UNQUALIFIED_NO_PARENTHESES_CALL
            && greatGrandParent.getFirstChildNode().getText().equals("defmodule")) {
          IElementType greatGrandParentLastChildElementType =
              greatGrandParent.getLastChildNode().getElementType();

          if (greatGrandParentLastChildElementType == ElixirTypes.DO_BLOCK) {
            shouldCreateStub = true;
          }
        }
      }
    }

    return shouldCreateStub;
  }
  /**
   * Tries to find node that is direct or indirect previous node of the given node.
   *
   * <p>E.g. there is a possible use-case:
   *
   * <pre>
   *                     n1
   *                  /    \
   *                n21    n22
   *                 |      |
   *                n31    n32
   * </pre>
   *
   * Let's assume that target node is <code>'n32'</code>. 'n31' is assumed to be returned from this
   * method then.
   *
   * <p><b>Note:</b> current method avoids going too deep if found node type is the same as start
   * node type
   *
   * @return direct or indirect previous node of the given one having target type if possible;
   *     <code>null</code> otherwise
   */
  private static boolean findPreviousNode(
      AlignmentInColumnsConfig config,
      ASTNode from,
      IElementType targetType,
      boolean processFrom,
      boolean processParent,
      NodeProcessor processor) {
    if (from == null) return false;

    for (ASTNode prev = processFrom ? from : from.getTreePrev();
        prev != null;
        prev = prev.getTreePrev()) {
      IElementType prevType = prev.getElementType();
      if (prevType == targetType) {
        if (processor.targetTypeFound(prev)) return true;
      } else if (config.getWhiteSpaceTokenTypes().contains(prevType)) {
        if (processor.whitespaceFound(prev)) return true;
      }

      if (findPreviousNode(config, prev.getLastChildNode(), targetType, true, false, processor))
        return true;
    }

    if (processParent) {
      for (ASTNode parent = from.getTreeParent(); parent != null; parent = parent.getTreeParent()) {
        if (findPreviousNode(config, parent, targetType, false, false, processor)) return true;
      }
    }
    return false;
  }
 public static boolean isIncomplete(@Nullable ASTNode node) {
   ASTNode lastChild = node == null ? null : node.getLastChildNode();
   while (lastChild != null && lastChild.getElementType() == TokenType.WHITE_SPACE) {
     lastChild = lastChild.getTreePrev();
   }
   if (lastChild == null) return false;
   if (lastChild.getElementType() == TokenType.ERROR_ELEMENT) return true;
   return isIncomplete(lastChild);
 }
  @Nullable
  public static ASTNode getPreviousLeaf(
      @Nullable ASTNode node, @NotNull IElementType... typesToIgnore) {
    ASTNode prev = getPrevious(node, typesToIgnore);
    if (prev == null) {
      return null;
    }

    ASTNode result = prev;
    ASTNode lastChild = prev.getLastChildNode();
    while (lastChild != null) {
      result = lastChild;
      lastChild = lastChild.getLastChildNode();
    }

    for (IElementType type : typesToIgnore) {
      if (result.getElementType() == type) {
        return getPreviousLeaf(result, typesToIgnore);
      }
    }
    return result;
  }
  @Nullable
  static XmlToken findEndTagName(@Nullable final PsiErrorElement element) {
    if (element == null) return null;

    final ASTNode astNode = element.getNode();
    if (astNode == null) return null;

    ASTNode current = astNode.getLastChildNode();
    ASTNode prev = current;

    while (current != null) {
      final IElementType elementType = prev.getElementType();

      if ((elementType == XmlElementType.XML_NAME || elementType == XmlElementType.XML_TAG_NAME)
          && current.getElementType() == XmlElementType.XML_END_TAG_START) {
        return (XmlToken) prev.getPsi();
      }

      prev = current;
      current = current.getTreePrev();
    }

    return null;
  }