Files
SwiftFormat/Tests/Rules/ExtensionAccessControlTests.swift

529 lines
14 KiB
Swift

//
// ExtensionAccessControlTests.swift
// SwiftFormatTests
//
// Created by Cal Stephens on 9/25/20.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//
import XCTest
@testable import SwiftFormat
final class ExtensionAccessControlTests: XCTestCase {
func testUpdatesVisibilityOfExtensionMembers() {
let input = """
private extension Foo {
var publicProperty: Int { 10 }
public func publicFunction1() {}
func publicFunction2() {}
internal func internalFunction() {}
private func privateFunction() {}
fileprivate var privateProperty: Int { 10 }
}
"""
let output = """
extension Foo {
fileprivate var publicProperty: Int { 10 }
public func publicFunction1() {}
fileprivate func publicFunction2() {}
internal func internalFunction() {}
private func privateFunction() {}
fileprivate var privateProperty: Int { 10 }
}
"""
testFormatting(
for: input, output, rule: .extensionAccessControl,
options: FormatOptions(extensionACLPlacement: .onDeclarations),
exclude: [.redundantInternal, .wrapPropertyBodies]
)
}
func testUpdatesVisibilityOfExtensionInConditionalCompilationBlock() {
let input = """
#if DEBUG
public extension Foo {
var publicProperty: Int { 10 }
}
#endif
"""
let output = """
#if DEBUG
extension Foo {
public var publicProperty: Int { 10 }
}
#endif
"""
testFormatting(
for: input, output, rule: .extensionAccessControl,
options: FormatOptions(extensionACLPlacement: .onDeclarations),
exclude: [.wrapPropertyBodies]
)
}
func testUpdatesVisibilityOfExtensionMembersInConditionalCompilationBlock() {
let input = """
public extension Foo {
#if DEBUG
var publicProperty: Int { 10 }
#endif
}
"""
let output = """
extension Foo {
#if DEBUG
public var publicProperty: Int { 10 }
#endif
}
"""
testFormatting(
for: input, output, rule: .extensionAccessControl,
options: FormatOptions(extensionACLPlacement: .onDeclarations),
exclude: [.wrapPropertyBodies]
)
}
func testDoesntUpdateDeclarationsInsideTypeInsideExtension() {
let input = """
public extension Foo {
struct Bar {
var baz: Int
var quux: Int
}
}
"""
let output = """
extension Foo {
public struct Bar {
var baz: Int
var quux: Int
}
}
"""
testFormatting(
for: input, output, rule: .extensionAccessControl,
options: FormatOptions(extensionACLPlacement: .onDeclarations),
exclude: [.wrapPropertyBodies]
)
}
func testDoesNothingForInternalExtension() {
let input = """
extension Foo {
func bar() {}
func baz() {}
public func quux() {}
}
"""
testFormatting(
for: input, rule: .extensionAccessControl,
options: FormatOptions(extensionACLPlacement: .onDeclarations)
)
}
func testPlacesVisibilityKeywordAfterAnnotations() {
let input = """
public extension Foo {
@discardableResult
func bar() -> Int { 10 }
/// Doc comment
@discardableResult
@available(iOS 10.0, *)
func baz() -> Int { 10 }
@objc func quux() {}
@available(iOS 10.0, *) func quixotic() {}
}
"""
let output = """
extension Foo {
@discardableResult
public func bar() -> Int { 10 }
/// Doc comment
@discardableResult
@available(iOS 10.0, *)
public func baz() -> Int { 10 }
@objc public func quux() {}
@available(iOS 10.0, *) public func quixotic() {}
}
"""
testFormatting(
for: input, output, rule: .extensionAccessControl,
options: FormatOptions(extensionACLPlacement: .onDeclarations),
exclude: [.wrapFunctionBodies]
)
}
func testConvertsExtensionPrivateToMemberFileprivate() {
let input = """
private extension Foo {
var bar: Int
}
let bar = Foo().bar
"""
let output = """
extension Foo {
fileprivate var bar: Int
}
let bar = Foo().bar
"""
testFormatting(
for: input, output, rule: .extensionAccessControl,
options: FormatOptions(extensionACLPlacement: .onDeclarations, swiftVersion: "4"),
exclude: [.propertyTypes]
)
}
// MARK: extensionAccessControl .onExtension
func testUpdatedVisibilityOfExtension() {
let input = """
extension Foo {
public func bar() {}
public var baz: Int { 10 }
public struct Foo2 {
var quux: Int
}
}
"""
let output = """
public extension Foo {
func bar() {}
var baz: Int { 10 }
struct Foo2 {
var quux: Int
}
}
"""
testFormatting(for: input, output, rule: .extensionAccessControl, exclude: [.wrapPropertyBodies])
}
func testUpdatedVisibilityOfExtensionWithDeclarationsInConditionalCompilation() {
let input = """
extension Foo {
#if DEBUG
public func bar() {}
public var baz: Int { 10 }
#endif
}
"""
let output = """
public extension Foo {
#if DEBUG
func bar() {}
var baz: Int { 10 }
#endif
}
"""
testFormatting(for: input, output, rule: .extensionAccessControl, exclude: [.wrapPropertyBodies])
}
func testDoesntUpdateExtensionVisibilityWithoutMajorityBodyVisibility() {
let input = """
extension Foo {
public func foo() {}
public func bar() {}
var baz: Int { 10 }
var quux: Int { 5 }
}
"""
testFormatting(for: input, rule: .extensionAccessControl, exclude: [.wrapPropertyBodies])
}
func testUpdateExtensionVisibilityWithMajorityBodyVisibility() {
let input = """
extension Foo {
public func foo() {}
public func bar() {}
public var baz: Int { 10 }
var quux: Int { 5 }
}
"""
let output = """
public extension Foo {
func foo() {}
func bar() {}
var baz: Int { 10 }
internal var quux: Int { 5 }
}
"""
testFormatting(for: input, output, rule: .extensionAccessControl, exclude: [.wrapPropertyBodies])
}
func testDoesntUpdateExtensionVisibilityWhenMajorityBodyVisibilityIsntMostVisible() {
let input = """
extension Foo {
func foo() {}
func bar() {}
public var baz: Int { 10 }
}
"""
testFormatting(for: input, rule: .extensionAccessControl, exclude: [.wrapPropertyBodies])
}
func testDoesntUpdateExtensionVisibilityWithInternalDeclarations() {
let input = """
extension Foo {
func bar() {}
var baz: Int { 10 }
}
"""
testFormatting(for: input, rule: .extensionAccessControl, exclude: [.wrapPropertyBodies])
}
func testDoesntUpdateExtensionThatAlreadyHasCorrectVisibilityKeyword() {
let input = """
public extension Foo {
func bar() {}
func baz() {}
}
"""
testFormatting(for: input, rule: .extensionAccessControl)
}
func testUpdatesExtensionThatHasHigherACLThanBodyDeclarations() {
let input = """
public extension Foo {
fileprivate func bar() {}
fileprivate func baz() {}
}
"""
let output = """
fileprivate extension Foo {
func bar() {}
func baz() {}
}
"""
testFormatting(for: input, output, rule: .extensionAccessControl,
exclude: [.redundantFileprivate])
}
func testDoesntHoistPrivateVisibilityFromExtensionBodyDeclarations() {
let input = """
extension Foo {
private var bar() {}
private func baz() {}
}
"""
testFormatting(for: input, rule: .extensionAccessControl)
}
func testDoesntUpdatesExtensionThatHasLowerACLThanBodyDeclarations() {
let input = """
private extension Foo {
public var bar() {}
public func baz() {}
}
"""
testFormatting(for: input, rule: .extensionAccessControl)
}
func testDoesntReduceVisibilityOfImplicitInternalDeclaration() {
let input = """
extension Foo {
fileprivate var bar() {}
func baz() {}
}
"""
testFormatting(for: input, rule: .extensionAccessControl)
}
func testUpdatesExtensionThatHasRedundantACLOnBodyDeclarations() {
let input = """
public extension Foo {
func bar() {}
public func baz() {}
}
"""
let output = """
public extension Foo {
func bar() {}
func baz() {}
}
"""
testFormatting(for: input, output, rule: .extensionAccessControl)
}
func testNoHoistAccessModifierForOpenMethod() {
let input = """
extension Foo {
open func bar() {}
}
"""
testFormatting(for: input, rule: .extensionAccessControl)
}
func testDontChangePrivateExtensionToFileprivate() {
let input = """
private extension Foo {
func bar() {}
}
"""
testFormatting(for: input, rule: .extensionAccessControl)
}
func testDontRemoveInternalKeywordFromExtension() {
let input = """
internal extension Foo {
func bar() {}
}
"""
testFormatting(for: input, rule: .extensionAccessControl, exclude: [.redundantInternal])
}
func testNoHoistAccessModifierForExtensionThatAddsProtocolConformance() {
let input = """
extension Foo: Bar {
public func bar() {}
}
"""
testFormatting(for: input, rule: .extensionAccessControl)
}
func testNoHoistAccessModifierForExtensionThatAddsPreconcurrencyProtocolConformance() {
let input = """
extension Foo: @preconcurrency Bar {
public func bar() {}
}
"""
testFormatting(for: input, rule: .extensionAccessControl)
}
func testProtocolConformanceCheckNotFooledByWhereClause() {
let input = """
extension Foo where Self: Bar {
public func bar() {}
}
"""
let output = """
public extension Foo where Self: Bar {
func bar() {}
}
"""
testFormatting(for: input, output, rule: .extensionAccessControl)
}
func testAccessNotHoistedIfTypeVisibilityIsLower() {
let input = """
class Foo {}
extension Foo {
public func bar() {}
}
"""
testFormatting(for: input, rule: .extensionAccessControl, exclude: [.redundantPublic])
}
func testAccessNotHoistedIfNestedTypeVisibilityIsLower() {
// Extension of a dot-separated nested type whose inner type is internal.
// SwiftFormat must not hoist `public` onto the extension because the type is internal.
let input = """
extension CategorySurface {
struct RetailerItemGroup: Hashable {
let collection: String
}
}
extension CategorySurface.RetailerItemGroup {
public static func placeholder(id: String) -> Self {
.init(collection: id)
}
}
"""
testFormatting(for: input, rule: .extensionAccessControl, exclude: [.redundantPublic])
}
func testAccessHoistedForPublicNestedType() {
// Extension of a dot-separated nested type that IS public should still allow hoisting.
let input = """
extension CategorySurface {
public struct RetailerItemGroup: Hashable {
let collection: String
}
}
extension CategorySurface.RetailerItemGroup {
public static func placeholder(id: String) -> Self {
.init(collection: id)
}
}
"""
// `public` is hoisted from `public struct RetailerItemGroup` to `public extension CategorySurface`,
// and from `public static func placeholder` to `public extension CategorySurface.RetailerItemGroup`.
let output = """
public extension CategorySurface {
struct RetailerItemGroup: Hashable {
let collection: String
}
}
public extension CategorySurface.RetailerItemGroup {
static func placeholder(id: String) -> Self {
.init(collection: id)
}
}
"""
testFormatting(for: input, output, rule: .extensionAccessControl)
}
func testExtensionAccessControlRuleTerminatesInFileWithConditionalCompilation() {
let input = """
#if os(Linux)
#error("Linux is currently not supported")
#endif
"""
testFormatting(for: input, rule: .extensionAccessControl)
}
func testExtensionAccessControlRuleTerminatesInFileWithEmptyType() {
let input = """
struct Foo {
// This type is empty
}
extension Foo {
// This extension is empty
}
"""
testFormatting(for: input, rule: .extensionAccessControl, exclude: [.emptyExtensions])
}
}