Example #1
0
        internal async Task <(InvocationExpressionSyntax?, FixDetails?)> CheckIfCanFixAsync(Document invocationDoc, TextSpan span, CancellationToken cancellationToken)
        {
            var root = await _getSyntaxRootAsync(invocationDoc, cancellationToken).ConfigureAwait(false);

            if (root?.FindNode(span) is not InvocationExpressionSyntax invocationExpression)
            {
                // shouldn't happen, we only get called for invocations
                return(null, null);
            }

            var sm = await _getSemanticModelAsync(invocationDoc, cancellationToken).ConfigureAwait(false);

            if (sm == null)
            {
                // shouldn't happen
                return(null, null);
            }

            var comp = sm.Compilation;

            var loggerExtensions = _getTypeByMetadataName1(comp, "Microsoft.Extensions.Logging.LoggerExtensions");

            if (loggerExtensions == null)
            {
                // shouldn't happen, we only get called for methods on this type
                return(null, null);
            }

            var invocationOp = _getOperation(sm, invocationExpression, cancellationToken) as IInvocationOperation;

            if (invocationOp == null)
            {
                // shouldn't happen, we're dealing with an invocation expression
                return(null, null);
            }

            var method = invocationOp.TargetMethod;

            var details = new FixDetails(method, invocationOp, invocationDoc.Project.DefaultNamespace, invocationDoc.Project.Documents);

            if (string.IsNullOrWhiteSpace(details.Message))
            {
                // can't auto-generate without a valid message string
                return(null, null);
            }

            if (details.EventIdParamIndex >= 0)
            {
                // can't auto-generate the variants using event id
                return(null, null);
            }

            if (string.IsNullOrWhiteSpace(details.Level))
            {
                // can't auto-generate without a valid level
                return(null, null);
            }

            return(invocationExpression, details);
        }
Example #2
0
        /// <summary>
        /// Given a LoggerExtensions method invocation, produce an argument list in the shape of a corresponding generated logging method.
        /// </summary>
        private static IReadOnlyList <ITypeSymbol> MakeArgumentList(FixDetails details, IInvocationOperation invocationOp)
        {
            var args = new List <ITypeSymbol>
            {
                invocationOp.Arguments[0].Value.Type
            };

            if (details.ExceptionParamIndex >= 0)
            {
                args.Add(invocationOp.Arguments[details.ExceptionParamIndex].Value.Type);
            }

            var paramsArg = invocationOp.Arguments[details.ArgsParamIndex];

            if (paramsArg != null)
            {
                var arrayCreation = paramsArg.Value as IArrayCreationOperation;
                foreach (var e in arrayCreation !.Initializer.ElementValues)
                {
                    foreach (var d in e.Descendants())
                    {
                        args.Add(d.Type);
                    }
                }
            }

            return(args);
        }
        // Load attachment from embedded resource.
        private Attachment CreateFixDetailsAttachment(FixDetails fixDetails)
        {
            List <string> jumperList = new List <string>()
            {
                "jumper", "sweater", "hoodie", "oodie", "jersey"
            };

            //To Do: Add logic for different fixes... call IFitIt api?
            var cardResourcePath = "FixerBot.Cards.welcomeCard.json";

            if (jumperList.Contains(fixDetails.Item.Split(" ").Last().ToLower()))
            {
                cardResourcePath = "FixerBot.Cards.fixJumperCard.json";
            }


            using (var stream = GetType().Assembly.GetManifestResourceStream(cardResourcePath))
            {
                using (var reader = new StreamReader(stream))
                {
                    var adaptiveCard = reader.ReadToEnd();
                    return(new Attachment()
                    {
                        ContentType = "application/vnd.microsoft.card.adaptive",
                        Content = JsonConvert.DeserializeObject(adaptiveCard),
                    });
                }
            }
        }
        private async Task <DialogTurnResult> ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            if (!_luisRecognizer.IsConfigured)
            {
                // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance.
                return(await stepContext.BeginDialogAsync(nameof(FixItYourselfDialog), new BookingDetails(), cancellationToken));

                //return await stepContext.BeginDialogAsync(nameof(BookingDialog), new BookingDetails(), cancellationToken);
            }

            // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
            var luisResult = await _luisRecognizer.RecognizeAsync <FlightBooking>(stepContext.Context, cancellationToken);

            switch (luisResult.TopIntent().intent)
            {
            case FlightBooking.Intent.BookFlight:
                await ShowWarningForUnsupportedCities(stepContext.Context, luisResult, cancellationToken);

                // Initialize BookingDetails with any entities we may have found in the response.
                var bookingDetails = new BookingDetails()
                {
                    // Get destination and origin from the composite entities arrays.
                    Destination = luisResult.ToEntities.Airport,
                    Origin      = luisResult.FromEntities.Airport,
                    TravelDate  = luisResult.TravelDate,
                };

                // Run the BookingDialog giving it whatever details we have from the LUIS call, it will fill out the remainder.
                return(await stepContext.BeginDialogAsync(nameof(BookingDialog), bookingDetails, cancellationToken));

            case FlightBooking.Intent.GetWeather:
                // We haven't implemented the GetWeatherDialog so we just display a TODO message.
                var getWeatherMessageText = "TODO: get weather flow here";
                var getWeatherMessage     = MessageFactory.Text(getWeatherMessageText, getWeatherMessageText, InputHints.IgnoringInput);
                await stepContext.Context.SendActivityAsync(getWeatherMessage, cancellationToken);

                break;

            case FlightBooking.Intent.FixItem:
                // Catch all for unhandled intents
                var fixDetails = new FixDetails();
                fixDetails.ConfidenceLevel = luisResult.ConfidenceLevel;
                fixDetails.Fixer           = luisResult.Fixer;
                fixDetails.Item            = luisResult.Item;
                fixDetails.Problem         = luisResult.Problem;

                return(await stepContext.BeginDialogAsync(nameof(FixerDialog), fixDetails, cancellationToken));

            default:
                // Catch all for unhandled intents
                var didntUnderstandMessageText = $"Sorry, I didn't get that (intent was {luisResult.TopIntent().intent}). I assume you want to fix something.";
                var didntUnderstandMessage     = MessageFactory.Text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
                await stepContext.Context.SendActivityAsync(didntUnderstandMessage, cancellationToken);

                return(await stepContext.BeginDialogAsync(nameof(FixerDialog), new FixDetails(), cancellationToken));
            }

            return(await stepContext.NextAsync(null, cancellationToken));
        }
Example #5
0
        private static async Task <Solution> RewriteLoggingCallAsync(
            Document doc,
            InvocationExpressionSyntax invocationExpression,
            FixDetails details,
            string methodName,
            CancellationToken cancellationToken)
        {
            var solEditor = new SolutionEditor(doc.Project.Solution);
            var docEditor = await solEditor.GetDocumentEditorAsync(doc.Id, cancellationToken).ConfigureAwait(false);

            var sm         = docEditor.SemanticModel;
            var comp       = sm.Compilation;
            var gen        = docEditor.Generator;
            var invocation = sm.GetOperation(invocationExpression, cancellationToken) as IInvocationOperation;
            var argList    = new List <SyntaxNode>();

            int index = 0;

            foreach (var arg in invocation !.Arguments)
            {
                if ((index == details.MessageParamIndex) || (index == details.LogLevelParamIndex))
                {
                    index++;
                    continue;
                }

                index++;

                if (arg.ArgumentKind == ArgumentKind.ParamArray)
                {
                    var arrayCreation = arg.Value as IArrayCreationOperation;
                    foreach (var e in arrayCreation !.Initializer.ElementValues)
                    {
                        argList.Add(e.Syntax);
                    }
                }
                else
                {
                    argList.Add(arg.Syntax);
                }
            }

            var typeSyntax             = comp.GetTypeByMetadataName(details.FullTargetClassName);
            var typeSymbol             = gen.TypeExpression(typeSyntax);
            var memberAccessExpression = gen.MemberAccessExpression(typeSymbol, methodName);
            var call = gen.InvocationExpression(memberAccessExpression, argList);

            docEditor.ReplaceNode(invocationExpression, call.WithTriviaFrom(invocationExpression));

            return(solEditor.GetChangedSolution());
        }
Example #6
0
        private async Task <Solution> InsertLoggingMethodSignatureAsync(
            Document targetDoc,
            ClassDeclarationSyntax targetClass,
            Document invocationDoc,
            InvocationExpressionSyntax invocationExpression,
            FixDetails details,
            CancellationToken cancellationToken)
        {
            var invocationSM = (await invocationDoc.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false)) !;
            var invocationOp = (invocationSM.GetOperation(invocationExpression, cancellationToken) as IInvocationOperation) !;

            var solEditor = new SolutionEditor(targetDoc.Project.Solution);
            var docEditor = await solEditor.GetDocumentEditorAsync(targetDoc.Id, cancellationToken).ConfigureAwait(false);

            var sm   = docEditor.SemanticModel;
            var comp = sm.Compilation;
            var gen  = docEditor.Generator;

            var logMethod = gen.MethodDeclaration(
                details.TargetMethodName,
                MakeParameterList(details, invocationOp, gen),
                accessibility: Accessibility.Internal,
                modifiers: DeclarationModifiers.Partial | DeclarationModifiers.Static);

            var attrArgs = new[]
            {
                gen.LiteralExpression(CalcEventId(comp, targetClass, cancellationToken)),
                gen.MemberAccessExpression(gen.TypeExpression(comp.GetTypeByMetadataName("Microsoft.Extensions.Logging.LogLevel")), details.Level),
                gen.LiteralExpression(details.Message),
            };

            var attr = gen.Attribute(
                gen.TypeExpression(comp.GetTypeByMetadataName(LoggerMessageAttribute)),
                attrArgs);

            logMethod = gen.AddAttributes(logMethod, attr);

            var comment = SyntaxFactory.ParseLeadingTrivia($@"
/// <summary>
/// Logs `{EscapeMessageString(details.Message)}` at `{details.Level}` level.
/// </summary>
");

            logMethod = logMethod.WithLeadingTrivia(comment);

            docEditor.AddMember(targetClass, logMethod);

            return(solEditor.GetChangedSolution());
        }
Example #7
0
        /// <summary>
        /// Given a LoggerExtensions method invocation, produce a parameter list for the corresponding generated logging method.
        /// </summary>
        private static IReadOnlyList <SyntaxNode> MakeParameterList(
            FixDetails details,
            IInvocationOperation invocationOp,
            SyntaxGenerator gen)
        {
            var parameters = new List <SyntaxNode>
            {
                gen.ParameterDeclaration("logger", gen.TypeExpression(invocationOp.Arguments[0].Value.Type))
            };

            if (details.ExceptionParamIndex >= 0)
            {
                parameters.Add(gen.ParameterDeclaration("exception", gen.TypeExpression(invocationOp.Arguments[details.ExceptionParamIndex].Value.Type)));
            }

            var paramsArg = invocationOp.Arguments[details.ArgsParamIndex];

            if (paramsArg != null)
            {
                var arrayCreation = paramsArg.Value as IArrayCreationOperation;
                var index         = 0;
                foreach (var e in arrayCreation !.Initializer.ElementValues)
                {
                    foreach (var d in e.Descendants())
                    {
                        string name;
                        if (index < details.MessageArgs.Count)
                        {
                            name = details.MessageArgs[index];
                        }
                        else
                        {
                            name = $"arg{index}";
                        }

                        parameters.Add(gen.ParameterDeclaration(name, gen.TypeExpression(d.Type)));
                        index++;
                    }
                }
            }

            return(parameters);
        }
Example #8
0
        /// <summary>
        /// Orchestrate all the work needed to fix an issue.
        /// </summary>
        private async Task <Solution> ApplyFixAsync(Document invocationDoc, InvocationExpressionSyntax invocationExpression, FixDetails details, CancellationToken cancellationToken)
        {
            ClassDeclarationSyntax targetClass;
            Document targetDoc;
            Solution sol;

            // stable id surviving across solution generations
            var invocationDocId = invocationDoc.Id;

            // get a reference to the class where to insert the logging method, creating it if necessary
            (sol, targetClass, targetDoc) = await GetOrMakeTargetClassAsync(invocationDoc.Project, details, cancellationToken).ConfigureAwait(false);

            // find the doc and invocation in the current solution
            (invocationDoc, invocationExpression) = await RemapAsync(sol, invocationDocId, invocationExpression).ConfigureAwait(false);

            // determine the final name of the logging method and whether we need to generate it or not
            var(methodName, existing) = await GetFinalTargetMethodNameAsync(targetDoc, targetClass, invocationDoc, invocationExpression, details, cancellationToken).ConfigureAwait(false);

            // if the target method doesn't already exist, go make it
            if (!existing)
            {
                // generate the logging method signature in the target class
                sol = await InsertLoggingMethodSignatureAsync(targetDoc, targetClass, invocationDoc, invocationExpression, details, cancellationToken).ConfigureAwait(false);

                // find the doc and invocation in the current solution
                (invocationDoc, invocationExpression) = await RemapAsync(sol, invocationDocId, invocationExpression).ConfigureAwait(false);
            }

            // rewrite the call site to invoke the generated logging method
            sol = await RewriteLoggingCallAsync(invocationDoc, invocationExpression, details, methodName, cancellationToken).ConfigureAwait(false);

            return(sol);
        }
Example #9
0
        /// <summary>
        /// Finds the class into which to create the logging method signature, or creates it if it doesn't exist.
        /// </summary>
        private static async Task <(Solution, ClassDeclarationSyntax, Document)> GetOrMakeTargetClassAsync(Project proj, FixDetails details, CancellationToken cancellationToken)
        {
            while (true)
            {
                var comp       = (await proj.GetCompilationAsync(cancellationToken).ConfigureAwait(false)) !;
                var allNodes   = comp.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes());
                var allClasses = allNodes.Where(d => d.IsKind(SyntaxKind.ClassDeclaration)).OfType <ClassDeclarationSyntax>();
                foreach (var cl in allClasses)
                {
                    var nspace = GetNamespace(cl);
                    if (nspace != details.TargetNamespace)
                    {
                        continue;
                    }

                    if (cl.Identifier.Text == details.TargetClassName)
                    {
                        return(proj.Solution, cl, proj.GetDocument(cl.SyntaxTree) !);
                    }
                }

                var text = $@"
#pragma warning disable CS8019
using Microsoft.Extensions.Logging;
using System;
#pragma warning restore CS8019

static partial class {details.TargetClassName}
{{
}}
";

                if (!string.IsNullOrEmpty(details.TargetNamespace))
                {
                    text = $@"
namespace {details.TargetNamespace}
{{
#pragma warning disable CS8019
    using Microsoft.Extensions.Logging;
    using System;
#pragma warning restore CS8019

    static partial class {details.TargetClassName}
    {{
    }}
}}
";
                }

                proj = proj.AddDocument(details.TargetFilename, text).Project;
            }
        }
Example #10
0
        /// <summary>
        /// Get the final name of the target method. If there's an existing method with the right
        /// message, level, and argument types, we just use that. Otherwise, we create a new method.
        /// </summary>
        internal async Task <(string methodName, bool existing)> GetFinalTargetMethodNameAsync(
            Document targetDoc,
            ClassDeclarationSyntax targetClass,
            Document invocationDoc,
            InvocationExpressionSyntax invocationExpression,
            FixDetails details,
            CancellationToken cancellationToken)
        {
            var invocationSM = (await invocationDoc.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false)) !;
            var invocationOp = (invocationSM.GetOperation(invocationExpression, cancellationToken) as IInvocationOperation) !;

            var docEditor = await DocumentEditor.CreateAsync(targetDoc, cancellationToken).ConfigureAwait(false);

            var sm   = docEditor.SemanticModel;
            var comp = sm.Compilation;

            var loggerMessageAttribute = _getTypeByMetadataName2(comp, LoggerMessageAttribute);

            if (loggerMessageAttribute is null)
            {
                // strange that we can't find the attribute, but supply a potential useful value instead
                return(details.TargetMethodName, false);
            }

            var invocationArgList = MakeArgumentList(details, invocationOp);

            var    conflict = false;
            var    count    = 2;
            string methodName;

            do
            {
                methodName = details.TargetMethodName;
                if (conflict)
                {
                    methodName = $"{methodName}{count}";
                    count++;
                    conflict = false;
                }

                foreach (var method in targetClass.Members.Where(m => m.IsKind(SyntaxKind.MethodDeclaration)).OfType <MethodDeclarationSyntax>())
                {
                    var methodSymbol = _getDeclaredSymbol(sm, method, cancellationToken);
                    if (methodSymbol == null)
                    {
                        // hmmm, this shouldn't happen should it?
                        continue;
                    }

                    var matchName = method.Identifier.ToString() == methodName;

                    var matchParams = invocationArgList.Count == methodSymbol.Parameters.Length;
                    if (matchParams)
                    {
                        for (int i = 0; i < invocationArgList.Count; i++)
                        {
                            matchParams = invocationArgList[i].Equals(methodSymbol.Parameters[i].Type, SymbolEqualityComparer.Default);
                            if (!matchParams)
                            {
                                break;
                            }
                        }
                    }

                    if (matchName && matchParams)
                    {
                        conflict = true;
                    }

                    foreach (var mal in method.AttributeLists)
                    {
                        foreach (var ma in mal.Attributes)
                        {
                            var mattrSymbolInfo = sm.GetSymbolInfo(ma, cancellationToken);
                            if (mattrSymbolInfo.Symbol is IMethodSymbol ms)
                            {
                                if (loggerMessageAttribute.Equals(ms.ContainingType, SymbolEqualityComparer.Default))
                                {
                                    var arg   = ma.ArgumentList !.Arguments[LoggerMessageAttrLevelArg];
                                    var level = (LogLevel)sm.GetConstantValue(arg.Expression, cancellationToken).Value !;

                                    arg = ma.ArgumentList.Arguments[LoggerMessageAttrMessageArg];
                                    var message = sm.GetConstantValue(arg.Expression, cancellationToken).ToString();

                                    var matchMessage = message == details.Message;
                                    var matchLevel   = FixDetails.GetLogLevelName(level) == details.Level;

                                    if (matchLevel && matchMessage && matchParams)
                                    {
                                        // found a match, use this one
                                        return(method.Identifier.ToString(), true);
                                    }

                                    break;
                                }
                            }
                        }
                    }
                }
            }while (conflict);

            return(methodName, false);
        }