示例#1
0
        /// <summary>
        /// Sends a new parsing request to the parser application, and returns its response.
        /// </summary>
        /// <param name="MethodSignature">The signature string of the method (the operation or
        /// function) to parse.</param>
        /// <returns>The parsed message signature response from the parser, or null if something
        /// went wrong.</returns>
        public MethodSignatureResponse RequestMethodSignatureParse(string MethodSignature)
        {
            if (Stream == null || !Stream.IsConnected)
            {
                Logger.Debug($"Tried to send a {nameof(MethodSignatureRequest)} to the parser but " +
                             $"it isn't connected.");
                return(null);
            }

            try
            {
                // Create and wrap the request message
                MethodSignatureRequest request = new MethodSignatureRequest
                {
                    MethodSignature = MethodSignature
                };
                Message message = MessageManager.WrapMessage(request);

                // Send the message to the parser
                byte[] requestBuffer = message.ToByteArray();
                byte[] bufferSize    = BitConverter.GetBytes(requestBuffer.Length);
                Stream.Write(bufferSize, 0, bufferSize.Length);
                Stream.Flush();
                Stream.Write(requestBuffer, 0, requestBuffer.Length);
                Stream.Flush();

                // Wait for a response, and read how big it is
                byte[] lengthBuffer = new byte[sizeof(int)];
                if (!ReadFromPipe(lengthBuffer, lengthBuffer.Length))
                {
                    Logger.Warn("Method parsing request failed, something went wrong while reading from the pipe.");
                    return(null);
                }

                // Get the actual response from the parser
                int    responseLength = BitConverter.ToInt32(lengthBuffer, 0);
                byte[] responseBuffer = new byte[responseLength];
                if (!ReadFromPipe(responseBuffer, responseLength))
                {
                    Logger.Warn("Method parsing request failed, something went wrong while reading from the pipe.");
                    return(null);
                }

                // Deserialize the response
                Message response = Message.Parser.ParseFrom(responseBuffer);
                switch (response.Type)
                {
                case MessageType.Error:
                    Error error = Error.Parser.ParseFrom(response.MessageBody);
                    Logger.Warn($"Parser failed during method signature processing: {error.ErrorType} - {error.Message}");
                    Logger.Trace(error.StackTrace);
                    return(null);

                case MessageType.MethodSignatureResponse:
                    MethodSignatureResponse signatureResponse = MethodSignatureResponse.Parser.ParseFrom(response.MessageBody);
                    return(signatureResponse);

                default:
                    throw new Exception($"Unexpected message response type: {response.Type}");
                }
            }
            catch (Exception ex)
            {
                Logger.Error($"Error during method signature processing: {ex.GetType().Name} - {ex.Message}");
                Logger.Trace(ex.StackTrace);
                return(null);
            }
        }
        /// <summary>
        /// Creates and adds documentation comment blocks when the user types a triple slash.
        /// </summary>
        private void HandleTripleSlash()
        {
            ThreadHelper.ThrowIfNotOnUIThread();

            // Get the original placement of the cursor in the code editor
            TextSelection ts        = (TextSelection)Dte.ActiveDocument.Selection;
            int           oldLine   = ts.ActivePoint.Line;
            int           oldOffset = ts.ActivePoint.LineCharOffset;

            // Check to see if the previous line starts with a triple-slash; if it does, we should probably
            // just return because there's most likely a docstring already in place.
            ts.LineUp();
            ts.StartOfLine();
            string previousLine = TextView.TextSnapshot.GetLineFromPosition(
                TextView.Caret.Position.BufferPosition.Position).GetText();

            if (previousLine.TrimStart().StartsWith("///"))
            {
                ts.MoveToLineAndOffset(oldLine, oldOffset);
                ts.Insert("/");     // Add the slash that the user just typed
                return;
            }

            // Get the contents of the next line (the one following the original line)
            ts.LineDown();
            ts.LineDown();
            ts.StartOfLine();
            int    currentCharIndex = TextView.Caret.Position.BufferPosition.Position;
            string fullText         = TextView.TextSnapshot.GetText();

            // Check if we just triple-slashed a method (a function or an operation)
            Match methodMatch = GetMethodSignatureMatch(currentCharIndex, fullText);

            if (methodMatch != null)
            {
                Logger.Debug($"Found a potential method match: [{methodMatch.Value}]");
                string signatureString = methodMatch.Groups["Signature"].Value;
                string leadingSpaces   = methodMatch.Groups["Spaces"].Value;

                // Build the summary section, which is going to go in no matter what
                StringBuilder commentBuilder = new StringBuilder();
                commentBuilder.AppendLine("/ # Summary");
                commentBuilder.Append(leadingSpaces + "/// ");

                // Ask the Q# parser application to pull out all of the method details so we know what to
                // put into the documentation comments, and add them if parsing succeeded
                Logger.Debug("Sending a parse request to the Q# parser...");
                try
                {
                    MethodSignatureResponse signature = Messenger.RequestMethodSignatureParse(signatureString);
                    if (signature != null)
                    {
                        Logger.Debug($"Parsing succeeded, method name = [{signature.Name}], " +
                                     $"{signature.ParameterNames.Count} parameters, returns something = {signature.HasReturnType}.");
                        BuildMethodCommentBlock(signature, commentBuilder, leadingSpaces);
                    }
                }
                catch (Exception ex)
                {
                    Logger.Error($"Error during method signature request: {ex.GetType().Name} - {ex.Message}");
                    Logger.Trace(ex.StackTrace);
                }

                // Move to the original cursor position and add the comment block to the code
                ts.MoveToLineAndOffset(oldLine, oldOffset);
                ts.Insert(commentBuilder.ToString());
                ts.MoveToLineAndOffset(oldLine, oldOffset);
                ts.LineDown();
                ts.EndOfLine();

                return;
            }

            // Check if we just triple-slashed a new type
            Match newtypeMatch = GetNewTypeMatch(currentCharIndex, fullText);

            if (newtypeMatch != null)
            {
                Logger.Debug($"Found a newtype match: [{newtypeMatch.Value}]");
                string leadingSpaces = newtypeMatch.Groups["Spaces"].Value;

                // Build the summary section
                StringBuilder commentBuilder = new StringBuilder();
                commentBuilder.AppendLine("/ # Summary");
                commentBuilder.Append(leadingSpaces + "/// ");

                // Move to the original cursor position and add the comment block to the code
                ts.MoveToLineAndOffset(oldLine, oldOffset);
                ts.Insert(commentBuilder.ToString());
                ts.MoveToLineAndOffset(oldLine, oldOffset);
                ts.LineDown();
                ts.EndOfLine();

                return;
            }

            // If this was a triple slash on something else, just add the slash and return.
            ts.MoveToLineAndOffset(oldLine, oldOffset);
            ts.Insert("/");
        }
        /// <summary>
        /// Adds the relevant Markdown sections for the given method signature to the provided
        /// comment block builder.
        /// </summary>
        /// <param name="MethodSignature">The method's signature</param>
        /// <param name="CommentBuilder">The <see cref="StringBuilder"/> used to generate the
        /// comment block</param>
        /// <param name="LeadingSpaces">A string containing the leading spaces used for
        /// indenting the comment block</param>
        private void BuildMethodCommentBlock(MethodSignatureResponse MethodSignature, StringBuilder CommentBuilder, string LeadingSpaces)
        {
            // Add sections for the input parameters
            if (MethodSignature.ParameterNames.Count > 0)
            {
                CommentBuilder.AppendLine();
                for (int i = 0; i < LinesBetweenSections - 1; i++)
                {
                    CommentBuilder.AppendLine(LeadingSpaces + "/// ");
                }
                CommentBuilder.Append(LeadingSpaces + "/// # Input");
                bool isFirstParameter = true;
                foreach (string parameterName in MethodSignature.ParameterNames)
                {
                    CommentBuilder.AppendLine();
                    if (!isFirstParameter)
                    {
                        for (int i = 0; i < LinesBetweenParameters - 1; i++)
                        {
                            CommentBuilder.AppendLine(LeadingSpaces + "/// ");
                        }
                    }
                    isFirstParameter = false;
                    CommentBuilder.AppendLine(LeadingSpaces + $"/// ## {parameterName}");
                    CommentBuilder.Append(LeadingSpaces + "/// ");
                }
            }

            // Add sections for the type parameters
            if (MethodSignature.TypeParameterNames.Count > 0)
            {
                CommentBuilder.AppendLine();
                for (int i = 0; i < LinesBetweenSections - 1; i++)
                {
                    CommentBuilder.AppendLine(LeadingSpaces + "/// ");
                }
                CommentBuilder.Append(LeadingSpaces + "/// # Type Parameters");
                bool isFirstParameter = true;
                foreach (string typeParameterName in MethodSignature.TypeParameterNames)
                {
                    CommentBuilder.AppendLine();
                    if (!isFirstParameter)
                    {
                        for (int i = 0; i < LinesBetweenParameters - 1; i++)
                        {
                            CommentBuilder.AppendLine(LeadingSpaces + "/// ");
                        }
                    }
                    isFirstParameter = false;
                    CommentBuilder.AppendLine(LeadingSpaces + $"/// ## '{typeParameterName}");
                    CommentBuilder.Append(LeadingSpaces + "/// ");
                }
            }

            // Add the output section if it has a return type
            if (MethodSignature.HasReturnType)
            {
                CommentBuilder.AppendLine();
                for (int i = 0; i < LinesBetweenSections - 1; i++)
                {
                    CommentBuilder.AppendLine(LeadingSpaces + "/// ");
                }
                CommentBuilder.AppendLine(LeadingSpaces + "/// # Output");
                CommentBuilder.Append(LeadingSpaces + "/// ");
            }
        }
示例#4
0
        /// <summary>
        /// Parses a raw Q# method signature and extracts the important details for documentation purposes.
        /// </summary>
        /// <param name="MethodSignature">The signature of the method, in plaintext form</param>
        /// <returns>A <see cref="MethodSignatureResponse"/> with the relevant information extracted from
        /// the raw signature, or an <see cref="ErrorMessage"/> if parsing failed.</returns>
        public IMessage ParseMethodSignature(string MethodSignature)
        {
            try
            {
                string        name               = null;
                List <string> parameterNames     = null;
                List <string> typeParameterNames = null;
                bool          hasReturnType      = false;

                // Parse the details out of the signature text
                QsFragment[] fragments = Parsing.ProcessCodeFragment(MethodSignature);
                if (fragments[0].Kind is QsFragmentKind.OperationDeclaration operationDeclaration)
                {
                    name               = GetSymbolName(operationDeclaration.Item1);
                    parameterNames     = GetParameterNames(operationDeclaration.Item2);
                    typeParameterNames = GetTypeParameterNames(operationDeclaration.Item2);
                    hasReturnType      = CheckForReturnType(operationDeclaration.Item2);
                }
                else if (fragments[0].Kind is QsFragmentKind.FunctionDeclaration functionDeclaration)
                {
                    name               = GetSymbolName(functionDeclaration.Item1);
                    parameterNames     = GetParameterNames(functionDeclaration.Item2);
                    typeParameterNames = GetTypeParameterNames(functionDeclaration.Item2);
                    hasReturnType      = CheckForReturnType(functionDeclaration.Item2);
                }
                else
                {
                    string warning = $"Warning: tried to parse a method signature, but it wasn't a function or operation. Contents: {MethodSignature}";
                    Logger.Warn(warning);
                    return(new Error
                    {
                        Message = warning
                    });
                }

                // Log and return them
                Logger.Debug($"Method name: {name ?? string.Empty}");
                Logger.Debug("Parameter names:");
                foreach (string parameter in parameterNames)
                {
                    Logger.Debug($"\t{parameter}");
                }
                Logger.Debug($"Returns something: {hasReturnType}");

                MethodSignatureResponse response = new MethodSignatureResponse
                {
                    Name          = name,
                    HasReturnType = hasReturnType
                };
                response.ParameterNames.AddRange(parameterNames);
                response.TypeParameterNames.AddRange(typeParameterNames);
                return(response);
            }
            catch (Exception ex)
            {
                Logger.Error($"Error processing method signature: {ex.GetType().Name} - {ex.Message}.");
                Logger.Trace(ex.StackTrace);

                return(new Error
                {
                    ErrorType = ex.GetType().Name,
                    Message = ex.Message,
                    StackTrace = ex.StackTrace
                });
            }
        }