Skip to content

Commit

Permalink
Implemented documentation comment generation.
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle committed Oct 31, 2023
1 parent 5e013b6 commit 52039bf
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 64 deletions.
4 changes: 2 additions & 2 deletions Generator/Sources/CodeWriters/IndentedTextOutputStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ public class IndentedTextOutputStream: TextOutputStream {
lineEnd == str.startIndex ? "" : String(str[str.startIndex..<lineEnd]))
endLine()

let nextLineStart = str.index(after: lineEnd)
if nextLineStart != str.endIndex {
if lineEnd != str.endIndex {
let nextLineStart = str.index(after: lineEnd)
beginLine(grouping: self.lineGrouping ?? .withDefault)
write(str[nextLineStart...])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ public struct SwiftDocumentationComment {

public struct Param {
public var name: String
public var description: String
public var description: [Span]

public init(name: String, description: String) {
public init(name: String, description: [Span]) {
self.name = name
self.description = description
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,37 @@

extension SwiftSyntaxWriter {
internal func writeDocumentationComment(_ documentationComment: SwiftDocumentationComment) {
output.writeLine("/**")
if let summary = documentationComment.summary {
for block in summary { writeDocCommentBlock(block) }
for block in summary { writeDocumentationCommentBlock(block) }
}

for param in documentationComment.parameters {
output.write("- Parameter ")
output.write("/// - Parameter ")
output.write(param.name)
output.write(": ")
output.write(param.description)
output.endLine()
for span in param.description { writeDocumentationCommentSpan(span) }
output.endLine(groupWithNext: true)
}

if let returns = documentationComment.returns {
output.write("- Returns: ")
for span in returns { writeDocCommentSpan(span) }
output.endLine()
output.write("/// - Returns: ")
for span in returns { writeDocumentationCommentSpan(span) }
output.endLine(groupWithNext: true)
}

output.writeLine("*/")
}

fileprivate func writeDocCommentBlock(_ block: SwiftDocumentationComment.Block) {
fileprivate func writeDocumentationCommentBlock(_ block: SwiftDocumentationComment.Block) {
switch block {
case .paragraph(let spans):
for span in spans { writeDocCommentSpan(span) }
output.endLine()
output.write("/// ")
for span in spans { writeDocumentationCommentSpan(span) }
output.endLine(groupWithNext: true)
default:
return // TODO: Support more block types
}
}

fileprivate func writeDocCommentSpan(_ span: SwiftDocumentationComment.Span) {
fileprivate func writeDocumentationCommentSpan(_ span: SwiftDocumentationComment.Span) {
switch span {
case .text(let string): output.write(string)
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import struct Foundation.UUID
extension SwiftAssemblyModuleFileWriter {
internal func writeClass(_ classDefinition: ClassDefinition) throws {
try sourceFileWriter.writeClass(
documentation: projection.getDocumentationComment(classDefinition),
visibility: SwiftProjection.toVisibility(classDefinition.visibility, inheritableClass: !classDefinition.isSealed),
final: classDefinition.isSealed,
name: projection.toTypeName(classDefinition),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DotNetMetadata
extension SwiftAssemblyModuleFileWriter {
internal func writeDelegate(_ delegateDefinition: DelegateDefinition) throws {
try sourceFileWriter.writeTypeAlias(
documentation: projection.getDocumentationComment(delegateDefinition),
visibility: SwiftProjection.toVisibility(delegateDefinition.visibility),
name: try projection.toTypeName(delegateDefinition),
typeParameters: delegateDefinition.genericParams.map { $0.name },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extension SwiftAssemblyModuleFileWriter {
// therefore we cannot project them to Swift enums
// since they would be unable to represent unknown values.
try sourceFileWriter.writeStruct(
documentation: projection.getDocumentationComment(enumDefinition),
visibility: SwiftProjection.toVisibility(enumDefinition.visibility),
name: try projection.toTypeName(enumDefinition),
protocolConformances: [
Expand All @@ -24,7 +25,8 @@ extension SwiftAssemblyModuleFileWriter {

for field in enumDefinition.fields.filter({ $0.visibility == .public && $0.isStatic }) {
let value = SwiftProjection.toConstant(try field.literalValue!)
writer.writeStoredProperty(
try writer.writeStoredProperty(
documentation: projection.getDocumentationComment(field),
visibility: .public, static: true, declarator: .let,
name: projection.toMemberName(field),
initialValue: "Self(rawValue: \(value))")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ extension SwiftAssemblyModuleFileWriter {
for property in interface.properties {
if let getter = try property.getter, getter.isPublic {
try writer.writeProperty(
documentation: projection.getDocumentationComment(property),
static: property.isStatic,
name: projection.toMemberName(property),
type: projection.toReturnType(property.type),
Expand All @@ -62,6 +63,7 @@ extension SwiftAssemblyModuleFileWriter {
for method in interface.methods.filter({ $0.visibility == .public }) {
guard method.nameKind == .regular else { continue }
try writer.writeFunc(
documentation: projection.getDocumentationComment(method),
static: method.isStatic,
name: projection.toMemberName(method),
typeParameters: method.genericParams.map { $0.name },
Expand All @@ -72,17 +74,18 @@ extension SwiftAssemblyModuleFileWriter {
}
}

fileprivate func writeProtocolTypeAlias(_ interface: InterfaceDefinition) throws {
fileprivate func writeProtocolTypeAlias(_ interfaceDefinition: InterfaceDefinition) throws {
// For every "protocol IFoo", we generate a "typealias AnyIFoo = any IFoo"
// This enables the shorter "AnyIFoo?" syntax instead of "(any IFoo)?" or "Optional<any IFoo>"
sourceFileWriter.writeTypeAlias(
visibility: SwiftProjection.toVisibility(interface.visibility),
name: try projection.toTypeName(interface),
typeParameters: interface.genericParams.map { $0.name },
documentation: projection.getDocumentationComment(interfaceDefinition),
visibility: SwiftProjection.toVisibility(interfaceDefinition.visibility),
name: try projection.toTypeName(interfaceDefinition),
typeParameters: interfaceDefinition.genericParams.map { $0.name },
target: .identifier(
protocolModifier: .existential,
name: try projection.toProtocolName(interface),
genericArgs: interface.genericParams.map { .identifier(name: $0.name) }))
name: try projection.toProtocolName(interfaceDefinition),
genericArgs: interfaceDefinition.genericParams.map { .identifier(name: $0.name) }))
}

internal func writeInterfaceProjection(_ interfaceDefinition: InterfaceDefinition, genericArgs: [TypeNode]?) throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import WindowsMetadata
extension SwiftAssemblyModuleFileWriter {
internal func writeStruct(_ structDefinition: StructDefinition) throws {
try sourceFileWriter.writeStruct(
documentation: projection.getDocumentationComment(structDefinition),
visibility: SwiftProjection.toVisibility(structDefinition.visibility),
name: try projection.toTypeName(structDefinition),
typeParameters: structDefinition.genericParams.map { $0.name },
Expand All @@ -20,8 +21,10 @@ extension SwiftAssemblyModuleFileWriter {
for field in structDefinition.fields {
assert(field.isInstance && !field.isInitOnly && field.isPublic)

let type = try projection.toType(field.type)
writer.writeStoredProperty(visibility: .public, declarator: .var, name: projection.toMemberName(field), type: type)
try writer.writeStoredProperty(
documentation: projection.getDocumentationComment(field),
visibility: .public, declarator: .var, name: projection.toMemberName(field),
type: projection.toType(field.type))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ public struct SwiftAssemblyModuleFileWriter {
case let interfaceDefinition as InterfaceDefinition:
try writeInterface(interfaceDefinition)
case _ as ClassDefinition:
// Skip until we can generate interface members correctly
// try writeClass(classDefinition)
break
break // Unified with the projection for now
case let structDefinition as StructDefinition:
try writeStruct(structDefinition)
case let enumDefinition as EnumDefinition:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ extension SwiftProjection {
}
}

static func toDocumentationComment(_ documentation: MemberDocumentation) -> SwiftDocumentationComment {
// TODO: Convert doc comments
return SwiftDocumentationComment()
}

// Windows.Foundation.Collections to WindowsFoundationCollections
public static func toCompactNamespace(_ namespace: String) -> String {
namespace.replacing(".", with: "")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import CodeWriters
import DotNetMetadata
import DotNetXMLDocs

extension SwiftProjection {
internal func getDocumentationComment(_ typeDefinition: TypeDefinition) -> SwiftDocumentationComment? {
guard let documentation = assembliesToModules[typeDefinition.assembly]?.documentation else { return nil }
return documentation.members[.type(fullName: typeDefinition.fullName)].map { toDocumentationComment($0) }
}

internal func getDocumentationComment(_ member: Member) throws -> SwiftDocumentationComment? {
guard let documentation = assembliesToModules[member.definingType.assembly]?.documentation else { return nil }

let memberKey: MemberDocumentationKey
switch member {
case let field as Field:
memberKey = .field(declaringType: field.definingType.fullName, name: field.name)
case let event as Event:
memberKey = .event(declaringType: event.definingType.fullName, name: event.name)
case let property as Property:
guard try (property.getter?.arity ?? 0) == 0 else { return nil }
memberKey = .property(declaringType: property.definingType.fullName, name: property.name)
case let method as Method:
memberKey = try .method(declaringType: method.definingType.fullName, name: method.name,
params: method.params.map { try .init(type: toParamType($0.type), isByRef: $0.isByRef) })
default:
return nil
}

return documentation.members[memberKey].map { toDocumentationComment($0) }
}
}

fileprivate func toParamType(_ type: TypeNode) -> MemberDocumentationKey.ParamType {
switch type {
case .bound(let type):
return .bound(
fullName: type.definition.fullName,
genericArgs: type.genericArgs.map { toParamType($0) })
case .array(of: let elementType):
return .array(of: toParamType(elementType))
case .pointer(to: let pointeeType):
return .pointer(to: toParamType(pointeeType!)) // TODO: Handle void*
case .genericParam(let param):
return .genericArg(index: param.index, kind: param is GenericTypeParam ? .type : .method)
}
}

fileprivate func toDocumentationComment(_ documentation: MemberDocumentation) -> SwiftDocumentationComment {
var swift = SwiftDocumentationComment()
if let summary = documentation.summary {
swift.summary = toBlocks(summary)
}
for param in documentation.params {
swift.parameters.append(SwiftDocumentationComment.Param(name: param.key, description: toSpans(param.value)))
}
if let returns = documentation.returns {
swift.returns = toSpans(returns)
}
return swift
}

fileprivate func toBlocks(_ node: DocumentationTextNode) -> [SwiftDocumentationComment.Block] {
var blocks = [SwiftDocumentationComment.Block]()
appendBlocks(node, to: &blocks)
return blocks
}

fileprivate func toSpans(_ node: DocumentationTextNode) -> [SwiftDocumentationComment.Span] {
var spans = [SwiftDocumentationComment.Span]()
appendSpans(node, to: &spans)
return spans
}

fileprivate func appendSpans(_ node: DocumentationTextNode, to spans: inout [SwiftDocumentationComment.Span]) {
switch node {
case .plain(let text):
spans.append(.text(text))
case .sequence(let nodes):
for node in nodes { appendSpans(node, to: &spans) }
case .codeSpan(let code):
spans.append(.code(code))
default:
break // TODO: Support all node types
}
}

fileprivate func appendBlocks(_ node: DocumentationTextNode, to blocks: inout [SwiftDocumentationComment.Block]) {
func appendSpan(_ span: SwiftDocumentationComment.Span) {
if case .paragraph(let paragraph) = blocks.last {
blocks[blocks.count - 1] = .paragraph(paragraph + [span])
}
else {
blocks.append(.paragraph([span]))
}
}

switch node {
case .plain(let text):
appendSpan(.text(text))
case .sequence(let nodes):
for node in nodes { appendBlocks(node, to: &blocks) }
case .paragraph(let body):
blocks.append(.paragraph([]))
appendBlocks(body, to: &blocks)
case .codeSpan(let code):
appendSpan(.code(code))
default:
break // TODO: Support all node types
}
}
28 changes: 0 additions & 28 deletions Generator/Sources/ProjectionGenerator/SwiftProjection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,4 @@ public class SwiftProjection {
public func getModule(_ assembly: Assembly) -> Module? {
assembliesToModules[assembly]?.module
}

internal func getDocumentation(_ assembly: Assembly) -> AssemblyDocumentation? {
assembliesToModules[assembly]?.documentation
}

internal func getDocumentation(_ typeDefinition: TypeDefinition) -> MemberDocumentation? {
guard let documentationFile = getDocumentation(typeDefinition.assembly) else { return nil }
return documentationFile.members[.type(fullName: typeDefinition.fullName)]
}

internal func getDocumentation(_ member: Member) throws -> MemberDocumentation? {
guard let documentationFile = getDocumentation(member.definingType.assembly) else { return nil }

let memberKey: MemberDocumentationKey
switch member {
case let field as Field:
memberKey = .field(declaringType: field.definingType.fullName, name: field.name)
case let event as Event:
memberKey = .event(declaringType: event.definingType.fullName, name: event.name)
case let property as Property:
guard try (property.getter?.arity ?? 0) == 0 else { return nil }
memberKey = .event(declaringType: property.definingType.fullName, name: property.name)
default:
return nil
}

return documentationFile.members[memberKey]
}
}
2 changes: 1 addition & 1 deletion Generator/Sources/SwiftWinRT/EntryPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ struct EntryPoint: ParsableCommand {
}
else if let languageCode {
let languageNestedDocsPath = "\(assemblyDirectoryPath)\\\(languageCode)\\\(assemblyFileNameWithoutExtension).xml"
if FileManager.default.fileExists(atPath: sideBySideDocsPath) {
if FileManager.default.fileExists(atPath: languageNestedDocsPath) {
docs = try AssemblyDocumentation(readingFileAtPath: languageNestedDocsPath)
}
}
Expand Down

0 comments on commit 52039bf

Please sign in to comment.