private void AnalyzeMethodReference(DocumentData documentData, InvokeFunctionReferenceData refData)
        {
            var nameNode = refData.ReferenceNameNode;

            // Find the actual usage of the method
            var currNode = nameNode.Parent;
            var ascend   = true;

            while (ascend)
            {
                ascend = false;
                switch (currNode.Kind())
                {
                case SyntaxKind.ConditionalExpression:
                    break;

                case SyntaxKind.InvocationExpression:
                    AnalyzeInvocationExpression(documentData, (InvocationExpressionSyntax)currNode, refData);
                    break;

                case SyntaxKind.Argument:
                    AnalyzeArgumentExpression(currNode, nameNode, refData);
                    break;

                case SyntaxKind.AddAssignmentExpression:
                    refData.Ignore = true;
                    Logger.Warn($"Cannot attach an async method to an event (void async is not an option as cannot be awaited):\r\n{nameNode.Parent}\r\n");
                    break;

                case SyntaxKind.VariableDeclaration:
                    refData.Ignore = true;
                    Logger.Warn($"Assigning async method to a variable is not supported:\r\n{nameNode.Parent}\r\n");
                    break;

                case SyntaxKind.CastExpression:
                    refData.AwaitInvocation = true;
                    ascend = true;
                    break;

                case SyntaxKind.ReturnStatement:
                    break;

                // skip
                case SyntaxKind.VariableDeclarator:
                case SyntaxKind.EqualsValueClause:
                case SyntaxKind.SimpleMemberAccessExpression:
                case SyntaxKind.ArgumentList:
                case SyntaxKind.ObjectCreationExpression:
                    ascend = true;
                    break;

                default:
                    throw new NotSupportedException($"Unknown node kind: {currNode.Kind()}");
                }

                if (ascend)
                {
                    currNode = currNode.Parent;
                }
            }
            refData.ReferenceNode = currNode;
            if (!refData.AwaitInvocation.HasValue)
            {
                refData.AwaitInvocation = !refData.Ignore;
            }
        }
        private void AnalyzeInvocationExpression(DocumentData documentData, InvocationExpressionSyntax node, InvokeFunctionReferenceData functionReferenceData)
        {
            var functionData     = functionReferenceData.FunctionData;
            var methodSymbol     = functionReferenceData.ReferenceSymbol;
            var functionNode     = functionData.GetNode();
            var functionBodyNode = functionData.GetBodyNode();
            var queryExpression  = node.Ancestors()
                                   .TakeWhile(o => o != functionNode)
                                   .OfType <QueryExpressionSyntax>()
                                   .FirstOrDefault();

            if (queryExpression != null)             // Await is not supported in a linq query
            {
                functionReferenceData.Ignore = true;
                Logger.Warn($"Cannot await async method in a query expression:\r\n{queryExpression}\r\n");
                return;
            }

            var searchOptions = AsyncCounterpartsSearchOptions.Default;

            if (_configuration.UseCancellationTokenOverload)
            {
                searchOptions |= AsyncCounterpartsSearchOptions.HasCancellationToken;
            }
            functionReferenceData.ReferenceAsyncSymbols = new HashSet <IMethodSymbol>(GetAsyncCounterparts(methodSymbol.OriginalDefinition, searchOptions));
            if (functionReferenceData.ReferenceAsyncSymbols.Any())
            {
                if (functionReferenceData.ReferenceAsyncSymbols.All(o => o.ReturnsVoid || !o.ReturnType.IsTaskType()))
                {
                    functionReferenceData.AwaitInvocation = false;
                    Logger.Info($"Cannot await method that is either void or do not return a Task:\r\n{methodSymbol}\r\n");
                }
                var nameGroups = functionReferenceData.ReferenceAsyncSymbols.GroupBy(o => o.Name).ToList();
                if (nameGroups.Count == 1)
                {
                    functionReferenceData.AsyncCounterpartName = nameGroups[0].Key;
                }
            }
            else if (!ProjectData.Contains(functionReferenceData.ReferenceSymbol))
            {
                // If we are dealing with an external method and there are no async counterparts for it, we cannot convert it to async
                functionReferenceData.Ignore = true;
                Logger.Info($"Method {methodSymbol} can not be async as there is no async counterparts for it");
                return;
            }
            else if (functionReferenceData.ReferenceFunctionData != null)
            {
                functionReferenceData.AsyncCounterpartName = functionReferenceData.ReferenceSymbol.Name + "Async";
            }

            // If the invocation returns a Task then we need to analyze it further to see how the Task is handled
            if (methodSymbol.ReturnType.IsTaskType())
            {
                var retrunType   = (INamedTypeSymbol)methodSymbol.ReturnType;
                var canBeAwaited = false;
                var currNode     = node.Parent;
                while (true)
                {
                    var memberExpression = currNode as MemberAccessExpressionSyntax;
                    if (memberExpression == null)
                    {
                        break;
                    }
                    var memberName = memberExpression.Name.ToString();
                    if (retrunType.IsGenericType && memberName == "Result")
                    {
                        canBeAwaited = true;
                        break;
                    }
                    if (memberName == "ConfigureAwait")
                    {
                        var invocationNode = currNode.Parent as InvocationExpressionSyntax;
                        if (invocationNode != null)
                        {
                            functionReferenceData.ConfigureAwaitParameter = invocationNode.ArgumentList.Arguments.First().Expression;
                            currNode = invocationNode.Parent;
                            continue;
                        }
                        break;
                    }
                    if (memberName == "GetAwaiter")
                    {
                        var invocationNode = currNode.Parent as InvocationExpressionSyntax;
                        if (invocationNode != null)
                        {
                            currNode = invocationNode.Parent;
                            continue;
                        }
                        break;
                    }
                    if (_taskResultMethods.Contains(memberName))
                    {
                        var invocationNode = currNode.Parent as InvocationExpressionSyntax;
                        if (invocationNode != null)
                        {
                            canBeAwaited = true;
                        }
                    }
                    break;
                }
                if (!canBeAwaited)
                {
                    functionReferenceData.AwaitInvocation = false;
                    Logger.Info(
                        $"Cannot await invocation of a method that returns a Task without be synchronously awaited:\r\n{methodSymbol}\r\n");
                }
                else
                {
                    functionReferenceData.SynchronouslyAwaited = true;
                }
            }

            if (node.Parent.IsKind(SyntaxKind.ReturnStatement))
            {
                functionReferenceData.UseAsReturnValue = true;
            }

            // Calculate if node is the last statement
            if (node.Parent.Equals(functionBodyNode) ||       //eg. bool ExpressionReturn() => SimpleFile.Write();
                node.Equals(functionBodyNode)                 // eg. Func<bool> fn = () => SimpleFile.Write();
                )
            {
                functionReferenceData.LastInvocation   = true;
                functionReferenceData.UseAsReturnValue = !methodSymbol.ReturnsVoid;
            }
            var bodyBlock = functionBodyNode as BlockSyntax;

            if (bodyBlock?.Statements.Last() == node.Parent)
            {
                functionReferenceData.LastInvocation = true;
            }

            // Set CancellationTokenRequired if we detect that one of the async counterparts has a cancellation token as a parameter
            if (_configuration.UseCancellationTokenOverload &&
                functionReferenceData.ReferenceAsyncSymbols.Any(o => o.Parameters.Length > methodSymbol.Parameters.Length))
            {
                functionReferenceData.CancellationTokenRequired = true;
            }

            foreach (var analyzer in _configuration.InvocationExpressionAnalyzers)
            {
                analyzer.Analyze(node, functionReferenceData, documentData.SemanticModel);
            }

            // Propagate CancellationTokenRequired to the method data only if the invocation can be async
            if (functionReferenceData.CancellationTokenRequired && functionReferenceData.GetConversion() == ReferenceConversion.ToAsync)
            {
                // We need to set CancellationTokenRequired to true for the method that contains this invocation
                var methodData = functionReferenceData.FunctionData.GetMethodData();
                methodData.CancellationTokenRequired = true;
            }
        }
        private void AnalyzeArgumentExpression(SyntaxNode node, SimpleNameSyntax nameNode, InvokeFunctionReferenceData result)
        {
            var documentData      = result.FunctionData.TypeData.NamespaceData.DocumentData;
            var methodArgTypeInfo = documentData.SemanticModel.GetTypeInfo(nameNode);

            if (methodArgTypeInfo.ConvertedType?.TypeKind != TypeKind.Delegate)
            {
                // TODO: debug and document
                return;
            }

            var delegateMethod = (IMethodSymbol)methodArgTypeInfo.ConvertedType.GetMembers("Invoke").First();

            if (!delegateMethod.IsAsync)
            {
                result.Ignore = true;
                Logger.Warn($"Cannot pass an async method as parameter to a non async Delegate method:\r\n{delegateMethod}\r\n");
            }
            else
            {
                var argumentMethodSymbol = (IMethodSymbol)documentData.SemanticModel.GetSymbolInfo(nameNode).Symbol;
                if (!argumentMethodSymbol.ReturnType.Equals(delegateMethod.ReturnType))                 // i.e IList<T> -> IEnumerable<T>
                {
                    result.AwaitInvocation = true;
                }
            }
        }
        private async Task ScanAllMethodReferenceLocations(IMethodSymbol methodSymbol, int depth)
        {
            if (_scannedMethodReferenceSymbols.Contains(methodSymbol.OriginalDefinition))
            {
                return;
            }
            _scannedMethodReferenceSymbols.TryAdd(methodSymbol.OriginalDefinition);

            var references = await SymbolFinder.FindReferencesAsync(methodSymbol.OriginalDefinition,
                                                                    _solution, _analyzeDocuments).ConfigureAwait(false);

            depth++;
            foreach (var refLocation in references.SelectMany(o => o.Locations))
            {
                if (refLocation.Document.Project != ProjectData.Project)
                {
                    throw new InvalidOperationException($"Reference {refLocation} is located in a document from another project");
                }

                var documentData = ProjectData.GetDocumentData(refLocation.Document);
                if (documentData == null)
                {
                    continue;
                }
                var symbol = documentData.GetEnclosingSymbol(refLocation);
                if (symbol == null)
                {
                    Logger.Debug($"Symbol not found for reference ${refLocation}");
                    continue;
                }

                var refMethodSymbol = symbol as IMethodSymbol;
                if (refMethodSymbol == null)
                {
                    if (symbol.Kind != SymbolKind.NamedType)
                    {
                        continue;
                    }
                    // A cref can be on a method or type trivia but we get always the type symbol
                    var crefTypeData = documentData.GetAllTypeDatas(o => o.Symbol.Equals(symbol)).FirstOrDefault();
                    if (crefTypeData == null)
                    {
                        continue;
                    }
                    // Try to find the real node where the cref is located
                    var crefReferenceNameNode   = crefTypeData.Node.GetSimpleName(refLocation.Location.SourceSpan, true);
                    var crefReferenceSymbol     = (IMethodSymbol)documentData.SemanticModel.GetSymbolInfo(crefReferenceNameNode).Symbol;
                    var crefReferenceMethodData = await ProjectData.GetMethodData(crefReferenceSymbol).ConfigureAwait(false);

                    var crefReferenceData = new CrefReferenceData(refLocation, crefReferenceNameNode, crefReferenceSymbol, crefReferenceMethodData);

                    var memberNode = crefReferenceNameNode.Ancestors().OfType <MemberDeclarationSyntax>().First();
                    var methodNode = memberNode as MethodDeclarationSyntax;
                    if (methodNode != null)
                    {
                        var crefMethodData = (MethodData)documentData.GetNodeData(methodNode, typeData: crefTypeData);
                        crefMethodData.CrefReferences.TryAdd(crefReferenceData);
                    }
                    else
                    {
                        crefTypeData.CrefReferences.TryAdd(crefReferenceData);
                    }
                    continue;
                }
                var baseMethodData = await documentData.GetAnonymousFunctionOrMethodData(refMethodSymbol).ConfigureAwait(false);

                if (baseMethodData == null)
                {
                    continue;
                }
                // Find the real method on that reference as FindReferencesAsync will also find references to base and interface methods
                // Save the reference as it can be made async
                var nameNode          = baseMethodData.GetNode().GetSimpleName(refLocation.Location.SourceSpan);
                var invokedSymbol     = (IMethodSymbol)documentData.SemanticModel.GetSymbolInfo(nameNode).Symbol;
                var invokedMethodData = await ProjectData.GetMethodData(invokedSymbol).ConfigureAwait(false);

                invokedMethodData?.InvokedBy.Add(baseMethodData);
                var methodReferenceData = new InvokeFunctionReferenceData(baseMethodData, refLocation, nameNode, invokedSymbol, invokedMethodData);
                if (!baseMethodData.InvokedMethodReferences.TryAdd(methodReferenceData))
                {
                    Logger.Debug($"Performance hit: method reference {invokedSymbol} already processed");
                    continue;                     // Reference already processed
                }

                var methodData = baseMethodData as MethodData;
                if (methodData != null)
                {
                    await ScanMethodData(methodData, depth).ConfigureAwait(false);
                }
            }
        }