diff --git a/src/OmniSharp.Abstractions/Models/v1/SignatureHelp/SignatureHelpItem.cs b/src/OmniSharp.Abstractions/Models/v1/SignatureHelp/SignatureHelpItem.cs index f88418aba6..067f17a83e 100644 --- a/src/OmniSharp.Abstractions/Models/v1/SignatureHelp/SignatureHelpItem.cs +++ b/src/OmniSharp.Abstractions/Models/v1/SignatureHelp/SignatureHelpItem.cs @@ -1,3 +1,4 @@ +using OmniSharp.Models.TypeLookup; using System.Collections.Generic; using System.Linq; @@ -13,6 +14,8 @@ public class SignatureHelpItem public IEnumerable<SignatureHelpParameter> Parameters { get; set; } + public DocumentationComment StructuredDocumentation { get; set; } + public override bool Equals(object obj) { var other = obj as SignatureHelpItem; diff --git a/src/OmniSharp.Abstractions/Models/v1/TypeLookup/DocumentationComment.cs b/src/OmniSharp.Abstractions/Models/v1/TypeLookup/DocumentationComment.cs index 4dab014516..ec7092067c 100644 --- a/src/OmniSharp.Abstractions/Models/v1/TypeLookup/DocumentationComment.cs +++ b/src/OmniSharp.Abstractions/Models/v1/TypeLookup/DocumentationComment.cs @@ -18,20 +18,31 @@ public class DocumentationComment public string ValueText { get; } public DocumentationItem[] Exception { get; } - private DocumentationComment(string summaryText, DocumentationItem[] typeParamElements, DocumentationItem[] paramElements, string returnsText, string remarksText, string exampleText, string valueText, DocumentationItem[ ] exception) + public DocumentationComment( + string summaryText = "", + DocumentationItem[] typeParamElements = null, + DocumentationItem[] paramElements = null, + string returnsText = "", + string remarksText = "", + string exampleText = "", + string valueText = "", + DocumentationItem[] exception = null) { SummaryText = summaryText; - TypeParamElements = typeParamElements; - ParamElements = paramElements; + TypeParamElements = typeParamElements ?? Array.Empty<DocumentationItem>(); + ParamElements = paramElements ?? Array.Empty<DocumentationItem>(); ReturnsText = returnsText; RemarksText = remarksText; ExampleText = exampleText; ValueText = valueText; - Exception = exception; + Exception = exception ?? Array.Empty<DocumentationItem>(); } public static DocumentationComment From(string xmlDocumentation, string lineEnding) { + if (string.IsNullOrEmpty(xmlDocumentation)) + return Empty; + var reader = new StringReader("<docroot>" + xmlDocumentation + "</docroot>"); StringBuilder summaryText = new StringBuilder(); List<DocumentationItemBuilder> typeParamElements = new List<DocumentationItemBuilder>(); @@ -175,6 +186,14 @@ private static string TrimStartRetainingSingleLeadingSpace(string input) return input; return $" {input.TrimStart()}"; } + + public string GetParameterText(string name) + => Array.Find(ParamElements, parameter => parameter.Name == name)?.Documentation ?? string.Empty; + + public string GetTypeParameterText(string name) + => Array.Find(TypeParamElements, typeParam => typeParam.Name == name)?.Documentation ?? string.Empty; + + public static readonly DocumentationComment Empty = new DocumentationComment(); } class DocumentationItemBuilder diff --git a/src/OmniSharp.Roslyn.CSharp/Services/DocumentationConverter.cs b/src/OmniSharp.Roslyn.CSharp/Services/DocumentationConverter.cs index b02153d1e9..993d4273fb 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/DocumentationConverter.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/DocumentationConverter.cs @@ -1,9 +1,9 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Xml; +using Microsoft.CodeAnalysis; using OmniSharp.Models.TypeLookup; namespace OmniSharp.Roslyn.CSharp.Services.Documentation @@ -131,10 +131,43 @@ private static string GetCref(string cref) public static DocumentationComment GetStructuredDocumentation(string xmlDocumentation, string lineEnding) { - if (string.IsNullOrEmpty(xmlDocumentation)) - return null; return DocumentationComment.From(xmlDocumentation, lineEnding); } + + public static DocumentationComment GetStructuredDocumentation(ISymbol symbol, string lineEnding = "\n") + { + switch (symbol) + { + case IParameterSymbol parameter: + return new DocumentationComment(summaryText: GetParameterDocumentation(parameter, lineEnding)); + case ITypeParameterSymbol typeParam: + return new DocumentationComment(summaryText: GetTypeParameterDocumentation(typeParam, lineEnding)); + case IAliasSymbol alias: + return new DocumentationComment(summaryText: GetAliasDocumentation(alias, lineEnding)); + default: + return GetStructuredDocumentation(symbol.GetDocumentationCommentXml(), lineEnding); + } + } + + private static string GetParameterDocumentation(IParameterSymbol parameter, string lineEnding = "\n") + { + var contaningSymbolDef = parameter.ContainingSymbol.OriginalDefinition; + return GetStructuredDocumentation(contaningSymbolDef.GetDocumentationCommentXml(), lineEnding) + .GetParameterText(parameter.Name); + } + + private static string GetTypeParameterDocumentation(ITypeParameterSymbol typeParam, string lineEnding = "\n") + { + var contaningSymbol = typeParam.ContainingSymbol; + return GetStructuredDocumentation(contaningSymbol.GetDocumentationCommentXml(), lineEnding) + .GetTypeParameterText(typeParam.Name); + } + + private static string GetAliasDocumentation(IAliasSymbol alias, string lineEnding = "\n") + { + var target = alias.Target; + return GetStructuredDocumentation(target.GetDocumentationCommentXml(), lineEnding).SummaryText; + } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs index 8687fd1fb7..d6dec7191d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs @@ -9,6 +9,7 @@ using OmniSharp.Mef; using OmniSharp.Models; using OmniSharp.Models.SignatureHelp; +using OmniSharp.Roslyn.CSharp.Services.Documentation; namespace OmniSharp.Roslyn.CSharp.Services.Signatures { @@ -168,6 +169,7 @@ private static SignatureHelpItem BuildSignature(IMethodSymbol symbol) signature.Documentation = symbol.GetDocumentationCommentXml(); signature.Name = symbol.MethodKind == MethodKind.Constructor ? symbol.ContainingType.Name : symbol.Name; signature.Label = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + signature.StructuredDocumentation = DocumentationConverter.GetStructuredDocumentation(symbol); signature.Parameters = symbol.Parameters.Select(parameter => { @@ -175,12 +177,11 @@ private static SignatureHelpItem BuildSignature(IMethodSymbol symbol) { Name = parameter.Name, Label = parameter.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), - Documentation = parameter.GetDocumentationCommentXml() + Documentation = signature.StructuredDocumentation.GetParameterText(parameter.Name) }; }); return signature; } - } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Types/TypeLookup.cs b/src/OmniSharp.Roslyn.CSharp/Services/Types/TypeLookup.cs index 13d98efa1a..16df110506 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Types/TypeLookup.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Types/TypeLookup.cs @@ -46,7 +46,7 @@ public async Task<TypeLookupResponse> Handle(TypeLookupRequest request) if (request.IncludeDocumentation) { response.Documentation = DocumentationConverter.ConvertDocumentation(symbol.GetDocumentationCommentXml(), _formattingOptions.NewLine); - response.StructuredDocumentation = DocumentationConverter.GetStructuredDocumentation(symbol.GetDocumentationCommentXml(), _formattingOptions.NewLine); + response.StructuredDocumentation = DocumentationConverter.GetStructuredDocumentation(symbol, _formattingOptions.NewLine); } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs index 9ce5fb4ce5..726e84f055 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs @@ -758,6 +758,90 @@ bool LocalFunction(int i) Assert.Equal("int i", signature.Parameters.ElementAt(0).Label); } + [Fact] + public async Task ReturnsDocumentationForParameters() + { + const string source = +@"class Program +{ + public static void Main() + { + var flag = Compare($$); + } + ///<summary>Checks if object is tagged with the tag.</summary> + /// <param name=""gameObject"">The game object.</param> + /// <param name=""tagName"">Name of the tag.</param> + /// <returns>Returns <c> true</c> if object is tagged with tag.</returns> + public static bool Compare(GameObject gameObject, string tagName) + { + return true; + } +}"; + var actual = await GetSignatureHelp(source); + Assert.Single(actual.Signatures); + + var signature = actual.Signatures.ElementAt(0); + Assert.Equal(2, signature.Parameters.Count()); + Assert.Equal("gameObject", signature.Parameters.ElementAt(0).Name); + Assert.Equal("The game object.", signature.Parameters.ElementAt(0).Documentation); + Assert.Equal("tagName", signature.Parameters.ElementAt(1).Name); + Assert.Equal("Name of the tag.", signature.Parameters.ElementAt(1).Documentation); + } + + [Fact] + public async Task ReturnsDocumentationForParametersNestedTags() + { + const string source = +@"class Program +{ + public static void Main() + { + var flag = Compare($$); + } + ///<summary>Checks if object is tagged with the tag.</summary> + /// <param name=""gameObject"">The game object.</param> + /// <param name=""tagName"">Name of the tag.It has the default value as <c>null</c></param> + /// <returns>Returns <c> true</c> if object is tagged with tag.</returns> + public static bool Compare(GameObject gameObject, string tagName = null) + { + return true; + } +}"; + var actual = await GetSignatureHelp(source); + Assert.Single(actual.Signatures); + + var signature = actual.Signatures.ElementAt(0); + Assert.Equal(2, signature.Parameters.Count()); + Assert.Equal("Name of the tag.It has the default value as null", signature.Parameters.ElementAt(1).Documentation); + } + + [Fact] + public async Task ReturnsStructuredDocumentation() + { + const string source = +@"class Program +{ + public static void Main() + { + var flag = Compare($$); + } + ///<summary>Checks if object is tagged with the tag.</summary> + /// <param name=""gameObject"">The game object.</param> + /// <param name=""tagName"">Name of the tag.</param> + /// <returns>Returns <c>true</c> if object is tagged with tag.</returns> + public static bool Compare(GameObject gameObject, string tagName) + { + return true; + } +}"; + var actual = await GetSignatureHelp(source); + Assert.Single(actual.Signatures); + + var signature = actual.Signatures.ElementAt(0); + Assert.Equal("Checks if object is tagged with the tag.", signature.StructuredDocumentation.SummaryText); + Assert.Equal("Returns true if object is tagged with tag.", signature.StructuredDocumentation.ReturnsText); + } + [Fact] public async Task SkipReceiverOfExtensionMethods() { diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/TypeLookupFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/TypeLookupFacts.cs index cf62e70fb8..3b837c4efb 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/TypeLookupFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/TypeLookupFacts.cs @@ -646,5 +646,37 @@ public class TestClass @"DoWork is a method in the TestClass class."; Assert.Equal(expected, response.StructuredDocumentation.SummaryText); } + + [Fact] + public async Task StructuredDocumentationForParameters1() + { + string content = @" +class testissue +{ + /// <param name=""gameObject"">The game object.</param> + /// <param name=""tagName"">Name of the tag.</param> + public static bool Compare(int gam$$eObject, string tagName) + { + } +}"; + var response = await GetTypeLookUpResponse(content); + Assert.Equal("The game object.", response.StructuredDocumentation.SummaryText); + } + + [Fact] + public async Task StructuredDocumentationForParameters2() + { + string content = @" +class testissue +{ + /// <param name=""gameObject"">The game object.</param> + /// <param name=""tagName"">Name of the tag.</param> + public static bool Compare(int gameObject, string tag$$Name) + { + } +}"; + var response = await GetTypeLookUpResponse(content); + Assert.Equal("Name of the tag.", response.StructuredDocumentation.SummaryText); + } } }