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