Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/FsAutoComplete.Core/TipFormatter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,30 @@ type private XmlDocMember(doc: XmlDocument, indentationSize: int, columnOffset:
let seeAlso =
doc.DocumentElement.GetElementsByTagName "seealso"
|> Seq.cast<XmlNode>
|> Seq.map (fun node -> "* `" + Format.extractMemberText node.Attributes.[0].InnerText + "`")
|> Seq.choose (fun node ->
let attrs =
if node.Attributes <> null then
[ for i in 0 .. node.Attributes.Count - 1 ->
node.Attributes.[i].Name.ToLowerInvariant(), node.Attributes.[i].InnerText ]
|> Map.ofList
else
Map.empty

match Map.tryFind "cref" attrs with
| Some cref -> Some("* `" + Format.extractMemberText cref + "`")
| None ->
match Map.tryFind "href" attrs with
| Some href ->
let innerText = node.InnerText.Trim()

if String.IsNullOrEmpty innerText then
Some $"* [{href}]({href})"
else
Some $"* [{innerText}]({href})"
| None ->
match Map.tryFind "langword" attrs with
| Some langword -> Some $"* `{langword}`"
| None -> None)

override x.ToString() =
summary
Expand Down
3 changes: 2 additions & 1 deletion test/FsAutoComplete.Tests.Lsp/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ let generalTests =
InlayHintTests.explicitTypeInfoTests sourceTextFactory
FindReferences.tryFixupRangeTests sourceTextFactory
UtilsTests.allTests
LspHelpersTests.allTests ]
LspHelpersTests.allTests
TipFormatterTests.allTests ]

[<Tests>]
let tests =
Expand Down
94 changes: 94 additions & 0 deletions test/FsAutoComplete.Tests.Lsp/TipFormatterTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/// Unit tests for FsAutoComplete.TipFormatter, specifically the <seealso> XML doc tag rendering
/// introduced / fixed in https://github.com/ionide/FsAutoComplete/pull/1463.
module FsAutoComplete.Tests.TipFormatterTests

open Expecto
open FSharp.Compiler.Symbols
open FSharp.Compiler.Text
open FsAutoComplete

/// Build an FSharpXmlDoc from raw XML lines (the same approach used in KeywordList.fs).
let private makeXmlDoc (lines: string[]) = FSharpXmlDoc.FromXmlText(FSharp.Compiler.Xml.XmlDoc(lines, Range.Zero))

/// Call formatDocumentationFromXmlDoc and return the Success string, failing the test otherwise.
let private getDoc (xmlDoc: FSharpXmlDoc) =
match TipFormatter.formatDocumentationFromXmlDoc xmlDoc with
| TipFormatter.TipFormatterResult.Success s -> s
| TipFormatter.TipFormatterResult.None -> failtest "Expected doc content but got None"
| TipFormatter.TipFormatterResult.Error e -> failtest $"Expected doc content but got Error: {e}"

let seeAlsoTests =
testList
"seealso rendering"
[ testCase "cref attribute renders member name as inline code"
<| fun _ ->
let xml =
makeXmlDoc [| "<summary>Description</summary>"; """<seealso cref="T:Foo.Bar"/>""" |]

let content = getDoc xml
Expect.stringContains content "* `Foo.Bar`" "cref should render as backtick-quoted member name"

testCase "href void element renders as auto-link"
<| fun _ ->
let xml =
makeXmlDoc
[| "<summary>Description</summary>"
"""<seealso href="https://example.com"/>""" |]

let content = getDoc xml

Expect.stringContains
content
"* [https://example.com](https://example.com)"
"href void element should produce a Markdown link where both label and URL are the href value"

testCase "href element with inner text renders as labelled link"
<| fun _ ->
let xml =
makeXmlDoc
[| "<summary>Description</summary>"
"""<seealso href="https://example.com">Click here</seealso>""" |]

let content = getDoc xml

Expect.stringContains
content
"* [Click here](https://example.com)"
"href with inner text should use the inner text as the link label"

testCase "langword attribute renders keyword as inline code"
<| fun _ ->
let xml =
makeXmlDoc [| "<summary>Description</summary>"; """<seealso langword="null"/>""" |]

let content = getDoc xml
Expect.stringContains content "* `null`" "langword should render as backtick-quoted keyword"

testCase "unrecognised attribute is silently skipped"
<| fun _ ->
let xml =
makeXmlDoc [| "<summary>Description</summary>"; """<seealso unknown="foo"/>""" |]

let content = getDoc xml

Expect.isFalse
(content.Contains "See also")
"an unrecognised attribute should produce no See also section rather than garbled output"

testCase "multiple seealso entries all appear in See also section"
<| fun _ ->
let xml =
makeXmlDoc
[| "<summary>Description</summary>"
"""<seealso cref="T:Foo.Bar"/>"""
"""<seealso href="https://example.com"/>"""
"""<seealso langword="null"/>""" |]

let content = getDoc xml
Expect.stringContains content "* `Foo.Bar`" "cref entry should be present"

Expect.stringContains content "* [https://example.com](https://example.com)" "href entry should be present"

Expect.stringContains content "* `null`" "langword entry should be present" ]

let allTests = testList "TipFormatter" [ seeAlsoTests ]