diff --git a/.editorconfig b/.editorconfig index 3c867811..17e3f289 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,5 +16,10 @@ fsharp_max_array_or_list_width=120 fsharp_max_infix_operator_expression=80 fsharp_max_value_binding_width=120 +# Specific settings for "View/HTML" related files +# It makes the code more consistent in term of spacing and indentation [GenerateHtml.fs] -fsharp_experimental_elmish = true \ No newline at end of file +fsharp_experimental_elmish = true +fsharp_multi_line_lambda_closing_newline = true +fsharp_multiline_bracket_style = aligned +fsharp_keep_max_number_of_blank_lines = 1 diff --git a/src/FSharp.Formatting.ApiDocs/GenerateHtml.fs b/src/FSharp.Formatting.ApiDocs/GenerateHtml.fs index a6067d23..da215d3d 100644 --- a/src/FSharp.Formatting.ApiDocs/GenerateHtml.fs +++ b/src/FSharp.Formatting.ApiDocs/GenerateHtml.fs @@ -42,20 +42,24 @@ type HtmlRender(model: ApiDocModel, ?menuTemplateFolder: string) = let id = UniqueID().ToString() code - [ OnMouseOut(sprintf "hideTip(event, '%s', %s)" id id) - OnMouseOver(sprintf "showTip(event, '%s', %s)" id id) ] + [ + OnMouseOut(sprintf "hideTip(event, '%s', %s)" id id) + OnMouseOver(sprintf "showTip(event, '%s', %s)" id id) + ] content div [ Class "fsdocs-tip"; Id id ] tip ] let sourceLink url = - [ match url with - | None -> () - | Some href -> - a [ Href href; Class "fsdocs-source-link"; HtmlProperties.Title "Source on GitHub" ] [ - iconifyIcon [ Icon "ri:github-fill"; Height "24"; Width "24" ] - ] ] + [ + match url with + | None -> () + | Some href -> + a [ Href href; Class "fsdocs-source-link"; HtmlProperties.Title "Source on GitHub" ] [ + iconifyIcon [ Icon "ri:github-fill"; Height "24"; Width "24" ] + ] + ] let removeParen (memberName: string) = let firstParen = memberName.IndexOf('(') @@ -74,10 +78,12 @@ type HtmlRender(model: ApiDocModel, ?menuTemplateFolder: string) = ] [ iconifyIcon [ HtmlProperties.Icon "bi:filetype-xml"; Height "24"; Width "24" ] ] let copyXmlSigIconForSymbol (symbol: FSharpSymbol) = - [ match symbol with - | :? FSharpMemberOrFunctionOrValue as v -> copyXmlSigIcon (removeParen v.XmlDocSig) - | :? FSharpEntity as v -> copyXmlSigIcon (removeParen v.XmlDocSig) - | _ -> () ] + [ + match symbol with + | :? FSharpMemberOrFunctionOrValue as v -> copyXmlSigIcon (removeParen v.XmlDocSig) + | :? FSharpEntity as v -> copyXmlSigIcon (removeParen v.XmlDocSig) + | _ -> () + ] // Copy XML sig for use in `cref` markdown let copyXmlSigIconMarkdown (xmlDocSig: string) = @@ -96,173 +102,180 @@ type HtmlRender(model: ApiDocModel, ?menuTemplateFolder: string) = ] [ iconifyIcon [ HtmlProperties.Icon "bi:filetype-md"; Height "24"; Width "24" ] ] let copyXmlSigIconForSymbolMarkdown (symbol: FSharpSymbol) = - [ match symbol with - | :? FSharpMemberOrFunctionOrValue as v -> copyXmlSigIconMarkdown (removeParen v.XmlDocSig) - | :? FSharpEntity as v -> copyXmlSigIconMarkdown (removeParen v.XmlDocSig) - | _ -> () ] + [ + match symbol with + | :? FSharpMemberOrFunctionOrValue as v -> copyXmlSigIconMarkdown (removeParen v.XmlDocSig) + | :? FSharpEntity as v -> copyXmlSigIconMarkdown (removeParen v.XmlDocSig) + | _ -> () + ] let renderMembers header tableHeader (members: ApiDocMember list) = - [ if members.Length > 0 then - h3 [] [ !!header ] - - table [ Class "table outer-list fsdocs-member-list" ] [ - thead [] [ - tr [] [ - td [ Class "fsdocs-member-list-header" ] [ !!tableHeader ] - td [ Class "fsdocs-member-list-header" ] [ !! "Description"; fsdocsDetailsToggle [] ] - ] - ] - tbody [] [ - for m in members do - tr [] [ - td [ Class "fsdocs-member-usage" ] [ - - codeWithToolTip [ - // This adds #MemberName anchor. These may be ambiguous due to overloading - p [] [ a [ Id m.Name ] [ a [ Href("#" + m.Name) ] [ embed m.UsageHtml ] ] ] - ] [ - div [ Class "member-tooltip" ] [ - !! "Full Usage: " - embed m.UsageHtml - br [] - br [] - if not m.Parameters.IsEmpty then - !! "Parameters: " - - ul [] [ - for p in m.Parameters do - span [] [ - b [] [ !!p.ParameterNameText ] - !! ":" - embed p.ParameterType - match p.ParameterDocs with - | None -> () - | Some d -> - !! " - " - embed d - ] - - br [] - ] - - br [] - match m.ReturnInfo.ReturnType with - | None -> () - | Some(_, rty) -> - span [] [ - !!(if m.Kind <> ApiDocMemberKind.RecordField then - "Returns: " - else - "Field type: ") - embed rty - ] - - match m.ReturnInfo.ReturnDocs with - | None -> () - | Some d -> embed d - - br [] - //!! "Signature: " - //encode(m.SignatureTooltip) - if not m.Modifiers.IsEmpty then - !! "Modifiers: " - encode (m.FormatModifiers) - br [] - - // We suppress the display of ill-formatted type parameters for places - // where these have not been explicitly declared - match m.FormatTypeArguments with - | None -> () - | Some v -> - !! "Type parameters: " - encode (v) - ] - ] - ] - - let smry = - div [ Class "fsdocs-summary" ] [ - fsdocsSummary m.Comment.Summary - div [ Class "icon-button-row" ] [ - yield! sourceLink m.SourceLocation - yield! copyXmlSigIconForSymbol m.Symbol - yield! copyXmlSigIconForSymbolMarkdown m.Symbol - ] - ] - - let dtls = - [ match m.Comment.Remarks with - | Some r -> p [ Class "fsdocs-remarks" ] [ embed r ] - | None -> () - - match m.ExtendedType with - | Some(_, extendedTypeHtml) -> p [] [ !! "Extended Type: "; embed extendedTypeHtml ] - | _ -> () - - if not m.Parameters.IsEmpty then - dl [ Class "fsdocs-params" ] [ - for parameter in m.Parameters do - dt [ Class "fsdocs-param" ] [ - span [ Class "fsdocs-param-name" ] [ !!parameter.ParameterNameText ] - !! ":" - embed parameter.ParameterType - ] + [ + if members.Length > 0 then + h3 [] [ !!header ] - dd [ Class "fsdocs-param-docs" ] [ - match parameter.ParameterDocs with - | None -> () - | Some d -> p [] [ embed d ] + table [ Class "table outer-list fsdocs-member-list" ] [ + thead [] [ + tr [] [ + td [ Class "fsdocs-member-list-header" ] [ !!tableHeader ] + td [ Class "fsdocs-member-list-header" ] [ !! "Description"; fsdocsDetailsToggle [] ] + ] + ] + tbody [] [ + for m in members do + tr [] [ + td [ Class "fsdocs-member-usage" ] [ + + codeWithToolTip [ + // This adds #MemberName anchor. These may be ambiguous due to overloading + p [] [ a [ Id m.Name ] [ a [ Href("#" + m.Name) ] [ embed m.UsageHtml ] ] ] + ] [ + div [ Class "member-tooltip" ] [ + !! "Full Usage: " + embed m.UsageHtml + br [] + br [] + if not m.Parameters.IsEmpty then + !! "Parameters: " + + ul [] [ + for p in m.Parameters do + span [] [ + b [] [ !!p.ParameterNameText ] + !! ":" + embed p.ParameterType + match p.ParameterDocs with + | None -> () + | Some d -> + !! " - " + embed d + ] + + br [] ] - ] - match m.ReturnInfo.ReturnType with - | None -> () - | Some(_, returnTypeHtml) -> - dl [ Class "fsdocs-returns" ] [ - dt [] [ - span [ Class "fsdocs-return-name" ] [ + br [] + match m.ReturnInfo.ReturnType with + | None -> () + | Some(_, rty) -> + span [] [ !!(if m.Kind <> ApiDocMemberKind.RecordField then "Returns: " else "Field type: ") + embed rty ] - embed returnTypeHtml - ] - dd [ Class "fsdocs-return-docs" ] [ + match m.ReturnInfo.ReturnDocs with | None -> () - | Some r -> p [] [ embed r ] - ] + | Some d -> embed d + + br [] + //!! "Signature: " + //encode(m.SignatureTooltip) + if not m.Modifiers.IsEmpty then + !! "Modifiers: " + encode (m.FormatModifiers) + br [] + + // We suppress the display of ill-formatted type parameters for places + // where these have not been explicitly declared + match m.FormatTypeArguments with + | None -> () + | Some v -> + !! "Type parameters: " + encode (v) ] + ] + ] + + let smry = + div [ Class "fsdocs-summary" ] [ + fsdocsSummary m.Comment.Summary + div [ Class "icon-button-row" ] [ + yield! sourceLink m.SourceLocation + yield! copyXmlSigIconForSymbol m.Symbol + yield! copyXmlSigIconForSymbolMarkdown m.Symbol + ] + ] - if not m.Comment.Exceptions.IsEmpty then - //p [] [ !! "Exceptions:" ] - table [ Class "fsdocs-exception-list" ] [ - for (nm, link, html) in m.Comment.Exceptions do - tr [] [ - td - [] - (match link with - | None -> [] - | Some href -> [ a [ Href href ] [ !!nm ] ]) - td [] [ embed html ] + let dtls = + [ + match m.Comment.Remarks with + | Some r -> p [ Class "fsdocs-remarks" ] [ embed r ] + | None -> () + + match m.ExtendedType with + | Some(_, extendedTypeHtml) -> + p [] [ !! "Extended Type: "; embed extendedTypeHtml ] + | _ -> () + + if not m.Parameters.IsEmpty then + dl [ Class "fsdocs-params" ] [ + for parameter in m.Parameters do + dt [ Class "fsdocs-param" ] [ + span [ Class "fsdocs-param-name" ] [ + !!parameter.ParameterNameText + ] + !! ":" + embed parameter.ParameterType + ] + + dd [ Class "fsdocs-param-docs" ] [ + match parameter.ParameterDocs with + | None -> () + | Some d -> p [] [ embed d ] + ] + ] + + match m.ReturnInfo.ReturnType with + | None -> () + | Some(_, returnTypeHtml) -> + dl [ Class "fsdocs-returns" ] [ + dt [] [ + span [ Class "fsdocs-return-name" ] [ + !!(if m.Kind <> ApiDocMemberKind.RecordField then + "Returns: " + else + "Field type: ") + ] + embed returnTypeHtml ] - ] + dd [ Class "fsdocs-return-docs" ] [ + match m.ReturnInfo.ReturnDocs with + | None -> () + | Some r -> p [] [ embed r ] + ] + ] - for e in m.Comment.Notes do - h5 [ Class "fsdocs-note-header" ] [ !! "Note" ] + if not m.Comment.Exceptions.IsEmpty then + //p [] [ !! "Exceptions:" ] + table [ Class "fsdocs-exception-list" ] [ + for (nm, link, html) in m.Comment.Exceptions do + tr [] [ + td + [] + (match link with + | None -> [] + | Some href -> [ a [ Href href ] [ !!nm ] ]) + td [] [ embed html ] + ] + ] - p [ Class "fsdocs-note" ] [ embed e ] + for e in m.Comment.Notes do + h5 [ Class "fsdocs-note-header" ] [ !! "Note" ] - for e in m.Comment.Examples do - h5 [ Class "fsdocs-example-header" ] [ !! "Example" ] + p [ Class "fsdocs-note" ] [ embed e ] - p [ - yield Class "fsdocs-example" - match e.Id with - | None -> () - | Some id -> yield Id id - ] [ embed e ] + for e in m.Comment.Examples do + h5 [ Class "fsdocs-example-header" ] [ !! "Example" ] + + p [ + yield Class "fsdocs-example" + match e.Id with + | None -> () + | Some id -> yield Id id + ] [ embed e ] //if m.IsObsolete then // obsoleteMessage m.ObsoleteMessage @@ -270,77 +283,80 @@ type HtmlRender(model: ApiDocModel, ?menuTemplateFolder: string) = // p [] [!!"CompiledName: "; code [] [!!m.Details.FormatCompiledName]] ] - td [ Class "fsdocs-member-xmldoc" ] [ - if List.isEmpty dtls then - smry - elif String.IsNullOrWhiteSpace(m.Comment.Summary.HtmlText) then - div [ Class "fsdocs-member-xmldoc-column" ] [ - div [ Class "icon-button-row" ] (sourceLink m.SourceLocation) - yield! dtls - ] - else - details [] ((summary [] [ smry ]) :: dtls) - ] - ] - ] - ] ] + td [ Class "fsdocs-member-xmldoc" ] [ + if List.isEmpty dtls then + smry + elif String.IsNullOrWhiteSpace(m.Comment.Summary.HtmlText) then + div [ Class "fsdocs-member-xmldoc-column" ] [ + div [ Class "icon-button-row" ] (sourceLink m.SourceLocation) + yield! dtls + ] + else + details [] ((summary [] [ smry ]) :: dtls) + ] + ] + ] + ] + ] let renderEntities (entities: ApiDocEntity list) = - [ if entities.Length > 0 then - let hasTypes = entities |> List.exists (fun e -> e.IsTypeDefinition) - - let hasModules = entities |> List.exists (fun e -> not e.IsTypeDefinition) - - table [ Class "table outer-list fsdocs-entity-list" ] [ - thead [] [ - tr [] [ - td [] [ - !!(if hasTypes && hasModules then "Type/Module" - elif hasTypes then "Type" - else "Modules") - ] - td [] [ !! "Description" ] - ] - ] - tbody [] [ - for e in entities do - tr [] [ - td [ Class "fsdocs-entity-name" ] [ - let nm = e.Name - - let multi = (entities |> List.filter (fun e -> e.Name = nm) |> List.length) > 1 - - let nmWithSiffix = - if multi then - (if e.IsTypeDefinition then - nm + " (Type)" - else - nm + " (Module)") - else - nm - - // This adds #EntityName anchor. These may currently be ambiguous - p [] [ - a [ Name nm ] [ - a [ Href(e.Url(root, collectionName, qualify, model.FileExtensions.InUrl)) ] [ - !!nmWithSiffix - ] - ] - ] - ] - td [ Class "fsdocs-entity-xmldoc" ] [ - div [] [ - fsdocsSummary e.Comment.Summary - div [ Class "icon-button-row" ] [ - yield! sourceLink e.SourceLocation - yield! copyXmlSigIconForSymbol e.Symbol - yield! copyXmlSigIconForSymbolMarkdown e.Symbol - ] - ] - ] - ] - ] - ] ] + [ + if entities.Length > 0 then + let hasTypes = entities |> List.exists (fun e -> e.IsTypeDefinition) + + let hasModules = entities |> List.exists (fun e -> not e.IsTypeDefinition) + + table [ Class "table outer-list fsdocs-entity-list" ] [ + thead [] [ + tr [] [ + td [] [ + !!(if hasTypes && hasModules then "Type/Module" + elif hasTypes then "Type" + else "Modules") + ] + td [] [ !! "Description" ] + ] + ] + tbody [] [ + for e in entities do + tr [] [ + td [ Class "fsdocs-entity-name" ] [ + let nm = e.Name + + let multi = (entities |> List.filter (fun e -> e.Name = nm) |> List.length) > 1 + + let nmWithSiffix = + if multi then + (if e.IsTypeDefinition then + nm + " (Type)" + else + nm + " (Module)") + else + nm + + // This adds #EntityName anchor. These may currently be ambiguous + p [] [ + a [ Name nm ] [ + a [ Href(e.Url(root, collectionName, qualify, model.FileExtensions.InUrl)) ] [ + !!nmWithSiffix + ] + ] + ] + ] + td [ Class "fsdocs-entity-xmldoc" ] [ + div [] [ + fsdocsSummary e.Comment.Summary + div [ Class "icon-button-row" ] [ + yield! sourceLink e.SourceLocation + yield! copyXmlSigIconForSymbol e.Symbol + yield! copyXmlSigIconForSymbolMarkdown e.Symbol + ] + ] + ] + ] + ] + ] + ] let entityContent (info: ApiDocEntityInfo) = // Get all the members & comment for the type @@ -355,267 +371,273 @@ type HtmlRender(model: ApiDocModel, ?menuTemplateFolder: string) = | Some m when m.RequiresQualifiedAccess -> m.Name + "." + entity.Name | _ -> entity.Name - [ h2 [] [ !!(usageName + (if entity.IsTypeDefinition then " Type" else " Module")) ] - dl [ Class "fsdocs-metadata" ] [ - dt [] [ - !! "Namespace: " - a [ Href(info.Namespace.Url(root, collectionName, qualify, model.FileExtensions.InUrl)) ] [ - !!info.Namespace.Name - ] - ] - dt [] [ !!("Assembly: " + entity.Assembly.Name + ".dll") ] - - match info.ParentModule with - | None -> () - | Some parentModule -> - dt [] [ - !! "Parent Module: " - a [ Href(parentModule.Url(root, collectionName, qualify, model.FileExtensions.InUrl)) ] [ - !!parentModule.Name - ] - ] - - - match entity.AbbreviatedType with - | Some(_, abbreviatedTypHtml) -> dt [] [ !! "Abbreviation For: "; embed abbreviatedTypHtml ] - - | None -> () - - match entity.BaseType with - | Some(_, baseTypeHtml) -> dt [] [ !! "Base Type: "; embed baseTypeHtml ] - | None -> () - - match entity.AllInterfaces with - | [] -> () - | l -> - dt [] [ - !!("All Interfaces: ") - for (i, (_, ityHtml)) in Seq.indexed l do - if i <> 0 then - !! ", " - - embed ityHtml - ] - - if entity.Symbol.IsValueType then - dt [] [ !!("Kind: Struct") ] - - match entity.DelegateSignature with - | Some(_, delegateSigHtml) -> dt [] [ !!("Delegate Signature: "); embed delegateSigHtml ] - | None -> () - - if entity.Symbol.IsProvided then - dt [] [ !!("This is a provided type definition") ] - - if entity.Symbol.IsAttributeType then - dt [] [ !!("This is an attribute type definition") ] - - if entity.Symbol.IsEnum then - dt [] [ !!("This is an enum type definition") ] - - //if info.Entity.IsObsolete then - // obsoleteMessage entity.ObsoleteMessage - ] - // Show the summary (and sectioned docs without any members) - div [ Class "fsdocs-xmldoc" ] [ - div [] [ - //yield! copyXmlSigIconForSymbol entity.Symbol - //yield! sourceLink entity.SourceLocation - fsdocsSummary entity.Comment.Summary - ] - // Show the remarks etc. - match entity.Comment.Remarks with - | Some r -> p [ Class "fsdocs-remarks" ] [ embed r ] - | None -> () - for note in entity.Comment.Notes do - h5 [ Class "fsdocs-note-header" ] [ !! "Note" ] - - p [ Class "fsdocs-note" ] [ embed note ] - - for example in entity.Comment.Examples do - h5 [ Class "fsdocs-example-header" ] [ !! "Example" ] - - p [ Class "fsdocs-example" ] [ embed example ] - - ] - - if (byCategory.Length > 1) then - // If there is more than 1 category in the type, generate TOC - h3 [] [ !! "Table of contents" ] - - ul [] [ - for (index, _, name) in byCategory do - li [] [ a [ Href(sprintf "#section%d" index) ] [ !!name ] ] - ] - - // - - let nestedEntities = entity.NestedEntities |> List.filter (fun e -> not e.IsObsolete) - - if (nestedEntities.Length > 0) then - div [] [ - h3 [] [ - !!(if nestedEntities |> List.forall (fun e -> not e.IsTypeDefinition) then - "Nested modules" - elif nestedEntities |> List.forall (fun e -> e.IsTypeDefinition) then - "Types" - else - "Types and nested modules") - ] - yield! renderEntities nestedEntities - ] - - for (index, ms, name) in byCategory do - // Iterate over all the categories and print members. If there are more than one - // categories, print the category heading (as