From a2f322ba12fe76d74f7d086cbfc4e39256891540 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Sun, 29 Oct 2023 22:26:02 -0400 Subject: [PATCH] Added infrastructure for supporting writing documentation comments. --- .../CodeWriters/Swift/SwiftDocComment.swift | 31 ++++++ .../Swift/SyntaxWriters/DocComments.swift | 43 ++++++++ .../Swift/SyntaxWriters/Members.swift | 15 ++- .../Swift/SyntaxWriters/Protocols.swift | 6 ++ .../SyntaxWriters/TypeDeclarations.swift | 8 ++ .../SwiftProjection+Module.swift | 79 ++++++++++++++ .../SwiftProjection+conversion.swift | 22 ++-- .../ProjectionGenerator/SwiftProjection.swift | 101 ++++++------------ Generator/Sources/SwiftWinRT/EntryPoint.swift | 4 +- 9 files changed, 229 insertions(+), 80 deletions(-) create mode 100644 Generator/Sources/CodeWriters/Swift/SwiftDocComment.swift create mode 100644 Generator/Sources/CodeWriters/Swift/SyntaxWriters/DocComments.swift create mode 100644 Generator/Sources/ProjectionGenerator/SwiftProjection+Module.swift diff --git a/Generator/Sources/CodeWriters/Swift/SwiftDocComment.swift b/Generator/Sources/CodeWriters/Swift/SwiftDocComment.swift new file mode 100644 index 00000000..17b5b3f1 --- /dev/null +++ b/Generator/Sources/CodeWriters/Swift/SwiftDocComment.swift @@ -0,0 +1,31 @@ +public struct SwiftDocComment { + public var summary: [Block]? + public var parameters = [Param]() + public var returns: [Span]? + + public init() {} + + public enum Block: Hashable { + case paragraph([Span]) + case code(String) + case list([Span]) + + public static func paragraph(_ span: Span) -> Block { .paragraph([span]) } + public static func paragraph(_ text: String) -> Block { .paragraph(.text(text)) } + } + + public enum Span: Hashable { + case text(String) + case code(String) + } + + public struct Param { + public var name: String + public var description: String + + public init(name: String, description: String) { + self.name = name + self.description = description + } + } +} \ No newline at end of file diff --git a/Generator/Sources/CodeWriters/Swift/SyntaxWriters/DocComments.swift b/Generator/Sources/CodeWriters/Swift/SyntaxWriters/DocComments.swift new file mode 100644 index 00000000..ae3136f9 --- /dev/null +++ b/Generator/Sources/CodeWriters/Swift/SyntaxWriters/DocComments.swift @@ -0,0 +1,43 @@ + +extension SwiftSyntaxWriter { + internal func writeDocComment(_ docComment: SwiftDocComment) { + output.writeLine("/**") + if let summary = docComment.summary { + for block in summary { writeDocCommentBlock(block) } + } + + for param in docComment.parameters { + output.write("- Parameter ") + output.write(param.name) + output.write(": ") + output.write(param.description) + output.endLine() + } + + if let returns = docComment.returns { + output.write("- Returns: ") + for span in returns { writeDocCommentSpan(span) } + output.endLine() + } + + output.writeLine("*/") + } + + fileprivate func writeDocCommentBlock(_ block: SwiftDocComment.Block) { + switch block { + case .paragraph(let spans): + for span in spans { writeDocCommentSpan(span) } + output.endLine() + default: + return // TODO: Support more block types + } + } + + fileprivate func writeDocCommentSpan(_ span: SwiftDocComment.Span) { + switch span { + case .text(let string): output.write(string) + default: + return // TODO: Support more inline types + } + } +} \ No newline at end of file diff --git a/Generator/Sources/CodeWriters/Swift/SyntaxWriters/Members.swift b/Generator/Sources/CodeWriters/Swift/SyntaxWriters/Members.swift index 38f2eb5a..013b248a 100644 --- a/Generator/Sources/CodeWriters/Swift/SyntaxWriters/Members.swift +++ b/Generator/Sources/CodeWriters/Swift/SyntaxWriters/Members.swift @@ -1,5 +1,6 @@ extension SwiftDeclarationWriter { public func writeStoredProperty( + docComments: SwiftDocComment? = nil, visibility: SwiftVisibility = .implicit, setVisibility: SwiftVisibility = .implicit, static: Bool = false, @@ -13,6 +14,7 @@ extension SwiftDeclarationWriter { precondition(initialValue == nil || initializer == nil) var output = output + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .withName("storedProperty")) visibility.write(to: &output, trailingSpace: true) if setVisibility != .implicit { @@ -39,6 +41,7 @@ extension SwiftDeclarationWriter { } public func writeComputedProperty( + docComments: SwiftDocComment? = nil, visibility: SwiftVisibility = .implicit, static: Bool = false, override: Bool = false, @@ -51,6 +54,7 @@ extension SwiftDeclarationWriter { precondition(set == nil || !`throws`) var output = output + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .never) visibility.write(to: &output, trailingSpace: true) if `static` { output.write("static ") } @@ -84,6 +88,7 @@ extension SwiftDeclarationWriter { } public func writeFunc( + docComments: SwiftDocComment? = nil, visibility: SwiftVisibility = .implicit, static: Bool = false, override: Bool = false, @@ -94,6 +99,7 @@ extension SwiftDeclarationWriter { returnType: SwiftType? = nil, body: (inout SwiftStatementWriter) throws -> Void) rethrows { + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .never) writeFuncHeader( visibility: visibility, @@ -110,8 +116,13 @@ extension SwiftDeclarationWriter { } } - public func writeEnumCase(name: String, rawValue: String? = nil) { + public func writeEnumCase( + docComments: SwiftDocComment? = nil, + name: String, + rawValue: String? = nil) { + var output = output + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .withName("case")) output.write("case ") SwiftIdentifier.write(name, to: &output) @@ -125,6 +136,7 @@ extension SwiftDeclarationWriter { } public func writeInit( + docComments: SwiftDocComment? = nil, visibility: SwiftVisibility = .implicit, required: Bool = false, convenience: Bool = false, @@ -135,6 +147,7 @@ extension SwiftDeclarationWriter { body: (inout SwiftStatementWriter) throws -> Void) rethrows { var output = output + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .never) visibility.write(to: &output, trailingSpace: true) if `required` { output.write("required ") } diff --git a/Generator/Sources/CodeWriters/Swift/SyntaxWriters/Protocols.swift b/Generator/Sources/CodeWriters/Swift/SyntaxWriters/Protocols.swift index 78a09f2a..c7523c1c 100644 --- a/Generator/Sources/CodeWriters/Swift/SyntaxWriters/Protocols.swift +++ b/Generator/Sources/CodeWriters/Swift/SyntaxWriters/Protocols.swift @@ -1,5 +1,6 @@ extension SwiftSourceFileWriter { public func writeProtocol( + docComments: SwiftDocComment? = nil, visibility: SwiftVisibility = .implicit, name: String, typeParameters: [String] = [], @@ -8,6 +9,7 @@ extension SwiftSourceFileWriter { members: (SwiftProtocolBodyWriter) throws -> Void) rethrows { var output = output + if let docComments = docComments { writeDocComment(docComments) } output.beginLine(grouping: .never) visibility.write(to: &output, trailingSpace: true) output.write("protocol ") @@ -36,6 +38,7 @@ public struct SwiftProtocolBodyWriter: SwiftSyntaxWriter { } public func writeProperty( + docComments: SwiftDocComment? = nil, static: Bool = false, name: String, type: SwiftType, @@ -45,6 +48,7 @@ public struct SwiftProtocolBodyWriter: SwiftSyntaxWriter { precondition(!set || !`throws`) var output = output + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .withName("protocolProperty")) if `static` { output.write("static ") } output.write("var ") @@ -58,6 +62,7 @@ public struct SwiftProtocolBodyWriter: SwiftSyntaxWriter { } public func writeFunc( + docComments: SwiftDocComment? = nil, isPropertySetter: Bool = false, static: Bool = false, name: String, @@ -66,6 +71,7 @@ public struct SwiftProtocolBodyWriter: SwiftSyntaxWriter { throws: Bool = false, returnType: SwiftType? = nil) { + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .withName(isPropertySetter ? "protocolProperty" : "protocolFunc")) writeFuncHeader( visibility: .implicit, diff --git a/Generator/Sources/CodeWriters/Swift/SyntaxWriters/TypeDeclarations.swift b/Generator/Sources/CodeWriters/Swift/SyntaxWriters/TypeDeclarations.swift index 3fc15a15..9379d404 100644 --- a/Generator/Sources/CodeWriters/Swift/SyntaxWriters/TypeDeclarations.swift +++ b/Generator/Sources/CodeWriters/Swift/SyntaxWriters/TypeDeclarations.swift @@ -4,6 +4,7 @@ public struct SwiftTypeDefinitionWriter: SwiftDeclarationWriter { extension SwiftDeclarationWriter { public func writeClass( + docComments: SwiftDocComment? = nil, visibility: SwiftVisibility = .implicit, final: Bool = false, name: String, @@ -13,6 +14,7 @@ extension SwiftDeclarationWriter { definition: (SwiftTypeDefinitionWriter) throws -> Void) rethrows { var output = output + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .never) visibility.write(to: &output, trailingSpace: true) if final { output.write("final ") } @@ -26,6 +28,7 @@ extension SwiftDeclarationWriter { } public func writeStruct( + docComments: SwiftDocComment? = nil, visibility: SwiftVisibility = .implicit, name: String, typeParameters: [String] = [], @@ -33,6 +36,7 @@ extension SwiftDeclarationWriter { definition: (SwiftTypeDefinitionWriter) throws -> Void) rethrows { var output = output + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .never) visibility.write(to: &output, trailingSpace: true) output.write("struct ") @@ -45,6 +49,7 @@ extension SwiftDeclarationWriter { } public func writeEnum( + docComments: SwiftDocComment? = nil, visibility: SwiftVisibility = .implicit, name: String, typeParameters: [String] = [], @@ -53,6 +58,7 @@ extension SwiftDeclarationWriter { definition: (SwiftTypeDefinitionWriter) throws -> Void) rethrows { var output = output + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .never) visibility.write(to: &output, trailingSpace: true) output.write("enum ") @@ -65,12 +71,14 @@ extension SwiftDeclarationWriter { } public func writeTypeAlias( + docComments: SwiftDocComment? = nil, visibility: SwiftVisibility = .implicit, name: String, typeParameters: [String] = [], target: SwiftType) { var output = output + if let docComments { writeDocComment(docComments) } output.beginLine(grouping: .withName("typealias")) visibility.write(to: &output, trailingSpace: true) output.write("typealias ") diff --git a/Generator/Sources/ProjectionGenerator/SwiftProjection+Module.swift b/Generator/Sources/ProjectionGenerator/SwiftProjection+Module.swift new file mode 100644 index 00000000..18da1a55 --- /dev/null +++ b/Generator/Sources/ProjectionGenerator/SwiftProjection+Module.swift @@ -0,0 +1,79 @@ +import DotNetMetadata +import DotNetXMLDocs + +extension SwiftProjection { + public class Module { + public unowned let projection: SwiftProjection + public let shortName: String + public private(set) var typeDefinitionsByNamespace = [String: Set]() + public private(set) var closedGenericTypesByDefinition = [TypeDefinition: [[TypeNode]]]() + private(set) var references: Set = [] + + internal init(projection: SwiftProjection, shortName: String) { + self.projection = projection + self.shortName = shortName + } + + public var assemblyModuleName: String { shortName + "Assembly" } + + public func addAssembly(_ assembly: Assembly, documentation: DocumentationFile? = nil) { + projection.assembliesToModules[assembly] = AssemblyEntry(module: self, documentation: documentation) + } + + public func hasTypeDefinition(_ type: TypeDefinition) -> Bool { + typeDefinitionsByNamespace[Module.getNamespaceOrEmpty(type)]?.contains(type) ?? false + } + + public func addTypeDefinition(_ type: TypeDefinition) { + precondition(projection.getModule(type.assembly) === self) + typeDefinitionsByNamespace[Module.getNamespaceOrEmpty(type), default: Set()].insert(type) + } + + public func addClosedGenericType(_ type: BoundType) { + precondition(!type.genericArgs.isEmpty && !type.isParameterized) + closedGenericTypesByDefinition[type.definition, default: []].append(type.genericArgs) + } + + public func addReference(_ other: Module) { + references.insert(Reference(target: other)) + } + + internal func getName(_ typeDefinition: TypeDefinition, namespaced: Bool = true) throws -> String { + // Map: Namespace.TypeName + // To: Namespace_TypeName + // Map: Namespace.Subnamespace.TypeName/NestedTypeName + // To: NamespaceSubnamespace_TypeName_NestedTypeName + var result: String = "" + if let enclosingType = try typeDefinition.enclosingType { + result += try getName(enclosingType, namespaced: namespaced) + "_" + } + else if namespaced { + result += typeDefinition.namespace.flatMap { SwiftProjection.toCompactNamespace($0) + "_" } ?? "" + } + + result += typeDefinition.nameWithoutGenericSuffix + + return result + } + + private static func getNamespaceOrEmpty(_ type: TypeDefinition) -> String { + var namespacedType = type + while namespacedType.namespace == nil, let enclosingType = try? namespacedType.enclosingType { + namespacedType = enclosingType + } + return namespacedType.namespace ?? "" + } + + struct Reference: Hashable { + unowned var target: Module + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(target)) + } + + static func == (lhs: Module.Reference, rhs: Module.Reference) -> Bool { + lhs.target === rhs.target + } + } + } +} \ No newline at end of file diff --git a/Generator/Sources/ProjectionGenerator/SwiftProjection+conversion.swift b/Generator/Sources/ProjectionGenerator/SwiftProjection+conversion.swift index 06d8d6ff..a6c0d68a 100644 --- a/Generator/Sources/ProjectionGenerator/SwiftProjection+conversion.swift +++ b/Generator/Sources/ProjectionGenerator/SwiftProjection+conversion.swift @@ -1,5 +1,6 @@ -import DotNetMetadata import CodeWriters +import DotNetMetadata +import DotNetXMLDocs extension SwiftProjection { static func toVisibility(_ visibility: DotNetMetadata.Visibility, inheritableClass: Bool = false) -> SwiftVisibility { @@ -12,6 +13,11 @@ extension SwiftProjection { } } + static func toDocComments(_ entry: MemberEntry) -> SwiftDocComment { + // TODO: Convert doc comments + return SwiftDocComment() + } + // Windows.Foundation.Collections to WindowsFoundationCollections public static func toCompactNamespace(_ namespace: String) -> String { namespace.replacing(".", with: "") @@ -39,17 +45,17 @@ extension SwiftProjection { } } - func toTypeName(_ type: TypeDefinition, namespaced: Bool = true) throws -> String { - try assembliesToModules[type.assembly]!.getName(type, namespaced: namespaced) + func toTypeName(_ typeDefinition: TypeDefinition, namespaced: Bool = true) throws -> String { + try getModule(typeDefinition.assembly)!.getName(typeDefinition, namespaced: namespaced) } - func toProtocolName(_ type: InterfaceDefinition, namespaced: Bool = true) throws -> String { - try toTypeName(type, namespaced: namespaced) + "Protocol" + func toProtocolName(_ typeDefinition: InterfaceDefinition, namespaced: Bool = true) throws -> String { + try toTypeName(typeDefinition, namespaced: namespaced) + "Protocol" } - public func toProjectionTypeName(_ type: TypeDefinition, namespaced: Bool = true) throws -> String { - var typeName = try toTypeName(type, namespaced: namespaced) - if type is InterfaceDefinition || type is DelegateDefinition { + public func toProjectionTypeName(_ typeDefinition: TypeDefinition, namespaced: Bool = true) throws -> String { + var typeName = try toTypeName(typeDefinition, namespaced: namespaced) + if typeDefinition is InterfaceDefinition || typeDefinition is DelegateDefinition { // protocols and function pointers cannot serve as the projection class, // so an accompanying type provides the ABIProjection conformance. typeName += "Projection" diff --git a/Generator/Sources/ProjectionGenerator/SwiftProjection.swift b/Generator/Sources/ProjectionGenerator/SwiftProjection.swift index b72dba4b..e0b84c8e 100644 --- a/Generator/Sources/ProjectionGenerator/SwiftProjection.swift +++ b/Generator/Sources/ProjectionGenerator/SwiftProjection.swift @@ -1,8 +1,14 @@ import DotNetMetadata +import DotNetXMLDocs public class SwiftProjection { - public private(set) var modulesByShortName: [String: Module] = .init() - public private(set) var assembliesToModules: [Assembly: Module] = .init() + internal struct AssemblyEntry { + var module: Module + var documentation: DocumentationFile? + } + + public private(set) var modulesByShortName = [String: Module]() + internal var assembliesToModules = [Assembly: AssemblyEntry]() public let abiModuleName: String public var referenceReturnNullability: ReferenceNullability { .explicit } @@ -16,78 +22,35 @@ public class SwiftProjection { return module } - public class Module { - public unowned let projection: SwiftProjection - public let shortName: String - public private(set) var typeDefinitionsByNamespace: [String: Set] = .init() - public private(set) var closedGenericTypesByDefinition: [TypeDefinition: [[TypeNode]]] = .init() - private(set) var references: Set = [] - - internal init(projection: SwiftProjection, shortName: String) { - self.projection = projection - self.shortName = shortName - } - - var assemblyModuleName: String { shortName + "Assembly" } - - func getName(_ type: TypeDefinition, namespaced: Bool = true) throws -> String { - // Map: Namespace.TypeName - // To: Namespace_TypeName - // Map: Namespace.Subnamespace.TypeName/NestedTypeName - // To: NamespaceSubnamespace_TypeName_NestedTypeName - var result: String = "" - if let enclosingType = try type.enclosingType { - result += try getName(enclosingType, namespaced: namespaced) + "_" - } - else if namespaced { - result += type.namespace.flatMap { SwiftProjection.toCompactNamespace($0) + "_" } ?? "" - } - - result += type.nameWithoutGenericSuffix - - return result - } - - public func addAssembly(_ assembly: Assembly) { - projection.assembliesToModules[assembly] = self - } - - public func hasTypeDefinition(_ type: TypeDefinition) -> Bool { - typeDefinitionsByNamespace[Module.getNamespaceOrEmpty(type)]?.contains(type) ?? false - } + public func getModule(_ assembly: Assembly) -> Module? { + assembliesToModules[assembly]?.module + } - public func addTypeDefinition(_ type: TypeDefinition) { - precondition(projection.assembliesToModules[type.assembly] === self) - typeDefinitionsByNamespace[Module.getNamespaceOrEmpty(type), default: Set()].insert(type) - } + internal func getDocumentation(_ assembly: Assembly) -> DocumentationFile? { + assembliesToModules[assembly]?.documentation + } - public func addClosedGenericType(_ type: BoundType) { - precondition(!type.genericArgs.isEmpty && !type.isParameterized) - closedGenericTypesByDefinition[type.definition, default: []].append(type.genericArgs) - } + internal func getDocumentation(_ typeDefinition: TypeDefinition) -> DotNetXMLDocs.MemberEntry? { + guard let documentationFile = getDocumentation(typeDefinition.assembly) else { return nil } + return documentationFile.members[.type(fullName: typeDefinition.fullName)] + } - public func addReference(_ other: Module) { - references.insert(Reference(target: other)) - } + internal func getDocumentation(_ member: Member) throws -> DotNetXMLDocs.MemberEntry? { + guard let documentationFile = getDocumentation(member.definingType.assembly) else { return nil } - private static func getNamespaceOrEmpty(_ type: TypeDefinition) -> String { - var namespacedType = type - while namespacedType.namespace == nil, let enclosingType = try? namespacedType.enclosingType { - namespacedType = enclosingType - } - return namespacedType.namespace ?? "" + let memberKey: DotNetXMLDocs.MemberKey + switch member { + case let field as Field: + memberKey = .field(typeFullName: field.definingType.fullName, name: field.name) + case let event as Event: + memberKey = .event(typeFullName: event.definingType.fullName, name: event.name) + case let property as Property: + guard try (property.getter?.arity ?? 0) == 0 else { return nil } + memberKey = .event(typeFullName: property.definingType.fullName, name: property.name) + default: + return nil } - struct Reference: Hashable { - unowned var target: Module - - func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(target)) - } - - static func == (lhs: Module.Reference, rhs: Module.Reference) -> Bool { - lhs.target === rhs.target - } - } + return documentationFile.members[memberKey] } } \ No newline at end of file diff --git a/Generator/Sources/SwiftWinRT/EntryPoint.swift b/Generator/Sources/SwiftWinRT/EntryPoint.swift index 09cad764..91bbb2af 100644 --- a/Generator/Sources/SwiftWinRT/EntryPoint.swift +++ b/Generator/Sources/SwiftWinRT/EntryPoint.swift @@ -75,9 +75,9 @@ struct EntryPoint: ParsableCommand { for assembly in context.loadedAssembliesByName.values { guard !(assembly is Mscorlib) else { continue } - if let sourceModule = swiftProjection.assembliesToModules[assembly] { + if let sourceModule = swiftProjection.getModule(assembly) { for reference in assembly.references { - if let targetModule = swiftProjection.assembliesToModules[try reference.resolve()] { + if let targetModule = swiftProjection.getModule(try reference.resolve()) { sourceModule.addReference(targetModule) } }