Skip to content

Commit

Permalink
Fix public extensions exposing nested code of all access levels (swif…
Browse files Browse the repository at this point in the history
…tlang#195)

* Fix public extensions exposing nested code of all access levels

* Inline access checking closure

Co-authored-by: Max Desiatov <[email protected]>

* 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 <[email protected]>
Co-authored-by: Mattt <[email protected]>
  • Loading branch information
3 people authored Oct 8, 2020
1 parent 0a93f3b commit de1b87a
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 1 deletion.
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added end-to-end tests for command-line interface.
#199 by @MaxDesiatov and @mattt.

### Fixed

- Fixed public extensions exposing nested code of all access levels.
#195 by @Tunous.

## [1.0.0-beta.5] - 2020-09-29

### Added
Expand Down
5 changes: 4 additions & 1 deletion Sources/SwiftDoc/Symbol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ public final class Symbol {

if let `extension` = `extension`,
`extension`.modifiers.contains(where: { $0.name == "public" }) {
return true

return api.modifiers.allSatisfy { modifier in
modifier.detail != nil || (modifier.name != "internal" && modifier.name != "fileprivate" && modifier.name != "private")
}
}

if let symbol = context.compactMap({ $0 as? Symbol }).last,
Expand Down
136 changes: 136 additions & 0 deletions Tests/SwiftDocTests/InterfaceTypeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,140 @@ final class InterfaceTypeTests: XCTestCase {
XCTAssertEqual(symbol.api.name, "A")
}
}

func testFunctionsInPublicExtension() throws {
let source = #"""
public extension Int {
func a() {}
public func b() {}
internal func c() {}
fileprivate func d() {}
private func e() {}
}
"""#

let url = try temporaryFile(contents: source)
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
let module = Module(name: "Module", sourceFiles: [sourceFile])

XCTAssertEqual(sourceFile.symbols.count, 5)
XCTAssertTrue(sourceFile.symbols[0].isPublic, "Function `a()` should BE marked as public - its visibility is specified by extension")
XCTAssertTrue(sourceFile.symbols[1].isPublic, "Function `b()` should BE marked as public - its visibility is public")
XCTAssertFalse(sourceFile.symbols[2].isPublic, "Function `c()` should NOT be marked as public - its visibility is internal")
XCTAssertFalse(sourceFile.symbols[3].isPublic, "Function `d()` should NOT be marked as public - its visibility is fileprivate")
XCTAssertFalse(sourceFile.symbols[4].isPublic, "Function `e()` should NOT be marked as public - its visibility is private")

XCTAssertEqual(module.interface.symbols.count, 2)
XCTAssertEqual(module.interface.symbols[0].name, "a()", "Function `a()` should be in documented interface")
XCTAssertEqual(module.interface.symbols[1].name, "b()", "Function `b()` should be in documented interface")
}

func testComputedPropertiesInPublicExtension() throws {
let source = #"""
public extension Int {
var a: Int { 1 }
public var b: Int { 1 }
internal var c: Int { 1 }
fileprivate var d: Int { 1 }
private var e: Int { 1 }
}
"""#

let url = try temporaryFile(contents: source)
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
let module = Module(name: "Module", sourceFiles: [sourceFile])

XCTAssertEqual(sourceFile.symbols.count, 5)
XCTAssertTrue(sourceFile.symbols[0].isPublic, "Property `a` should BE marked as public - its visibility is specified by extension")
XCTAssertTrue(sourceFile.symbols[1].isPublic, "Property `b` should BE marked as public - its visibility is public")
XCTAssertFalse(sourceFile.symbols[2].isPublic, "Property `c` should NOT be marked as public - its visibility is internal")
XCTAssertFalse(sourceFile.symbols[3].isPublic, "Property `d` should NOT be marked as public - its visibility is fileprivate")
XCTAssertFalse(sourceFile.symbols[4].isPublic, "Property `e` should NOT be marked as public - its visibility is private")

XCTAssertEqual(module.interface.symbols.count, 2)
XCTAssertEqual(module.interface.symbols[0].name, "a", "Property `a` should be in documented interface")
XCTAssertEqual(module.interface.symbols[1].name, "b", "Property `b` should be in documented interface")
}

func testComputedPropertiesWithMultipleAccessModifiersInPublicExtension() throws {
let source = #"""
public extension Int {
internal(set) var a: Int {
get { 1 }
set {}
}
private(set) var b: Int {
get { 1 }
set {}
}
public internal(set) var c: Int {
get { 1 }
set {}
}
public fileprivate(set) var d: Int {
get { 1 }
set {}
}
public private(set) var e: Int {
get { 1 }
set {}
}
}
"""#

let url = try temporaryFile(contents: source)
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
let module = Module(name: "Module", sourceFiles: [sourceFile])

XCTAssertEqual(sourceFile.symbols.count, 5)
XCTAssertTrue(sourceFile.symbols[0].isPublic, "Property `a` should be marked as public - the visibility of its getter is public")
XCTAssertTrue(sourceFile.symbols[1].isPublic, "Property `b` should be marked as public - the visibility of its getter is public")
XCTAssertTrue(sourceFile.symbols[2].isPublic, "Property `c` should be marked as public - the visibility of its getter is public")
XCTAssertTrue(sourceFile.symbols[3].isPublic, "Property `d` should be marked as public - the visibility of its getter is public")
XCTAssertTrue(sourceFile.symbols[4].isPublic, "Property `e` should be marked as public - the visibility of its getter is public")

XCTAssertEqual(module.interface.symbols.count, 5)
XCTAssertEqual(module.interface.symbols[0].name, "a", "Property `a` should be in documented interface")
XCTAssertEqual(module.interface.symbols[1].name, "b", "Property `b` should be in documented interface")
XCTAssertEqual(module.interface.symbols[2].name, "c", "Property `c` should be in documented interface")
XCTAssertEqual(module.interface.symbols[3].name, "d", "Property `d` should be in documented interface")
XCTAssertEqual(module.interface.symbols[4].name, "e", "Property `e` should be in documented interface")
}

func testNestedPropertiesInPublicExtension() throws {
let source = #"""
public class RootController {}
public extension RootController {
class ControllerExtension {
public var public_properties: ExtendedProperties = ExtendedProperties()
internal var internal_properties: InternalProperties = InternalProperties()
}
}
public extension RootController.ControllerExtension {
struct ExtendedProperties {
public var public_prop: Int = 1
}
}
internal extension RootController.ControllerExtension {
struct InternalProperties {
internal var internal_prop: String = "FOO"
}
}
"""#


let url = try temporaryFile(contents: source)
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
let module = Module(name: "Module", sourceFiles: [sourceFile])

XCTAssertEqual(module.interface.symbols.count, 5)
XCTAssertEqual(module.interface.symbols[0].name, "RootController")
XCTAssertEqual(module.interface.symbols[1].name, "ControllerExtension")
XCTAssertEqual(module.interface.symbols[2].name, "public_properties")
XCTAssertEqual(module.interface.symbols[3].name, "ExtendedProperties")
XCTAssertEqual(module.interface.symbols[4].name, "public_prop")
}
}

0 comments on commit de1b87a

Please sign in to comment.