Code review comment: I think this visitor is useful for us for use cases like this. Rather than checking each expression kind (via syntax kind), this is clean. Is it ok if we use it everywhere?

Code under review:
public static Hover getHover(HoverContext context) {
        Optional<Document> srcFile = context.currentDocument();
        Optional<SemanticModel> semanticModel = context.currentSemanticModel();
        if (semanticModel.isEmpty() || srcFile.isEmpty()) {
            return HoverUtil.getDefaultHoverObject();
        }
        
        Position cursorPosition = context.getCursorPosition();
        LinePosition linePosition = LinePosition.from(cursorPosition.getLine(), cursorPosition.getCharacter());
        
        context.checkCancelled();
        Optional<Symbol> symbolAtCursor = semanticModel.get().symbol(srcFile.get(), linePosition);
        
        context.checkCancelled();
        if (symbolAtCursor.isEmpty()) {
            Range nodeRange = new Range(context.getCursorPosition(), context.getCursorPosition());
            NonTerminalNode nodeAtCursor = CommonUtil.findNode(nodeRange, srcFile.get().syntaxTree());
            if (nodeAtCursor != null) {
                MatchedExpressionNodeResolver expressionResolver = new MatchedExpressionNodeResolver(nodeAtCursor);
                Optional<ExpressionNode> expr = nodeAtCursor.apply(expressionResolver);
                if (expr.isPresent()) {
                    return getHoverForExpression(context, expr.get());
                }
            }
            
            return HoverUtil.getDefaultHoverObject();
        }

        return getHoverForSymbol(symbolAtCursor.get(), context);
    }

Line commented on: MatchedExpressionNodeResolver expressionResolver = new MatchedExpressionNodeResolver(nodeAtCursor);

Revised code:
public static Hover getHover(HoverContext context) {
        Optional<Document> srcFile = context.currentDocument();
        Optional<SemanticModel> semanticModel = context.currentSemanticModel();
        if (semanticModel.isEmpty() || srcFile.isEmpty()) {
            return HoverUtil.getDefaultHoverObject();
        }
        
        Position cursorPosition = context.getCursorPosition();
        LinePosition linePosition = LinePosition.from(cursorPosition.getLine(), cursorPosition.getCharacter());
        
        context.checkCancelled();
        Optional<Symbol> symbolAtCursor = semanticModel.get().symbol(srcFile.get(), linePosition);
        
        context.checkCancelled();
        if (symbolAtCursor.isEmpty()) {
            Range nodeRange = new Range(context.getCursorPosition(), context.getCursorPosition());
            NonTerminalNode nodeAtCursor = CommonUtil.findNode(nodeRange, srcFile.get().syntaxTree());
            if (nodeAtCursor != null) {
                MatchedExpressionNodeResolver expressionResolver = new MatchedExpressionNodeResolver(nodeAtCursor);
                Optional<ExpressionNode> expr = nodeAtCursor.apply(expressionResolver);
                if (expr.isPresent()) {
                    return getHoverForExpression(context, expr.get());
                }
            }
            
            return HoverUtil.getDefaultHoverObject();
        }

        return getHoverForSymbol(symbolAtCursor.get(), context);
    }