Skip to content
Open
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
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixed
* Fix tooltip not being interactive: moving the mouse from a code token into its tooltip now keeps the tooltip open, allowing users to select and copy the tooltip text. [#949](https://github.com/fsprojects/FSharp.Formatting/issues/949)
* `fsdocs watch` rebuilds a page when a file its script depends on changes, following `#load` transitively and `#r` to local files, wherever those files are (a dot folder, outside the input folder). The directives are read from the syntax tree, so a `#load` in a comment does not count. [#1309](https://github.com/fsprojects/FSharp.Formatting/issues/1309)
* API docs keep the comment of a type abbreviation whose target is a tuple, a list or a BCL type such as `string`, instead of dropping it with "The entity ... was not registered before". An abbreviation page no longer lists the members of the target type (`Item1`, `Item2`, ...) as its own. [#1314](https://github.com/fsprojects/FSharp.Formatting/issues/1314)

## [23.0.0-alpha.1] - 2026-09-08

Expand Down
28 changes: 19 additions & 9 deletions src/FSharp.Formatting.ApiDocs/CrossReferenceResolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,12 @@ type internal CrossReferenceResolver(root, collectionName, qualify, extensions)
for nested in entity.NestedEntities do
registerEntity nested

for memb in entity.TryGetMembersFunctionsAndValues() do
registerMember memb
// A type abbreviation has no members of its own: the compiler reports the members of the
// abbreviated type (e.g. System.Tuple.Item1), whose declaring entity is not part of this
// documentation set. Registering them would make them look local.
if not entity.IsFSharpAbbreviation then
for memb in entity.TryGetMembersFunctionsAndValues() do
registerMember memb

/// Returns the previously-assigned URL base name for a registered entity,
/// raising an exception if the entity has not been registered.
Expand Down Expand Up @@ -473,13 +477,19 @@ type internal CrossReferenceResolver(root, collectionName, qualify, extensions)
match mfv.DeclaringEntity with
| None -> failwith $"%s{mfv.DisplayName} does not have a DeclaringEntity"
| Some declaringEntity ->
let entityUrlBaseName = getUrlBaseNameForRegisteredEntity declaringEntity

{
IsInternal = true
ReferenceLink = internalCrossReferenceForMember entityUrlBaseName mfv
NiceName = declaringEntity.DisplayName + "." + mfv.DisplayName
}
match registeredSymbolsToUrlBaseName.TryGetValue(declaringEntity) with
| true, entityUrlBaseName ->
{
IsInternal = true
ReferenceLink = internalCrossReferenceForMember entityUrlBaseName mfv
NiceName = declaringEntity.DisplayName + "." + mfv.DisplayName
}
| _ ->
// The declaring entity is not part of this documentation set (e.g. a member of
// System.Tuple reached through a type abbreviation), so link externally instead.
let typeName = defaultArg declaringEntity.TryFullName declaringEntity.DisplayName
let memberName = typeName + "." + mfv.DisplayName
externalDocsLink true (declaringEntity.DisplayName + "." + mfv.DisplayName) typeName memberName

/// Tries to resolve a cross-reference for a member given its XML doc signature
/// (must start with <c>"M:"</c>, <c>"P:"</c>, <c>"F:"</c>, or <c>"E:"</c>).
Expand Down
7 changes: 6 additions & 1 deletion src/FSharp.Formatting.ApiDocs/SymbolReader.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1014,9 +1014,14 @@ module internal SymbolReader =
readCommentsInto typ ctx xmlDocSig (fun cat catidx exclude _cmds comment ->
let entityUrl = ctx.UrlMap.ResolveUrlBaseNameForEntity typ

// A type abbreviation declares no members of its own. The compiler reports the
// members of the abbreviated type (e.g. Item1/Item2 for a tuple), which belong to
// and are documented on that target type, so they must not be listed here.
let rec getMembers (typ: FSharpEntity) =
[
yield! typ.MembersFunctionsAndValues
if not typ.IsFSharpAbbreviation then
yield! typ.MembersFunctionsAndValues

match typ.BaseType with
| Some baseType ->
let loc = typ.DeclarationLocation
Expand Down
31 changes: 31 additions & 0 deletions tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,37 @@ let ``ApiDocs InheritedMembers is populated for derived types (issue 590)`` () =
memberNames |> shouldContain "BaseMethod"
memberNames |> shouldContain "BaseStaticMethod"

[<Test>]
let ``ApiDocs reads comments of type abbreviations to tuples, lists and BCL types (issue 1314)`` () =
let libraries = [ testBin </> "FsLib2.dll" ]

// warn=true is what triggers the cross-reference lookup that used to throw
let inputs = [ for lib in libraries -> ApiDocInput.FromFile(lib, mdcomments = false, warn = true) ]

let model =
ApiDocs.GenerateModel(inputs, collectionName = "FsLib", substitutions = substitutions, libDirs = [ testBin ])

let abbreviations =
model.Collection.Namespaces.[0].Entities
|> List.find (fun e -> e.Name = "Abbreviations")

let find name =
abbreviations.NestedEntities |> List.find (fun e -> e.Name = name)

let expectations =
[
"Position01", "A position as a zero-based line and column"
"LongIdent", "A long identifier"
"FileName", "A file name"
]

for name, summary in expectations do
let entity = find name
entity.Comment.Summary.HtmlText |> shouldContainText summary
entity.AbbreviatedType.IsSome |> shouldEqual true
// An abbreviation declares no members; the target type's members must not leak in
entity.AllMembers |> shouldEqual []

[<Test>]
[<TestCaseSource("formats")>]
let ``ApiDocs renders inherited members section in output (issue 590)`` (format: OutputFormat) =
Expand Down
11 changes: 11 additions & 0 deletions tests/FSharp.ApiDocs.Tests/files/FsLib2/Library2.fs
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,14 @@ module TypeConstraintTests =
member _.Value = value
/// Compares with another wrapper
member x.CompareTo(other: ComparisonWrapper<'T>) = compare x.Value other.Value

/// Type abbreviations whose target is a tuple, a list or a BCL type (issue 1314)
module Abbreviations =
/// A position as a zero-based line and column
type Position01 = int * int

/// A long identifier
type LongIdent = string list

/// A file name
type FileName = string
Loading