Skip to content

Commit de1b87a

Browse files
TunousMaxDesiatovmattt
authored
Fix public extensions exposing nested code of all access levels (swiftlang#195)
* Fix public extensions exposing nested code of all access levels * Inline access checking closure Co-authored-by: Max Desiatov <max@desiatov.com> * Update Changelog.md * Update Changelog.md * Add testComputedPropertiesInPublicExtension * Add testComputedPropertiesWithMultipleAccessModifiersInPublicExtension * Do not skip properties which have limited access of setters only * Add missing case and fix incorrect checks in access level tests Co-authored-by: Max Desiatov <max@desiatov.com> Co-authored-by: Mattt <mattt@me.com>
1 parent 0a93f3b commit de1b87a

File tree

3 files changed

+145
-1
lines changed

3 files changed

+145
-1
lines changed

Changelog.md

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Added end-to-end tests for command-line interface.
1313
#199 by @MaxDesiatov and @mattt.
1414

15+
### Fixed
16+
17+
- Fixed public extensions exposing nested code of all access levels.
18+
#195 by @Tunous.
19+
1520
## [1.0.0-beta.5] - 2020-09-29
1621

1722
### Added

Sources/SwiftDoc/Symbol.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ public final class Symbol {
4343

4444
if let `extension` = `extension`,
4545
`extension`.modifiers.contains(where: { $0.name == "public" }) {
46-
return true
46+
47+
return api.modifiers.allSatisfy { modifier in
48+
modifier.detail != nil || (modifier.name != "internal" && modifier.name != "fileprivate" && modifier.name != "private")
49+
}
4750
}
4851

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

Tests/SwiftDocTests/InterfaceTypeTests.swift

+136
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,140 @@ 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 testComputedPropertiesInPublicExtension() throws {
101+
let source = #"""
102+
public extension Int {
103+
var a: Int { 1 }
104+
public var b: Int { 1 }
105+
internal var c: Int { 1 }
106+
fileprivate var d: Int { 1 }
107+
private var e: Int { 1 }
108+
}
109+
"""#
110+
111+
let url = try temporaryFile(contents: source)
112+
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
113+
let module = Module(name: "Module", sourceFiles: [sourceFile])
114+
115+
XCTAssertEqual(sourceFile.symbols.count, 5)
116+
XCTAssertTrue(sourceFile.symbols[0].isPublic, "Property `a` should BE marked as public - its visibility is specified by extension")
117+
XCTAssertTrue(sourceFile.symbols[1].isPublic, "Property `b` should BE marked as public - its visibility is public")
118+
XCTAssertFalse(sourceFile.symbols[2].isPublic, "Property `c` should NOT be marked as public - its visibility is internal")
119+
XCTAssertFalse(sourceFile.symbols[3].isPublic, "Property `d` should NOT be marked as public - its visibility is fileprivate")
120+
XCTAssertFalse(sourceFile.symbols[4].isPublic, "Property `e` should NOT be marked as public - its visibility is private")
121+
122+
XCTAssertEqual(module.interface.symbols.count, 2)
123+
XCTAssertEqual(module.interface.symbols[0].name, "a", "Property `a` should be in documented interface")
124+
XCTAssertEqual(module.interface.symbols[1].name, "b", "Property `b` should be in documented interface")
125+
}
126+
127+
func testComputedPropertiesWithMultipleAccessModifiersInPublicExtension() throws {
128+
let source = #"""
129+
public extension Int {
130+
internal(set) var a: Int {
131+
get { 1 }
132+
set {}
133+
}
134+
private(set) var b: Int {
135+
get { 1 }
136+
set {}
137+
}
138+
public internal(set) var c: Int {
139+
get { 1 }
140+
set {}
141+
}
142+
public fileprivate(set) var d: Int {
143+
get { 1 }
144+
set {}
145+
}
146+
public private(set) var e: Int {
147+
get { 1 }
148+
set {}
149+
}
150+
}
151+
"""#
152+
153+
let url = try temporaryFile(contents: source)
154+
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
155+
let module = Module(name: "Module", sourceFiles: [sourceFile])
156+
157+
XCTAssertEqual(sourceFile.symbols.count, 5)
158+
XCTAssertTrue(sourceFile.symbols[0].isPublic, "Property `a` should be marked as public - the visibility of its getter is public")
159+
XCTAssertTrue(sourceFile.symbols[1].isPublic, "Property `b` should be marked as public - the visibility of its getter is public")
160+
XCTAssertTrue(sourceFile.symbols[2].isPublic, "Property `c` should be marked as public - the visibility of its getter is public")
161+
XCTAssertTrue(sourceFile.symbols[3].isPublic, "Property `d` should be marked as public - the visibility of its getter is public")
162+
XCTAssertTrue(sourceFile.symbols[4].isPublic, "Property `e` should be marked as public - the visibility of its getter is public")
163+
164+
XCTAssertEqual(module.interface.symbols.count, 5)
165+
XCTAssertEqual(module.interface.symbols[0].name, "a", "Property `a` should be in documented interface")
166+
XCTAssertEqual(module.interface.symbols[1].name, "b", "Property `b` should be in documented interface")
167+
XCTAssertEqual(module.interface.symbols[2].name, "c", "Property `c` should be in documented interface")
168+
XCTAssertEqual(module.interface.symbols[3].name, "d", "Property `d` should be in documented interface")
169+
XCTAssertEqual(module.interface.symbols[4].name, "e", "Property `e` should be in documented interface")
170+
}
171+
172+
func testNestedPropertiesInPublicExtension() throws {
173+
let source = #"""
174+
public class RootController {}
175+
176+
public extension RootController {
177+
class ControllerExtension {
178+
public var public_properties: ExtendedProperties = ExtendedProperties()
179+
internal var internal_properties: InternalProperties = InternalProperties()
180+
}
181+
}
182+
183+
public extension RootController.ControllerExtension {
184+
struct ExtendedProperties {
185+
public var public_prop: Int = 1
186+
}
187+
}
188+
189+
internal extension RootController.ControllerExtension {
190+
struct InternalProperties {
191+
internal var internal_prop: String = "FOO"
192+
}
193+
}
194+
"""#
195+
196+
197+
let url = try temporaryFile(contents: source)
198+
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
199+
let module = Module(name: "Module", sourceFiles: [sourceFile])
200+
201+
XCTAssertEqual(module.interface.symbols.count, 5)
202+
XCTAssertEqual(module.interface.symbols[0].name, "RootController")
203+
XCTAssertEqual(module.interface.symbols[1].name, "ControllerExtension")
204+
XCTAssertEqual(module.interface.symbols[2].name, "public_properties")
205+
XCTAssertEqual(module.interface.symbols[3].name, "ExtendedProperties")
206+
XCTAssertEqual(module.interface.symbols[4].name, "public_prop")
207+
}
72208
}

0 commit comments

Comments
 (0)