Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation and tests for <seealso> when cref omits arguments #790

Merged
merged 4 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 17.2.2

* Improvement for `<seealso/>` [#789](https://github.com/fsprojects/FSharp.Formatting/issues/789)

## 17.2.1

* Fix support for `<exclude/>` [#786](https://github.com/fsprojects/FSharp.Formatting/issues/786)
Expand Down
38 changes: 38 additions & 0 deletions docs/apidocs.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,44 @@ type GenericClass2<'T>() =
/// and <see cref="M:TheNamespace.GenericClass2`1.GenericMethod``1(`0,``0)" />
let referringFunction2 () = "result"

(**
### Cross-referencing with &lt;seealso&gt;

Use `<seealso cref="..."/>` within `<summary>` to create cross-references.

For example:
*)

module Forest =

/// <summary>
/// Find at most <c>limit</c> foxes in current forest
///
/// See also: <seealso cref="M:App.Forest.findSquirrels(System.Int32)"/>
/// </summary>
let findFoxes (limit : int) = []

/// <summary>
/// Find at most <c>limit</c> squirrels in current forest
///
/// See also: <seealso cref="M:App.Forest.findFoxes(System.Int32)"/>
/// </summary>
let findSquirrels (limit : int) = []


(** You can find the correct value for `cref` in the generated `.xml` documentation file (this will be generated alongside the assembly's `.dll``).

You can also omit the `cref`'s arguments, and `fsdocs` will make an attempt to find the first member that matches.

For example:
```
/// See also: <seealso cref="M:App.Forest.findSquirrels"/>
```

If the member cannot be found, a link to the containing module/type will be used instead.
*)


(**
### Classic XMl Doc Comments: Excluding APIs from the docs

Expand Down
51 changes: 43 additions & 8 deletions src/FSharp.Formatting.ApiDocs/GenerateModel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,15 @@ type internal CrossReferenceResolver(root, collectionName, qualify, extensions)
else
None

let tryGetShortMemberNameFromMemberName (memberName: string) =
let sub = removeParen memberName
let lastPeriod = sub.LastIndexOf(".")

if lastPeriod > 0 then
Some(memberName.Substring(lastPeriod + 1))
else
None

let getMemberName keepParts hasModuleSuffix (memberNameNoParen: string) =
let splits = memberNameNoParen.Split('.') |> Array.toList

Expand Down Expand Up @@ -992,6 +1001,32 @@ type internal CrossReferenceResolver(root, collectionName, qualify, extensions)
let simple = getMemberName 1 false typeName
externalDocsLink false simple typeName typeName

// If there's a quicker way to search an FSharpEntity for members then this linear
// search can disappear - see usage below in tryResolveCrossReferenceForMemberByXmlSig
// It's like writing C#...
let findIList (list: IList<'T>) (p: ('T -> bool)) =
let n = list.Count
let mutable i = 0
let mutable result: 'T option = None

while i < n do
let item = list.[i]

if p item then
i <- n
result <- Some item

i <- i + 1

result

let mfvToCref (mfv: FSharpMemberOrFunctionOrValue) =
let entityUrlBaseName = getUrlBaseNameForRegisteredEntity mfv.DeclaringEntity.Value

{ IsInternal = true
ReferenceLink = internalCrossReferenceForMember entityUrlBaseName mfv
NiceName = mfv.DeclaringEntity.Value.DisplayName + "." + mfv.DisplayName }

let tryResolveCrossReferenceForMemberByXmlSig (memberXmlSig: string) =
assert
(memberXmlSig.StartsWith("M:")
Expand All @@ -1000,13 +1035,7 @@ type internal CrossReferenceResolver(root, collectionName, qualify, extensions)
|| memberXmlSig.StartsWith("E:"))

match xmlDocNameToSymbol.TryGetValue(memberXmlSig) with
| true, (:? FSharpMemberOrFunctionOrValue as memb) when memb.DeclaringEntity.IsSome ->
let entityUrlBaseName = getUrlBaseNameForRegisteredEntity memb.DeclaringEntity.Value

{ IsInternal = true
ReferenceLink = internalCrossReferenceForMember entityUrlBaseName memb
NiceName = memb.DeclaringEntity.Value.DisplayName + "." + memb.DisplayName }
|> Some
| true, (:? FSharpMemberOrFunctionOrValue as memb) when memb.DeclaringEntity.IsSome -> memb |> mfvToCref |> Some
| _ ->
// If we can't find the exact symbol for the member, don't despair, look for the type
let memberName = memberXmlSig.Substring(2) |> removeParen
Expand All @@ -1019,10 +1048,16 @@ type internal CrossReferenceResolver(root, collectionName, qualify, extensions)
| true, (:? FSharpEntity as entity) ->
let urlBaseName = getUrlBaseNameForRegisteredEntity entity

Some
// See if we find the member that was intended, otherwise default to containing entity
tryGetShortMemberNameFromMemberName memberName
|> Option.bind (fun shortName ->
findIList (entity.MembersFunctionsAndValues) (fun mfv -> mfv.DisplayName = shortName))
davedawkins marked this conversation as resolved.
Show resolved Hide resolved
|> Option.map mfvToCref
davedawkins marked this conversation as resolved.
Show resolved Hide resolved
|> Option.defaultValue
{ IsInternal = true
ReferenceLink = internalCrossReference urlBaseName
NiceName = getMemberName 2 entity.HasFSharpModuleSuffix memberName }
|> Some
| _ ->
// A reference to something external, currently assumed to be in .NET
let simple = getMemberName 2 false memberName
Expand Down
16 changes: 16 additions & 0 deletions tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,22 @@ let generateApiDocs (libraries: string list) (format: OutputFormat) useMdComment

do FSharp.Formatting.TestHelpers.enableLogging ()

[<Test>]
[<TestCaseSource("formats")>]
let ``ApiDocs seealso can find members`` (format: OutputFormat) =
let library = testBin </> "TestLib3.dll" |> fullpath

let files = generateApiDocs [ library ] format false "TestLib3"

let (textA, textB) =
if format = OutputFormat.Html then
"seealso.html#disposeOnUnmount", "seealso.html#unsubscribeOnUnmount"
else
"seealso#disposeOnUnmount", "seealso#unsubscribeOnUnmount"

files.[(sprintf "test-seealso.%s" format.Extension)] |> shouldContainText textA

files.[(sprintf "test-seealso.%s" format.Extension)] |> shouldContainText textB

[<Test>]
[<TestCaseSource("formats")>]
Expand Down
18 changes: 18 additions & 0 deletions tests/FSharp.ApiDocs.Tests/files/TestLib3/SeeAlso.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <summary>
/// DOM low-level helper functions
/// </summary>
module Test.SeeAlso

/// <summary>
/// Dispose all given items when the parent <c>SutilElement</c> is unmounted. Each item should implement <c>System.IDisposable</c>.
///
/// See also: <seealso cref="M:Test.SeeAlso.unsubscribeOnUnmount"/>
/// </summary>
let disposeOnUnmount (ds: System.IDisposable list) = ignore ds

/// <summary>
/// Call each function of type `(unit -> unit)` when the element is unmounted
///
/// See also: <seealso cref="M:Test.SeeAlso.disposeOnUnmount"/>
/// </summary>
let unsubscribeOnUnmount (ds: (unit -> unit) list) = ignore ds
1 change: 1 addition & 0 deletions tests/FSharp.ApiDocs.Tests/files/TestLib3/TestLib3.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<OutputPath>..\bin\$(Configuration)</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="SeeAlso.fs" />
<Compile Include="DOM.fs" />
<Compile Include="Library3.fs" />
<Compile Include="UndocumentedModule.fs" />
Expand Down