diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md
index 03afba56f0..f8d7f1c807 100644
--- a/src/Fable.Cli/CHANGELOG.md
+++ b/src/Fable.Cli/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+### Added
+
+* [Python] - Print root module and module function comments (by @alfonsogarciacaro)
+
## 5.0.0-alpha.9 - 2025-01-28
### Fixed
@@ -40,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
-* [Py] Add missing unicode categories in python library (by @joprice)
+* [Python] Add missing unicode categories in python library (by @joprice)
* [All] Log JSON output if we fail to parse MSBuild result (by @MangelMaxime)
## 5.0.0-alpha.5 - 2025-01-09
diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md
index 0d5286e860..d61905d0c9 100644
--- a/src/Fable.Compiler/CHANGELOG.md
+++ b/src/Fable.Compiler/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+### Added
+
+* [Python] - Print root module and module function comments (by @alfonsogarciacaro)
+
## 5.0.0-alpha.9 - 2025-01-28
### Fixed
@@ -40,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
-* [Py] Add missing unicode categories in python library (by @joprice)
+* [Python] Add missing unicode categories in python library (by @joprice)
* [All] Log JSON output if we fail to parse MSBuild result (by @MangelMaxime)
## 5.0.0-alpha.5 - 2025-01-09
diff --git a/src/Fable.Transforms/BabelPrinter.fs b/src/Fable.Transforms/BabelPrinter.fs
index 22f9612a43..b8503cf4b4 100644
--- a/src/Fable.Transforms/BabelPrinter.fs
+++ b/src/Fable.Transforms/BabelPrinter.fs
@@ -635,13 +635,11 @@ module PrinterExtensions =
match doc with
| None -> ()
| Some doc ->
- // TODO: Check docs with params, etc
- let regex = Regex(@"([\s\S]*?)", RegexOptions.Compiled)
+ let doc = ParsedXmlDoc.Parse doc
- let m = regex.Match(doc)
-
- if m.Success then
- let lines = m.Groups[1].Value.Trim().Split('\n')
+ match doc.Summary with
+ | Some summary ->
+ let lines = summary.Split('\n')
printer.Print("/**")
printer.PrintNewLine()
@@ -654,6 +652,7 @@ module PrinterExtensions =
printer.Print(" */")
printer.PrintNewLine()
+ | None -> ()
member printer.PrintDeclaration(decl: Declaration) =
match decl with
diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs
index 671875d80d..c59c844727 100644
--- a/src/Fable.Transforms/FSharp2Fable.Util.fs
+++ b/src/Fable.Transforms/FSharp2Fable.Util.fs
@@ -613,7 +613,7 @@ module Helpers =
match trimRootModule, ent.Path with
| TrimRootModule com, (Fable.SourcePath sourcePath | Fable.PrecompiledLib(sourcePath, _)) ->
- let rootMod = com.GetRootModule(sourcePath)
+ let rootMod, _ = com.GetRootModule(sourcePath)
if fullName.StartsWith(rootMod, StringComparison.Ordinal) then
fullName.Substring(rootMod.Length).TrimStart('.')
diff --git a/src/Fable.Transforms/FSharp2Fable.fs b/src/Fable.Transforms/FSharp2Fable.fs
index 3fbbbf6502..1035075af2 100644
--- a/src/Fable.Transforms/FSharp2Fable.fs
+++ b/src/Fable.Transforms/FSharp2Fable.fs
@@ -2113,14 +2113,14 @@ let rec getRootFSharpEntities (declarations: FSharpImplementationFileDeclaration
Seq.collect getRootFSharpEntitiesInner declarations
-let getRootModule (declarations: FSharpImplementationFileDeclaration list) =
+let getRootModule (declarations: FSharpImplementationFileDeclaration list) : string * (FSharpXmlDoc option) =
let rec getRootModuleInner outerEnt decls =
match decls, outerEnt with
| [ FSharpImplementationFileDeclaration.Entity(ent, decls) ], _ when ent.IsFSharpModule || ent.IsNamespace ->
getRootModuleInner (Some ent) decls
| CommonNamespace(ent, decls), _ -> getRootModuleInner (Some ent) decls
- | _, Some e -> FsEnt.FullName e
- | _, None -> ""
+ | _, Some e -> FsEnt.FullName e, Some e.XmlDoc
+ | _, None -> "", None
getRootModuleInner None declarations
diff --git a/src/Fable.Transforms/FSharp2Fable.fsi b/src/Fable.Transforms/FSharp2Fable.fsi
index c745083fc7..6db4a83b2d 100644
--- a/src/Fable.Transforms/FSharp2Fable.fsi
+++ b/src/Fable.Transforms/FSharp2Fable.fsi
@@ -6,7 +6,7 @@ open Fable.AST
val getRootFSharpEntities: declarations: FSharpImplementationFileDeclaration list -> FSharpEntity seq
-val getRootModule: declarations: FSharpImplementationFileDeclaration list -> string
+val getRootModule: declarations: FSharpImplementationFileDeclaration list -> string * FSharpXmlDoc option
val getInlineExprs:
fileName: string -> declarations: FSharpImplementationFileDeclaration list -> (string * InlineExprLazy) list
diff --git a/src/Fable.Transforms/Global/Compiler.fs b/src/Fable.Transforms/Global/Compiler.fs
index fb70c32991..64b7afe23d 100644
--- a/src/Fable.Transforms/Global/Compiler.fs
+++ b/src/Fable.Transforms/Global/Compiler.fs
@@ -76,7 +76,7 @@ type Compiler =
abstract GetImplementationFile: fileName: string -> FSharpImplementationFileDeclaration list
- abstract GetRootModule: fileName: string -> string
+ abstract GetRootModule: fileName: string -> string * FSharpXmlDoc option
abstract TryGetEntity: Fable.EntityRef -> Fable.Entity option
abstract GetInlineExpr: string -> InlineExpr
abstract AddWatchDependency: file: string -> unit
@@ -167,7 +167,7 @@ module CompilerExt =
member _.ProjectFile = com.ProjectFile
member _.SourceFiles = com.SourceFiles
member _.Options = com.Options
- member _.GetRootModule(fileName) = com.GetRootModule(fileName)
+ member _.GetRootModule(fileName) = com.GetRootModule(fileName) |> fst
member _.GetEntity(ref) = com.GetEntity(ref)
member _.GetMember(ref) = com.GetMember(ref)
diff --git a/src/Fable.Transforms/Php/Fable2Php.fs b/src/Fable.Transforms/Php/Fable2Php.fs
index 6a841cc5f1..651f3b1240 100644
--- a/src/Fable.Transforms/Php/Fable2Php.fs
+++ b/src/Fable.Transforms/Php/Fable2Php.fs
@@ -828,7 +828,7 @@ and getPhpTypeForEntity (com: IPhpCompiler) (entity: Fable.Entity) =
| Some path ->
match entity with
| :? Fable.Transforms.FSharp2Fable.FsEnt as fs ->
- let ns = com.GetRootModule(path) |> nsreplacement |> Some
+ let ns = com.GetRootModule(path) |> fst |> nsreplacement |> Some
{
Name = fixName fs.FSharpEntity.CompiledName
@@ -837,7 +837,7 @@ and getPhpTypeForEntity (com: IPhpCompiler) (entity: Fable.Entity) =
}
| _ ->
- let rootModule = com.GetRootModule(path)
+ let rootModule = com.GetRootModule(path) |> fst
{
Name = fixName entity.DisplayName
@@ -1408,7 +1408,7 @@ and convertValue (com: IPhpCompiler) (value: Fable.ValueKind) range =
match ent.Ref.SourcePath with
| Some p ->
com.AddRequire(p)
- Some(com.GetRootModule(p))
+ Some(com.GetRootModule(p) |> fst)
| None -> None
let t = withNamespace rootModule name
@@ -2169,7 +2169,7 @@ module Compiler =
let phpComp = PhpCompiler(com) :> IPhpCompiler
phpComp.ClearRequire(__SOURCE_DIRECTORY__ + @"/src/")
- let rootModule = com.GetRootModule(phpComp.CurrentFile) |> nsreplacement
+ let rootModule = com.GetRootModule(phpComp.CurrentFile) |> fst |> nsreplacement
phpComp.SetPhpNamespace(rootModule)
let decls =
diff --git a/src/Fable.Transforms/Printer.fs b/src/Fable.Transforms/Printer.fs
index b465a7b283..b274701e1a 100644
--- a/src/Fable.Transforms/Printer.fs
+++ b/src/Fable.Transforms/Printer.fs
@@ -2,9 +2,27 @@
module Fable.Transforms.Printer
open System
+open System.Text.RegularExpressions
open Fable
open Fable.AST
+type ParsedXmlDoc =
+ {
+ Summary: string option
+ }
+ // TODO: Parse the whole XML
+ static member Parse(xml: string) =
+ let regex = Regex(@"([\s\S]*?)", RegexOptions.Compiled)
+ let m = regex.Match(xml)
+
+ {
+ Summary =
+ if m.Success then
+ Some(m.Groups.[1].Value.Trim())
+ else
+ None
+ }
+
type Writer =
inherit IDisposable
diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs
index 742eb4a20a..92a73803ad 100644
--- a/src/Fable.Transforms/Python/Fable2Python.fs
+++ b/src/Fable.Transforms/Python/Fable2Python.fs
@@ -1487,10 +1487,10 @@ module Util =
|> Option.map Identifier
|> Option.defaultWith (fun _ -> Helpers.getUniqueIdentifier "_arrow")
- let func = createFunction ident args body [] returnType
+ let func = createFunction ident args body [] returnType None
Expression.name ident, [ func ]
- let createFunction name args body decoratorList returnType =
+ let createFunction name args body decoratorList returnType (comment: string option) =
let (|Awaitable|_|) expr =
match expr with
| Expression.Call {
@@ -1557,7 +1557,8 @@ module Util =
args = args,
body = body',
decoratorList = decoratorList,
- returns = returnType
+ returns = returnType,
+ ?comment = comment
)
| _ ->
Statement.functionDef (
@@ -1565,13 +1566,14 @@ module Util =
args = args,
body = body,
decoratorList = decoratorList,
- returns = returnType
+ returns = returnType,
+ ?comment = comment
)
let makeFunction name (args: Arguments, body: Expression, decoratorList, returnType) : Statement =
// printfn "makeFunction: %A" name
let body = wrapExprInBlockWithReturn (body, [])
- createFunction name args body decoratorList returnType
+ createFunction name args body decoratorList returnType None
let makeFunctionExpression
(com: IPythonCompiler)
@@ -3769,7 +3771,7 @@ module Util =
getMemberArgsAndBody com ctx (NonAttached membName) info.HasSpread args body
let name = com.GetIdentifier(ctx, membName)
- let stmt = createFunction name args body' [] returnType
+ let stmt = createFunction name args body' [] returnType info.XmlDoc
let expr = Expression.name name
info.Attributes
@@ -4461,8 +4463,13 @@ module Compiler =
//printfn "file: %A" file.Declarations
let rootDecls = List.collect (transformDeclaration com ctx) file.Declarations
+ let rootComment =
+ com.GetRootModule(com.CurrentFile)
+ |> snd
+ |> Option.bind FSharp2Fable.TypeHelpers.tryGetXmlDoc
+
let typeVars = com.GetAllTypeVars() |> transformTypeVars com ctx
let importDecls = com.GetAllImports() |> transformImports com
let exports = com.GetAllExports() |> transformExports com ctx
let body = importDecls @ typeVars @ rootDecls @ exports
- Module.module' body
+ Module.module' (body, ?comment = rootComment)
diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs
index 41b2ba6a72..4bc49840d5 100644
--- a/src/Fable.Transforms/Python/Python.fs
+++ b/src/Fable.Transforms/Python/Python.fs
@@ -124,7 +124,11 @@ type Statement =
| FunctionDef of FunctionDef
| AsyncFunctionDef of AsyncFunctionDef
-type Module = { Body: Statement list }
+type Module =
+ {
+ Body: Statement list
+ Comment: string option
+ }
/// Both parameters are raw strings of the names. asname can be None if the regular name is to be used.
///
@@ -425,6 +429,7 @@ type FunctionDef =
DecoratorList: Expression list
Returns: Expression option
TypeComment: string option
+ Comment: string option
}
/// global and nonlocal statements. names is a list of raw strings.
@@ -485,9 +490,10 @@ type AsyncFunctionDef =
DecoratorList: Expression list
Returns: Expression option
TypeComment: string option
+ Comment: string option
}
- static member Create(name, args, body, decoratorList, ?returns, ?typeComment) =
+ static member Create(name, args, body, decoratorList, ?returns, ?typeComment, ?comment) =
{
Name = name
Args = args
@@ -495,6 +501,7 @@ type AsyncFunctionDef =
DecoratorList = decoratorList
Returns = returns
TypeComment = typeComment
+ Comment = comment
}
/// An import statement. names is a list of alias nodes.
@@ -902,7 +909,7 @@ module PythonExtensions =
}
|> ClassDef
- static member functionDef(name, args, body, ?decoratorList, ?returns, ?typeComment) : Statement =
+ static member functionDef(name, args, body, ?decoratorList, ?returns, ?typeComment, ?comment) : Statement =
{
FunctionDef.Name = name
Args = args
@@ -910,10 +917,11 @@ module PythonExtensions =
DecoratorList = defaultArg decoratorList []
Returns = returns
TypeComment = typeComment
+ Comment = comment
}
|> FunctionDef
- static member asyncFunctionDef(name, args, body, ?decoratorList, ?returns, ?typeComment) : Statement =
+ static member asyncFunctionDef(name, args, body, ?decoratorList, ?returns, ?typeComment, ?comment) : Statement =
{
AsyncFunctionDef.Name = name
Args = args
@@ -921,6 +929,7 @@ module PythonExtensions =
DecoratorList = defaultArg decoratorList []
Returns = returns
TypeComment = typeComment
+ Comment = comment
}
|> AsyncFunctionDef
@@ -1215,7 +1224,11 @@ module PythonExtensions =
type Module with
- static member module'(body) = { Body = body }
+ static member module'(body, ?comment) =
+ {
+ Body = body
+ Comment = comment
+ }
type Arg with
diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs
index 8cb6fd6496..bfd1553079 100644
--- a/src/Fable.Transforms/Python/PythonPrinter.fs
+++ b/src/Fable.Transforms/Python/PythonPrinter.fs
@@ -226,6 +226,7 @@ module PrinterExtensions =
func.Body,
func.Returns,
func.DecoratorList,
+ ?comment = func.Comment,
isDeclaration = true
)
@@ -238,6 +239,7 @@ module PrinterExtensions =
func.Body,
func.Returns,
func.DecoratorList,
+ ?comment = func.Comment,
isDeclaration = true,
isAsync = true
)
@@ -753,6 +755,7 @@ module PrinterExtensions =
body: Statement list,
returnType: Expression option,
decoratorList: Expression list,
+ ?comment: string,
?isDeclaration,
?isAsync
)
@@ -777,6 +780,24 @@ module PrinterExtensions =
printer.PrintOptional(returnType)
printer.Print(":")
+
+ match Option.map ParsedXmlDoc.Parse comment with
+ | Some { Summary = Some summary } ->
+ let lines = summary.Split('\n')
+
+ printer.PrintNewLine()
+ printer.PushIndentation()
+ printer.Print("\"\"\"")
+
+ for line in lines do
+ let line = Naming.xmlDecode line
+ printer.Print(line.Trim())
+ printer.PrintNewLine()
+
+ printer.Print("\"\"\"")
+ printer.PopIndentation()
+ | _ -> ()
+
printer.PrintBlock(body, skipNewLineAtEnd = true)
member printer.WithParens(expr: Expression) =
@@ -823,6 +844,20 @@ let run writer (program: Module) : Async =
use printerImpl = new PrinterImpl(writer)
let printer = printerImpl :> Printer
+
+ match Option.map ParsedXmlDoc.Parse program.Comment with
+ | Some { Summary = Some summary } ->
+ printer.Print("\"\"\"")
+
+ for line in summary.Split('\n') do
+ let line = Naming.xmlDecode line
+ printer.Print(line.Trim())
+ printer.PrintNewLine()
+
+ printer.Print("\"\"\"")
+ printer.PrintNewLine()
+ | _ -> ()
+
let imports, restDecls =
program.Body
|> List.splitWhile (
diff --git a/src/Fable.Transforms/State.fs b/src/Fable.Transforms/State.fs
index 73e2721b2d..5d2304bf4b 100644
--- a/src/Fable.Transforms/State.fs
+++ b/src/Fable.Transforms/State.fs
@@ -123,6 +123,7 @@ type ImplFile =
{
Declarations: FSharpImplementationFileDeclaration list
RootModule: string
+ RootComment: FSharpXmlDoc option
Entities: IReadOnlyDictionary
InlineExprs: (string * InlineExprLazy) list
}
@@ -147,11 +148,13 @@ type ImplFile =
[]
FSharp2Fable.Compiler.getRootFSharpEntities declarations |> loop entities
+ let rootModule, rootComment = FSharp2Fable.Compiler.getRootModule declarations
{
Declarations = declarations
Entities = entities
- RootModule = FSharp2Fable.Compiler.getRootModule declarations
+ RootModule = rootModule
+ RootComment = rootComment
InlineExprs = FSharp2Fable.Compiler.getInlineExprs file.FileName declarations
}
@@ -336,17 +339,17 @@ type CompilerImpl
let fileName = Path.normalizePathAndEnsureFsExtension fileName
match Dictionary.tryFind fileName project.ImplementationFiles with
- | Some file -> file.RootModule
+ | Some file -> file.RootModule, file.RootComment
| None ->
match project.PrecompiledInfo.TryGetRootModule(fileName) with
- | Some r -> r
+ | Some r -> r, None
| None ->
let msg =
$"Cannot find root module for {fileName}. If this belongs to a package, make sure it includes the source files."
(this :> Compiler).AddLog(msg, Severity.Warning, fileName = currentFile)
- "" // failwith msg
+ "", None // failwith msg
member _.TryGetEntity(entityRef: Fable.EntityRef) =
match entityRef.Path with