diff --git a/Changelog.md b/Changelog.md index 7903b0f7..7eb28fcf 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #199 by @MaxDesiatov and @mattt. - Added `--minimum-access-level` option to `generate` and `coverage` commands. #219 by @Lukas-Stuehrk. +- Added support for documenting default implementations. + #221 by @Lukas-Stuehrk. ### Fixed diff --git a/Sources/SwiftDoc/Interface.swift b/Sources/SwiftDoc/Interface.swift index 65e37993..c82c06aa 100644 --- a/Sources/SwiftDoc/Interface.swift +++ b/Sources/SwiftDoc/Interface.swift @@ -155,4 +155,8 @@ public final class Interface { public func conditionalCounterparts(of symbol: Symbol) -> [Symbol] { return symbolsGroupedByIdentifier[symbol.id]?.filter { $0 != symbol }.sorted() ?? [] } + + public func defaultImplementations(of symbol: Symbol) -> [Symbol] { + return relationshipsByObject[symbol.id]?.filter { $0.predicate == .defaultImplementationOf }.map { $0.subject }.sorted() ?? [] + } } diff --git a/Sources/swift-doc/Supporting Types/Components/Members.swift b/Sources/swift-doc/Supporting Types/Components/Members.swift index b653b25d..994eca5c 100644 --- a/Sources/swift-doc/Supporting Types/Components/Members.swift +++ b/Sources/swift-doc/Supporting Types/Components/Members.swift @@ -17,6 +17,7 @@ struct Members: Component { var properties: [Symbol] var methods: [Symbol] var genericallyConstrainedMembers: [[GenericRequirement] : [Symbol]] + let defaultImplementations: [Symbol] init(of symbol: Symbol, in module: Module, baseURL: String, symbolFilter: (Symbol) -> Bool) { self.symbol = symbol @@ -33,6 +34,7 @@ struct Members: Component { self.properties = members.filter { $0.api is Variable } self.methods = members.filter { $0.api is Function } self.genericallyConstrainedMembers = Dictionary(grouping: members) { $0.`extension`?.genericRequirements ?? [] }.filter { !$0.key.isEmpty } + self.defaultImplementations = module.interface.defaultImplementations(of: symbol).filter(symbolFilter) } var sections: [(title: String, members: [Symbol])] { @@ -41,14 +43,15 @@ struct Members: Component { ("Initializers", initializers), ("Enumeration Cases", cases), ("Properties", properties), - ("Methods", methods) + ("Methods", methods), + ("Default Implementations", defaultImplementations), ].filter { !$0.members.isEmpty } } // MARK: - Component var fragment: Fragment { - guard !members.isEmpty else { return Fragment { "" } } + guard !members.isEmpty || !defaultImplementations.isEmpty else { return Fragment { "" } } return Fragment { ForEach(in: sections) { section -> BlockConvertible in diff --git a/Tests/SwiftDocTests/InterfaceTypeTests.swift b/Tests/SwiftDocTests/InterfaceTypeTests.swift index 42ba0ba9..559f1cc7 100644 --- a/Tests/SwiftDocTests/InterfaceTypeTests.swift +++ b/Tests/SwiftDocTests/InterfaceTypeTests.swift @@ -240,4 +240,30 @@ final class InterfaceTypeTests: XCTestCase { XCTAssertEqual(module.interface.symbols[6].name, "InternalProperties") XCTAssertEqual(module.interface.symbols[7].name, "internal_prop") } + + func testDefaultImplementationsOfProtocols() throws { + let source = #""" + public protocol SomeProtocol { + func someMethod() + } + + public extension SomeProtocol { + func someMethod() { } + + func someOtherMethod() { } + } + """# + + + let url = try temporaryFile(contents: source) + let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent()) + let module = Module(name: "Module", sourceFiles: [sourceFile]) + + let protocolSymbol = module.interface.symbols[0] + XCTAssertEqual(protocolSymbol.name, "SomeProtocol") + let defaultImplementations = module.interface.defaultImplementations(of: protocolSymbol) + XCTAssertEqual(defaultImplementations.count, 2) + XCTAssertEqual(defaultImplementations[0].name, "someMethod()") + XCTAssertEqual(defaultImplementations[1].name, "someOtherMethod()") + } }