From 1ade941ee762084a8941e5c6075f093b4dc23149 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 14 Sep 2023 15:40:01 -0400 Subject: [PATCH 1/6] Format parameter packs. --- .../PrettyPrint/TokenStreamCreator.swift | 23 ++++++++++ .../PrettyPrint/ParameterPackTests.swift | 46 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 Tests/SwiftFormatTests/PrettyPrint/ParameterPackTests.swift diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 5d00dd005..a6f0bfc8c 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2293,6 +2293,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: GenericParameterSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(node.eachKeyword, tokens: .break) after(node.colon, tokens: .break) if let trailingComma = node.trailingComma { after(trailingComma, tokens: .close, .break(.same)) @@ -2312,6 +2313,28 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: PackElementExprSyntax) -> SyntaxVisitorContinueKind { + // `each` cannot be separated from the following token, or it is parsed as an identifier itself. + after(node.eachKeyword, tokens: .space) + return .visitChildren + } + + override func visit(_ node: PackElementTypeSyntax) -> SyntaxVisitorContinueKind { + // `each` cannot be separated from the following token, or it is parsed as an identifier itself. + after(node.eachKeyword, tokens: .space) + return .visitChildren + } + + override func visit(_ node: PackExpansionExprSyntax) -> SyntaxVisitorContinueKind { + after(node.repeatKeyword, tokens: .break) + return .visitChildren + } + + override func visit(_ node: PackExpansionTypeSyntax) -> SyntaxVisitorContinueKind { + after(node.repeatKeyword, tokens: .break) + return .visitChildren + } + override func visit(_ node: ExpressionPatternSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } diff --git a/Tests/SwiftFormatTests/PrettyPrint/ParameterPackTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ParameterPackTests.swift new file mode 100644 index 000000000..c4615dfab --- /dev/null +++ b/Tests/SwiftFormatTests/PrettyPrint/ParameterPackTests.swift @@ -0,0 +1,46 @@ +final class ParameterPackTests: PrettyPrintTestCase { + func testGenericPackArgument() { + assertPrettyPrintEqual( + input: """ + func someFunction() {} + struct SomeStruct {} + """, + expected: """ + func someFunction< + each P + >() {} + struct SomeStruct< + each P + > {} + + """, + linelength: 22) + } + + func testPackExpansionsAndElements() { + assertPrettyPrintEqual( + input: """ + repeat checkNilness(of: each value) + """, + expected: """ + repeat checkNilness( + of: each value) + + """, + linelength: 25) + + assertPrettyPrintEqual( + input: """ + repeat f(of: each v) + """, + expected: """ + repeat + f( + of: + each v + ) + + """, + linelength: 7) + } +} From 71cedaa345211ef41082d84169f95dba4b3037b5 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 14 Sep 2023 15:42:59 -0400 Subject: [PATCH 2/6] Format `consume` expressions. --- .../PrettyPrint/TokenStreamCreator.swift | 7 +++++++ .../PrettyPrint/ConsumeExprTests.swift | 14 ++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 Tests/SwiftFormatTests/PrettyPrint/ConsumeExprTests.swift diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index a6f0bfc8c..5a21b4dda 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2504,6 +2504,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: ConsumeExprSyntax) -> SyntaxVisitorContinueKind { + // The `consume` keyword cannot be separated from the following token or it will be parsed as + // an identifier. + after(node.consumeKeyword, tokens: .space) + return .visitChildren + } + override func visit(_ node: InheritanceClauseSyntax) -> SyntaxVisitorContinueKind { // Normally, the open-break is placed before the open token. In this case, it's intentionally // ordered differently so that the inheritance list can start on the current line and only diff --git a/Tests/SwiftFormatTests/PrettyPrint/ConsumeExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ConsumeExprTests.swift new file mode 100644 index 000000000..30a1b6a1c --- /dev/null +++ b/Tests/SwiftFormatTests/PrettyPrint/ConsumeExprTests.swift @@ -0,0 +1,14 @@ +final class ConsumeExprTests: PrettyPrintTestCase { + func testConsume() { + assertPrettyPrintEqual( + input: """ + let x = consume y + """, + expected: """ + let x = + consume y + + """, + linelength: 16) + } +} From 1e9b89b73bf16915cd3f70e8b4d92f3212356d13 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 14 Sep 2023 16:01:48 -0400 Subject: [PATCH 3/6] Format `discard` statements. --- .../PrettyPrint/TokenStreamCreator.swift | 7 +++++++ .../PrettyPrint/DiscardStmtTests.swift | 13 +++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 Tests/SwiftFormatTests/PrettyPrint/DiscardStmtTests.swift diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 5a21b4dda..70d2d5cc7 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2511,6 +2511,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: DiscardStmtSyntax) -> SyntaxVisitorContinueKind { + // The `discard` keyword cannot be separated from the following token or it will be parsed as + // an identifier. + after(node.discardKeyword, tokens: .space) + return .visitChildren + } + override func visit(_ node: InheritanceClauseSyntax) -> SyntaxVisitorContinueKind { // Normally, the open-break is placed before the open token. In this case, it's intentionally // ordered differently so that the inheritance list can start on the current line and only diff --git a/Tests/SwiftFormatTests/PrettyPrint/DiscardStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DiscardStmtTests.swift new file mode 100644 index 000000000..63637da85 --- /dev/null +++ b/Tests/SwiftFormatTests/PrettyPrint/DiscardStmtTests.swift @@ -0,0 +1,13 @@ +final class DiscardStmtTests: PrettyPrintTestCase { + func testDiscard() { + assertPrettyPrintEqual( + input: """ + discard self + """, + expected: """ + discard self + + """, + linelength: 9) + } +} From 7af3b10948d77306ecef8ecc8f6e5a0f1e0efe51 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 14 Sep 2023 16:10:06 -0400 Subject: [PATCH 4/6] Format `copy` expressions. --- .../PrettyPrint/TokenStreamCreator.swift | 7 +++++++ .../PrettyPrint/CopyExprSyntax.swift | 14 ++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 Tests/SwiftFormatTests/PrettyPrint/CopyExprSyntax.swift diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 70d2d5cc7..048f0c866 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2511,6 +2511,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: CopyExprSyntax) -> SyntaxVisitorContinueKind { + // The `copy` keyword cannot be separated from the following token or it will be parsed as an + // identifier. + after(node.copyKeyword, tokens: .space) + return .visitChildren + } + override func visit(_ node: DiscardStmtSyntax) -> SyntaxVisitorContinueKind { // The `discard` keyword cannot be separated from the following token or it will be parsed as // an identifier. diff --git a/Tests/SwiftFormatTests/PrettyPrint/CopyExprSyntax.swift b/Tests/SwiftFormatTests/PrettyPrint/CopyExprSyntax.swift new file mode 100644 index 000000000..188121eef --- /dev/null +++ b/Tests/SwiftFormatTests/PrettyPrint/CopyExprSyntax.swift @@ -0,0 +1,14 @@ +final class CopyExprTests: PrettyPrintTestCase { + func testCopy() { + assertPrettyPrintEqual( + input: """ + let x = copy y + """, + expected: """ + let x = + copy y + + """, + linelength: 13) + } +} From 62390f68245caba8a8a5d3febee041a33041c106 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 14 Sep 2023 16:16:38 -0400 Subject: [PATCH 5/6] Remove a warning from `AlwaysUseLiteralForEmptyCollectionInit`. --- .../Rules/AlwaysUseLiteralForEmptyCollectionInit.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift index 49b850a50..28c52d64a 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift @@ -166,8 +166,7 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { } private func getLiteralType(_ arrayLiteral: ArrayExprSyntax) -> TypeSyntax? { - guard let elementExpr = arrayLiteral.elements.firstAndOnly, - elementExpr.is(ArrayElementSyntax.self) else { + guard arrayLiteral.elements.count == 1 else { return nil } From 57c612b1ea0d00c61754d1b4f0321b6d6fced78d Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 14 Sep 2023 16:22:51 -0400 Subject: [PATCH 6/6] Support `package` access in `NoAccessLevelOnExtensionDeclaration`. --- .../Core/ModifierListSyntax+Convenience.swift | 3 ++- .../NoAccessLevelOnExtensionDeclaration.swift | 4 +-- ...cessLevelOnExtensionDeclarationTests.swift | 25 +++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift index 2a544b9d2..b1e58cc12 100644 --- a/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift +++ b/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift @@ -17,7 +17,8 @@ extension DeclModifierListSyntax { var accessLevelModifier: DeclModifierSyntax? { for modifier in self { switch modifier.name.tokenKind { - case .keyword(.public), .keyword(.private), .keyword(.fileprivate), .keyword(.internal): + case .keyword(.public), .keyword(.private), .keyword(.fileprivate), .keyword(.internal), + .keyword(.package): return modifier default: continue diff --git a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift index b9be2483e..e16e82cf6 100644 --- a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift @@ -32,8 +32,8 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { var result = node switch keyword { - // Public, private, or fileprivate keywords need to be moved to members - case .public, .private, .fileprivate: + // Public, private, fileprivate, or package keywords need to be moved to members + case .public, .private, .fileprivate, .package: // The effective access level of the members of a `private` extension is `fileprivate`, so // we have to update the keyword to ensure that the result is correct. var accessKeywordToAdd = accessKeyword diff --git a/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift index 030fa707a..f020059cb 100644 --- a/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift @@ -128,6 +128,31 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { ) } + func testPackageAccessLevel() { + assertFormatting( + NoAccessLevelOnExtensionDeclaration.self, + input: """ + 1️⃣package extension Foo { + 2️⃣func f() {} + } + """, + expected: """ + extension Foo { + package func f() {} + } + """, + findings: [ + FindingSpec( + "1️⃣", + message: "move this 'package' access modifier to precede each member inside this extension", + notes: [ + NoteSpec("2️⃣", message: "add 'package' access modifier to this declaration"), + ] + ), + ] + ) + } + func testPrivateIsEffectivelyFileprivate() { assertFormatting( NoAccessLevelOnExtensionDeclaration.self,