Skip to content
This repository was archived by the owner on Jun 1, 2023. It is now read-only.

Commit c62b1bf

Browse files
committed
Fix public extensions exposing nested code of all access levels
1 parent f441648 commit c62b1bf

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

Sources/SwiftDoc/Symbol.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ public final class Symbol {
4343

4444
if let `extension` = `extension`,
4545
`extension`.modifiers.contains(where: { $0.name == "public" }) {
46-
return true
46+
47+
let isNotLimitedAccessModifier = { (modifier: Modifier) in
48+
modifier.name != "internal" && modifier.name != "fileprivate" && modifier.name != "private"
49+
}
50+
return api.modifiers.allSatisfy(isNotLimitedAccessModifier)
4751
}
4852

4953
if let symbol = context.compactMap({ $0 as? Symbol }).last,

Tests/SwiftDocTests/InterfaceTypeTests.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,68 @@ final class InterfaceTypeTests: XCTestCase {
6969
XCTAssertEqual(symbol.api.name, "A")
7070
}
7171
}
72+
73+
func testFunctionsInPublicExtension() throws {
74+
let source = #"""
75+
public extension Int {
76+
func a() {}
77+
public func b() {}
78+
internal func c() {}
79+
fileprivate func d() {}
80+
private func e() {}
81+
}
82+
"""#
83+
84+
let url = try temporaryFile(contents: source)
85+
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
86+
let module = Module(name: "Module", sourceFiles: [sourceFile])
87+
88+
XCTAssertEqual(sourceFile.symbols.count, 5)
89+
XCTAssertTrue(sourceFile.symbols[0].isPublic, "Function `a()` should BE marked as public - its visibility is specified by extension")
90+
XCTAssertTrue(sourceFile.symbols[1].isPublic, "Function `b()` should BE marked as public - its visibility is public")
91+
XCTAssertFalse(sourceFile.symbols[2].isPublic, "Function `c()` should NOT be marked as public - its visibility is internal")
92+
XCTAssertFalse(sourceFile.symbols[3].isPublic, "Function `d()` should NOT be marked as public - its visibility is fileprivate")
93+
XCTAssertFalse(sourceFile.symbols[4].isPublic, "Function `e()` should NOT be marked as public - its visibility is private")
94+
95+
XCTAssertEqual(module.interface.symbols.count, 2)
96+
XCTAssertEqual(module.interface.symbols[0].name, "a()", "Function `a()` should be in documented interface")
97+
XCTAssertEqual(module.interface.symbols[1].name, "b()", "Function `b()` should be in documented interface")
98+
}
99+
100+
func testNestedPropertiesInPublicExtension() throws {
101+
let source = #"""
102+
public class RootController {}
103+
104+
public extension RootController {
105+
class ControllerExtension {
106+
public var public_properties: ExtendedProperties = ExtendedProperties()
107+
internal var internal_properties: InternalProperties = InternalProperties()
108+
}
109+
}
110+
111+
public extension RootController.ControllerExtension {
112+
struct ExtendedProperties {
113+
public var public_prop: Int = 1
114+
}
115+
}
116+
117+
internal extension RootController.ControllerExtension {
118+
struct InternalProperties {
119+
internal var internal_prop: String = "FOO"
120+
}
121+
}
122+
"""#
123+
124+
125+
let url = try temporaryFile(contents: source)
126+
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
127+
let module = Module(name: "Module", sourceFiles: [sourceFile])
128+
129+
XCTAssertEqual(module.interface.symbols.count, 5)
130+
XCTAssertEqual(module.interface.symbols[0].name, "RootController")
131+
XCTAssertEqual(module.interface.symbols[1].name, "ControllerExtension")
132+
XCTAssertEqual(module.interface.symbols[2].name, "public_properties")
133+
XCTAssertEqual(module.interface.symbols[3].name, "ExtendedProperties")
134+
XCTAssertEqual(module.interface.symbols[4].name, "public_prop")
135+
}
72136
}

0 commit comments

Comments
 (0)