Skip to content

Commit 0fbc618

Browse files
committed
Make members of a private extension fileprivate.
Extensions are declared at file scope, so "private" on an extension means that the members are actually "fileprivate". In this case we need to add the correct keyword to the individual members, or we'll actually change their visibility incorrectly. Fixes SR-11110.
1 parent f38829e commit 0fbc618

File tree

3 files changed

+31
-1
lines changed

3 files changed

+31
-1
lines changed

Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,20 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
3636
// Public, private, or fileprivate keywords need to be moved to members
3737
case .publicKeyword, .privateKeyword, .fileprivateKeyword:
3838
diagnose(.moveAccessKeyword(keyword: accessKeyword.name.text), on: accessKeyword)
39+
40+
// The effective access level of the members of a `private` extension is `fileprivate`, so
41+
// we have to update the keyword to ensure that the result is correct.
42+
let accessKeywordToAdd: DeclModifierSyntax
43+
if keywordKind == .privateKeyword {
44+
accessKeywordToAdd
45+
= accessKeyword.withName(accessKeyword.name.withKind(.fileprivateKeyword))
46+
} else {
47+
accessKeywordToAdd = accessKeyword
48+
}
49+
3950
let newMembers = SyntaxFactory.makeMemberDeclBlock(
4051
leftBrace: node.members.leftBrace,
41-
members: addMemberAccessKeywords(memDeclBlock: node.members, keyword: accessKeyword),
52+
members: addMemberAccessKeywords(memDeclBlock: node.members, keyword: accessKeywordToAdd),
4253
rightBrace: node.members.rightBrace)
4354
let newKeyword = replaceTrivia(
4455
on: node.extensionKeyword,
@@ -47,6 +58,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
4758
return node.withMembers(newMembers)
4859
.withModifiers(modifiers.remove(name: accessKeyword.name.text))
4960
.withExtensionKeyword(newKeyword)
61+
5062
// Internal keyword redundant, delete
5163
case .internalKeyword:
5264
diagnose(
@@ -58,6 +70,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
5870
leadingTrivia: accessKeyword.leadingTrivia) as! TokenSyntax
5971
return node.withModifiers(modifiers.remove(name: accessKeyword.name.text))
6072
.withExtensionKeyword(newKeyword)
73+
6174
default:
6275
break
6376
}

Tests/SwiftFormatRulesTests/NoAccessLevelOnExtensionDeclarationTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,20 @@ public class NoAccessLevelOnExtensionDeclarationTests: DiagnosingTestCase {
8484
"""
8585
)
8686
}
87+
88+
public func testPrivateIsEffectivelyFileprivate() {
89+
XCTAssertFormatting(
90+
NoAccessLevelOnExtensionDeclaration.self,
91+
input: """
92+
private extension Foo {
93+
func f() {}
94+
}
95+
""",
96+
expected: """
97+
extension Foo {
98+
fileprivate func f() {}
99+
}
100+
"""
101+
)
102+
}
87103
}

Tests/SwiftFormatRulesTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ extension NoAccessLevelOnExtensionDeclarationTests {
157157
static let __allTests__NoAccessLevelOnExtensionDeclarationTests = [
158158
("testExtensionDeclarationAccessLevel", testExtensionDeclarationAccessLevel),
159159
("testPreservesCommentOnRemovedModifier", testPreservesCommentOnRemovedModifier),
160+
("testPrivateIsEffectivelyFileprivate", testPrivateIsEffectivelyFileprivate),
160161
]
161162
}
162163

0 commit comments

Comments
 (0)